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 ) if( (n) >= 0) sleep(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
123 #include "gettext.h"
\r
126 # define _(s) gettext (s)
\r
127 # define N_(s) gettext_noop (s)
\r
134 /* A point in time */
\r
136 long sec; /* Assuming this is >= 32 bits */
\r
137 int ms; /* Assuming this is >= 16 bits */
\r
140 int establish P((void));
\r
141 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
\r
142 char *buf, int count, int error));
\r
143 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
\r
144 char *buf, int count, int error));
\r
145 void SendToICS P((char *s));
\r
146 void SendToICSDelayed P((char *s, long msdelay));
\r
147 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
\r
148 int toX, int toY));
\r
149 void InitPosition P((int redraw));
\r
150 void HandleMachineMove P((char *message, ChessProgramState *cps));
\r
151 int AutoPlayOneMove P((void));
\r
152 int LoadGameOneMove P((ChessMove readAhead));
\r
153 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
\r
154 int LoadPositionFromFile P((char *filename, int n, char *title));
\r
155 int SavePositionToFile P((char *filename));
\r
156 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
\r
158 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
\r
159 void ShowMove P((int fromX, int fromY, int toX, int toY));
\r
160 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
\r
161 /*char*/int promoChar));
\r
162 void BackwardInner P((int target));
\r
163 void ForwardInner P((int target));
\r
164 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
\r
165 void EditPositionDone P((void));
\r
166 void PrintOpponents P((FILE *fp));
\r
167 void PrintPosition P((FILE *fp, int move));
\r
168 void StartChessProgram P((ChessProgramState *cps));
\r
169 void SendToProgram P((char *message, ChessProgramState *cps));
\r
170 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
\r
171 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
\r
172 char *buf, int count, int error));
\r
173 void SendTimeControl P((ChessProgramState *cps,
\r
174 int mps, long tc, int inc, int sd, int st));
\r
175 char *TimeControlTagValue P((void));
\r
176 void Attention P((ChessProgramState *cps));
\r
177 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
\r
178 void ResurrectChessProgram P((void));
\r
179 void DisplayComment P((int moveNumber, char *text));
\r
180 void DisplayMove P((int moveNumber));
\r
181 void DisplayAnalysis P((void));
\r
183 void ParseGameHistory P((char *game));
\r
184 void ParseBoard12 P((char *string));
\r
185 void StartClocks P((void));
\r
186 void SwitchClocks P((void));
\r
187 void StopClocks P((void));
\r
188 void ResetClocks P((void));
\r
189 char *PGNDate P((void));
\r
190 void SetGameInfo P((void));
\r
191 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
\r
192 int RegisterMove P((void));
\r
193 void MakeRegisteredMove P((void));
\r
194 void TruncateGame P((void));
\r
195 int looking_at P((char *, int *, char *));
\r
196 void CopyPlayerNameIntoFileName P((char **, char *));
\r
197 char *SavePart P((char *));
\r
198 int SaveGameOldStyle P((FILE *));
\r
199 int SaveGamePGN P((FILE *));
\r
200 void GetTimeMark P((TimeMark *));
\r
201 long SubtractTimeMarks P((TimeMark *, TimeMark *));
\r
202 int CheckFlags P((void));
\r
203 long NextTickLength P((long));
\r
204 void CheckTimeControl P((void));
\r
205 void show_bytes P((FILE *, char *, int));
\r
206 int string_to_rating P((char *str));
\r
207 void ParseFeatures P((char* args, ChessProgramState *cps));
\r
208 void InitBackEnd3 P((void));
\r
209 void FeatureDone P((ChessProgramState* cps, int val));
\r
210 void InitChessProgram P((ChessProgramState *cps, int setup));
\r
213 extern void ConsoleCreate();
\r
216 ChessProgramState *WhitePlayer();
\r
217 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
\r
218 int VerifyDisplayMode P(());
\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 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
236 int opponentKibitzes;
\r
238 /* States for ics_getting_history */
\r
240 #define H_REQUESTED 1
\r
241 #define H_GOT_REQ_HEADER 2
\r
242 #define H_GOT_UNREQ_HEADER 3
\r
243 #define H_GETTING_MOVES 4
\r
244 #define H_GOT_UNWANTED_HEADER 5
\r
246 /* whosays values for GameEnds */
\r
248 #define GE_ENGINE 1
\r
249 #define GE_PLAYER 2
\r
251 #define GE_XBOARD 4
\r
252 #define GE_ENGINE1 5
\r
253 #define GE_ENGINE2 6
\r
255 /* Maximum number of games in a cmail message */
\r
256 #define CMAIL_MAX_GAMES 20
\r
258 /* Different types of move when calling RegisterMove */
\r
259 #define CMAIL_MOVE 0
\r
260 #define CMAIL_RESIGN 1
\r
261 #define CMAIL_DRAW 2
\r
262 #define CMAIL_ACCEPT 3
\r
264 /* Different types of result to remember for each game */
\r
265 #define CMAIL_NOT_RESULT 0
\r
266 #define CMAIL_OLD_RESULT 1
\r
267 #define CMAIL_NEW_RESULT 2
\r
269 /* Telnet protocol constants */
\r
270 #define TN_WILL 0373
\r
271 #define TN_WONT 0374
\r
273 #define TN_DONT 0376
\r
274 #define TN_IAC 0377
\r
275 #define TN_ECHO 0001
\r
276 #define TN_SGA 0003
\r
280 static char * safeStrCpy( char * dst, const char * src, size_t count )
\r
282 assert( dst != NULL );
\r
283 assert( src != NULL );
\r
284 assert( count > 0 );
\r
286 strncpy( dst, src, count );
\r
287 dst[ count-1 ] = '\0';
\r
291 static char * safeStrCat( char * dst, const char * src, size_t count )
\r
295 assert( dst != NULL );
\r
296 assert( src != NULL );
\r
297 assert( count > 0 );
\r
299 dst_len = strlen(dst);
\r
301 assert( count > dst_len ); /* Buffer size must be greater than current length */
\r
303 safeStrCpy( dst + dst_len, src, count - dst_len );
\r
308 /* Some compiler can't cast u64 to double
\r
309 * This function do the job for us:
\r
311 * We use the highest bit for cast, this only
\r
312 * works if the highest bit is not
\r
313 * in use (This should not happen)
\r
315 * We used this for all compiler
\r
318 u64ToDouble(u64 value)
\r
321 u64 tmp = value & u64Const(0x7fffffffffffffff);
\r
322 r = (double)(s64)tmp;
\r
323 if (value & u64Const(0x8000000000000000))
\r
324 r += 9.2233720368547758080e18; /* 2^63 */
\r
328 /* Fake up flags for now, as we aren't keeping track of castling
\r
329 availability yet. [HGM] Change of logic: the flag now only
\r
330 indicates the type of castlings allowed by the rule of the game.
\r
331 The actual rights themselves are maintained in the array
\r
332 castlingRights, as part of the game history, and are not probed
\r
338 int flags = F_ALL_CASTLE_OK;
\r
339 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
\r
340 switch (gameInfo.variant) {
\r
341 case VariantSuicide:
\r
342 flags &= ~F_ALL_CASTLE_OK;
\r
343 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
\r
344 flags |= F_IGNORE_CHECK;
\r
346 case VariantAtomic:
\r
347 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
\r
349 case VariantKriegspiel:
\r
350 flags |= F_KRIEGSPIEL_CAPTURE;
\r
352 case VariantCapaRandom:
\r
353 case VariantFischeRandom:
\r
354 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
\r
355 case VariantNoCastle:
\r
356 case VariantShatranj:
\r
357 case VariantCourier:
\r
358 flags &= ~F_ALL_CASTLE_OK;
\r
366 FILE *gameFileFP, *debugFP;
\r
369 [AS] Note: sometimes, the sscanf() function is used to parse the input
\r
370 into a fixed-size buffer. Because of this, we must be prepared to
\r
371 receive strings as long as the size of the input buffer, which is currently
\r
372 set to 4K for Windows and 8K for the rest.
\r
373 So, we must either allocate sufficiently large buffers here, or
\r
374 reduce the size of the input buffer in the input reading part.
\r
377 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
\r
378 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
\r
379 char thinkOutput1[MSG_SIZ*10];
\r
381 ChessProgramState first, second;
\r
383 /* premove variables */
\r
384 int premoveToX = 0;
\r
385 int premoveToY = 0;
\r
386 int premoveFromX = 0;
\r
387 int premoveFromY = 0;
\r
388 int premovePromoChar = 0;
\r
389 int gotPremove = 0;
\r
390 Boolean alarmSounded;
\r
391 /* end premove variables */
\r
393 char *ics_prefix = "$";
\r
394 int ics_type = ICS_GENERIC;
\r
396 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
\r
397 int pauseExamForwardMostMove = 0;
\r
398 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
\r
399 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
\r
400 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
\r
401 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
\r
402 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
\r
403 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
\r
404 int whiteFlag = FALSE, blackFlag = FALSE;
\r
405 int userOfferedDraw = FALSE;
\r
406 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
\r
407 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
\r
408 int cmailMoveType[CMAIL_MAX_GAMES];
\r
409 long ics_clock_paused = 0;
\r
410 ProcRef icsPR = NoProc, cmailPR = NoProc;
\r
411 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
\r
412 GameMode gameMode = BeginningOfGame;
\r
413 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
\r
414 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
\r
415 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
\r
416 int hiddenThinkOutputState = 0; /* [AS] */
\r
417 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
\r
418 int adjudicateLossPlies = 6;
\r
419 char white_holding[64], black_holding[64];
\r
420 TimeMark lastNodeCountTime;
\r
421 long lastNodeCount=0;
\r
422 int have_sent_ICS_logon = 0;
\r
423 int movesPerSession;
\r
424 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
\r
425 long timeControl_2; /* [AS] Allow separate time controls */
\r
426 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
\r
427 long timeRemaining[2][MAX_MOVES];
\r
429 TimeMark programStartTime;
\r
430 char ics_handle[MSG_SIZ];
\r
431 int have_set_title = 0;
\r
433 /* animateTraining preserves the state of appData.animate
\r
434 * when Training mode is activated. This allows the
\r
435 * response to be animated when appData.animate == TRUE and
\r
436 * appData.animateDragging == TRUE.
\r
438 Boolean animateTraining;
\r
444 Board boards[MAX_MOVES];
\r
445 /* [HGM] Following 7 needed for accurate legality tests: */
\r
446 char epStatus[MAX_MOVES];
\r
447 char castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1
\r
448 char castlingRank[BOARD_SIZE]; // and corresponding ranks
\r
449 char initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];
\r
450 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
\r
451 int initialRulePlies, FENrulePlies;
\r
453 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
\r
455 int shuffleOpenings;
\r
457 ChessSquare FIDEArray[2][BOARD_SIZE] = {
\r
458 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
\r
459 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
\r
460 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
\r
461 BlackKing, BlackBishop, BlackKnight, BlackRook }
\r
464 ChessSquare twoKingsArray[2][BOARD_SIZE] = {
\r
465 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
\r
466 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
\r
467 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
\r
468 BlackKing, BlackKing, BlackKnight, BlackRook }
\r
471 ChessSquare KnightmateArray[2][BOARD_SIZE] = {
\r
472 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
\r
473 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
\r
474 { BlackRook, BlackMan, BlackBishop, BlackQueen,
\r
475 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
\r
478 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */
\r
479 { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
\r
480 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
\r
481 { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
\r
482 BlackKing, BlackBishop, BlackKnight, BlackRook }
\r
485 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
\r
486 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
\r
487 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
\r
488 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
\r
489 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
\r
493 #if (BOARD_SIZE>=10)
\r
494 ChessSquare ShogiArray[2][BOARD_SIZE] = {
\r
495 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
\r
496 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
\r
497 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
\r
498 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
\r
501 ChessSquare XiangqiArray[2][BOARD_SIZE] = {
\r
502 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
\r
503 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
\r
504 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
\r
505 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
\r
508 ChessSquare CapablancaArray[2][BOARD_SIZE] = {
\r
509 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
\r
510 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
\r
511 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
\r
512 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
\r
515 ChessSquare GreatArray[2][BOARD_SIZE] = {
\r
516 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
\r
517 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
\r
518 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
\r
519 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
\r
522 ChessSquare JanusArray[2][BOARD_SIZE] = {
\r
523 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
\r
524 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
\r
525 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
\r
526 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
\r
530 ChessSquare GothicArray[2][BOARD_SIZE] = {
\r
531 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
\r
532 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
\r
533 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
\r
534 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
\r
537 #define GothicArray CapablancaArray
\r
541 ChessSquare FalconArray[2][BOARD_SIZE] = {
\r
542 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
\r
543 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
\r
544 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
\r
545 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
\r
548 #define FalconArray CapablancaArray
\r
551 #else // !(BOARD_SIZE>=10)
\r
552 #define XiangqiPosition FIDEArray
\r
553 #define CapablancaArray FIDEArray
\r
554 #define GothicArray FIDEArray
\r
555 #define GreatArray FIDEArray
\r
556 #endif // !(BOARD_SIZE>=10)
\r
558 #if (BOARD_SIZE>=12)
\r
559 ChessSquare CourierArray[2][BOARD_SIZE] = {
\r
560 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
\r
561 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
\r
562 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
\r
563 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
\r
565 #else // !(BOARD_SIZE>=12)
\r
566 #define CourierArray CapablancaArray
\r
567 #endif // !(BOARD_SIZE>=12)
\r
570 Board initialPosition;
\r
573 /* Convert str to a rating. Checks for special cases of "----",
\r
575 "++++", etc. Also strips ()'s */
\r
577 string_to_rating(str)
\r
580 while(*str && !isdigit(*str)) ++str;
\r
582 return 0; /* One of the special "no rating" cases */
\r
588 ClearProgramStats()
\r
590 /* Init programStats */
\r
591 programStats.movelist[0] = 0;
\r
592 programStats.depth = 0;
\r
593 programStats.nr_moves = 0;
\r
594 programStats.moves_left = 0;
\r
595 programStats.nodes = 0;
\r
596 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
\r
597 programStats.score = 0;
\r
598 programStats.got_only_move = 0;
\r
599 programStats.got_fail = 0;
\r
600 programStats.line_is_book = 0;
\r
606 int matched, min, sec;
\r
608 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
\r
610 GetTimeMark(&programStartTime);
\r
612 ClearProgramStats();
\r
613 programStats.ok_to_send = 1;
\r
614 programStats.seen_stat = 0;
\r
617 * Initialize game list
\r
619 ListNew(&gameList);
\r
623 * Internet chess server status
\r
625 if (appData.icsActive) {
\r
626 appData.matchMode = FALSE;
\r
627 appData.matchGames = 0;
\r
629 appData.noChessProgram = !appData.zippyPlay;
\r
631 appData.zippyPlay = FALSE;
\r
632 appData.zippyTalk = FALSE;
\r
633 appData.noChessProgram = TRUE;
\r
635 if (*appData.icsHelper != NULLCHAR) {
\r
636 appData.useTelnet = TRUE;
\r
637 appData.telnetProgram = appData.icsHelper;
\r
640 appData.zippyTalk = appData.zippyPlay = FALSE;
\r
643 /* [AS] Initialize pv info list [HGM] and game state */
\r
647 for( i=0; i<MAX_MOVES; i++ ) {
\r
648 pvInfoList[i].depth = -1;
\r
649 epStatus[i]=EP_NONE;
\r
650 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
\r
655 * Parse timeControl resource
\r
657 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
\r
658 appData.movesPerSession)) {
\r
660 sprintf(buf, _("bad timeControl option %s"), appData.timeControl);
\r
661 DisplayFatalError(buf, 0, 2);
\r
665 * Parse searchTime resource
\r
667 if (*appData.searchTime != NULLCHAR) {
\r
668 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
\r
669 if (matched == 1) {
\r
670 searchTime = min * 60;
\r
671 } else if (matched == 2) {
\r
672 searchTime = min * 60 + sec;
\r
675 sprintf(buf, _("bad searchTime option %s"), appData.searchTime);
\r
676 DisplayFatalError(buf, 0, 2);
\r
680 /* [AS] Adjudication threshold */
\r
681 adjudicateLossThreshold = appData.adjudicateLossThreshold;
\r
683 first.which = "first";
\r
684 second.which = "second";
\r
685 first.maybeThinking = second.maybeThinking = FALSE;
\r
686 first.pr = second.pr = NoProc;
\r
687 first.isr = second.isr = NULL;
\r
688 first.sendTime = second.sendTime = 2;
\r
689 first.sendDrawOffers = 1;
\r
690 if (appData.firstPlaysBlack) {
\r
691 first.twoMachinesColor = "black\n";
\r
692 second.twoMachinesColor = "white\n";
\r
694 first.twoMachinesColor = "white\n";
\r
695 second.twoMachinesColor = "black\n";
\r
697 first.program = appData.firstChessProgram;
\r
698 second.program = appData.secondChessProgram;
\r
699 first.host = appData.firstHost;
\r
700 second.host = appData.secondHost;
\r
701 first.dir = appData.firstDirectory;
\r
702 second.dir = appData.secondDirectory;
\r
703 first.other = &second;
\r
704 second.other = &first;
\r
705 first.initString = appData.initString;
\r
706 second.initString = appData.secondInitString;
\r
707 first.computerString = appData.firstComputerString;
\r
708 second.computerString = appData.secondComputerString;
\r
709 first.useSigint = second.useSigint = TRUE;
\r
710 first.useSigterm = second.useSigterm = TRUE;
\r
711 first.reuse = appData.reuseFirst;
\r
712 second.reuse = appData.reuseSecond;
\r
713 first.nps = appData.firstNPS; // [HGM] nps: copy nodes per second
\r
714 second.nps = appData.secondNPS;
\r
715 first.useSetboard = second.useSetboard = FALSE;
\r
716 first.useSAN = second.useSAN = FALSE;
\r
717 first.usePing = second.usePing = FALSE;
\r
718 first.lastPing = second.lastPing = 0;
\r
719 first.lastPong = second.lastPong = 0;
\r
720 first.usePlayother = second.usePlayother = FALSE;
\r
721 first.useColors = second.useColors = TRUE;
\r
722 first.useUsermove = second.useUsermove = FALSE;
\r
723 first.sendICS = second.sendICS = FALSE;
\r
724 first.sendName = second.sendName = appData.icsActive;
\r
725 first.sdKludge = second.sdKludge = FALSE;
\r
726 first.stKludge = second.stKludge = FALSE;
\r
727 TidyProgramName(first.program, first.host, first.tidy);
\r
728 TidyProgramName(second.program, second.host, second.tidy);
\r
729 first.matchWins = second.matchWins = 0;
\r
730 strcpy(first.variants, appData.variant);
\r
731 strcpy(second.variants, appData.variant);
\r
732 first.analysisSupport = second.analysisSupport = 2; /* detect */
\r
733 first.analyzing = second.analyzing = FALSE;
\r
734 first.initDone = second.initDone = FALSE;
\r
736 /* New features added by Tord: */
\r
737 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
\r
738 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
\r
739 /* End of new features added by Tord. */
\r
741 /* [HGM] time odds: set factor for each machine */
\r
742 first.timeOdds = appData.firstTimeOdds;
\r
743 second.timeOdds = appData.secondTimeOdds;
\r
745 if(appData.timeOddsMode) {
\r
746 norm = first.timeOdds;
\r
747 if(norm > second.timeOdds) norm = second.timeOdds;
\r
749 first.timeOdds /= norm;
\r
750 second.timeOdds /= norm;
\r
753 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
\r
754 first.accumulateTC = appData.firstAccumulateTC;
\r
755 second.accumulateTC = appData.secondAccumulateTC;
\r
756 first.maxNrOfSessions = second.maxNrOfSessions = 1;
\r
759 first.debug = second.debug = FALSE;
\r
760 first.supportsNPS = second.supportsNPS = UNKNOWN;
\r
762 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
\r
763 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
\r
764 first.isUCI = appData.firstIsUCI; /* [AS] */
\r
765 second.isUCI = appData.secondIsUCI; /* [AS] */
\r
766 first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
\r
767 second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
\r
769 if (appData.firstProtocolVersion > PROTOVER ||
\r
770 appData.firstProtocolVersion < 1) {
\r
772 sprintf(buf, _("protocol version %d not supported"),
\r
773 appData.firstProtocolVersion);
\r
774 DisplayFatalError(buf, 0, 2);
\r
776 first.protocolVersion = appData.firstProtocolVersion;
\r
779 if (appData.secondProtocolVersion > PROTOVER ||
\r
780 appData.secondProtocolVersion < 1) {
\r
782 sprintf(buf, _("protocol version %d not supported"),
\r
783 appData.secondProtocolVersion);
\r
784 DisplayFatalError(buf, 0, 2);
\r
786 second.protocolVersion = appData.secondProtocolVersion;
\r
789 if (appData.icsActive) {
\r
790 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
\r
791 } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
\r
792 appData.clockMode = FALSE;
\r
793 first.sendTime = second.sendTime = 0;
\r
797 /* Override some settings from environment variables, for backward
\r
798 compatibility. Unfortunately it's not feasible to have the env
\r
799 vars just set defaults, at least in xboard. Ugh.
\r
801 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
\r
806 if (appData.noChessProgram) {
\r
807 programVersion = (char*) malloc(5 + strlen(PRODUCT) + strlen(VERSION)
\r
808 + strlen(PATCHLEVEL));
\r
809 sprintf(programVersion, "%s %s.%s", PRODUCT, VERSION, PATCHLEVEL);
\r
814 while (*q != ' ' && *q != NULLCHAR) q++;
\r
816 while (p > first.program && *(p-1) != '/' && *(p-1) != '\\') p--; /* [HGM] bckslash added */
\r
817 programVersion = (char*) malloc(8 + strlen(PRODUCT) + strlen(VERSION)
\r
818 + strlen(PATCHLEVEL) + (q - p));
\r
819 sprintf(programVersion, "%s %s.%s + ", PRODUCT, VERSION, PATCHLEVEL);
\r
820 strncat(programVersion, p, q - p);
\r
822 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
\r
823 programVersion = (char*) malloc(8 + strlen(PRODUCT) + strlen(VERSION)
\r
824 + strlen(PATCHLEVEL) + strlen(first.tidy));
\r
825 sprintf(programVersion, "%s %s.%s + %s", PRODUCT, VERSION, PATCHLEVEL, first.tidy);
\r
829 if (!appData.icsActive) {
\r
831 /* Check for variants that are supported only in ICS mode,
\r
832 or not at all. Some that are accepted here nevertheless
\r
833 have bugs; see comments below.
\r
835 VariantClass variant = StringToVariant(appData.variant);
\r
837 case VariantBughouse: /* need four players and two boards */
\r
838 case VariantKriegspiel: /* need to hide pieces and move details */
\r
839 /* case VariantFischeRandom: (Fabien: moved below) */
\r
840 sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
\r
841 DisplayFatalError(buf, 0, 2);
\r
844 case VariantUnknown:
\r
845 case VariantLoadable:
\r
855 sprintf(buf, _("Unknown variant name %s"), appData.variant);
\r
856 DisplayFatalError(buf, 0, 2);
\r
859 case VariantXiangqi: /* [HGM] repetition rules not implemented */
\r
860 case VariantFairy: /* [HGM] TestLegality definitely off! */
\r
861 case VariantGothic: /* [HGM] should work */
\r
862 case VariantCapablanca: /* [HGM] should work */
\r
863 case VariantCourier: /* [HGM] initial forced moves not implemented */
\r
864 case VariantShogi: /* [HGM] drops not tested for legality */
\r
865 case VariantKnightmate: /* [HGM] should work */
\r
866 case VariantCylinder: /* [HGM] untested */
\r
867 case VariantFalcon: /* [HGM] untested */
\r
868 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
\r
869 offboard interposition not understood */
\r
870 case VariantNormal: /* definitely works! */
\r
871 case VariantWildCastle: /* pieces not automatically shuffled */
\r
872 case VariantNoCastle: /* pieces not automatically shuffled */
\r
873 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
\r
874 case VariantLosers: /* should work except for win condition,
\r
875 and doesn't know captures are mandatory */
\r
876 case VariantSuicide: /* should work except for win condition,
\r
877 and doesn't know captures are mandatory */
\r
878 case VariantGiveaway: /* should work except for win condition,
\r
879 and doesn't know captures are mandatory */
\r
880 case VariantTwoKings: /* should work */
\r
881 case VariantAtomic: /* should work except for win condition */
\r
882 case Variant3Check: /* should work except for win condition */
\r
883 case VariantShatranj: /* should work except for all win conditions */
\r
884 case VariantBerolina: /* might work if TestLegality is off */
\r
885 case VariantCapaRandom: /* should work */
\r
886 case VariantJanus: /* should work */
\r
887 case VariantSuper: /* experimental */
\r
888 case VariantGreat: /* experimental, requires legality testing to be off */
\r
893 InitEngineUCI( installDir, &first ); // [HGM] moved here from winboard.c, to make available in xboard
\r
894 InitEngineUCI( installDir, &second );
\r
897 int NextIntegerFromString( char ** str, long * value )
\r
902 while( *s == ' ' || *s == '\t' ) {
\r
908 if( *s >= '0' && *s <= '9' ) {
\r
909 while( *s >= '0' && *s <= '9' ) {
\r
910 *value = *value * 10 + (*s - '0');
\r
922 int NextTimeControlFromString( char ** str, long * value )
\r
925 int result = NextIntegerFromString( str, &temp );
\r
927 if( result == 0 ) {
\r
928 *value = temp * 60; /* Minutes */
\r
929 if( **str == ':' ) {
\r
931 result = NextIntegerFromString( str, &temp );
\r
932 *value += temp; /* Seconds */
\r
939 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
\r
940 { /* [HGM] routine added to read '+moves/time' for secondary time control */
\r
941 int result = -1; long temp, temp2;
\r
943 if(**str != '+') return -1; // old params remain in force!
\r
945 if( NextTimeControlFromString( str, &temp ) ) return -1;
\r
948 /* time only: incremental or sudden-death time control */
\r
949 if(**str == '+') { /* increment follows; read it */
\r
951 if(result = NextIntegerFromString( str, &temp2)) return -1;
\r
952 *inc = temp2 * 1000;
\r
954 *moves = 0; *tc = temp * 1000;
\r
956 } else if(temp % 60 != 0) return -1; /* moves was given as min:sec */
\r
958 (*str)++; /* classical time control */
\r
959 result = NextTimeControlFromString( str, &temp2);
\r
962 *tc = temp2 * 1000;
\r
968 int GetTimeQuota(int movenr)
\r
969 { /* [HGM] get time to add from the multi-session time-control string */
\r
970 int moves=1; /* kludge to force reading of first session */
\r
971 long time, increment;
\r
972 char *s = fullTimeControlString;
\r
974 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
\r
976 if(moves) NextSessionFromString(&s, &moves, &time, &increment);
\r
977 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
\r
978 if(movenr == -1) return time; /* last move before new session */
\r
979 if(!moves) return increment; /* current session is incremental */
\r
980 if(movenr >= 0) movenr -= moves; /* we already finished this session */
\r
981 } while(movenr >= -1); /* try again for next session */
\r
983 return 0; // no new time quota on this move
\r
987 ParseTimeControl(tc, ti, mps)
\r
993 int matched, min, sec;
\r
995 matched = sscanf(tc, "%d:%d", &min, &sec);
\r
996 if (matched == 1) {
\r
997 timeControl = min * 60 * 1000;
\r
998 } else if (matched == 2) {
\r
999 timeControl = (min * 60 + sec) * 1000;
\r
1006 char buf[MSG_SIZ];
\r
1008 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
\r
1011 sprintf(buf, "+%d/%s+%d", mps, tc, ti);
\r
1012 else sprintf(buf, "+%s+%d", tc, ti);
\r
1015 sprintf(buf, "+%d/%s", mps, tc);
\r
1016 else sprintf(buf, "+%s", tc);
\r
1018 fullTimeControlString = StrSave(buf);
\r
1020 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
\r
1024 if( *tc == '/' ) {
\r
1025 /* Parse second time control */
\r
1028 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
\r
1036 timeControl_2 = tc2 * 1000;
\r
1039 timeControl_2 = 0;
\r
1046 timeControl = tc1 * 1000;
\r
1050 timeIncrement = ti * 1000; /* convert to ms */
\r
1051 movesPerSession = 0;
\r
1053 timeIncrement = 0;
\r
1054 movesPerSession = mps;
\r
1062 if (appData.debugMode) {
\r
1063 fprintf(debugFP, "%s\n", programVersion);
\r
1066 if (appData.matchGames > 0) {
\r
1067 appData.matchMode = TRUE;
\r
1068 } else if (appData.matchMode) {
\r
1069 appData.matchGames = 1;
\r
1071 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
\r
1072 appData.matchGames = appData.sameColorGames;
\r
1073 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
\r
1074 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
\r
1075 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
\r
1077 Reset(TRUE, FALSE);
\r
1078 if (appData.noChessProgram || first.protocolVersion == 1) {
\r
1081 /* kludge: allow timeout for initial "feature" commands */
\r
1083 DisplayMessage("", _("Starting chess program"));
\r
1084 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
\r
1089 InitBackEnd3 P((void))
\r
1091 GameMode initialMode;
\r
1092 char buf[MSG_SIZ];
\r
1095 InitChessProgram(&first, startedFromSetupPosition);
\r
1098 if (appData.icsActive) {
\r
1100 /* [DM] Make a console window if needed [HGM] merged ifs */
\r
1103 err = establish();
\r
1105 if (*appData.icsCommPort != NULLCHAR) {
\r
1106 sprintf(buf, _("Could not open comm port %s"),
\r
1107 appData.icsCommPort);
\r
1109 sprintf(buf, _("Could not connect to host %s, port %s"),
\r
1110 appData.icsHost, appData.icsPort);
\r
1112 DisplayFatalError(buf, err, 1);
\r
1117 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
\r
1119 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
\r
1120 } else if (appData.noChessProgram) {
\r
1126 if (*appData.cmailGameName != NULLCHAR) {
\r
1128 OpenLoopback(&cmailPR);
\r
1130 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
\r
1134 DisplayMessage("", "");
\r
1135 if (StrCaseCmp(appData.initialMode, "") == 0) {
\r
1136 initialMode = BeginningOfGame;
\r
1137 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
\r
1138 initialMode = TwoMachinesPlay;
\r
1139 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
\r
1140 initialMode = AnalyzeFile;
\r
1141 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
\r
1142 initialMode = AnalyzeMode;
\r
1143 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
\r
1144 initialMode = MachinePlaysWhite;
\r
1145 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
\r
1146 initialMode = MachinePlaysBlack;
\r
1147 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
\r
1148 initialMode = EditGame;
\r
1149 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
\r
1150 initialMode = EditPosition;
\r
1151 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
\r
1152 initialMode = Training;
\r
1154 sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
\r
1155 DisplayFatalError(buf, 0, 2);
\r
1159 if (appData.matchMode) {
\r
1160 /* Set up machine vs. machine match */
\r
1161 if (appData.noChessProgram) {
\r
1162 DisplayFatalError(_("Can't have a match with no chess programs"),
\r
1168 if (*appData.loadGameFile != NULLCHAR) {
\r
1169 int index = appData.loadGameIndex; // [HGM] autoinc
\r
1170 if(index<0) lastIndex = index = 1;
\r
1171 if (!LoadGameFromFile(appData.loadGameFile,
\r
1173 appData.loadGameFile, FALSE)) {
\r
1174 DisplayFatalError(_("Bad game file"), 0, 1);
\r
1177 } else if (*appData.loadPositionFile != NULLCHAR) {
\r
1178 int index = appData.loadPositionIndex; // [HGM] autoinc
\r
1179 if(index<0) lastIndex = index = 1;
\r
1180 if (!LoadPositionFromFile(appData.loadPositionFile,
\r
1182 appData.loadPositionFile)) {
\r
1183 DisplayFatalError(_("Bad position file"), 0, 1);
\r
1187 TwoMachinesEvent();
\r
1188 } else if (*appData.cmailGameName != NULLCHAR) {
\r
1189 /* Set up cmail mode */
\r
1190 ReloadCmailMsgEvent(TRUE);
\r
1192 /* Set up other modes */
\r
1193 if (initialMode == AnalyzeFile) {
\r
1194 if (*appData.loadGameFile == NULLCHAR) {
\r
1195 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
\r
1199 if (*appData.loadGameFile != NULLCHAR) {
\r
1200 (void) LoadGameFromFile(appData.loadGameFile,
\r
1201 appData.loadGameIndex,
\r
1202 appData.loadGameFile, TRUE);
\r
1203 } else if (*appData.loadPositionFile != NULLCHAR) {
\r
1204 (void) LoadPositionFromFile(appData.loadPositionFile,
\r
1205 appData.loadPositionIndex,
\r
1206 appData.loadPositionFile);
\r
1207 /* [HGM] try to make self-starting even after FEN load */
\r
1208 /* to allow automatic setup of fairy variants with wtm */
\r
1209 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
\r
1210 gameMode = BeginningOfGame;
\r
1211 setboardSpoiledMachineBlack = 1;
\r
1213 /* [HGM] loadPos: make that every new game uses the setup */
\r
1214 /* from file as long as we do not switch variant */
\r
1215 if(!blackPlaysFirst) { int i;
\r
1216 startedFromPositionFile = TRUE;
\r
1217 CopyBoard(filePosition, boards[0]);
\r
1218 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
\r
1221 if (initialMode == AnalyzeMode) {
\r
1222 if (appData.noChessProgram) {
\r
1223 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
\r
1226 if (appData.icsActive) {
\r
1227 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
\r
1230 AnalyzeModeEvent();
\r
1231 } else if (initialMode == AnalyzeFile) {
\r
1232 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
\r
1233 ShowThinkingEvent();
\r
1234 AnalyzeFileEvent();
\r
1235 AnalysisPeriodicEvent(1);
\r
1236 } else if (initialMode == MachinePlaysWhite) {
\r
1237 if (appData.noChessProgram) {
\r
1238 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
\r
1242 if (appData.icsActive) {
\r
1243 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
\r
1247 MachineWhiteEvent();
\r
1248 } else if (initialMode == MachinePlaysBlack) {
\r
1249 if (appData.noChessProgram) {
\r
1250 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
\r
1254 if (appData.icsActive) {
\r
1255 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
\r
1259 MachineBlackEvent();
\r
1260 } else if (initialMode == TwoMachinesPlay) {
\r
1261 if (appData.noChessProgram) {
\r
1262 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
\r
1266 if (appData.icsActive) {
\r
1267 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
\r
1271 TwoMachinesEvent();
\r
1272 } else if (initialMode == EditGame) {
\r
1274 } else if (initialMode == EditPosition) {
\r
1275 EditPositionEvent();
\r
1276 } else if (initialMode == Training) {
\r
1277 if (*appData.loadGameFile == NULLCHAR) {
\r
1278 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
\r
1287 * Establish will establish a contact to a remote host.port.
\r
1288 * Sets icsPR to a ProcRef for a process (or pseudo-process)
\r
1289 * used to talk to the host.
\r
1290 * Returns 0 if okay, error code if not.
\r
1295 char buf[MSG_SIZ];
\r
1297 if (*appData.icsCommPort != NULLCHAR) {
\r
1298 /* Talk to the host through a serial comm port */
\r
1299 return OpenCommPort(appData.icsCommPort, &icsPR);
\r
1301 } else if (*appData.gateway != NULLCHAR) {
\r
1302 if (*appData.remoteShell == NULLCHAR) {
\r
1303 /* Use the rcmd protocol to run telnet program on a gateway host */
\r
1304 sprintf(buf, "%s %s %s",
\r
1305 appData.telnetProgram, appData.icsHost, appData.icsPort);
\r
1306 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
\r
1309 /* Use the rsh program to run telnet program on a gateway host */
\r
1310 if (*appData.remoteUser == NULLCHAR) {
\r
1311 sprintf(buf, "%s %s %s %s %s", appData.remoteShell,
\r
1312 appData.gateway, appData.telnetProgram,
\r
1313 appData.icsHost, appData.icsPort);
\r
1315 sprintf(buf, "%s %s -l %s %s %s %s",
\r
1316 appData.remoteShell, appData.gateway,
\r
1317 appData.remoteUser, appData.telnetProgram,
\r
1318 appData.icsHost, appData.icsPort);
\r
1320 return StartChildProcess(buf, "", &icsPR);
\r
1323 } else if (appData.useTelnet) {
\r
1324 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
\r
1327 /* TCP socket interface differs somewhat between
\r
1328 Unix and NT; handle details in the front end.
\r
1330 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
\r
1335 show_bytes(fp, buf, count)
\r
1341 if (*buf < 040 || *(unsigned char *) buf > 0177) {
\r
1342 fprintf(fp, "\\%03o", *buf & 0xff);
\r
1351 /* Returns an errno value */
\r
1353 OutputMaybeTelnet(pr, message, count, outError)
\r
1359 char buf[8192], *p, *q, *buflim;
\r
1360 int left, newcount, outcount;
\r
1362 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
\r
1363 *appData.gateway != NULLCHAR) {
\r
1364 if (appData.debugMode) {
\r
1365 fprintf(debugFP, ">ICS: ");
\r
1366 show_bytes(debugFP, message, count);
\r
1367 fprintf(debugFP, "\n");
\r
1369 return OutputToProcess(pr, message, count, outError);
\r
1372 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
\r
1378 if (q >= buflim) {
\r
1379 if (appData.debugMode) {
\r
1380 fprintf(debugFP, ">ICS: ");
\r
1381 show_bytes(debugFP, buf, newcount);
\r
1382 fprintf(debugFP, "\n");
\r
1384 outcount = OutputToProcess(pr, buf, newcount, outError);
\r
1385 if (outcount < newcount) return -1; /* to be sure */
\r
1392 } else if (((unsigned char) *p) == TN_IAC) {
\r
1393 *q++ = (char) TN_IAC;
\r
1400 if (appData.debugMode) {
\r
1401 fprintf(debugFP, ">ICS: ");
\r
1402 show_bytes(debugFP, buf, newcount);
\r
1403 fprintf(debugFP, "\n");
\r
1405 outcount = OutputToProcess(pr, buf, newcount, outError);
\r
1406 if (outcount < newcount) return -1; /* to be sure */
\r
1411 read_from_player(isr, closure, message, count, error)
\r
1412 InputSourceRef isr;
\r
1418 int outError, outCount;
\r
1419 static int gotEof = 0;
\r
1421 /* Pass data read from player on to ICS */
\r
1424 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
\r
1425 if (outCount < count) {
\r
1426 DisplayFatalError(_("Error writing to ICS"), outError, 1);
\r
1428 } else if (count < 0) {
\r
1429 RemoveInputSource(isr);
\r
1430 DisplayFatalError(_("Error reading from keyboard"), error, 1);
\r
1431 } else if (gotEof++ > 0) {
\r
1432 RemoveInputSource(isr);
\r
1433 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
\r
1441 int count, outCount, outError;
\r
1443 if (icsPR == NULL) return;
\r
1445 count = strlen(s);
\r
1446 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
\r
1447 if (outCount < count) {
\r
1448 DisplayFatalError(_("Error writing to ICS"), outError, 1);
\r
1452 /* This is used for sending logon scripts to the ICS. Sending
\r
1453 without a delay causes problems when using timestamp on ICC
\r
1454 (at least on my machine). */
\r
1456 SendToICSDelayed(s,msdelay)
\r
1460 int count, outCount, outError;
\r
1462 if (icsPR == NULL) return;
\r
1464 count = strlen(s);
\r
1465 if (appData.debugMode) {
\r
1466 fprintf(debugFP, ">ICS: ");
\r
1467 show_bytes(debugFP, s, count);
\r
1468 fprintf(debugFP, "\n");
\r
1470 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
\r
1472 if (outCount < count) {
\r
1473 DisplayFatalError(_("Error writing to ICS"), outError, 1);
\r
1478 /* Remove all highlighting escape sequences in s
\r
1479 Also deletes any suffix starting with '('
\r
1482 StripHighlightAndTitle(s)
\r
1485 static char retbuf[MSG_SIZ];
\r
1488 while (*s != NULLCHAR) {
\r
1489 while (*s == '\033') {
\r
1490 while (*s != NULLCHAR && !isalpha(*s)) s++;
\r
1491 if (*s != NULLCHAR) s++;
\r
1493 while (*s != NULLCHAR && *s != '\033') {
\r
1494 if (*s == '(' || *s == '[') {
\r
1505 /* Remove all highlighting escape sequences in s */
\r
1510 static char retbuf[MSG_SIZ];
\r
1513 while (*s != NULLCHAR) {
\r
1514 while (*s == '\033') {
\r
1515 while (*s != NULLCHAR && !isalpha(*s)) s++;
\r
1516 if (*s != NULLCHAR) s++;
\r
1518 while (*s != NULLCHAR && *s != '\033') {
\r
1526 char *variantNames[] = VARIANT_NAMES;
\r
1531 return variantNames[v];
\r
1535 /* Identify a variant from the strings the chess servers use or the
\r
1536 PGN Variant tag names we use. */
\r
1538 StringToVariant(e)
\r
1543 VariantClass v = VariantNormal;
\r
1544 int i, found = FALSE;
\r
1545 char buf[MSG_SIZ];
\r
1549 /* [HGM] skip over optional board-size prefixes */
\r
1550 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
\r
1551 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
\r
1552 while( *e++ != '_');
\r
1555 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
\r
1556 if (StrCaseStr(e, variantNames[i])) {
\r
1557 v = (VariantClass) i;
\r
1564 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
\r
1565 || StrCaseStr(e, "wild/fr")
\r
1566 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
\r
1567 v = VariantFischeRandom;
\r
1568 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
\r
1569 (i = 1, p = StrCaseStr(e, "w"))) {
\r
1571 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
\r
1572 if (isdigit(*p)) {
\r
1578 case 0: /* FICS only, actually */
\r
1580 /* Castling legal even if K starts on d-file */
\r
1581 v = VariantWildCastle;
\r
1586 /* Castling illegal even if K & R happen to start in
\r
1587 normal positions. */
\r
1588 v = VariantNoCastle;
\r
1601 /* Castling legal iff K & R start in normal positions */
\r
1602 v = VariantNormal;
\r
1607 /* Special wilds for position setup; unclear what to do here */
\r
1608 v = VariantLoadable;
\r
1611 /* Bizarre ICC game */
\r
1612 v = VariantTwoKings;
\r
1615 v = VariantKriegspiel;
\r
1618 v = VariantLosers;
\r
1621 v = VariantFischeRandom;
\r
1624 v = VariantCrazyhouse;
\r
1627 v = VariantBughouse;
\r
1630 v = Variant3Check;
\r
1633 /* Not quite the same as FICS suicide! */
\r
1634 v = VariantGiveaway;
\r
1637 v = VariantAtomic;
\r
1640 v = VariantShatranj;
\r
1643 /* Temporary names for future ICC types. The name *will* change in
\r
1644 the next xboard/WinBoard release after ICC defines it. */
\r
1673 v = VariantXiangqi;
\r
1676 v = VariantCourier;
\r
1679 v = VariantGothic;
\r
1682 v = VariantCapablanca;
\r
1685 v = VariantKnightmate;
\r
1691 v = VariantCylinder;
\r
1694 v = VariantFalcon;
\r
1697 v = VariantCapaRandom;
\r
1700 v = VariantBerolina;
\r
1712 /* Found "wild" or "w" in the string but no number;
\r
1713 must assume it's normal chess. */
\r
1714 v = VariantNormal;
\r
1717 sprintf(buf, _("Unknown wild type %d"), wnum);
\r
1718 DisplayError(buf, 0);
\r
1719 v = VariantUnknown;
\r
1724 if (appData.debugMode) {
\r
1725 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
\r
1726 e, wnum, VariantName(v));
\r
1731 static int leftover_start = 0, leftover_len = 0;
\r
1732 char star_match[STAR_MATCH_N][MSG_SIZ];
\r
1734 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
\r
1735 advance *index beyond it, and set leftover_start to the new value of
\r
1736 *index; else return FALSE. If pattern contains the character '*', it
\r
1737 matches any sequence of characters not containing '\r', '\n', or the
\r
1738 character following the '*' (if any), and the matched sequence(s) are
\r
1739 copied into star_match.
\r
1742 looking_at(buf, index, pattern)
\r
1747 char *bufp = &buf[*index], *patternp = pattern;
\r
1748 int star_count = 0;
\r
1749 char *matchp = star_match[0];
\r
1752 if (*patternp == NULLCHAR) {
\r
1753 *index = leftover_start = bufp - buf;
\r
1754 *matchp = NULLCHAR;
\r
1757 if (*bufp == NULLCHAR) return FALSE;
\r
1758 if (*patternp == '*') {
\r
1759 if (*bufp == *(patternp + 1)) {
\r
1760 *matchp = NULLCHAR;
\r
1761 matchp = star_match[++star_count];
\r
1765 } else if (*bufp == '\n' || *bufp == '\r') {
\r
1767 if (*patternp == NULLCHAR)
\r
1772 *matchp++ = *bufp++;
\r
1776 if (*patternp != *bufp) return FALSE;
\r
1783 SendToPlayer(data, length)
\r
1787 int error, outCount;
\r
1788 outCount = OutputToProcess(NoProc, data, length, &error);
\r
1789 if (outCount < length) {
\r
1790 DisplayFatalError(_("Error writing to display"), error, 1);
\r
1795 PackHolding(packed, holding)
\r
1799 char *p = holding;
\r
1801 int runlength = 0;
\r
1807 switch (runlength) {
\r
1818 sprintf(q, "%d", runlength);
\r
1830 /* Telnet protocol requests from the front end */
\r
1832 TelnetRequest(ddww, option)
\r
1833 unsigned char ddww, option;
\r
1835 unsigned char msg[3];
\r
1836 int outCount, outError;
\r
1838 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
\r
1840 if (appData.debugMode) {
\r
1841 char buf1[8], buf2[8], *ddwwStr, *optionStr;
\r
1857 sprintf(buf1, "%d", ddww);
\r
1862 optionStr = "ECHO";
\r
1866 sprintf(buf2, "%d", option);
\r
1869 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
\r
1874 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
\r
1875 if (outCount < 3) {
\r
1876 DisplayFatalError(_("Error writing to ICS"), outError, 1);
\r
1883 if (!appData.icsActive) return;
\r
1884 TelnetRequest(TN_DO, TN_ECHO);
\r
1890 if (!appData.icsActive) return;
\r
1891 TelnetRequest(TN_DONT, TN_ECHO);
\r
1895 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
\r
1897 /* put the holdings sent to us by the server on the board holdings area */
\r
1898 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
\r
1900 ChessSquare piece;
\r
1902 if(gameInfo.holdingsWidth < 2) return;
\r
1904 if( (int)lowestPiece >= BlackPawn ) {
\r
1905 holdingsColumn = 0;
\r
1907 holdingsStartRow = BOARD_HEIGHT-1;
\r
1910 holdingsColumn = BOARD_WIDTH-1;
\r
1911 countsColumn = BOARD_WIDTH-2;
\r
1912 holdingsStartRow = 0;
\r
1916 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
\r
1917 board[i][holdingsColumn] = EmptySquare;
\r
1918 board[i][countsColumn] = (ChessSquare) 0;
\r
1920 while( (p=*holdings++) != NULLCHAR ) {
\r
1921 piece = CharToPiece( ToUpper(p) );
\r
1922 if(piece == EmptySquare) continue;
\r
1923 /*j = (int) piece - (int) WhitePawn;*/
\r
1924 j = PieceToNumber(piece);
\r
1925 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
\r
1926 if(j < 0) continue; /* should not happen */
\r
1927 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
\r
1928 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
\r
1929 board[holdingsStartRow+j*direction][countsColumn]++;
\r
1936 VariantSwitch(Board board, VariantClass newVariant)
\r
1938 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
\r
1939 int oldCurrentMove = currentMove, oldForwardMostMove = forwardMostMove, oldBackwardMostMove = backwardMostMove;
\r
1940 Board tempBoard; int saveCastling[BOARD_SIZE], saveEP;
\r
1942 startedFromPositionFile = FALSE;
\r
1943 if(gameInfo.variant == newVariant) return;
\r
1945 /* [HGM] This routine is called each time an assignment is made to
\r
1946 * gameInfo.variant during a game, to make sure the board sizes
\r
1947 * are set to match the new variant. If that means adding or deleting
\r
1948 * holdings, we shift the playing board accordingly
\r
1949 * This kludge is needed because in ICS observe mode, we get boards
\r
1950 * of an ongoing game without knowing the variant, and learn about the
\r
1951 * latter only later. This can be because of the move list we requested,
\r
1952 * in which case the game history is refilled from the beginning anyway,
\r
1953 * but also when receiving holdings of a crazyhouse game. In the latter
\r
1954 * case we want to add those holdings to the already received position.
\r
1958 if (appData.debugMode) {
\r
1959 fprintf(debugFP, "Switch board from %s to %s\n",
\r
1960 VariantName(gameInfo.variant), VariantName(newVariant));
\r
1961 setbuf(debugFP, NULL);
\r
1963 shuffleOpenings = 0; /* [HGM] shuffle */
\r
1964 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
\r
1965 switch(newVariant) {
\r
1966 case VariantShogi:
\r
1967 newWidth = 9; newHeight = 9;
\r
1968 gameInfo.holdingsSize = 7;
\r
1969 case VariantBughouse:
\r
1970 case VariantCrazyhouse:
\r
1971 newHoldingsWidth = 2; break;
\r
1973 newHoldingsWidth = gameInfo.holdingsSize = 0;
\r
1976 if(newWidth != gameInfo.boardWidth ||
\r
1977 newHeight != gameInfo.boardHeight ||
\r
1978 newHoldingsWidth != gameInfo.holdingsWidth ) {
\r
1980 /* shift position to new playing area, if needed */
\r
1981 if(newHoldingsWidth > gameInfo.holdingsWidth) {
\r
1982 for(i=0; i<BOARD_HEIGHT; i++)
\r
1983 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
\r
1984 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
\r
1986 for(i=0; i<newHeight; i++) {
\r
1987 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
\r
1988 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
\r
1990 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
\r
1991 for(i=0; i<BOARD_HEIGHT; i++)
\r
1992 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
\r
1993 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
\r
1997 gameInfo.boardWidth = newWidth;
\r
1998 gameInfo.boardHeight = newHeight;
\r
1999 gameInfo.holdingsWidth = newHoldingsWidth;
\r
2000 gameInfo.variant = newVariant;
\r
2001 InitDrawingSizes(-2, 0);
\r
2003 /* [HGM] The following should definitely be solved in a better way */
\r
2005 CopyBoard(board, tempBoard); /* save position in case it is board[0] */
\r
2006 for(i=0; i<BOARD_SIZE; i++) saveCastling[i] = castlingRights[0][i];
\r
2007 saveEP = epStatus[0];
\r
2009 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
\r
2011 epStatus[0] = saveEP;
\r
2012 for(i=0; i<BOARD_SIZE; i++) castlingRights[0][i] = saveCastling[i];
\r
2013 CopyBoard(tempBoard, board); /* restore position received from ICS */
\r
2015 } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
\r
2017 forwardMostMove = oldForwardMostMove;
\r
2018 backwardMostMove = oldBackwardMostMove;
\r
2019 currentMove = oldCurrentMove; /* InitPos reset these, but we need still to redraw the position */
\r
2022 static int loggedOn = FALSE;
\r
2024 /*-- Game start info cache: --*/
\r
2026 char gs_kind[MSG_SIZ];
\r
2027 static char player1Name[128] = "";
\r
2028 static char player2Name[128] = "";
\r
2029 static int player1Rating = -1;
\r
2030 static int player2Rating = -1;
\r
2031 /*----------------------------*/
\r
2033 ColorClass curColor = ColorNormal;
\r
2034 int suppressKibitz = 0;
\r
2037 read_from_ics(isr, closure, data, count, error)
\r
2038 InputSourceRef isr;
\r
2044 #define BUF_SIZE 8192
\r
2045 #define STARTED_NONE 0
\r
2046 #define STARTED_MOVES 1
\r
2047 #define STARTED_BOARD 2
\r
2048 #define STARTED_OBSERVE 3
\r
2049 #define STARTED_HOLDINGS 4
\r
2050 #define STARTED_CHATTER 5
\r
2051 #define STARTED_COMMENT 6
\r
2052 #define STARTED_MOVES_NOHIDE 7
\r
2054 static int started = STARTED_NONE;
\r
2055 static char parse[20000];
\r
2056 static int parse_pos = 0;
\r
2057 static char buf[BUF_SIZE + 1];
\r
2058 static int firstTime = TRUE, intfSet = FALSE;
\r
2059 static ColorClass prevColor = ColorNormal;
\r
2060 static int savingComment = FALSE;
\r
2066 int backup; /* [DM] For zippy color lines */
\r
2069 if (appData.debugMode) {
\r
2071 fprintf(debugFP, "<ICS: ");
\r
2072 show_bytes(debugFP, data, count);
\r
2073 fprintf(debugFP, "\n");
\r
2077 if (appData.debugMode) { int f = forwardMostMove;
\r
2078 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
\r
2079 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
\r
2082 /* If last read ended with a partial line that we couldn't parse,
\r
2083 prepend it to the new read and try again. */
\r
2084 if (leftover_len > 0) {
\r
2085 for (i=0; i<leftover_len; i++)
\r
2086 buf[i] = buf[leftover_start + i];
\r
2089 /* Copy in new characters, removing nulls and \r's */
\r
2090 buf_len = leftover_len;
\r
2091 for (i = 0; i < count; i++) {
\r
2092 if (data[i] != NULLCHAR && data[i] != '\r')
\r
2093 buf[buf_len++] = data[i];
\r
2094 if(buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' &&
\r
2095 buf[buf_len-3]==' ' && buf[buf_len-2]==' ' && buf[buf_len-1]==' ')
\r
2096 buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous
\r
2099 buf[buf_len] = NULLCHAR;
\r
2100 next_out = leftover_len;
\r
2101 leftover_start = 0;
\r
2104 while (i < buf_len) {
\r
2105 /* Deal with part of the TELNET option negotiation
\r
2106 protocol. We refuse to do anything beyond the
\r
2107 defaults, except that we allow the WILL ECHO option,
\r
2108 which ICS uses to turn off password echoing when we are
\r
2109 directly connected to it. We reject this option
\r
2110 if localLineEditing mode is on (always on in xboard)
\r
2111 and we are talking to port 23, which might be a real
\r
2112 telnet server that will try to keep WILL ECHO on permanently.
\r
2114 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
\r
2115 static int remoteEchoOption = FALSE; /* telnet ECHO option */
\r
2116 unsigned char option;
\r
2118 switch ((unsigned char) buf[++i]) {
\r
2120 if (appData.debugMode)
\r
2121 fprintf(debugFP, "\n<WILL ");
\r
2122 switch (option = (unsigned char) buf[++i]) {
\r
2124 if (appData.debugMode)
\r
2125 fprintf(debugFP, "ECHO ");
\r
2126 /* Reply only if this is a change, according
\r
2127 to the protocol rules. */
\r
2128 if (remoteEchoOption) break;
\r
2129 if (appData.localLineEditing &&
\r
2130 atoi(appData.icsPort) == TN_PORT) {
\r
2131 TelnetRequest(TN_DONT, TN_ECHO);
\r
2134 TelnetRequest(TN_DO, TN_ECHO);
\r
2135 remoteEchoOption = TRUE;
\r
2139 if (appData.debugMode)
\r
2140 fprintf(debugFP, "%d ", option);
\r
2141 /* Whatever this is, we don't want it. */
\r
2142 TelnetRequest(TN_DONT, option);
\r
2147 if (appData.debugMode)
\r
2148 fprintf(debugFP, "\n<WONT ");
\r
2149 switch (option = (unsigned char) buf[++i]) {
\r
2151 if (appData.debugMode)
\r
2152 fprintf(debugFP, "ECHO ");
\r
2153 /* Reply only if this is a change, according
\r
2154 to the protocol rules. */
\r
2155 if (!remoteEchoOption) break;
\r
2157 TelnetRequest(TN_DONT, TN_ECHO);
\r
2158 remoteEchoOption = FALSE;
\r
2161 if (appData.debugMode)
\r
2162 fprintf(debugFP, "%d ", (unsigned char) option);
\r
2163 /* Whatever this is, it must already be turned
\r
2164 off, because we never agree to turn on
\r
2165 anything non-default, so according to the
\r
2166 protocol rules, we don't reply. */
\r
2171 if (appData.debugMode)
\r
2172 fprintf(debugFP, "\n<DO ");
\r
2173 switch (option = (unsigned char) buf[++i]) {
\r
2175 /* Whatever this is, we refuse to do it. */
\r
2176 if (appData.debugMode)
\r
2177 fprintf(debugFP, "%d ", option);
\r
2178 TelnetRequest(TN_WONT, option);
\r
2183 if (appData.debugMode)
\r
2184 fprintf(debugFP, "\n<DONT ");
\r
2185 switch (option = (unsigned char) buf[++i]) {
\r
2187 if (appData.debugMode)
\r
2188 fprintf(debugFP, "%d ", option);
\r
2189 /* Whatever this is, we are already not doing
\r
2190 it, because we never agree to do anything
\r
2191 non-default, so according to the protocol
\r
2192 rules, we don't reply. */
\r
2197 if (appData.debugMode)
\r
2198 fprintf(debugFP, "\n<IAC ");
\r
2199 /* Doubled IAC; pass it through */
\r
2203 if (appData.debugMode)
\r
2204 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
\r
2205 /* Drop all other telnet commands on the floor */
\r
2208 if (oldi > next_out)
\r
2209 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2210 if (++i > next_out)
\r
2215 /* OK, this at least will *usually* work */
\r
2216 if (!loggedOn && looking_at(buf, &i, "ics%")) {
\r
2220 if (loggedOn && !intfSet) {
\r
2221 if (ics_type == ICS_ICC) {
\r
2223 "/set-quietly interface %s\n/set-quietly style 12\n",
\r
2226 } else if (ics_type == ICS_CHESSNET) {
\r
2227 sprintf(str, "/style 12\n");
\r
2229 strcpy(str, "alias $ @\n$set interface ");
\r
2230 strcat(str, programVersion);
\r
2231 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
\r
2233 strcat(str, "$iset nohighlight 1\n");
\r
2235 strcat(str, "$iset lock 1\n$style 12\n");
\r
2241 if (started == STARTED_COMMENT) {
\r
2242 /* Accumulate characters in comment */
\r
2243 parse[parse_pos++] = buf[i];
\r
2244 if (buf[i] == '\n') {
\r
2245 parse[parse_pos] = NULLCHAR;
\r
2246 if(!suppressKibitz) // [HGM] kibitz
\r
2247 AppendComment(forwardMostMove, StripHighlight(parse));
\r
2248 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
\r
2249 int nrDigit = 0, nrAlph = 0, i;
\r
2250 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
\r
2251 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
\r
2252 parse[parse_pos] = NULLCHAR;
\r
2253 // try to be smart: if it does not look like search info, it should go to
\r
2254 // ICS interaction window after all, not to engine-output window.
\r
2255 for(i=0; i<parse_pos; i++) { // count letters and digits
\r
2256 nrDigit += (parse[i] >= '0' && parse[i] <= '9');
\r
2257 nrAlph += (parse[i] >= 'a' && parse[i] <= 'z');
\r
2258 nrAlph += (parse[i] >= 'A' && parse[i] <= 'Z');
\r
2260 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
\r
2261 OutputKibitz(suppressKibitz, parse);
\r
2263 char tmp[MSG_SIZ];
\r
2264 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
\r
2265 SendToPlayer(tmp, strlen(tmp));
\r
2268 started = STARTED_NONE;
\r
2270 /* Don't match patterns against characters in chatter */
\r
2275 if (started == STARTED_CHATTER) {
\r
2276 if (buf[i] != '\n') {
\r
2277 /* Don't match patterns against characters in chatter */
\r
2281 started = STARTED_NONE;
\r
2284 /* Kludge to deal with rcmd protocol */
\r
2285 if (firstTime && looking_at(buf, &i, "\001*")) {
\r
2286 DisplayFatalError(&buf[1], 0, 1);
\r
2289 firstTime = FALSE;
\r
2292 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
\r
2293 ics_type = ICS_ICC;
\r
2295 if (appData.debugMode)
\r
2296 fprintf(debugFP, "ics_type %d\n", ics_type);
\r
2299 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
\r
2300 ics_type = ICS_FICS;
\r
2302 if (appData.debugMode)
\r
2303 fprintf(debugFP, "ics_type %d\n", ics_type);
\r
2306 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
\r
2307 ics_type = ICS_CHESSNET;
\r
2309 if (appData.debugMode)
\r
2310 fprintf(debugFP, "ics_type %d\n", ics_type);
\r
2315 (looking_at(buf, &i, "\"*\" is *a registered name") ||
\r
2316 looking_at(buf, &i, "Logging you in as \"*\"") ||
\r
2317 looking_at(buf, &i, "will be \"*\""))) {
\r
2318 strcpy(ics_handle, star_match[0]);
\r
2322 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
\r
2323 char buf[MSG_SIZ];
\r
2324 sprintf(buf, "%s@%s", ics_handle, appData.icsHost);
\r
2325 DisplayIcsInteractionTitle(buf);
\r
2326 have_set_title = TRUE;
\r
2329 /* skip finger notes */
\r
2330 if (started == STARTED_NONE &&
\r
2331 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
\r
2332 (buf[i] == '1' && buf[i+1] == '0')) &&
\r
2333 buf[i+2] == ':' && buf[i+3] == ' ') {
\r
2334 started = STARTED_CHATTER;
\r
2339 /* skip formula vars */
\r
2340 if (started == STARTED_NONE &&
\r
2341 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
\r
2342 started = STARTED_CHATTER;
\r
2348 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
\r
2349 if (appData.autoKibitz && started == STARTED_NONE &&
\r
2350 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
\r
2351 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
\r
2352 if(looking_at(buf, &i, "* kibitzes: ") &&
\r
2353 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
\r
2354 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
\r
2355 suppressKibitz = TRUE;
\r
2356 if((StrStr(star_match[0], gameInfo.white) == star_match[0])
\r
2357 && (gameMode == IcsPlayingWhite) ||
\r
2358 (StrStr(star_match[0], gameInfo.black) == star_match[0])
\r
2359 && (gameMode == IcsPlayingBlack) ) // opponent kibitz
\r
2360 started = STARTED_CHATTER; // own kibitz we simply discard
\r
2362 started = STARTED_COMMENT; // make sure it will be collected in parse[]
\r
2363 parse_pos = 0; parse[0] = NULLCHAR;
\r
2364 savingComment = TRUE;
\r
2365 suppressKibitz = gameMode != IcsObserving ? 2 :
\r
2366 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
\r
2370 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
\r
2371 started = STARTED_CHATTER;
\r
2372 suppressKibitz = TRUE;
\r
2374 } // [HGM] kibitz: end of patch
\r
2376 if (appData.zippyTalk || appData.zippyPlay) {
\r
2377 /* [DM] Backup address for color zippy lines */
\r
2381 if (loggedOn == TRUE)
\r
2382 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
\r
2383 (appData.zippyPlay && ZippyMatch(buf, &backup)));
\r
2385 if (ZippyControl(buf, &i) ||
\r
2386 ZippyConverse(buf, &i) ||
\r
2387 (appData.zippyPlay && ZippyMatch(buf, &i))) {
\r
2389 if (!appData.colorize) continue;
\r
2393 } // [DM] 'else { ' deleted
\r
2394 if (/* Don't color "message" or "messages" output */
\r
2395 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
\r
2396 looking_at(buf, &i, "*. * at *:*: ") ||
\r
2397 looking_at(buf, &i, "--* (*:*): ") ||
\r
2398 /* Regular tells and says */
\r
2399 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
\r
2400 looking_at(buf, &i, "* (your partner) tells you: ") ||
\r
2401 looking_at(buf, &i, "* says: ") ||
\r
2402 /* Message notifications (same color as tells) */
\r
2403 looking_at(buf, &i, "* has left a message ") ||
\r
2404 looking_at(buf, &i, "* just sent you a message:\n") ||
\r
2405 /* Whispers and kibitzes */
\r
2406 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
\r
2407 looking_at(buf, &i, "* kibitzes: ") ||
\r
2408 /* Channel tells */
\r
2409 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
\r
2411 if (tkind == 1 && strchr(star_match[0], ':')) {
\r
2412 /* Avoid "tells you:" spoofs in channels */
\r
2415 if (star_match[0][0] == NULLCHAR ||
\r
2416 strchr(star_match[0], ' ') ||
\r
2417 (tkind == 3 && strchr(star_match[1], ' '))) {
\r
2418 /* Reject bogus matches */
\r
2421 if (appData.colorize) {
\r
2422 if (oldi > next_out) {
\r
2423 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2428 Colorize(ColorTell, FALSE);
\r
2429 curColor = ColorTell;
\r
2432 Colorize(ColorKibitz, FALSE);
\r
2433 curColor = ColorKibitz;
\r
2436 p = strrchr(star_match[1], '(');
\r
2438 p = star_match[1];
\r
2442 if (atoi(p) == 1) {
\r
2443 Colorize(ColorChannel1, FALSE);
\r
2444 curColor = ColorChannel1;
\r
2446 Colorize(ColorChannel, FALSE);
\r
2447 curColor = ColorChannel;
\r
2451 curColor = ColorNormal;
\r
2455 if (started == STARTED_NONE && appData.autoComment &&
\r
2456 (gameMode == IcsObserving ||
\r
2457 gameMode == IcsPlayingWhite ||
\r
2458 gameMode == IcsPlayingBlack)) {
\r
2459 parse_pos = i - oldi;
\r
2460 memcpy(parse, &buf[oldi], parse_pos);
\r
2461 parse[parse_pos] = NULLCHAR;
\r
2462 started = STARTED_COMMENT;
\r
2463 savingComment = TRUE;
\r
2465 started = STARTED_CHATTER;
\r
2466 savingComment = FALSE;
\r
2473 if (looking_at(buf, &i, "* s-shouts: ") ||
\r
2474 looking_at(buf, &i, "* c-shouts: ")) {
\r
2475 if (appData.colorize) {
\r
2476 if (oldi > next_out) {
\r
2477 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2480 Colorize(ColorSShout, FALSE);
\r
2481 curColor = ColorSShout;
\r
2484 started = STARTED_CHATTER;
\r
2488 if (looking_at(buf, &i, "--->")) {
\r
2493 if (looking_at(buf, &i, "* shouts: ") ||
\r
2494 looking_at(buf, &i, "--> ")) {
\r
2495 if (appData.colorize) {
\r
2496 if (oldi > next_out) {
\r
2497 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2500 Colorize(ColorShout, FALSE);
\r
2501 curColor = ColorShout;
\r
2504 started = STARTED_CHATTER;
\r
2508 if (looking_at( buf, &i, "Challenge:")) {
\r
2509 if (appData.colorize) {
\r
2510 if (oldi > next_out) {
\r
2511 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2514 Colorize(ColorChallenge, FALSE);
\r
2515 curColor = ColorChallenge;
\r
2521 if (looking_at(buf, &i, "* offers you") ||
\r
2522 looking_at(buf, &i, "* offers to be") ||
\r
2523 looking_at(buf, &i, "* would like to") ||
\r
2524 looking_at(buf, &i, "* requests to") ||
\r
2525 looking_at(buf, &i, "Your opponent offers") ||
\r
2526 looking_at(buf, &i, "Your opponent requests")) {
\r
2528 if (appData.colorize) {
\r
2529 if (oldi > next_out) {
\r
2530 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2533 Colorize(ColorRequest, FALSE);
\r
2534 curColor = ColorRequest;
\r
2539 if (looking_at(buf, &i, "* (*) seeking")) {
\r
2540 if (appData.colorize) {
\r
2541 if (oldi > next_out) {
\r
2542 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2545 Colorize(ColorSeek, FALSE);
\r
2546 curColor = ColorSeek;
\r
2551 if (looking_at(buf, &i, "\\ ")) {
\r
2552 if (prevColor != ColorNormal) {
\r
2553 if (oldi > next_out) {
\r
2554 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2557 Colorize(prevColor, TRUE);
\r
2558 curColor = prevColor;
\r
2560 if (savingComment) {
\r
2561 parse_pos = i - oldi;
\r
2562 memcpy(parse, &buf[oldi], parse_pos);
\r
2563 parse[parse_pos] = NULLCHAR;
\r
2564 started = STARTED_COMMENT;
\r
2566 started = STARTED_CHATTER;
\r
2571 if (looking_at(buf, &i, "Black Strength :") ||
\r
2572 looking_at(buf, &i, "<<< style 10 board >>>") ||
\r
2573 looking_at(buf, &i, "<10>") ||
\r
2574 looking_at(buf, &i, "#@#")) {
\r
2575 /* Wrong board style */
\r
2577 SendToICS(ics_prefix);
\r
2578 SendToICS("set style 12\n");
\r
2579 SendToICS(ics_prefix);
\r
2580 SendToICS("refresh\n");
\r
2584 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
\r
2586 have_sent_ICS_logon = 1;
\r
2590 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
\r
2591 (looking_at(buf, &i, "\n<12> ") ||
\r
2592 looking_at(buf, &i, "<12> "))) {
\r
2594 if (oldi > next_out) {
\r
2595 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2598 started = STARTED_BOARD;
\r
2603 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
\r
2604 looking_at(buf, &i, "<b1> ")) {
\r
2605 if (oldi > next_out) {
\r
2606 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2609 started = STARTED_HOLDINGS;
\r
2614 if (looking_at(buf, &i, "* *vs. * *--- *")) {
\r
2616 /* Header for a move list -- first line */
\r
2618 switch (ics_getting_history) {
\r
2620 switch (gameMode) {
\r
2622 case BeginningOfGame:
\r
2623 /* User typed "moves" or "oldmoves" while we
\r
2624 were idle. Pretend we asked for these
\r
2625 moves and soak them up so user can step
\r
2626 through them and/or save them.
\r
2628 Reset(FALSE, TRUE);
\r
2629 gameMode = IcsObserving;
\r
2632 ics_getting_history = H_GOT_UNREQ_HEADER;
\r
2634 case EditGame: /*?*/
\r
2635 case EditPosition: /*?*/
\r
2636 /* Should above feature work in these modes too? */
\r
2637 /* For now it doesn't */
\r
2638 ics_getting_history = H_GOT_UNWANTED_HEADER;
\r
2641 ics_getting_history = H_GOT_UNWANTED_HEADER;
\r
2646 /* Is this the right one? */
\r
2647 if (gameInfo.white && gameInfo.black &&
\r
2648 strcmp(gameInfo.white, star_match[0]) == 0 &&
\r
2649 strcmp(gameInfo.black, star_match[2]) == 0) {
\r
2651 ics_getting_history = H_GOT_REQ_HEADER;
\r
2654 case H_GOT_REQ_HEADER:
\r
2655 case H_GOT_UNREQ_HEADER:
\r
2656 case H_GOT_UNWANTED_HEADER:
\r
2657 case H_GETTING_MOVES:
\r
2658 /* Should not happen */
\r
2659 DisplayError(_("Error gathering move list: two headers"), 0);
\r
2660 ics_getting_history = H_FALSE;
\r
2664 /* Save player ratings into gameInfo if needed */
\r
2665 if ((ics_getting_history == H_GOT_REQ_HEADER ||
\r
2666 ics_getting_history == H_GOT_UNREQ_HEADER) &&
\r
2667 (gameInfo.whiteRating == -1 ||
\r
2668 gameInfo.blackRating == -1)) {
\r
2670 gameInfo.whiteRating = string_to_rating(star_match[1]);
\r
2671 gameInfo.blackRating = string_to_rating(star_match[3]);
\r
2672 if (appData.debugMode)
\r
2673 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
\r
2674 gameInfo.whiteRating, gameInfo.blackRating);
\r
2679 if (looking_at(buf, &i,
\r
2680 "* * match, initial time: * minute*, increment: * second")) {
\r
2681 /* Header for a move list -- second line */
\r
2682 /* Initial board will follow if this is a wild game */
\r
2683 if (gameInfo.event != NULL) free(gameInfo.event);
\r
2684 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
\r
2685 gameInfo.event = StrSave(str);
\r
2686 /* [HGM] we switched variant. Translate boards if needed. */
\r
2687 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
\r
2691 if (looking_at(buf, &i, "Move ")) {
\r
2692 /* Beginning of a move list */
\r
2693 switch (ics_getting_history) {
\r
2695 /* Normally should not happen */
\r
2696 /* Maybe user hit reset while we were parsing */
\r
2699 /* Happens if we are ignoring a move list that is not
\r
2700 * the one we just requested. Common if the user
\r
2701 * tries to observe two games without turning off
\r
2704 case H_GETTING_MOVES:
\r
2705 /* Should not happen */
\r
2706 DisplayError(_("Error gathering move list: nested"), 0);
\r
2707 ics_getting_history = H_FALSE;
\r
2709 case H_GOT_REQ_HEADER:
\r
2710 ics_getting_history = H_GETTING_MOVES;
\r
2711 started = STARTED_MOVES;
\r
2713 if (oldi > next_out) {
\r
2714 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2717 case H_GOT_UNREQ_HEADER:
\r
2718 ics_getting_history = H_GETTING_MOVES;
\r
2719 started = STARTED_MOVES_NOHIDE;
\r
2722 case H_GOT_UNWANTED_HEADER:
\r
2723 ics_getting_history = H_FALSE;
\r
2729 if (looking_at(buf, &i, "% ") ||
\r
2730 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
\r
2731 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
\r
2732 savingComment = FALSE;
\r
2733 switch (started) {
\r
2734 case STARTED_MOVES:
\r
2735 case STARTED_MOVES_NOHIDE:
\r
2736 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
\r
2737 parse[parse_pos + i - oldi] = NULLCHAR;
\r
2738 ParseGameHistory(parse);
\r
2740 if (appData.zippyPlay && first.initDone) {
\r
2741 FeedMovesToProgram(&first, forwardMostMove);
\r
2742 if (gameMode == IcsPlayingWhite) {
\r
2743 if (WhiteOnMove(forwardMostMove)) {
\r
2744 if (first.sendTime) {
\r
2745 if (first.useColors) {
\r
2746 SendToProgram("black\n", &first);
\r
2748 SendTimeRemaining(&first, TRUE);
\r
2751 if (first.useColors) {
\r
2752 SendToProgram("white\ngo\n", &first);
\r
2754 SendToProgram("go\n", &first);
\r
2757 if (first.useColors) {
\r
2758 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
\r
2760 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
\r
2762 first.maybeThinking = TRUE;
\r
2764 if (first.usePlayother) {
\r
2765 if (first.sendTime) {
\r
2766 SendTimeRemaining(&first, TRUE);
\r
2768 SendToProgram("playother\n", &first);
\r
2769 firstMove = FALSE;
\r
2774 } else if (gameMode == IcsPlayingBlack) {
\r
2775 if (!WhiteOnMove(forwardMostMove)) {
\r
2776 if (first.sendTime) {
\r
2777 if (first.useColors) {
\r
2778 SendToProgram("white\n", &first);
\r
2780 SendTimeRemaining(&first, FALSE);
\r
2783 if (first.useColors) {
\r
2784 SendToProgram("black\ngo\n", &first);
\r
2786 SendToProgram("go\n", &first);
\r
2789 if (first.useColors) {
\r
2790 SendToProgram("black\n", &first);
\r
2792 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
\r
2794 first.maybeThinking = TRUE;
\r
2796 if (first.usePlayother) {
\r
2797 if (first.sendTime) {
\r
2798 SendTimeRemaining(&first, FALSE);
\r
2800 SendToProgram("playother\n", &first);
\r
2801 firstMove = FALSE;
\r
2809 if (gameMode == IcsObserving && ics_gamenum == -1) {
\r
2810 /* Moves came from oldmoves or moves command
\r
2811 while we weren't doing anything else.
\r
2813 currentMove = forwardMostMove;
\r
2814 ClearHighlights();/*!!could figure this out*/
\r
2815 flipView = appData.flipView;
\r
2816 DrawPosition(FALSE, boards[currentMove]);
\r
2817 DisplayBothClocks();
\r
2818 sprintf(str, "%s vs. %s",
\r
2819 gameInfo.white, gameInfo.black);
\r
2820 DisplayTitle(str);
\r
2821 gameMode = IcsIdle;
\r
2823 /* Moves were history of an active game */
\r
2824 if (gameInfo.resultDetails != NULL) {
\r
2825 free(gameInfo.resultDetails);
\r
2826 gameInfo.resultDetails = NULL;
\r
2829 HistorySet(parseList, backwardMostMove,
\r
2830 forwardMostMove, currentMove-1);
\r
2831 DisplayMove(currentMove - 1);
\r
2832 if (started == STARTED_MOVES) next_out = i;
\r
2833 started = STARTED_NONE;
\r
2834 ics_getting_history = H_FALSE;
\r
2837 case STARTED_OBSERVE:
\r
2838 started = STARTED_NONE;
\r
2839 SendToICS(ics_prefix);
\r
2840 SendToICS("refresh\n");
\r
2846 if(bookHit) { // [HGM] book: simulate book reply
\r
2847 static char bookMove[MSG_SIZ]; // a bit generous?
\r
2849 programStats.depth = programStats.nodes = programStats.time =
\r
2850 programStats.score = programStats.got_only_move = 0;
\r
2851 sprintf(programStats.movelist, "%s (xbook)", bookHit);
\r
2853 strcpy(bookMove, "move ");
\r
2854 strcat(bookMove, bookHit);
\r
2855 HandleMachineMove(bookMove, &first);
\r
2860 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
\r
2861 started == STARTED_HOLDINGS ||
\r
2862 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
\r
2863 /* Accumulate characters in move list or board */
\r
2864 parse[parse_pos++] = buf[i];
\r
2867 /* Start of game messages. Mostly we detect start of game
\r
2868 when the first board image arrives. On some versions
\r
2869 of the ICS, though, we need to do a "refresh" after starting
\r
2870 to observe in order to get the current board right away. */
\r
2871 if (looking_at(buf, &i, "Adding game * to observation list")) {
\r
2872 started = STARTED_OBSERVE;
\r
2876 /* Handle auto-observe */
\r
2877 if (appData.autoObserve &&
\r
2878 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
\r
2879 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
\r
2881 /* Choose the player that was highlighted, if any. */
\r
2882 if (star_match[0][0] == '\033' ||
\r
2883 star_match[1][0] != '\033') {
\r
2884 player = star_match[0];
\r
2886 player = star_match[2];
\r
2888 sprintf(str, "%sobserve %s\n",
\r
2889 ics_prefix, StripHighlightAndTitle(player));
\r
2892 /* Save ratings from notify string */
\r
2893 strcpy(player1Name, star_match[0]);
\r
2894 player1Rating = string_to_rating(star_match[1]);
\r
2895 strcpy(player2Name, star_match[2]);
\r
2896 player2Rating = string_to_rating(star_match[3]);
\r
2898 if (appData.debugMode)
\r
2900 "Ratings from 'Game notification:' %s %d, %s %d\n",
\r
2901 player1Name, player1Rating,
\r
2902 player2Name, player2Rating);
\r
2907 /* Deal with automatic examine mode after a game,
\r
2908 and with IcsObserving -> IcsExamining transition */
\r
2909 if (looking_at(buf, &i, "Entering examine mode for game *") ||
\r
2910 looking_at(buf, &i, "has made you an examiner of game *")) {
\r
2912 int gamenum = atoi(star_match[0]);
\r
2913 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
\r
2914 gamenum == ics_gamenum) {
\r
2915 /* We were already playing or observing this game;
\r
2916 no need to refetch history */
\r
2917 gameMode = IcsExamining;
\r
2919 pauseExamForwardMostMove = forwardMostMove;
\r
2920 } else if (currentMove < forwardMostMove) {
\r
2921 ForwardInner(forwardMostMove);
\r
2924 /* I don't think this case really can happen */
\r
2925 SendToICS(ics_prefix);
\r
2926 SendToICS("refresh\n");
\r
2931 /* Error messages */
\r
2932 if (ics_user_moved) {
\r
2933 if (looking_at(buf, &i, "Illegal move") ||
\r
2934 looking_at(buf, &i, "Not a legal move") ||
\r
2935 looking_at(buf, &i, "Your king is in check") ||
\r
2936 looking_at(buf, &i, "It isn't your turn") ||
\r
2937 looking_at(buf, &i, "It is not your move")) {
\r
2938 /* Illegal move */
\r
2939 ics_user_moved = 0;
\r
2940 if (forwardMostMove > backwardMostMove) {
\r
2941 currentMove = --forwardMostMove;
\r
2942 DisplayMove(currentMove - 1); /* before DMError */
\r
2943 DisplayMoveError(_("Illegal move (rejected by ICS)"));
\r
2944 DrawPosition(FALSE, boards[currentMove]);
\r
2946 DisplayBothClocks();
\r
2952 if (looking_at(buf, &i, "still have time") ||
\r
2953 looking_at(buf, &i, "not out of time") ||
\r
2954 looking_at(buf, &i, "either player is out of time") ||
\r
2955 looking_at(buf, &i, "has timeseal; checking")) {
\r
2956 /* We must have called his flag a little too soon */
\r
2957 whiteFlag = blackFlag = FALSE;
\r
2961 if (looking_at(buf, &i, "added * seconds to") ||
\r
2962 looking_at(buf, &i, "seconds were added to")) {
\r
2963 /* Update the clocks */
\r
2964 SendToICS(ics_prefix);
\r
2965 SendToICS("refresh\n");
\r
2969 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
\r
2970 ics_clock_paused = TRUE;
\r
2975 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
\r
2976 ics_clock_paused = FALSE;
\r
2981 /* Grab player ratings from the Creating: message.
\r
2982 Note we have to check for the special case when
\r
2983 the ICS inserts things like [white] or [black]. */
\r
2984 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
\r
2985 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
\r
2987 0 player 1 name (not necessarily white)
\r
2989 2 empty, white, or black (IGNORED)
\r
2990 3 player 2 name (not necessarily black)
\r
2993 The names/ratings are sorted out when the game
\r
2994 actually starts (below).
\r
2996 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
\r
2997 player1Rating = string_to_rating(star_match[1]);
\r
2998 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
\r
2999 player2Rating = string_to_rating(star_match[4]);
\r
3001 if (appData.debugMode)
\r
3003 "Ratings from 'Creating:' %s %d, %s %d\n",
\r
3004 player1Name, player1Rating,
\r
3005 player2Name, player2Rating);
\r
3010 /* Improved generic start/end-of-game messages */
\r
3011 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
\r
3012 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
\r
3013 /* If tkind == 0: */
\r
3014 /* star_match[0] is the game number */
\r
3015 /* [1] is the white player's name */
\r
3016 /* [2] is the black player's name */
\r
3017 /* For end-of-game: */
\r
3018 /* [3] is the reason for the game end */
\r
3019 /* [4] is a PGN end game-token, preceded by " " */
\r
3020 /* For start-of-game: */
\r
3021 /* [3] begins with "Creating" or "Continuing" */
\r
3022 /* [4] is " *" or empty (don't care). */
\r
3023 int gamenum = atoi(star_match[0]);
\r
3024 char *whitename, *blackname, *why, *endtoken;
\r
3025 ChessMove endtype = (ChessMove) 0;
\r
3028 whitename = star_match[1];
\r
3029 blackname = star_match[2];
\r
3030 why = star_match[3];
\r
3031 endtoken = star_match[4];
\r
3033 whitename = star_match[1];
\r
3034 blackname = star_match[3];
\r
3035 why = star_match[5];
\r
3036 endtoken = star_match[6];
\r
3039 /* Game start messages */
\r
3040 if (strncmp(why, "Creating ", 9) == 0 ||
\r
3041 strncmp(why, "Continuing ", 11) == 0) {
\r
3042 gs_gamenum = gamenum;
\r
3043 strcpy(gs_kind, strchr(why, ' ') + 1);
\r
3045 if (appData.zippyPlay) {
\r
3046 ZippyGameStart(whitename, blackname);
\r
3052 /* Game end messages */
\r
3053 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
\r
3054 ics_gamenum != gamenum) {
\r
3057 while (endtoken[0] == ' ') endtoken++;
\r
3058 switch (endtoken[0]) {
\r
3061 endtype = GameUnfinished;
\r
3064 endtype = BlackWins;
\r
3067 if (endtoken[1] == '/')
\r
3068 endtype = GameIsDrawn;
\r
3070 endtype = WhiteWins;
\r
3073 GameEnds(endtype, why, GE_ICS);
\r
3075 if (appData.zippyPlay && first.initDone) {
\r
3076 ZippyGameEnd(endtype, why);
\r
3077 if (first.pr == NULL) {
\r
3078 /* Start the next process early so that we'll
\r
3079 be ready for the next challenge */
\r
3080 StartChessProgram(&first);
\r
3082 /* Send "new" early, in case this command takes
\r
3083 a long time to finish, so that we'll be ready
\r
3084 for the next challenge. */
\r
3085 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
\r
3086 Reset(TRUE, TRUE);
\r
3092 if (looking_at(buf, &i, "Removing game * from observation") ||
\r
3093 looking_at(buf, &i, "no longer observing game *") ||
\r
3094 looking_at(buf, &i, "Game * (*) has no examiners")) {
\r
3095 if (gameMode == IcsObserving &&
\r
3096 atoi(star_match[0]) == ics_gamenum)
\r
3098 /* icsEngineAnalyze */
\r
3099 if (appData.icsEngineAnalyze) {
\r
3100 ExitAnalyzeMode();
\r
3104 gameMode = IcsIdle;
\r
3106 ics_user_moved = FALSE;
\r
3111 if (looking_at(buf, &i, "no longer examining game *")) {
\r
3112 if (gameMode == IcsExamining &&
\r
3113 atoi(star_match[0]) == ics_gamenum)
\r
3115 gameMode = IcsIdle;
\r
3117 ics_user_moved = FALSE;
\r
3122 /* Advance leftover_start past any newlines we find,
\r
3123 so only partial lines can get reparsed */
\r
3124 if (looking_at(buf, &i, "\n")) {
\r
3125 prevColor = curColor;
\r
3126 if (curColor != ColorNormal) {
\r
3127 if (oldi > next_out) {
\r
3128 SendToPlayer(&buf[next_out], oldi - next_out);
\r
3131 Colorize(ColorNormal, FALSE);
\r
3132 curColor = ColorNormal;
\r
3134 if (started == STARTED_BOARD) {
\r
3135 started = STARTED_NONE;
\r
3136 parse[parse_pos] = NULLCHAR;
\r
3137 ParseBoard12(parse);
\r
3138 ics_user_moved = 0;
\r
3140 /* Send premove here */
\r
3141 if (appData.premove) {
\r
3142 char str[MSG_SIZ];
\r
3143 if (currentMove == 0 &&
\r
3144 gameMode == IcsPlayingWhite &&
\r
3145 appData.premoveWhite) {
\r
3146 sprintf(str, "%s%s\n", ics_prefix,
\r
3147 appData.premoveWhiteText);
\r
3148 if (appData.debugMode)
\r
3149 fprintf(debugFP, "Sending premove:\n");
\r
3151 } else if (currentMove == 1 &&
\r
3152 gameMode == IcsPlayingBlack &&
\r
3153 appData.premoveBlack) {
\r
3154 sprintf(str, "%s%s\n", ics_prefix,
\r
3155 appData.premoveBlackText);
\r
3156 if (appData.debugMode)
\r
3157 fprintf(debugFP, "Sending premove:\n");
\r
3159 } else if (gotPremove) {
\r
3161 ClearPremoveHighlights();
\r
3162 if (appData.debugMode)
\r
3163 fprintf(debugFP, "Sending premove:\n");
\r
3164 UserMoveEvent(premoveFromX, premoveFromY,
\r
3165 premoveToX, premoveToY,
\r
3166 premovePromoChar);
\r
3170 /* Usually suppress following prompt */
\r
3171 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
\r
3172 if (looking_at(buf, &i, "*% ")) {
\r
3173 savingComment = FALSE;
\r
3177 } else if (started == STARTED_HOLDINGS) {
\r
3179 char new_piece[MSG_SIZ];
\r
3180 started = STARTED_NONE;
\r
3181 parse[parse_pos] = NULLCHAR;
\r
3182 if (appData.debugMode)
\r
3183 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
\r
3184 parse, currentMove);
\r
3185 if (sscanf(parse, " game %d", &gamenum) == 1 &&
\r
3186 gamenum == ics_gamenum) {
\r
3187 if (gameInfo.variant == VariantNormal) {
\r
3188 /* [HGM] We seem to switch variant during a game!
\r
3189 * Presumably no holdings were displayed, so we have
\r
3190 * to move the position two files to the right to
\r
3191 * create room for them!
\r
3193 VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
\r
3194 /* Get a move list just to see the header, which
\r
3195 will tell us whether this is really bug or zh */
\r
3196 if (ics_getting_history == H_FALSE) {
\r
3197 ics_getting_history = H_REQUESTED;
\r
3198 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
\r
3202 new_piece[0] = NULLCHAR;
\r
3203 sscanf(parse, "game %d white [%s black [%s <- %s",
\r
3204 &gamenum, white_holding, black_holding,
\r
3206 white_holding[strlen(white_holding)-1] = NULLCHAR;
\r
3207 black_holding[strlen(black_holding)-1] = NULLCHAR;
\r
3208 /* [HGM] copy holdings to board holdings area */
\r
3209 CopyHoldings(boards[currentMove], white_holding, WhitePawn);
\r
3210 CopyHoldings(boards[currentMove], black_holding, BlackPawn);
\r
3212 if (appData.zippyPlay && first.initDone) {
\r
3213 ZippyHoldings(white_holding, black_holding,
\r
3217 if (tinyLayout || smallLayout) {
\r
3218 char wh[16], bh[16];
\r
3219 PackHolding(wh, white_holding);
\r
3220 PackHolding(bh, black_holding);
\r
3221 sprintf(str, "[%s-%s] %s-%s", wh, bh,
\r
3222 gameInfo.white, gameInfo.black);
\r
3224 sprintf(str, "%s [%s] vs. %s [%s]",
\r
3225 gameInfo.white, white_holding,
\r
3226 gameInfo.black, black_holding);
\r
3229 DrawPosition(FALSE, boards[currentMove]);
\r
3230 DisplayTitle(str);
\r
3232 /* Suppress following prompt */
\r
3233 if (looking_at(buf, &i, "*% ")) {
\r
3234 savingComment = FALSE;
\r
3241 i++; /* skip unparsed character and loop back */
\r
3244 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
\r
3245 started != STARTED_HOLDINGS && i > next_out) {
\r
3246 SendToPlayer(&buf[next_out], i - next_out);
\r
3249 suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
\r
3251 leftover_len = buf_len - leftover_start;
\r
3252 /* if buffer ends with something we couldn't parse,
\r
3253 reparse it after appending the next read */
\r
3255 } else if (count == 0) {
\r
3256 RemoveInputSource(isr);
\r
3257 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
\r
3259 DisplayFatalError(_("Error reading from ICS"), error, 1);
\r
3264 /* Board style 12 looks like this:
\r
3266 <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
3268 * The "<12> " is stripped before it gets to this routine. The two
\r
3269 * trailing 0's (flip state and clock ticking) are later addition, and
\r
3270 * some chess servers may not have them, or may have only the first.
\r
3271 * Additional trailing fields may be added in the future.
\r
3274 #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
3276 #define RELATION_OBSERVING_PLAYED 0
\r
3277 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
\r
3278 #define RELATION_PLAYING_MYMOVE 1
\r
3279 #define RELATION_PLAYING_NOTMYMOVE -1
\r
3280 #define RELATION_EXAMINING 2
\r
3281 #define RELATION_ISOLATED_BOARD -3
\r
3282 #define RELATION_STARTING_POSITION -4 /* FICS only */
\r
3285 ParseBoard12(string)
\r
3288 GameMode newGameMode;
\r
3289 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
\r
3290 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
\r
3291 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
\r
3292 char to_play, board_chars[200];
\r
3293 char move_str[500], str[500], elapsed_time[500];
\r
3294 char black[32], white[32];
\r
3296 int prevMove = currentMove;
\r
3298 ChessMove moveType;
\r
3299 int fromX, fromY, toX, toY;
\r
3301 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
\r
3302 char *bookHit = NULL; // [HGM] book
\r
3304 fromX = fromY = toX = toY = -1;
\r
3308 if (appData.debugMode)
\r
3309 fprintf(debugFP, _("Parsing board: %s\n"), string);
\r
3311 move_str[0] = NULLCHAR;
\r
3312 elapsed_time[0] = NULLCHAR;
\r
3313 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
\r
3315 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
\r
3316 if(string[i] == ' ') { ranks++; files = 0; }
\r
3320 for(j = 0; j <i; j++) board_chars[j] = string[j];
\r
3321 board_chars[i] = '\0';
\r
3324 n = sscanf(string, PATTERN, &to_play, &double_push,
\r
3325 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
\r
3326 &gamenum, white, black, &relation, &basetime, &increment,
\r
3327 &white_stren, &black_stren, &white_time, &black_time,
\r
3328 &moveNum, str, elapsed_time, move_str, &ics_flip,
\r
3332 sprintf(str, _("Failed to parse board string:\n\"%s\""), string);
\r
3333 DisplayError(str, 0);
\r
3337 /* Convert the move number to internal form */
\r
3338 moveNum = (moveNum - 1) * 2;
\r
3339 if (to_play == 'B') moveNum++;
\r
3340 if (moveNum >= MAX_MOVES) {
\r
3341 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
\r
3346 switch (relation) {
\r
3347 case RELATION_OBSERVING_PLAYED:
\r
3348 case RELATION_OBSERVING_STATIC:
\r
3349 if (gamenum == -1) {
\r
3350 /* Old ICC buglet */
\r
3351 relation = RELATION_OBSERVING_STATIC;
\r
3353 newGameMode = IcsObserving;
\r
3355 case RELATION_PLAYING_MYMOVE:
\r
3356 case RELATION_PLAYING_NOTMYMOVE:
\r
3358 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
\r
3359 IcsPlayingWhite : IcsPlayingBlack;
\r
3361 case RELATION_EXAMINING:
\r
3362 newGameMode = IcsExamining;
\r
3364 case RELATION_ISOLATED_BOARD:
\r
3366 /* Just display this board. If user was doing something else,
\r
3367 we will forget about it until the next board comes. */
\r
3368 newGameMode = IcsIdle;
\r
3370 case RELATION_STARTING_POSITION:
\r
3371 newGameMode = gameMode;
\r
3375 /* Modify behavior for initial board display on move listing
\r
3378 switch (ics_getting_history) {
\r
3382 case H_GOT_REQ_HEADER:
\r
3383 case H_GOT_UNREQ_HEADER:
\r
3384 /* This is the initial position of the current game */
\r
3385 gamenum = ics_gamenum;
\r
3386 moveNum = 0; /* old ICS bug workaround */
\r
3387 if (to_play == 'B') {
\r
3388 startedFromSetupPosition = TRUE;
\r
3389 blackPlaysFirst = TRUE;
\r
3391 if (forwardMostMove == 0) forwardMostMove = 1;
\r
3392 if (backwardMostMove == 0) backwardMostMove = 1;
\r
3393 if (currentMove == 0) currentMove = 1;
\r
3395 newGameMode = gameMode;
\r
3396 relation = RELATION_STARTING_POSITION; /* ICC needs this */
\r
3398 case H_GOT_UNWANTED_HEADER:
\r
3399 /* This is an initial board that we don't want */
\r
3401 case H_GETTING_MOVES:
\r
3402 /* Should not happen */
\r
3403 DisplayError(_("Error gathering move list: extra board"), 0);
\r
3404 ics_getting_history = H_FALSE;
\r
3408 /* Take action if this is the first board of a new game, or of a
\r
3409 different game than is currently being displayed. */
\r
3410 if (gamenum != ics_gamenum || newGameMode != gameMode ||
\r
3411 relation == RELATION_ISOLATED_BOARD) {
\r
3413 /* Forget the old game and get the history (if any) of the new one */
\r
3414 if (gameMode != BeginningOfGame) {
\r
3415 Reset(FALSE, TRUE);
\r
3418 if (appData.autoRaiseBoard) BoardToTop();
\r
3420 if (gamenum == -1) {
\r
3421 newGameMode = IcsIdle;
\r
3422 } else if (moveNum > 0 && newGameMode != IcsIdle &&
\r
3423 appData.getMoveList) {
\r
3424 /* Need to get game history */
\r
3425 ics_getting_history = H_REQUESTED;
\r
3426 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
\r
3430 /* Initially flip the board to have black on the bottom if playing
\r
3431 black or if the ICS flip flag is set, but let the user change
\r
3432 it with the Flip View button. */
\r
3433 flipView = appData.autoFlipView ?
\r
3434 (newGameMode == IcsPlayingBlack) || ics_flip :
\r
3437 /* Done with values from previous mode; copy in new ones */
\r
3438 gameMode = newGameMode;
\r
3440 ics_gamenum = gamenum;
\r
3441 if (gamenum == gs_gamenum) {
\r
3442 int klen = strlen(gs_kind);
\r
3443 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
\r
3444 sprintf(str, "ICS %s", gs_kind);
\r
3445 gameInfo.event = StrSave(str);
\r
3447 gameInfo.event = StrSave("ICS game");
\r
3449 gameInfo.site = StrSave(appData.icsHost);
\r
3450 gameInfo.date = PGNDate();
\r
3451 gameInfo.round = StrSave("-");
\r
3452 gameInfo.white = StrSave(white);
\r
3453 gameInfo.black = StrSave(black);
\r
3454 timeControl = basetime * 60 * 1000;
\r
3455 timeControl_2 = 0;
\r
3456 timeIncrement = increment * 1000;
\r
3457 movesPerSession = 0;
\r
3458 gameInfo.timeControl = TimeControlTagValue();
\r
3459 VariantSwitch(board, StringToVariant(gameInfo.event) );
\r
3460 if (appData.debugMode) {
\r
3461 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
\r
3462 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
\r
3463 setbuf(debugFP, NULL);
\r
3466 gameInfo.outOfBook = NULL;
\r
3468 /* Do we have the ratings? */
\r
3469 if (strcmp(player1Name, white) == 0 &&
\r
3470 strcmp(player2Name, black) == 0) {
\r
3471 if (appData.debugMode)
\r
3472 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
\r
3473 player1Rating, player2Rating);
\r
3474 gameInfo.whiteRating = player1Rating;
\r
3475 gameInfo.blackRating = player2Rating;
\r
3476 } else if (strcmp(player2Name, white) == 0 &&
\r
3477 strcmp(player1Name, black) == 0) {
\r
3478 if (appData.debugMode)
\r
3479 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
\r
3480 player2Rating, player1Rating);
\r
3481 gameInfo.whiteRating = player2Rating;
\r
3482 gameInfo.blackRating = player1Rating;
\r
3484 player1Name[0] = player2Name[0] = NULLCHAR;
\r
3486 /* Silence shouts if requested */
\r
3487 if (appData.quietPlay &&
\r
3488 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
\r
3489 SendToICS(ics_prefix);
\r
3490 SendToICS("set shout 0\n");
\r
3494 /* Deal with midgame name changes */
\r
3496 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
\r
3497 if (gameInfo.white) free(gameInfo.white);
\r
3498 gameInfo.white = StrSave(white);
\r
3500 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
\r
3501 if (gameInfo.black) free(gameInfo.black);
\r
3502 gameInfo.black = StrSave(black);
\r
3506 /* Throw away game result if anything actually changes in examine mode */
\r
3507 if (gameMode == IcsExamining && !newGame) {
\r
3508 gameInfo.result = GameUnfinished;
\r
3509 if (gameInfo.resultDetails != NULL) {
\r
3510 free(gameInfo.resultDetails);
\r
3511 gameInfo.resultDetails = NULL;
\r
3515 /* In pausing && IcsExamining mode, we ignore boards coming
\r
3516 in if they are in a different variation than we are. */
\r
3517 if (pauseExamInvalid) return;
\r
3518 if (pausing && gameMode == IcsExamining) {
\r
3519 if (moveNum <= pauseExamForwardMostMove) {
\r
3520 pauseExamInvalid = TRUE;
\r
3521 forwardMostMove = pauseExamForwardMostMove;
\r
3526 if (appData.debugMode) {
\r
3527 fprintf(debugFP, "load %dx%d board\n", files, ranks);
\r
3529 /* Parse the board */
\r
3530 for (k = 0; k < ranks; k++) {
\r
3531 for (j = 0; j < files; j++)
\r
3532 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
\r
3533 if(gameInfo.holdingsWidth > 1) {
\r
3534 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
\r
3535 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
\r
3538 CopyBoard(boards[moveNum], board);
\r
3539 if (moveNum == 0) {
\r
3540 startedFromSetupPosition =
\r
3541 !CompareBoards(board, initialPosition);
\r
3542 if(startedFromSetupPosition)
\r
3543 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
\r
3546 /* [HGM] Set castling rights. Take the outermost Rooks,
\r
3547 to make it also work for FRC opening positions. Note that board12
\r
3548 is really defective for later FRC positions, as it has no way to
\r
3549 indicate which Rook can castle if they are on the same side of King.
\r
3550 For the initial position we grant rights to the outermost Rooks,
\r
3551 and remember thos rights, and we then copy them on positions
\r
3552 later in an FRC game. This means WB might not recognize castlings with
\r
3553 Rooks that have moved back to their original position as illegal,
\r
3554 but in ICS mode that is not its job anyway.
\r
3556 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
\r
3557 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
\r
3559 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
\r
3560 if(board[0][i] == WhiteRook) j = i;
\r
3561 initialRights[0] = castlingRights[moveNum][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
\r
3562 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
\r
3563 if(board[0][i] == WhiteRook) j = i;
\r
3564 initialRights[1] = castlingRights[moveNum][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
\r
3565 for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
\r
3566 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
\r
3567 initialRights[3] = castlingRights[moveNum][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
\r
3568 for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
\r
3569 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
\r
3570 initialRights[4] = castlingRights[moveNum][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? -1 : j);
\r
3572 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
\r
3573 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
\r
3574 if(board[0][k] == wKing) initialRights[2] = castlingRights[moveNum][2] = k;
\r
3575 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
\r
3576 if(board[BOARD_HEIGHT-1][k] == bKing)
\r
3577 initialRights[5] = castlingRights[moveNum][5] = k;
\r
3579 r = castlingRights[moveNum][0] = initialRights[0];
\r
3580 if(board[0][r] != WhiteRook) castlingRights[moveNum][0] = -1;
\r
3581 r = castlingRights[moveNum][1] = initialRights[1];
\r
3582 if(board[0][r] != WhiteRook) castlingRights[moveNum][1] = -1;
\r
3583 r = castlingRights[moveNum][3] = initialRights[3];
\r
3584 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][3] = -1;
\r
3585 r = castlingRights[moveNum][4] = initialRights[4];
\r
3586 if(board[BOARD_HEIGHT-1][r] != BlackRook) castlingRights[moveNum][4] = -1;
\r
3587 /* wildcastle kludge: always assume King has rights */
\r
3588 r = castlingRights[moveNum][2] = initialRights[2];
\r
3589 r = castlingRights[moveNum][5] = initialRights[5];
\r
3591 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
\r
3592 epStatus[moveNum] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
\r
3595 if (ics_getting_history == H_GOT_REQ_HEADER ||
\r
3596 ics_getting_history == H_GOT_UNREQ_HEADER) {
\r
3597 /* This was an initial position from a move list, not
\r
3598 the current position */
\r
3602 /* Update currentMove and known move number limits */
\r
3603 newMove = newGame || moveNum > forwardMostMove;
\r
3605 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
\r
3606 if (!newGame && appData.icsEngineAnalyze && moveNum < forwardMostMove) {
\r
3607 takeback = forwardMostMove - moveNum;
\r
3608 for (i = 0; i < takeback; i++) {
\r
3609 if (appData.debugMode) fprintf(debugFP, "take back move\n");
\r
3610 SendToProgram("undo\n", &first);
\r
3615 forwardMostMove = backwardMostMove = currentMove = moveNum;
\r
3616 if (gameMode == IcsExamining && moveNum == 0) {
\r
3617 /* Workaround for ICS limitation: we are not told the wild
\r
3618 type when starting to examine a game. But if we ask for
\r
3619 the move list, the move list header will tell us */
\r
3620 ics_getting_history = H_REQUESTED;
\r
3621 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
\r
3624 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
\r
3625 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
\r
3626 forwardMostMove = moveNum;
\r
3627 if (!pausing || currentMove > forwardMostMove)
\r
3628 currentMove = forwardMostMove;
\r
3630 /* New part of history that is not contiguous with old part */
\r
3631 if (pausing && gameMode == IcsExamining) {
\r
3632 pauseExamInvalid = TRUE;
\r
3633 forwardMostMove = pauseExamForwardMostMove;
\r
3636 forwardMostMove = backwardMostMove = currentMove = moveNum;
\r
3637 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
\r
3638 ics_getting_history = H_REQUESTED;
\r
3639 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
\r
3644 /* Update the clocks */
\r
3645 if (strchr(elapsed_time, '.')) {
\r
3646 /* Time is in ms */
\r
3647 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
\r
3648 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
\r
3650 /* Time is in seconds */
\r
3651 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
\r
3652 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
\r
3657 if (appData.zippyPlay && newGame &&
\r
3658 gameMode != IcsObserving && gameMode != IcsIdle &&
\r
3659 gameMode != IcsExamining)
\r
3660 ZippyFirstBoard(moveNum, basetime, increment);
\r
3663 /* Put the move on the move list, first converting
\r
3664 to canonical algebraic form. */
\r
3665 if (moveNum > 0) {
\r
3666 if (appData.debugMode) {
\r
3667 if (appData.debugMode) { int f = forwardMostMove;
\r
3668 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
\r
3669 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
\r
3671 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
\r
3672 fprintf(debugFP, "moveNum = %d\n", moveNum);
\r
3673 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
\r
3674 setbuf(debugFP, NULL);
\r
3676 if (moveNum <= backwardMostMove) {
\r
3677 /* We don't know what the board looked like before
\r
3678 this move. Punt. */
\r
3679 strcpy(parseList[moveNum - 1], move_str);
\r
3680 strcat(parseList[moveNum - 1], " ");
\r
3681 strcat(parseList[moveNum - 1], elapsed_time);
\r
3682 moveList[moveNum - 1][0] = NULLCHAR;
\r
3683 } else if (strcmp(move_str, "none") == 0) {
\r
3684 // [HGM] long SAN: swapped order; test for 'none' before parsing move
\r
3685 /* Again, we don't know what the board looked like;
\r
3686 this is really the start of the game. */
\r
3687 parseList[moveNum - 1][0] = NULLCHAR;
\r
3688 moveList[moveNum - 1][0] = NULLCHAR;
\r
3689 backwardMostMove = moveNum;
\r
3690 startedFromSetupPosition = TRUE;
\r
3691 fromX = fromY = toX = toY = -1;
\r
3693 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
\r
3694 // So we parse the long-algebraic move string in stead of the SAN move
\r
3695 int valid; char buf[MSG_SIZ], *prom;
\r
3697 // str looks something like "Q/a1-a2"; kill the slash
\r
3698 if(str[1] == '/')
\r
3699 sprintf(buf, "%c%s", str[0], str+2);
\r
3700 else strcpy(buf, str); // might be castling
\r
3701 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
\r
3702 strcat(buf, prom); // long move lacks promo specification!
\r
3703 if(!appData.testLegality) {
\r
3704 if(appData.debugMode)
\r
3705 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
\r
3706 strcpy(move_str, buf);
\r
3708 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
\r
3709 &fromX, &fromY, &toX, &toY, &promoChar)
\r
3710 || ParseOneMove(buf, moveNum - 1, &moveType,
\r
3711 &fromX, &fromY, &toX, &toY, &promoChar);
\r
3712 // end of long SAN patch
\r
3714 (void) CoordsToAlgebraic(boards[moveNum - 1],
\r
3715 PosFlags(moveNum - 1), EP_UNKNOWN,
\r
3716 fromY, fromX, toY, toX, promoChar,
\r
3717 parseList[moveNum-1]);
\r
3718 switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
\r
3719 castlingRights[moveNum]) ) {
\r
3721 case MT_STALEMATE:
\r
3725 if(gameInfo.variant != VariantShogi)
\r
3726 strcat(parseList[moveNum - 1], "+");
\r
3728 case MT_CHECKMATE:
\r
3729 strcat(parseList[moveNum - 1], "#");
\r
3732 strcat(parseList[moveNum - 1], " ");
\r
3733 strcat(parseList[moveNum - 1], elapsed_time);
\r
3734 /* currentMoveString is set as a side-effect of ParseOneMove */
\r
3735 strcpy(moveList[moveNum - 1], currentMoveString);
\r
3736 strcat(moveList[moveNum - 1], "\n");
\r
3738 /* Move from ICS was illegal!? Punt. */
\r
3739 if (appData.debugMode) {
\r
3740 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
\r
3741 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
\r
3744 if (appData.testLegality && appData.debugMode) {
\r
3745 sprintf(str, "Illegal move \"%s\" from ICS", move_str);
\r
3746 DisplayError(str, 0);
\r
3749 strcpy(parseList[moveNum - 1], move_str);
\r
3750 strcat(parseList[moveNum - 1], " ");
\r
3751 strcat(parseList[moveNum - 1], elapsed_time);
\r
3752 moveList[moveNum - 1][0] = NULLCHAR;
\r
3753 fromX = fromY = toX = toY = -1;
\r
3756 if (appData.debugMode) {
\r
3757 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
\r
3758 setbuf(debugFP, NULL);
\r
3762 /* Send move to chess program (BEFORE animating it). */
\r
3763 if (appData.zippyPlay && !newGame && newMove &&
\r
3764 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
\r
3766 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
\r
3767 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
\r
3768 if (moveList[moveNum - 1][0] == NULLCHAR) {
\r
3769 sprintf(str, _("Couldn't parse move \"%s\" from ICS"),
\r
3771 DisplayError(str, 0);
\r
3773 if (first.sendTime) {
\r
3774 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
\r
3776 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
\r
3777 if (firstMove && !bookHit) {
\r
3778 firstMove = FALSE;
\r
3779 if (first.useColors) {
\r
3780 SendToProgram(gameMode == IcsPlayingWhite ?
\r
3782 "black\ngo\n", &first);
\r
3784 SendToProgram("go\n", &first);
\r
3786 first.maybeThinking = TRUE;
\r
3789 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
\r
3790 if (moveList[moveNum - 1][0] == NULLCHAR) {
\r
3791 sprintf(str, _("Couldn't parse move \"%s\" from ICS"), move_str);
\r
3792 DisplayError(str, 0);
\r
3794 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
\r
3795 SendMoveToProgram(moveNum - 1, &first);
\r
3802 if (moveNum > 0 && !gotPremove) {
\r
3803 /* If move comes from a remote source, animate it. If it
\r
3804 isn't remote, it will have already been animated. */
\r
3805 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
\r
3806 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
\r
3808 if (!pausing && appData.highlightLastMove) {
\r
3809 SetHighlights(fromX, fromY, toX, toY);
\r
3813 /* Start the clocks */
\r
3814 whiteFlag = blackFlag = FALSE;
\r
3815 appData.clockMode = !(basetime == 0 && increment == 0);
\r
3816 if (ticking == 0) {
\r
3817 ics_clock_paused = TRUE;
\r
3819 } else if (ticking == 1) {
\r
3820 ics_clock_paused = FALSE;
\r
3822 if (gameMode == IcsIdle ||
\r
3823 relation == RELATION_OBSERVING_STATIC ||
\r
3824 relation == RELATION_EXAMINING ||
\r
3826 DisplayBothClocks();
\r
3830 /* Display opponents and material strengths */
\r
3831 if (gameInfo.variant != VariantBughouse &&
\r
3832 gameInfo.variant != VariantCrazyhouse) {
\r
3833 if (tinyLayout || smallLayout) {
\r
3834 if(gameInfo.variant == VariantNormal)
\r
3835 sprintf(str, "%s(%d) %s(%d) {%d %d}",
\r
3836 gameInfo.white, white_stren, gameInfo.black, black_stren,
\r
3837 basetime, increment);
\r
3839 sprintf(str, "%s(%d) %s(%d) {%d %d w%d}",
\r
3840 gameInfo.white, white_stren, gameInfo.black, black_stren,
\r
3841 basetime, increment, (int) gameInfo.variant);
\r
3843 if(gameInfo.variant == VariantNormal)
\r
3844 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
\r
3845 gameInfo.white, white_stren, gameInfo.black, black_stren,
\r
3846 basetime, increment);
\r
3848 sprintf(str, "%s (%d) vs. %s (%d) {%d %d %s}",
\r
3849 gameInfo.white, white_stren, gameInfo.black, black_stren,
\r
3850 basetime, increment, VariantName(gameInfo.variant));
\r
3852 DisplayTitle(str);
\r
3853 if (appData.debugMode) {
\r
3854 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
\r
3859 /* Display the board */
\r
3862 if (appData.premove)
\r
3863 if (!gotPremove ||
\r
3864 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
\r
3865 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
\r
3866 ClearPremoveHighlights();
\r
3868 DrawPosition(FALSE, boards[currentMove]);
\r
3869 DisplayMove(moveNum - 1);
\r
3870 if (appData.ringBellAfterMoves && !ics_user_moved)
\r
3874 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
3876 if(bookHit) { // [HGM] book: simulate book reply
\r
3877 static char bookMove[MSG_SIZ]; // a bit generous?
\r
3879 programStats.depth = programStats.nodes = programStats.time =
\r
3880 programStats.score = programStats.got_only_move = 0;
\r
3881 sprintf(programStats.movelist, "%s (xbook)", bookHit);
\r
3883 strcpy(bookMove, "move ");
\r
3884 strcat(bookMove, bookHit);
\r
3885 HandleMachineMove(bookMove, &first);
\r
3891 GetMoveListEvent()
\r
3893 char buf[MSG_SIZ];
\r
3894 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
\r
3895 ics_getting_history = H_REQUESTED;
\r
3896 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
\r
3902 AnalysisPeriodicEvent(force)
\r
3905 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
\r
3906 && !force) || !appData.periodicUpdates)
\r
3909 /* Send . command to Crafty to collect stats */
\r
3910 SendToProgram(".\n", &first);
\r
3912 /* Don't send another until we get a response (this makes
\r
3913 us stop sending to old Crafty's which don't understand
\r
3914 the "." command (sending illegal cmds resets node count & time,
\r
3915 which looks bad)) */
\r
3916 programStats.ok_to_send = 0;
\r
3920 SendMoveToProgram(moveNum, cps)
\r
3922 ChessProgramState *cps;
\r
3924 char buf[MSG_SIZ];
\r
3926 if (cps->useUsermove) {
\r
3927 SendToProgram("usermove ", cps);
\r
3929 if (cps->useSAN) {
\r
3931 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
\r
3932 int len = space - parseList[moveNum];
\r
3933 memcpy(buf, parseList[moveNum], len);
\r
3934 buf[len++] = '\n';
\r
3935 buf[len] = NULLCHAR;
\r
3937 sprintf(buf, "%s\n", parseList[moveNum]);
\r
3939 SendToProgram(buf, cps);
\r
3941 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
\r
3942 AlphaRank(moveList[moveNum], 4);
\r
3943 SendToProgram(moveList[moveNum], cps);
\r
3944 AlphaRank(moveList[moveNum], 4); // and back
\r
3946 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
\r
3947 * the engine. It would be nice to have a better way to identify castle
\r
3949 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
\r
3950 && cps->useOOCastle) {
\r
3951 int fromX = moveList[moveNum][0] - AAA;
\r
3952 int fromY = moveList[moveNum][1] - ONE;
\r
3953 int toX = moveList[moveNum][2] - AAA;
\r
3954 int toY = moveList[moveNum][3] - ONE;
\r
3955 if((boards[moveNum][fromY][fromX] == WhiteKing
\r
3956 && boards[moveNum][toY][toX] == WhiteRook)
\r
3957 || (boards[moveNum][fromY][fromX] == BlackKing
\r
3958 && boards[moveNum][toY][toX] == BlackRook)) {
\r
3959 if(toX > fromX) SendToProgram("O-O\n", cps);
\r
3960 else SendToProgram("O-O-O\n", cps);
\r
3962 else SendToProgram(moveList[moveNum], cps);
\r
3964 else SendToProgram(moveList[moveNum], cps);
\r
3965 /* End of additions by Tord */
\r
3968 /* [HGM] setting up the opening has brought engine in force mode! */
\r
3969 /* Send 'go' if we are in a mode where machine should play. */
\r
3970 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
\r
3971 (gameMode == TwoMachinesPlay ||
\r
3973 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
\r
3975 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
\r
3976 SendToProgram("go\n", cps);
\r
3977 if (appData.debugMode) {
\r
3978 fprintf(debugFP, "(extra)\n");
\r
3981 setboardSpoiledMachineBlack = 0;
\r
3985 SendMoveToICS(moveType, fromX, fromY, toX, toY)
\r
3986 ChessMove moveType;
\r
3987 int fromX, fromY, toX, toY;
\r
3989 char user_move[MSG_SIZ];
\r
3991 switch (moveType) {
\r
3993 sprintf(user_move, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
\r
3994 (int)moveType, fromX, fromY, toX, toY);
\r
3995 DisplayError(user_move + strlen("say "), 0);
\r
3997 case WhiteKingSideCastle:
\r
3998 case BlackKingSideCastle:
\r
3999 case WhiteQueenSideCastleWild:
\r
4000 case BlackQueenSideCastleWild:
\r
4002 case WhiteHSideCastleFR:
\r
4003 case BlackHSideCastleFR:
\r
4005 sprintf(user_move, "o-o\n");
\r
4007 case WhiteQueenSideCastle:
\r
4008 case BlackQueenSideCastle:
\r
4009 case WhiteKingSideCastleWild:
\r
4010 case BlackKingSideCastleWild:
\r
4012 case WhiteASideCastleFR:
\r
4013 case BlackASideCastleFR:
\r
4015 sprintf(user_move, "o-o-o\n");
\r
4017 case WhitePromotionQueen:
\r
4018 case BlackPromotionQueen:
\r
4019 case WhitePromotionRook:
\r
4020 case BlackPromotionRook:
\r
4021 case WhitePromotionBishop:
\r
4022 case BlackPromotionBishop:
\r
4023 case WhitePromotionKnight:
\r
4024 case BlackPromotionKnight:
\r
4025 case WhitePromotionKing:
\r
4026 case BlackPromotionKing:
\r
4027 case WhitePromotionChancellor:
\r
4028 case BlackPromotionChancellor:
\r
4029 case WhitePromotionArchbishop:
\r
4030 case BlackPromotionArchbishop:
\r
4031 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
\r
4032 sprintf(user_move, "%c%c%c%c=%c\n",
\r
4033 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
\r
4034 PieceToChar(WhiteFerz));
\r
4035 else if(gameInfo.variant == VariantGreat)
\r
4036 sprintf(user_move, "%c%c%c%c=%c\n",
\r
4037 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
\r
4038 PieceToChar(WhiteMan));
\r
4040 sprintf(user_move, "%c%c%c%c=%c\n",
\r
4041 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
\r
4042 PieceToChar(PromoPiece(moveType)));
\r
4046 sprintf(user_move, "%c@%c%c\n",
\r
4047 ToUpper(PieceToChar((ChessSquare) fromX)),
\r
4048 AAA + toX, ONE + toY);
\r
4051 case WhiteCapturesEnPassant:
\r
4052 case BlackCapturesEnPassant:
\r
4053 case IllegalMove: /* could be a variant we don't quite understand */
\r
4054 sprintf(user_move, "%c%c%c%c\n",
\r
4055 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
\r
4058 SendToICS(user_move);
\r
4062 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
\r
4063 int rf, ff, rt, ft;
\r
4067 if (rf == DROP_RANK) {
\r
4068 sprintf(move, "%c@%c%c\n",
\r
4069 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
\r
4071 if (promoChar == 'x' || promoChar == NULLCHAR) {
\r
4072 sprintf(move, "%c%c%c%c\n",
\r
4073 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
\r
4075 sprintf(move, "%c%c%c%c%c\n",
\r
4076 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
\r
4082 ProcessICSInitScript(f)
\r
4085 char buf[MSG_SIZ];
\r
4087 while (fgets(buf, MSG_SIZ, f)) {
\r
4088 SendToICSDelayed(buf,(long)appData.msLoginDelay);
\r
4095 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
\r
4097 AlphaRank(char *move, int n)
\r
4099 char *p = move, c; int x, y;
\r
4101 if (appData.debugMode) {
\r
4102 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
\r
4105 if(move[1]=='*' &&
\r
4106 move[2]>='0' && move[2]<='9' &&
\r
4107 move[3]>='a' && move[3]<='x' ) {
\r
4109 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
\r
4110 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
\r
4112 if(move[0]>='0' && move[0]<='9' &&
\r
4113 move[1]>='a' && move[1]<='x' &&
\r
4114 move[2]>='0' && move[2]<='9' &&
\r
4115 move[3]>='a' && move[3]<='x' ) {
\r
4116 /* input move, Shogi -> normal */
\r
4117 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
\r
4118 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
\r
4119 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
\r
4120 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
\r
4122 if(move[1]=='@' &&
\r
4123 move[3]>='0' && move[3]<='9' &&
\r
4124 move[2]>='a' && move[2]<='x' ) {
\r
4126 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
\r
4127 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
\r
4130 move[0]>='a' && move[0]<='x' &&
\r
4131 move[3]>='0' && move[3]<='9' &&
\r
4132 move[2]>='a' && move[2]<='x' ) {
\r
4133 /* output move, normal -> Shogi */
\r
4134 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
\r
4135 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
\r
4136 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
\r
4137 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
\r
4138 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
\r
4140 if (appData.debugMode) {
\r
4141 fprintf(debugFP, " out = '%s'\n", move);
\r
4145 /* Parser for moves from gnuchess, ICS, or user typein box */
\r
4147 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
\r
4150 ChessMove *moveType;
\r
4151 int *fromX, *fromY, *toX, *toY;
\r
4154 if (appData.debugMode) {
\r
4155 fprintf(debugFP, "move to parse: %s\n", move);
\r
4157 *moveType = yylexstr(moveNum, move);
\r
4159 switch (*moveType) {
\r
4160 case WhitePromotionChancellor:
\r
4161 case BlackPromotionChancellor:
\r
4162 case WhitePromotionArchbishop:
\r
4163 case BlackPromotionArchbishop:
\r
4164 case WhitePromotionQueen:
\r
4165 case BlackPromotionQueen:
\r
4166 case WhitePromotionRook:
\r
4167 case BlackPromotionRook:
\r
4168 case WhitePromotionBishop:
\r
4169 case BlackPromotionBishop:
\r
4170 case WhitePromotionKnight:
\r
4171 case BlackPromotionKnight:
\r
4172 case WhitePromotionKing:
\r
4173 case BlackPromotionKing:
\r
4175 case WhiteCapturesEnPassant:
\r
4176 case BlackCapturesEnPassant:
\r
4177 case WhiteKingSideCastle:
\r
4178 case WhiteQueenSideCastle:
\r
4179 case BlackKingSideCastle:
\r
4180 case BlackQueenSideCastle:
\r
4181 case WhiteKingSideCastleWild:
\r
4182 case WhiteQueenSideCastleWild:
\r
4183 case BlackKingSideCastleWild:
\r
4184 case BlackQueenSideCastleWild:
\r
4185 /* Code added by Tord: */
\r
4186 case WhiteHSideCastleFR:
\r
4187 case WhiteASideCastleFR:
\r
4188 case BlackHSideCastleFR:
\r
4189 case BlackASideCastleFR:
\r
4190 /* End of code added by Tord */
\r
4191 case IllegalMove: /* bug or odd chess variant */
\r
4192 *fromX = currentMoveString[0] - AAA;
\r
4193 *fromY = currentMoveString[1] - ONE;
\r
4194 *toX = currentMoveString[2] - AAA;
\r
4195 *toY = currentMoveString[3] - ONE;
\r
4196 *promoChar = currentMoveString[4];
\r
4197 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
\r
4198 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
\r
4199 if (appData.debugMode) {
\r
4200 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
\r
4202 *fromX = *fromY = *toX = *toY = 0;
\r
4205 if (appData.testLegality) {
\r
4206 return (*moveType != IllegalMove);
\r
4208 return !(fromX == fromY && toX == toY);
\r
4213 *fromX = *moveType == WhiteDrop ?
\r
4214 (int) CharToPiece(ToUpper(currentMoveString[0])) :
\r
4215 (int) CharToPiece(ToLower(currentMoveString[0]));
\r
4216 *fromY = DROP_RANK;
\r
4217 *toX = currentMoveString[2] - AAA;
\r
4218 *toY = currentMoveString[3] - ONE;
\r
4219 *promoChar = NULLCHAR;
\r
4222 case AmbiguousMove:
\r
4223 case ImpossibleMove:
\r
4224 case (ChessMove) 0: /* end of file */
\r
4233 if (appData.debugMode) {
\r
4234 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
\r
4237 *fromX = *fromY = *toX = *toY = 0;
\r
4238 *promoChar = NULLCHAR;
\r
4243 /* [AS] FRC game initialization */
\r
4244 static int FindEmptySquare( Board board, int n )
\r
4249 while( board[0][i] != EmptySquare ) i++;
\r
4260 static void ShuffleFRC( Board board )
\r
4266 for( i=0; i<8; i++ ) {
\r
4267 board[0][i] = EmptySquare;
\r
4270 board[0][(rand() % 4)*2 ] = WhiteBishop; /* On dark square */
\r
4271 board[0][(rand() % 4)*2+1] = WhiteBishop; /* On lite square */
\r
4272 board[0][FindEmptySquare(board, rand() % 6)] = WhiteQueen;
\r
4273 board[0][FindEmptySquare(board, rand() % 5)] = WhiteKnight;
\r
4274 board[0][FindEmptySquare(board, rand() % 4)] = WhiteKnight;
\r
4275 board[0][ i=FindEmptySquare(board, 0) ] = WhiteRook;
\r
4276 initialRights[1] = initialRights[4] =
\r
4277 castlingRights[0][1] = castlingRights[0][4] = i;
\r
4278 board[0][ i=FindEmptySquare(board, 0) ] = WhiteKing;
\r
4279 initialRights[2] = initialRights[5] =
\r
4280 castlingRights[0][2] = castlingRights[0][5] = i;
\r
4281 board[0][ i=FindEmptySquare(board, 0) ] = WhiteRook;
\r
4282 initialRights[0] = initialRights[3] =
\r
4283 castlingRights[0][0] = castlingRights[0][3] = i;
\r
4285 for( i=BOARD_LEFT; i<BOARD_RGHT; i++ ) {
\r
4286 board[BOARD_HEIGHT-1][i] = board[0][i] + BlackPawn - WhitePawn;
\r
4290 static unsigned char FRC_KnightTable[10] = {
\r
4291 0x00, 0x01, 0x02, 0x03, 0x11, 0x12, 0x13, 0x22, 0x23, 0x33
\r
4294 static void SetupFRC( Board board, int pos_index )
\r
4297 unsigned char knights;
\r
4299 /* Bring the position index into a safe range (just in case...) */
\r
4300 if( pos_index < 0 ) pos_index = 0;
\r
4304 /* Clear the board */
\r
4305 for( i=0; i<8; i++ ) {
\r
4306 board[0][i] = EmptySquare;
\r
4309 /* Place bishops and queen */
\r
4310 board[0][ (pos_index % 4)*2 + 1 ] = WhiteBishop; /* On lite square */
\r
4313 board[0][ (pos_index % 4)*2 ] = WhiteBishop; /* On dark square */
\r
4316 board[0][ FindEmptySquare(board, pos_index % 6) ] = WhiteQueen;
\r
4319 /* Place knigths */
\r
4320 knights = FRC_KnightTable[ pos_index ];
\r
4322 board[0][ FindEmptySquare(board, knights / 16) ] = WhiteKnight;
\r
4323 board[0][ FindEmptySquare(board, knights % 16) ] = WhiteKnight;
\r
4325 /* Place rooks and king */
\r
4326 board[0][ i=FindEmptySquare(board, 0) ] = WhiteRook;
\r
4327 initialRights[1] = initialRights[4] =
\r
4328 castlingRights[0][1] = castlingRights[0][4] = i;
\r
4329 board[0][ i=FindEmptySquare(board, 0) ] = WhiteKing;
\r
4330 initialRights[2] = initialRights[5] =
\r
4331 castlingRights[0][2] = castlingRights[0][5] = i;
\r
4332 board[0][ i=FindEmptySquare(board, 0) ] = WhiteRook;
\r
4333 initialRights[0] = initialRights[3] =
\r
4334 castlingRights[0][0] = castlingRights[0][3] = i;
\r
4336 /* Mirror piece placement for black */
\r
4337 for( i=BOARD_LEFT; i<BOARD_RGHT; i++ ) {
\r
4338 board[BOARD_HEIGHT-1][i] = board[0][i] + BlackPawn - WhitePawn;
\r
4342 // [HGM] shuffle: a more general way to suffle opening setups, applicable to arbitrry variants.
\r
4343 // All positions will have equal probability, but the current method will not provide a unique
\r
4344 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
\r
4349 int squaresLeft[4];
\r
4350 int piecesLeft[(int)BlackPawn];
\r
4351 long long int seed, nrOfShuffles;
\r
4353 void GetPositionNumber()
\r
4354 { // sets global variable seed
\r
4357 seed = appData.defaultFrcPosition;
\r
4358 if(seed < 0) { // randomize based on time for negative FRC position numbers
\r
4359 srandom(time(0));
\r
4360 for(i=0; i<50; i++) seed += random();
\r
4361 seed = random() ^ random() >> 8 ^ random() << 8;
\r
4362 if(seed<0) seed = -seed;
\r
4366 int put(Board board, int pieceType, int rank, int n, int shade)
\r
4367 // put the piece on the (n-1)-th empty squares of the given shade
\r
4371 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
\r
4372 if( ((i-BOARD_LEFT)&1)+1 & shade && board[rank][i] == EmptySquare && n-- == 0) {
\r
4373 board[rank][i] = (ChessSquare) pieceType;
\r
4374 squaresLeft[(i-BOARD_LEFT&1) + 1]--;
\r
4375 squaresLeft[ANY]--;
\r
4376 piecesLeft[pieceType]--;
\r
4384 void AddOnePiece(Board board, int pieceType, int rank, int shade)
\r
4385 // calculate where the next piece goes, (any empty square), and put it there
\r
4389 i = seed % squaresLeft[shade];
\r
4390 nrOfShuffles *= squaresLeft[shade];
\r
4391 seed /= squaresLeft[shade];
\r
4392 put(board, pieceType, rank, i, shade);
\r
4395 void AddTwoPieces(Board board, int pieceType, int rank)
\r
4396 // calculate where the next 2 identical pieces go, (any empty square), and put it there
\r
4398 int i, n=squaresLeft[ANY], j=n-1, k;
\r
4400 k = n*(n-1)/2; // nr of possibilities, not counting permutations
\r
4401 i = seed % k; // pick one
\r
4402 nrOfShuffles *= k;
\r
4404 while(i >= j) i -= j--;
\r
4405 j = n - 1 - j; i += j;
\r
4406 put(board, pieceType, rank, j, ANY);
\r
4407 put(board, pieceType, rank, i, ANY);
\r
4410 void SetUpShuffle(Board board, int number)
\r
4412 int i, p, first=1;
\r
4414 GetPositionNumber(); nrOfShuffles = 1;
\r
4416 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
\r
4417 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
\r
4418 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
\r
4420 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
\r
4422 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
\r
4423 p = (int) board[0][i];
\r
4424 if(p < (int) BlackPawn) piecesLeft[p] ++;
\r
4425 board[0][i] = EmptySquare;
\r
4428 if(PosFlags(0) & F_ALL_CASTLE_OK) {
\r
4429 // shuffles restricted to allow normal castling put KRR first
\r
4430 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
\r
4431 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
\r
4432 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
\r
4433 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
\r
4434 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
\r
4435 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
\r
4436 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
\r
4437 put(board, WhiteRook, 0, 0, ANY);
\r
4438 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
\r
4441 if((BOARD_RGHT-BOARD_LEFT & 1) == 0)
\r
4442 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
\r
4443 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
\r
4444 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
\r
4445 while(piecesLeft[p] >= 2) {
\r
4446 AddOnePiece(board, p, 0, LITE);
\r
4447 AddOnePiece(board, p, 0, DARK);
\r
4449 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
\r
4452 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
\r
4453 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
\r
4454 // but we leave King and Rooks for last, to possibly obey FRC restriction
\r
4455 if(p == (int)WhiteRook) continue;
\r
4456 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
\r
4457 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
\r
4460 // now everything is placed, except perhaps King (Unicorn) and Rooks
\r
4462 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
\r
4463 // Last King gets castling rights
\r
4464 while(piecesLeft[(int)WhiteUnicorn]) {
\r
4465 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
\r
4466 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
\r
4469 while(piecesLeft[(int)WhiteKing]) {
\r
4470 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
\r
4471 initialRights[2] = initialRights[5] = castlingRights[0][2] = castlingRights[0][5] = i;
\r
4476 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
\r
4477 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
\r
4480 // Only Rooks can be left; simply place them all
\r
4481 while(piecesLeft[(int)WhiteRook]) {
\r
4482 i = put(board, WhiteRook, 0, 0, ANY);
\r
4483 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
\r
4486 initialRights[1] = initialRights[4] = castlingRights[0][1] = castlingRights[0][4] = i;
\r
4488 initialRights[0] = initialRights[3] = castlingRights[0][0] = castlingRights[0][3] = i;
\r
4491 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
\r
4492 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
\r
4495 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
\r
4500 int SetCharTable( char *table, const char * map )
\r
4501 /* [HGM] moved here from winboard.c because of its general usefulness */
\r
4502 /* Basically a safe strcpy that uses the last character as King */
\r
4504 int result = FALSE; int NrPieces;
\r
4506 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
\r
4507 && NrPieces >= 12 && !(NrPieces&1)) {
\r
4508 int i; /* [HGM] Accept even length from 12 to 34 */
\r
4510 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
\r
4511 for( i=0; i<NrPieces/2-1; i++ ) {
\r
4512 table[i] = map[i];
\r
4513 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
\r
4515 table[(int) WhiteKing] = map[NrPieces/2-1];
\r
4516 table[(int) BlackKing] = map[NrPieces-1];
\r
4524 void Prelude(Board board)
\r
4525 { // [HGM] superchess: random selection of exo-pieces
\r
4526 int i, j, k; ChessSquare p;
\r
4527 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
\r
4529 GetPositionNumber(); // use FRC position number
\r
4531 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
\r
4532 SetCharTable(pieceToChar, appData.pieceToCharTable);
\r
4533 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
\r
4534 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
\r
4537 j = seed%4; seed /= 4;
\r
4538 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
\r
4539 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
\r
4540 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
\r
4541 j = seed%3 + (seed%3 >= j); seed /= 3;
\r
4542 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
\r
4543 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
\r
4544 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
\r
4545 j = seed%3; seed /= 3;
\r
4546 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
\r
4547 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
\r
4548 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
\r
4549 j = seed%2 + (seed%2 >= j); seed /= 2;
\r
4550 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
\r
4551 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
\r
4552 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
\r
4553 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
\r
4554 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
\r
4555 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
\r
4556 put(board, exoPieces[0], 0, 0, ANY);
\r
4557 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
\r
4561 InitPosition(redraw)
\r
4564 ChessSquare (* pieces)[BOARD_SIZE];
\r
4565 int i, j, pawnRow, overrule,
\r
4566 oldx = gameInfo.boardWidth,
\r
4567 oldy = gameInfo.boardHeight,
\r
4568 oldh = gameInfo.holdingsWidth,
\r
4569 oldv = gameInfo.variant;
\r
4571 currentMove = forwardMostMove = backwardMostMove = 0;
\r
4572 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
\r
4574 /* [AS] Initialize pv info list [HGM] and game status */
\r
4576 for( i=0; i<MAX_MOVES; i++ ) {
\r
4577 pvInfoList[i].depth = 0;
\r
4578 epStatus[i]=EP_NONE;
\r
4579 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
\r
4582 initialRulePlies = 0; /* 50-move counter start */
\r
4584 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
\r
4585 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
\r
4589 /* [HGM] logic here is completely changed. In stead of full positions */
\r
4590 /* the initialized data only consist of the two backranks. The switch */
\r
4591 /* selects which one we will use, which is than copied to the Board */
\r
4592 /* initialPosition, which for the rest is initialized by Pawns and */
\r
4593 /* empty squares. This initial position is then copied to boards[0], */
\r
4594 /* possibly after shuffling, so that it remains available. */
\r
4596 gameInfo.holdingsWidth = 0; /* default board sizes */
\r
4597 gameInfo.boardWidth = 8;
\r
4598 gameInfo.boardHeight = 8;
\r
4599 gameInfo.holdingsSize = 0;
\r
4600 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
\r
4601 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1; /* but no rights yet */
\r
4602 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
\r
4604 switch (gameInfo.variant) {
\r
4605 case VariantFischeRandom:
\r
4606 shuffleOpenings = TRUE;
\r
4608 pieces = FIDEArray;
\r
4610 case VariantShatranj:
\r
4611 pieces = ShatranjArray;
\r
4612 nrCastlingRights = 0;
\r
4613 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
\r
4615 case VariantTwoKings:
\r
4616 pieces = twoKingsArray;
\r
4617 nrCastlingRights = 8; /* add rights for second King */
\r
4618 castlingRights[0][6] = initialRights[2] = 5;
\r
4619 castlingRights[0][7] = initialRights[5] = 5;
\r
4620 castlingRank[6] = 0;
\r
4621 castlingRank[7] = BOARD_HEIGHT-1;
\r
4623 case VariantCapaRandom:
\r
4624 shuffleOpenings = TRUE;
\r
4625 case VariantCapablanca:
\r
4626 pieces = CapablancaArray;
\r
4627 gameInfo.boardWidth = 10;
\r
4628 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
\r
4630 case VariantGothic:
\r
4631 pieces = GothicArray;
\r
4632 gameInfo.boardWidth = 10;
\r
4633 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
\r
4635 case VariantJanus:
\r
4636 pieces = JanusArray;
\r
4637 gameInfo.boardWidth = 10;
\r
4638 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
\r
4639 nrCastlingRights = 6;
\r
4640 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
\r
4641 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
\r
4642 castlingRights[0][2] = initialRights[2] = BOARD_WIDTH-1>>1;
\r
4643 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
\r
4644 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
\r
4645 castlingRights[0][5] = initialRights[5] = BOARD_WIDTH-1>>1;
\r
4647 case VariantFalcon:
\r
4648 pieces = FalconArray;
\r
4649 gameInfo.boardWidth = 10;
\r
4650 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
\r
4652 case VariantXiangqi:
\r
4653 pieces = XiangqiArray;
\r
4654 gameInfo.boardWidth = 9;
\r
4655 gameInfo.boardHeight = 10;
\r
4656 nrCastlingRights = 0;
\r
4657 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
\r
4659 case VariantShogi:
\r
4660 pieces = ShogiArray;
\r
4661 gameInfo.boardWidth = 9;
\r
4662 gameInfo.boardHeight = 9;
\r
4663 gameInfo.holdingsSize = 7;
\r
4664 nrCastlingRights = 0;
\r
4665 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
\r
4667 case VariantCourier:
\r
4668 pieces = CourierArray;
\r
4669 gameInfo.boardWidth = 12;
\r
4670 nrCastlingRights = 0;
\r
4671 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
\r
4672 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
\r
4674 case VariantKnightmate:
\r
4675 pieces = KnightmateArray;
\r
4676 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
\r
4678 case VariantFairy:
\r
4679 pieces = fairyArray;
\r
4680 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk");
\r
4682 case VariantGreat:
\r
4683 pieces = GreatArray;
\r
4684 gameInfo.boardWidth = 10;
\r
4685 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
\r
4686 gameInfo.holdingsSize = 8;
\r
4688 case VariantSuper:
\r
4689 pieces = FIDEArray;
\r
4690 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
\r
4691 gameInfo.holdingsSize = 8;
\r
4692 startedFromSetupPosition = TRUE;
\r
4694 case VariantCrazyhouse:
\r
4695 case VariantBughouse:
\r
4696 pieces = FIDEArray;
\r
4697 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
\r
4698 gameInfo.holdingsSize = 5;
\r
4700 case VariantWildCastle:
\r
4701 pieces = FIDEArray;
\r
4702 /* !!?shuffle with kings guaranteed to be on d or e file */
\r
4703 shuffleOpenings = 1;
\r
4705 case VariantNoCastle:
\r
4706 pieces = FIDEArray;
\r
4707 nrCastlingRights = 0;
\r
4708 for(i=0; i<BOARD_SIZE; i++) initialRights[i] = -1;
\r
4709 /* !!?unconstrained back-rank shuffle */
\r
4710 shuffleOpenings = 1;
\r
4715 if(appData.NrFiles >= 0) {
\r
4716 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
\r
4717 gameInfo.boardWidth = appData.NrFiles;
\r
4719 if(appData.NrRanks >= 0) {
\r
4720 gameInfo.boardHeight = appData.NrRanks;
\r
4722 if(appData.holdingsSize >= 0) {
\r
4723 i = appData.holdingsSize;
\r
4724 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
\r
4725 gameInfo.holdingsSize = i;
\r
4727 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
\r
4728 if(BOARD_HEIGHT > BOARD_SIZE || BOARD_WIDTH > BOARD_SIZE)
\r
4729 DisplayFatalError(_("Recompile to support this BOARD_SIZE!"), 0, 2);
\r
4731 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
\r
4732 if(pawnRow < 1) pawnRow = 1;
\r
4734 /* User pieceToChar list overrules defaults */
\r
4735 if(appData.pieceToCharTable != NULL)
\r
4736 SetCharTable(pieceToChar, appData.pieceToCharTable);
\r
4738 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
\r
4740 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
\r
4741 s = (ChessSquare) 0; /* account holding counts in guard band */
\r
4742 for( i=0; i<BOARD_HEIGHT; i++ )
\r
4743 initialPosition[i][j] = s;
\r
4745 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
\r
4746 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
\r
4747 initialPosition[pawnRow][j] = WhitePawn;
\r
4748 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = BlackPawn;
\r
4749 if(gameInfo.variant == VariantXiangqi) {
\r
4751 initialPosition[pawnRow][j] =
\r
4752 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
\r
4753 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
\r
4754 initialPosition[2][j] = WhiteCannon;
\r
4755 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
\r
4759 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
\r
4761 if( (gameInfo.variant == VariantShogi) && !overrule ) {
\r
4764 initialPosition[1][j] = WhiteBishop;
\r
4765 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
\r
4767 initialPosition[1][j] = WhiteRook;
\r
4768 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
\r
4771 if( nrCastlingRights == -1) {
\r
4772 /* [HGM] Build normal castling rights (must be done after board sizing!) */
\r
4773 /* This sets default castling rights from none to normal corners */
\r
4774 /* Variants with other castling rights must set them themselves above */
\r
4775 nrCastlingRights = 6;
\r
4777 castlingRights[0][0] = initialRights[0] = BOARD_RGHT-1;
\r
4778 castlingRights[0][1] = initialRights[1] = BOARD_LEFT;
\r
4779 castlingRights[0][2] = initialRights[2] = BOARD_WIDTH>>1;
\r
4780 castlingRights[0][3] = initialRights[3] = BOARD_RGHT-1;
\r
4781 castlingRights[0][4] = initialRights[4] = BOARD_LEFT;
\r
4782 castlingRights[0][5] = initialRights[5] = BOARD_WIDTH>>1;
\r
4785 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
\r
4786 if(gameInfo.variant == VariantGreat) { // promotion commoners
\r
4787 initialPosition[PieceToNumber(WhiteMan)][BOARD_RGHT-1] = WhiteMan;
\r
4788 initialPosition[PieceToNumber(WhiteMan)][BOARD_RGHT-2] = 9;
\r
4789 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
\r
4790 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
\r
4793 if(gameInfo.variant == VariantFischeRandom) {
\r
4794 if( appData.defaultFrcPosition < 0 ) {
\r
4795 ShuffleFRC( initialPosition );
\r
4798 SetupFRC( initialPosition, appData.defaultFrcPosition );
\r
4800 startedFromSetupPosition = TRUE;
\r
4803 if (appData.debugMode) {
\r
4804 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
\r
4806 if(shuffleOpenings) {
\r
4807 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
\r
4808 startedFromSetupPosition = TRUE;
\r
4811 if(startedFromPositionFile) {
\r
4812 /* [HGM] loadPos: use PositionFile for every new game */
\r
4813 CopyBoard(initialPosition, filePosition);
\r
4814 for(i=0; i<nrCastlingRights; i++)
\r
4815 castlingRights[0][i] = initialRights[i] = fileRights[i];
\r
4816 startedFromSetupPosition = TRUE;
\r
4819 CopyBoard(boards[0], initialPosition);
\r
4821 if(oldx != gameInfo.boardWidth ||
\r
4822 oldy != gameInfo.boardHeight ||
\r
4823 oldh != gameInfo.holdingsWidth
\r
4825 || oldv == VariantGothic || // For licensing popups
\r
4826 gameInfo.variant == VariantGothic
\r
4829 || oldv == VariantFalcon ||
\r
4830 gameInfo.variant == VariantFalcon
\r
4833 InitDrawingSizes(-2 ,0);
\r
4836 DrawPosition(TRUE, boards[currentMove]);
\r
4840 SendBoard(cps, moveNum)
\r
4841 ChessProgramState *cps;
\r
4844 char message[MSG_SIZ];
\r
4846 if (cps->useSetboard) {
\r
4847 char* fen = PositionToFEN(moveNum, cps->useFEN960);
\r
4848 sprintf(message, "setboard %s\n", fen);
\r
4849 SendToProgram(message, cps);
\r
4855 /* Kludge to set black to move, avoiding the troublesome and now
\r
4856 * deprecated "black" command.
\r
4858 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
\r
4860 SendToProgram("edit\n", cps);
\r
4861 SendToProgram("#\n", cps);
\r
4862 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
4863 bp = &boards[moveNum][i][BOARD_LEFT];
\r
4864 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
\r
4865 if ((int) *bp < (int) BlackPawn) {
\r
4866 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
\r
4867 AAA + j, ONE + i);
\r
4868 if(message[0] == '+' || message[0] == '~') {
\r
4869 sprintf(message, "%c%c%c+\n",
\r
4870 PieceToChar((ChessSquare)(DEMOTED *bp)),
\r
4871 AAA + j, ONE + i);
\r
4873 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
\r
4874 message[1] = BOARD_RGHT - 1 - j + '1';
\r
4875 message[2] = BOARD_HEIGHT - 1 - i + 'a';
\r
4877 SendToProgram(message, cps);
\r
4882 SendToProgram("c\n", cps);
\r
4883 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
4884 bp = &boards[moveNum][i][BOARD_LEFT];
\r
4885 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
\r
4886 if (((int) *bp != (int) EmptySquare)
\r
4887 && ((int) *bp >= (int) BlackPawn)) {
\r
4888 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
\r
4889 AAA + j, ONE + i);
\r
4890 if(message[0] == '+' || message[0] == '~') {
\r
4891 sprintf(message, "%c%c%c+\n",
\r
4892 PieceToChar((ChessSquare)(DEMOTED *bp)),
\r
4893 AAA + j, ONE + i);
\r
4895 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
\r
4896 message[1] = BOARD_RGHT - 1 - j + '1';
\r
4897 message[2] = BOARD_HEIGHT - 1 - i + 'a';
\r
4899 SendToProgram(message, cps);
\r
4904 SendToProgram(".\n", cps);
\r
4906 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
\r
4910 IsPromotion(fromX, fromY, toX, toY)
\r
4911 int fromX, fromY, toX, toY;
\r
4913 /* [HGM] add Shogi promotions */
\r
4914 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
\r
4915 ChessSquare piece;
\r
4917 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi ||
\r
4918 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) return FALSE;
\r
4919 /* [HGM] Note to self: line above also weeds out drops */
\r
4920 piece = boards[currentMove][fromY][fromX];
\r
4921 if(gameInfo.variant == VariantShogi) {
\r
4922 promotionZoneSize = 3;
\r
4923 highestPromotingPiece = (int)WhiteKing;
\r
4924 /* [HGM] Should be Silver = Ferz, really, but legality testing is off,
\r
4925 and if in normal chess we then allow promotion to King, why not
\r
4926 allow promotion of other piece in Shogi? */
\r
4928 if((int)piece >= BlackPawn) {
\r
4929 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
\r
4931 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
\r
4933 if( toY < BOARD_HEIGHT - promotionZoneSize &&
\r
4934 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
\r
4936 return ( (int)piece <= highestPromotingPiece );
\r
4940 InPalace(row, column)
\r
4942 { /* [HGM] for Xiangqi */
\r
4943 if( (row < 3 || row > BOARD_HEIGHT-4) &&
\r
4944 column < (BOARD_WIDTH + 4)/2 &&
\r
4945 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
\r
4950 PieceForSquare (x, y)
\r
4954 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
\r
4957 return boards[currentMove][y][x];
\r
4961 OKToStartUserMove(x, y)
\r
4964 ChessSquare from_piece;
\r
4967 if (matchMode) return FALSE;
\r
4968 if (gameMode == EditPosition) return TRUE;
\r
4970 if (x >= 0 && y >= 0)
\r
4971 from_piece = boards[currentMove][y][x];
\r
4973 from_piece = EmptySquare;
\r
4975 if (from_piece == EmptySquare) return FALSE;
\r
4977 white_piece = (int)from_piece >= (int)WhitePawn &&
\r
4978 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
\r
4980 switch (gameMode) {
\r
4981 case PlayFromGameFile:
\r
4983 case TwoMachinesPlay:
\r
4987 case IcsObserving:
\r
4991 case MachinePlaysWhite:
\r
4992 case IcsPlayingBlack:
\r
4993 if (appData.zippyPlay) return FALSE;
\r
4994 if (white_piece) {
\r
4995 DisplayMoveError(_("You are playing Black"));
\r
5000 case MachinePlaysBlack:
\r
5001 case IcsPlayingWhite:
\r
5002 if (appData.zippyPlay) return FALSE;
\r
5003 if (!white_piece) {
\r
5004 DisplayMoveError(_("You are playing White"));
\r
5010 if (!white_piece && WhiteOnMove(currentMove)) {
\r
5011 DisplayMoveError(_("It is White's turn"));
\r
5014 if (white_piece && !WhiteOnMove(currentMove)) {
\r
5015 DisplayMoveError(_("It is Black's turn"));
\r
5018 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
\r
5019 /* Editing correspondence game history */
\r
5020 /* Could disallow this or prompt for confirmation */
\r
5021 cmailOldMove = -1;
\r
5023 if (currentMove < forwardMostMove) {
\r
5024 /* Discarding moves */
\r
5025 /* Could prompt for confirmation here,
\r
5026 but I don't think that's such a good idea */
\r
5027 forwardMostMove = currentMove;
\r
5031 case BeginningOfGame:
\r
5032 if (appData.icsActive) return FALSE;
\r
5033 if (!appData.noChessProgram) {
\r
5034 if (!white_piece) {
\r
5035 DisplayMoveError(_("You are playing White"));
\r
5042 if (!white_piece && WhiteOnMove(currentMove)) {
\r
5043 DisplayMoveError(_("It is White's turn"));
\r
5046 if (white_piece && !WhiteOnMove(currentMove)) {
\r
5047 DisplayMoveError(_("It is Black's turn"));
\r
5053 case IcsExamining:
\r
5056 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
\r
5057 && gameMode != AnalyzeFile && gameMode != Training) {
\r
5058 DisplayMoveError(_("Displayed position is not current"));
\r
5064 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
\r
5065 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
\r
5066 int lastLoadGameUseList = FALSE;
\r
5067 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
\r
5068 ChessMove lastLoadGameStart = (ChessMove) 0;
\r
5072 UserMoveTest(fromX, fromY, toX, toY, promoChar)
\r
5073 int fromX, fromY, toX, toY;
\r
5076 ChessMove moveType;
\r
5077 ChessSquare pdown, pup;
\r
5079 if (fromX < 0 || fromY < 0) return ImpossibleMove;
\r
5080 if ((fromX == toX) && (fromY == toY)) {
\r
5081 return ImpossibleMove;
\r
5084 /* [HGM] suppress all moves into holdings area and guard band */
\r
5085 if( toX < BOARD_LEFT || toX >= BOARD_RGHT || toY < 0 )
\r
5086 return ImpossibleMove;
\r
5088 /* [HGM] <sameColor> moved to here from winboard.c */
\r
5089 /* note: this code seems to exist for filtering out some obviously illegal premoves */
\r
5090 pdown = boards[currentMove][fromY][fromX];
\r
5091 pup = boards[currentMove][toY][toX];
\r
5092 if ( gameMode != EditPosition &&
\r
5093 (WhitePawn <= pdown && pdown < BlackPawn &&
\r
5094 WhitePawn <= pup && pup < BlackPawn ||
\r
5095 BlackPawn <= pdown && pdown < EmptySquare &&
\r
5096 BlackPawn <= pup && pup < EmptySquare
\r
5097 ) && !((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
\r
5098 (pup == WhiteRook && pdown == WhiteKing && fromY == 0 && toY == 0||
\r
5099 pup == BlackRook && pdown == BlackKing && fromY == BOARD_HEIGHT-1 && toY == BOARD_HEIGHT-1 )
\r
5101 return ImpossibleMove;
\r
5103 /* Check if the user is playing in turn. This is complicated because we
\r
5104 let the user "pick up" a piece before it is his turn. So the piece he
\r
5105 tried to pick up may have been captured by the time he puts it down!
\r
5106 Therefore we use the color the user is supposed to be playing in this
\r
5107 test, not the color of the piece that is currently on the starting
\r
5108 square---except in EditGame mode, where the user is playing both
\r
5109 sides; fortunately there the capture race can't happen. (It can
\r
5110 now happen in IcsExamining mode, but that's just too bad. The user
\r
5111 will get a somewhat confusing message in that case.)
\r
5114 switch (gameMode) {
\r
5115 case PlayFromGameFile:
\r
5117 case TwoMachinesPlay:
\r
5119 case IcsObserving:
\r
5121 /* We switched into a game mode where moves are not accepted,
\r
5122 perhaps while the mouse button was down. */
\r
5123 return ImpossibleMove;
\r
5125 case MachinePlaysWhite:
\r
5126 /* User is moving for Black */
\r
5127 if (WhiteOnMove(currentMove)) {
\r
5128 DisplayMoveError(_("It is White's turn"));
\r
5129 return ImpossibleMove;
\r
5133 case MachinePlaysBlack:
\r
5134 /* User is moving for White */
\r
5135 if (!WhiteOnMove(currentMove)) {
\r
5136 DisplayMoveError(_("It is Black's turn"));
\r
5137 return ImpossibleMove;
\r
5142 case IcsExamining:
\r
5143 case BeginningOfGame:
\r
5146 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
\r
5147 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
\r
5148 /* User is moving for Black */
\r
5149 if (WhiteOnMove(currentMove)) {
\r
5150 DisplayMoveError(_("It is White's turn"));
\r
5151 return ImpossibleMove;
\r
5154 /* User is moving for White */
\r
5155 if (!WhiteOnMove(currentMove)) {
\r
5156 DisplayMoveError(_("It is Black's turn"));
\r
5157 return ImpossibleMove;
\r
5162 case IcsPlayingBlack:
\r
5163 /* User is moving for Black */
\r
5164 if (WhiteOnMove(currentMove)) {
\r
5165 if (!appData.premove) {
\r
5166 DisplayMoveError(_("It is White's turn"));
\r
5167 } else if (toX >= 0 && toY >= 0) {
\r
5170 premoveFromX = fromX;
\r
5171 premoveFromY = fromY;
\r
5172 premovePromoChar = promoChar;
\r
5174 if (appData.debugMode)
\r
5175 fprintf(debugFP, "Got premove: fromX %d,"
\r
5176 "fromY %d, toX %d, toY %d\n",
\r
5177 fromX, fromY, toX, toY);
\r
5179 return ImpossibleMove;
\r
5183 case IcsPlayingWhite:
\r
5184 /* User is moving for White */
\r
5185 if (!WhiteOnMove(currentMove)) {
\r
5186 if (!appData.premove) {
\r
5187 DisplayMoveError(_("It is Black's turn"));
\r
5188 } else if (toX >= 0 && toY >= 0) {
\r
5191 premoveFromX = fromX;
\r
5192 premoveFromY = fromY;
\r
5193 premovePromoChar = promoChar;
\r
5195 if (appData.debugMode)
\r
5196 fprintf(debugFP, "Got premove: fromX %d,"
\r
5197 "fromY %d, toX %d, toY %d\n",
\r
5198 fromX, fromY, toX, toY);
\r
5200 return ImpossibleMove;
\r
5207 case EditPosition:
\r
5208 /* EditPosition, empty square, or different color piece;
\r
5209 click-click move is possible */
\r
5210 if (toX == -2 || toY == -2) {
\r
5211 boards[0][fromY][fromX] = EmptySquare;
\r
5212 return AmbiguousMove;
\r
5213 } else if (toX >= 0 && toY >= 0) {
\r
5214 boards[0][toY][toX] = boards[0][fromY][fromX];
\r
5215 boards[0][fromY][fromX] = EmptySquare;
\r
5216 return AmbiguousMove;
\r
5218 return ImpossibleMove;
\r
5221 /* [HGM] If move started in holdings, it means a drop */
\r
5222 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
\r
5223 if( pup != EmptySquare ) return ImpossibleMove;
\r
5224 if(appData.testLegality) {
\r
5225 /* it would be more logical if LegalityTest() also figured out
\r
5226 * which drops are legal. For now we forbid pawns on back rank.
\r
5227 * Shogi is on its own here...
\r
5229 if( (pdown == WhitePawn || pdown == BlackPawn) &&
\r
5230 (toY == 0 || toY == BOARD_HEIGHT -1 ) )
\r
5231 return(ImpossibleMove); /* no pawn drops on 1st/8th */
\r
5233 return WhiteDrop; /* Not needed to specify white or black yet */
\r
5236 userOfferedDraw = FALSE;
\r
5238 /* [HGM] always test for legality, to get promotion info */
\r
5239 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
\r
5240 epStatus[currentMove], castlingRights[currentMove],
\r
5241 fromY, fromX, toY, toX, promoChar);
\r
5243 /* [HGM] but possibly ignore an IllegalMove result */
\r
5244 if (appData.testLegality) {
\r
5245 if (moveType == IllegalMove || moveType == ImpossibleMove) {
\r
5246 DisplayMoveError(_("Illegal move"));
\r
5247 return ImpossibleMove;
\r
5250 if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
\r
5252 /* [HGM] <popupFix> in stead of calling FinishMove directly, this
\r
5253 function is made into one that returns an OK move type if FinishMove
\r
5254 should be called. This to give the calling driver routine the
\r
5255 opportunity to finish the userMove input with a promotion popup,
\r
5256 without bothering the user with this for invalid or illegal moves */
\r
5258 /* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
\r
5261 /* Common tail of UserMoveEvent and DropMenuEvent */
\r
5263 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
\r
5264 ChessMove moveType;
\r
5265 int fromX, fromY, toX, toY;
\r
5266 /*char*/int promoChar;
\r
5268 char *bookHit = 0;
\r
5269 if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
\r
5270 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
\r
5271 // [HGM] superchess: suppress promotions to non-available piece
\r
5272 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
\r
5273 if(WhiteOnMove(currentMove)) {
\r
5274 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
\r
5276 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
\r
5280 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
\r
5281 move type in caller when we know the move is a legal promotion */
\r
5282 if(moveType == NormalMove && promoChar)
\r
5283 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
\r
5284 if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
\r
5285 /* [HGM] convert drag-and-drop piece drops to standard form */
\r
5286 if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
\r
5287 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
\r
5288 fromX = boards[currentMove][fromY][fromX];
\r
5289 fromY = DROP_RANK;
\r
5292 /* [HGM] <popupFix> The following if has been moved here from
\r
5293 UserMoveEvent(). Because it seemed to belon here (why not allow
\r
5294 piece drops in training games?), and because it can only be
\r
5295 performed after it is known to what we promote. */
\r
5296 if (gameMode == Training) {
\r
5297 /* compare the move played on the board to the next move in the
\r
5298 * game. If they match, display the move and the opponent's response.
\r
5299 * If they don't match, display an error message.
\r
5303 CopyBoard(testBoard, boards[currentMove]);
\r
5304 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
\r
5306 if (CompareBoards(testBoard, boards[currentMove+1])) {
\r
5307 ForwardInner(currentMove+1);
\r
5309 /* Autoplay the opponent's response.
\r
5310 * if appData.animate was TRUE when Training mode was entered,
\r
5311 * the response will be animated.
\r
5313 saveAnimate = appData.animate;
\r
5314 appData.animate = animateTraining;
\r
5315 ForwardInner(currentMove+1);
\r
5316 appData.animate = saveAnimate;
\r
5318 /* check for the end of the game */
\r
5319 if (currentMove >= forwardMostMove) {
\r
5320 gameMode = PlayFromGameFile;
\r
5322 SetTrainingModeOff();
\r
5323 DisplayInformation(_("End of game"));
\r
5326 DisplayError(_("Incorrect move"), 0);
\r
5331 /* Ok, now we know that the move is good, so we can kill
\r
5332 the previous line in Analysis Mode */
\r
5333 if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
\r
5334 forwardMostMove = currentMove;
\r
5337 /* If we need the chess program but it's dead, restart it */
\r
5338 ResurrectChessProgram();
\r
5340 /* A user move restarts a paused game*/
\r
5344 thinkOutput[0] = NULLCHAR;
\r
5346 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
\r
5348 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
\r
5349 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
\r
5350 // [HGM] superchess: take promotion piece out of holdings
\r
5351 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
\r
5352 if(WhiteOnMove(forwardMostMove-1)) {
\r
5353 if(!--boards[forwardMostMove][k][BOARD_WIDTH-2])
\r
5354 boards[forwardMostMove][k][BOARD_WIDTH-1] = EmptySquare;
\r
5356 if(!--boards[forwardMostMove][BOARD_HEIGHT-1-k][1])
\r
5357 boards[forwardMostMove][BOARD_HEIGHT-1-k][0] = EmptySquare;
\r
5361 if (gameMode == BeginningOfGame) {
\r
5362 if (appData.noChessProgram) {
\r
5363 gameMode = EditGame;
\r
5366 char buf[MSG_SIZ];
\r
5367 gameMode = MachinePlaysBlack;
\r
5370 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
\r
5371 DisplayTitle(buf);
\r
5372 if (first.sendName) {
\r
5373 sprintf(buf, "name %s\n", gameInfo.white);
\r
5374 SendToProgram(buf, &first);
\r
5380 if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
\r
5381 /* Relay move to ICS or chess engine */
\r
5382 if (appData.icsActive) {
\r
5383 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
\r
5384 gameMode == IcsExamining) {
\r
5385 SendMoveToICS(moveType, fromX, fromY, toX, toY);
\r
5386 ics_user_moved = 1;
\r
5389 if (first.sendTime && (gameMode == BeginningOfGame ||
\r
5390 gameMode == MachinePlaysWhite ||
\r
5391 gameMode == MachinePlaysBlack)) {
\r
5392 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
\r
5394 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
\r
5395 // [HGM] book: if program might be playing, let it use book
\r
5396 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
\r
5397 first.maybeThinking = TRUE;
\r
5398 } else SendMoveToProgram(forwardMostMove-1, &first);
\r
5399 if (currentMove == cmailOldMove + 1) {
\r
5400 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
\r
5404 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5406 switch (gameMode) {
\r
5408 switch (MateTest(boards[currentMove], PosFlags(currentMove),
\r
5409 EP_UNKNOWN, castlingRights[currentMove]) ) {
\r
5413 case MT_CHECKMATE:
\r
5414 if (WhiteOnMove(currentMove)) {
\r
5415 GameEnds(BlackWins, "Black mates", GE_PLAYER);
\r
5417 GameEnds(WhiteWins, "White mates", GE_PLAYER);
\r
5420 case MT_STALEMATE:
\r
5421 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
\r
5426 case MachinePlaysBlack:
\r
5427 case MachinePlaysWhite:
\r
5428 /* disable certain menu options while machine is thinking */
\r
5429 SetMachineThinkingEnables();
\r
5436 if(bookHit) { // [HGM] book: simulate book reply
\r
5437 static char bookMove[MSG_SIZ]; // a bit generous?
\r
5439 programStats.depth = programStats.nodes = programStats.time =
\r
5440 programStats.score = programStats.got_only_move = 0;
\r
5441 sprintf(programStats.movelist, "%s (xbook)", bookHit);
\r
5443 strcpy(bookMove, "move ");
\r
5444 strcat(bookMove, bookHit);
\r
5445 HandleMachineMove(bookMove, &first);
\r
5451 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
\r
5452 int fromX, fromY, toX, toY;
\r
5455 /* [HGM] This routine was added to allow calling of its two logical
\r
5456 parts from other modules in the old way. Before, UserMoveEvent()
\r
5457 automatically called FinishMove() if the move was OK, and returned
\r
5458 otherwise. I separated the two, in order to make it possible to
\r
5459 slip a promotion popup in between. But that it always needs two
\r
5460 calls, to the first part, (now called UserMoveTest() ), and to
\r
5461 FinishMove if the first part succeeded. Calls that do not need
\r
5462 to do anything in between, can call this routine the old way.
\r
5464 ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar);
\r
5465 if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
\r
5466 if(moveType != ImpossibleMove)
\r
5467 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
\r
5470 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
\r
5472 char * hint = lastHint;
\r
5473 FrontEndProgramStats stats;
\r
5475 stats.which = cps == &first ? 0 : 1;
\r
5476 stats.depth = cpstats->depth;
\r
5477 stats.nodes = cpstats->nodes;
\r
5478 stats.score = cpstats->score;
\r
5479 stats.time = cpstats->time;
\r
5480 stats.pv = cpstats->movelist;
\r
5481 stats.hint = lastHint;
\r
5482 stats.an_move_index = 0;
\r
5483 stats.an_move_count = 0;
\r
5485 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
\r
5486 stats.hint = cpstats->move_name;
\r
5487 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
\r
5488 stats.an_move_count = cpstats->nr_moves;
\r
5491 SetProgramStats( &stats );
\r
5494 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
\r
5495 { // [HGM] book: this routine intercepts moves to simulate book replies
\r
5496 char *bookHit = NULL;
\r
5498 //first determine if the incoming move brings opponent into his book
\r
5499 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
\r
5500 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
\r
5501 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
\r
5502 if(bookHit != NULL && !cps->bookSuspend) {
\r
5503 // make sure opponent is not going to reply after receiving move to book position
\r
5504 SendToProgram("force\n", cps);
\r
5505 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
\r
5507 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
\r
5508 // now arrange restart after book miss
\r
5510 // after a book hit we never send 'go', and the code after the call to this routine
\r
5511 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
\r
5512 char buf[MSG_SIZ];
\r
5513 if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
\r
5514 sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
\r
5515 SendToProgram(buf, cps);
\r
5516 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
\r
5517 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
\r
5518 SendToProgram("go\n", cps);
\r
5519 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
\r
5520 } else { // 'go' might be sent based on 'firstMove' after this routine returns
\r
5521 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
\r
5522 SendToProgram("go\n", cps);
\r
5523 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
\r
5525 return bookHit; // notify caller of hit, so it can take action to send move to opponent
\r
5528 char *savedMessage;
\r
5529 ChessProgramState *savedState;
\r
5530 void DeferredBookMove(void)
\r
5532 if(savedState->lastPing != savedState->lastPong)
\r
5533 ScheduleDelayedEvent(DeferredBookMove, 10);
\r
5535 HandleMachineMove(savedMessage, savedState);
\r
5539 HandleMachineMove(message, cps)
\r
5541 ChessProgramState *cps;
\r
5543 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
\r
5544 char realname[MSG_SIZ];
\r
5545 int fromX, fromY, toX, toY;
\r
5546 ChessMove moveType;
\r
5552 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
\r
5554 * Kludge to ignore BEL characters
\r
5556 while (*message == '\007') message++;
\r
5559 * [HGM] engine debug message: ignore lines starting with '#' character
\r
5561 if(cps->debug && *message == '#') return;
\r
5564 * Look for book output
\r
5566 if (cps == &first && bookRequested) {
\r
5567 if (message[0] == '\t' || message[0] == ' ') {
\r
5568 /* Part of the book output is here; append it */
\r
5569 strcat(bookOutput, message);
\r
5570 strcat(bookOutput, " \n");
\r
5572 } else if (bookOutput[0] != NULLCHAR) {
\r
5573 /* All of book output has arrived; display it */
\r
5574 char *p = bookOutput;
\r
5575 while (*p != NULLCHAR) {
\r
5576 if (*p == '\t') *p = ' ';
\r
5579 DisplayInformation(bookOutput);
\r
5580 bookRequested = FALSE;
\r
5581 /* Fall through to parse the current output */
\r
5586 * Look for machine move.
\r
5588 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
\r
5589 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
\r
5591 /* This method is only useful on engines that support ping */
\r
5592 if (cps->lastPing != cps->lastPong) {
\r
5593 if (gameMode == BeginningOfGame) {
\r
5594 /* Extra move from before last new; ignore */
\r
5595 if (appData.debugMode) {
\r
5596 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
\r
5599 if (appData.debugMode) {
\r
5600 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
\r
5601 cps->which, gameMode);
\r
5604 SendToProgram("undo\n", cps);
\r
5609 switch (gameMode) {
\r
5610 case BeginningOfGame:
\r
5611 /* Extra move from before last reset; ignore */
\r
5612 if (appData.debugMode) {
\r
5613 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
\r
5620 /* Extra move after we tried to stop. The mode test is
\r
5621 not a reliable way of detecting this problem, but it's
\r
5622 the best we can do on engines that don't support ping.
\r
5624 if (appData.debugMode) {
\r
5625 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
\r
5626 cps->which, gameMode);
\r
5628 SendToProgram("undo\n", cps);
\r
5631 case MachinePlaysWhite:
\r
5632 case IcsPlayingWhite:
\r
5633 machineWhite = TRUE;
\r
5636 case MachinePlaysBlack:
\r
5637 case IcsPlayingBlack:
\r
5638 machineWhite = FALSE;
\r
5641 case TwoMachinesPlay:
\r
5642 machineWhite = (cps->twoMachinesColor[0] == 'w');
\r
5645 if (WhiteOnMove(forwardMostMove) != machineWhite) {
\r
5646 if (appData.debugMode) {
\r
5648 "Ignoring move out of turn by %s, gameMode %d"
\r
5649 ", forwardMost %d\n",
\r
5650 cps->which, gameMode, forwardMostMove);
\r
5655 if (appData.debugMode) { int f = forwardMostMove;
\r
5656 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
\r
5657 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
\r
5659 if(cps->alphaRank) AlphaRank(machineMove, 4);
\r
5660 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
\r
5661 &fromX, &fromY, &toX, &toY, &promoChar)) {
\r
5662 /* Machine move could not be parsed; ignore it. */
\r
5663 sprintf(buf1, _("Illegal move \"%s\" from %s machine"),
\r
5664 machineMove, cps->which);
\r
5665 DisplayError(buf1, 0);
\r
5666 sprintf(buf1, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d%c",
\r
5667 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
\r
5668 if (gameMode == TwoMachinesPlay) {
\r
5669 GameEnds(machineWhite ? BlackWins : WhiteWins,
\r
5675 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
\r
5676 /* So we have to redo legality test with true e.p. status here, */
\r
5677 /* to make sure an illegal e.p. capture does not slip through, */
\r
5678 /* to cause a forfeit on a justified illegal-move complaint */
\r
5679 /* of the opponent. */
\r
5680 if( gameMode==TwoMachinesPlay && appData.testLegality
\r
5681 && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
\r
5683 ChessMove moveType;
\r
5684 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
\r
5685 epStatus[forwardMostMove], castlingRights[forwardMostMove],
\r
5686 fromY, fromX, toY, toX, promoChar);
\r
5687 if (appData.debugMode) {
\r
5689 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
\r
5690 castlingRights[forwardMostMove][i], castlingRank[i]);
\r
5691 fprintf(debugFP, "castling rights\n");
\r
5693 if(moveType == IllegalMove) {
\r
5694 sprintf(buf1, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
\r
5695 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
\r
5696 GameEnds(machineWhite ? BlackWins : WhiteWins,
\r
5698 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
\r
5699 /* [HGM] Kludge to handle engines that send FRC-style castling
\r
5700 when they shouldn't (like TSCP-Gothic) */
\r
5701 switch(moveType) {
\r
5702 case WhiteASideCastleFR:
\r
5703 case BlackASideCastleFR:
\r
5705 currentMoveString[2]++;
\r
5707 case WhiteHSideCastleFR:
\r
5708 case BlackHSideCastleFR:
\r
5710 currentMoveString[2]--;
\r
5714 hintRequested = FALSE;
\r
5715 lastHint[0] = NULLCHAR;
\r
5716 bookRequested = FALSE;
\r
5717 /* Program may be pondering now */
\r
5718 cps->maybeThinking = TRUE;
\r
5719 if (cps->sendTime == 2) cps->sendTime = 1;
\r
5720 if (cps->offeredDraw) cps->offeredDraw--;
\r
5723 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
\r
5725 SendMoveToICS(moveType, fromX, fromY, toX, toY);
\r
5726 ics_user_moved = 1;
\r
5727 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
\r
5728 char buf[3*MSG_SIZ];
\r
5730 sprintf(buf, "kibitz %d/%+.2f (%.2f sec, %.0f nodes, %1.0f knps) PV = %s\n",
\r
5731 programStats.depth,
\r
5732 programStats.score / 100.,
\r
5733 programStats.time / 100.,
\r
5734 (double) programStats.nodes,
\r
5735 programStats.nodes / (10*abs(programStats.time) + 1.),
\r
5736 programStats.movelist);
\r
5741 /* currentMoveString is set as a side-effect of ParseOneMove */
\r
5742 strcpy(machineMove, currentMoveString);
\r
5743 strcat(machineMove, "\n");
\r
5744 strcpy(moveList[forwardMostMove], machineMove);
\r
5746 /* [AS] Save move info and clear stats for next move */
\r
5747 pvInfoList[ forwardMostMove ].score = programStats.score;
\r
5748 pvInfoList[ forwardMostMove ].depth = programStats.depth;
\r
5749 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
\r
5750 ClearProgramStats();
\r
5751 thinkOutput[0] = NULLCHAR;
\r
5752 hiddenThinkOutputState = 0;
\r
5754 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
\r
5756 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
\r
5757 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
\r
5760 while( count < adjudicateLossPlies ) {
\r
5761 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
\r
5764 score = -score; /* Flip score for winning side */
\r
5767 if( score > adjudicateLossThreshold ) {
\r
5774 if( count >= adjudicateLossPlies ) {
\r
5775 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5777 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
\r
5778 "Xboard adjudication",
\r
5785 if( gameMode == TwoMachinesPlay ) {
\r
5786 // [HGM] some adjudications useful with buggy engines
\r
5787 int k, count = 0, epFile = epStatus[forwardMostMove]; static int bare = 1;
\r
5788 if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
\r
5790 if(appData.testLegality)
\r
5791 // don't wait for engine to announce game end if we can judge ourselves
\r
5792 switch (MateTest(boards[forwardMostMove],
\r
5793 PosFlags(forwardMostMove), epFile,
\r
5794 castlingRights[forwardMostMove]) ) {
\r
5799 case MT_STALEMATE:
\r
5800 epStatus[forwardMostMove] = EP_STALEMATE;
\r
5801 if(appData.checkMates) {
\r
5802 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
\r
5803 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5804 GameEnds( GameIsDrawn, "Xboard adjudication: Stalemate",
\r
5808 case MT_CHECKMATE:
\r
5809 epStatus[forwardMostMove] = EP_CHECKMATE;
\r
5810 if(appData.checkMates) {
\r
5811 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
\r
5812 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5813 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
\r
5814 "Xboard adjudication: Checkmate",
\r
5820 if( appData.testLegality )
\r
5821 { /* [HGM] Some more adjudications for obstinate engines */
\r
5822 int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
\r
5823 NrWQ=0, NrBQ=0, NrW=0, bishopsColor = 0,
\r
5824 NrPieces=0, NrPawns=0, PawnAdvance=0, i, j, k;
\r
5825 static int moveCount = 6;
\r
5827 /* First absolutely insufficient mating material. Count what is on board. */
\r
5828 for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
\r
5829 { ChessSquare p = boards[forwardMostMove][i][j];
\r
5833 { /* count B,N,R and other of each side */
\r
5837 case WhiteFerz: // [HGM] shatranj: kludge to mke it work in shatranj
\r
5838 bishopsColor |= 1 << ((i^j)&1);
\r
5843 case BlackFerz: // [HGM] shatranj: kludge to mke it work in shatranj
\r
5844 bishopsColor |= 1 << ((i^j)&1);
\r
5854 case EmptySquare:
\r
5859 PawnAdvance += m; NrPawns++;
\r
5861 NrPieces += (p != EmptySquare);
\r
5862 NrW += ((int)p < (int)BlackPawn);
\r
5863 if(gameInfo.variant == VariantXiangqi &&
\r
5864 (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
\r
5865 NrPieces--; // [HGM] XQ: do not count purely defensive pieces
\r
5866 NrW -= ((int)p < (int)BlackPawn);
\r
5870 if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
\r
5871 (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
\r
5872 NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
\r
5873 { /* KBK, KNK, KK of KBKB with like Bishops */
\r
5875 /* always flag draws, for judging claims */
\r
5876 epStatus[forwardMostMove] = EP_INSUF_DRAW;
\r
5878 if(appData.materialDraws) {
\r
5879 /* but only adjudicate them if adjudication enabled */
\r
5880 SendToProgram("force\n", cps->other); // suppress reply
\r
5881 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
\r
5882 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5883 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
\r
5888 /* Shatranj baring rule */
\r
5889 if( gameInfo.variant == VariantShatranj && (NrW == 1 || NrPieces - NrW == 1) )
\r
5892 if(--bare < 0 && appData.checkMates) {
\r
5893 /* but only adjudicate them if adjudication enabled */
\r
5894 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
\r
5895 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5896 GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
\r
5897 "Xboard adjudication: Bare king", GE_XBOARD );
\r
5902 /* Then some trivial draws (only adjudicate, cannot be claimed) */
\r
5903 if(NrPieces == 4 &&
\r
5904 ( NrWR == 1 && NrBR == 1 /* KRKR */
\r
5905 || NrWQ==1 && NrBQ==1 /* KQKQ */
\r
5906 || NrWN==2 || NrBN==2 /* KNNK */
\r
5907 || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
\r
5909 if(--moveCount < 0 && appData.trivialDraws)
\r
5910 { /* if the first 3 moves do not show a tactical win, declare draw */
\r
5911 SendToProgram("force\n", cps->other); // suppress reply
\r
5912 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
\r
5913 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5914 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
\r
5917 } else moveCount = 6;
\r
5921 if (appData.debugMode) { int i;
\r
5922 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
\r
5923 forwardMostMove, backwardMostMove, epStatus[backwardMostMove],
\r
5924 appData.drawRepeats);
\r
5925 for( i=forwardMostMove; i>=backwardMostMove; i-- )
\r
5926 fprintf(debugFP, "%d ep=%d\n", i, epStatus[i]);
\r
5930 /* Check for rep-draws */
\r
5932 for(k = forwardMostMove-2;
\r
5933 k>=backwardMostMove && k>=forwardMostMove-100 &&
\r
5934 epStatus[k] < EP_UNKNOWN &&
\r
5935 epStatus[k+2] <= EP_NONE && epStatus[k+1] <= EP_NONE;
\r
5939 if (appData.debugMode) {
\r
5940 fprintf(debugFP, " loop\n");
\r
5943 if(CompareBoards(boards[k], boards[forwardMostMove])) {
\r
5945 if (appData.debugMode) {
\r
5946 fprintf(debugFP, "match\n");
\r
5949 /* compare castling rights */
\r
5950 if( castlingRights[forwardMostMove][2] != castlingRights[k][2] &&
\r
5951 (castlingRights[k][0] >= 0 || castlingRights[k][1] >= 0) )
\r
5952 rights++; /* King lost rights, while rook still had them */
\r
5953 if( castlingRights[forwardMostMove][2] >= 0 ) { /* king has rights */
\r
5954 if( castlingRights[forwardMostMove][0] != castlingRights[k][0] ||
\r
5955 castlingRights[forwardMostMove][1] != castlingRights[k][1] )
\r
5956 rights++; /* but at least one rook lost them */
\r
5958 if( castlingRights[forwardMostMove][5] != castlingRights[k][5] &&
\r
5959 (castlingRights[k][3] >= 0 || castlingRights[k][4] >= 0) )
\r
5961 if( castlingRights[forwardMostMove][5] >= 0 ) {
\r
5962 if( castlingRights[forwardMostMove][3] != castlingRights[k][3] ||
\r
5963 castlingRights[forwardMostMove][4] != castlingRights[k][4] )
\r
5967 if (appData.debugMode) {
\r
5968 for(i=0; i<nrCastlingRights; i++)
\r
5969 fprintf(debugFP, " (%d,%d)", castlingRights[forwardMostMove][i], castlingRights[k][i]);
\r
5972 if (appData.debugMode) {
\r
5973 fprintf(debugFP, " %d %d\n", rights, k);
\r
5976 if( rights == 0 && ++count > appData.drawRepeats-2
\r
5977 && appData.drawRepeats > 1) {
\r
5978 /* adjudicate after user-specified nr of repeats */
\r
5979 SendToProgram("force\n", cps->other); // suppress reply
\r
5980 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
\r
5981 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
5982 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
\r
5983 // [HGM] xiangqi: check for forbidden perpetuals
\r
5984 int m, ourPerpetual = 1, hisPerpetual = 1;
\r
5985 for(m=forwardMostMove; m>k; m-=2) {
\r
5986 if(MateTest(boards[m], PosFlags(m),
\r
5987 EP_NONE, castlingRights[m]) != MT_CHECK)
\r
5988 ourPerpetual = 0; // the current mover did not always check
\r
5989 if(MateTest(boards[m-1], PosFlags(m-1),
\r
5990 EP_NONE, castlingRights[m-1]) != MT_CHECK)
\r
5991 hisPerpetual = 0; // the opponent did not always check
\r
5993 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
\r
5994 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
\r
5995 "Xboard adjudication: perpetual checking", GE_XBOARD );
\r
5998 if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet
\r
5999 break; // (or we would have caught him before). Abort repetition-checking loop.
\r
6000 // if neither of us is checking all the time, or both are, it is draw
\r
6001 // (illegal-chase forfeits not implemented yet!)
\r
6003 GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
\r
6006 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
\r
6007 epStatus[forwardMostMove] = EP_REP_DRAW;
\r
6011 /* Now we test for 50-move draws. Determine ply count */
\r
6012 count = forwardMostMove;
\r
6013 /* look for last irreversble move */
\r
6014 while( epStatus[count] <= EP_NONE && count > backwardMostMove )
\r
6016 /* if we hit starting position, add initial plies */
\r
6017 if( count == backwardMostMove )
\r
6018 count -= initialRulePlies;
\r
6019 count = forwardMostMove - count;
\r
6021 epStatus[forwardMostMove] = EP_RULE_DRAW;
\r
6022 /* this is used to judge if draw claims are legal */
\r
6023 if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
\r
6024 SendToProgram("force\n", cps->other); // suppress reply
\r
6025 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
\r
6026 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
6027 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
\r
6031 /* if draw offer is pending, treat it as a draw claim
\r
6032 * when draw condition present, to allow engines a way to
\r
6033 * claim draws before making their move to avoid a race
\r
6034 * condition occurring after their move
\r
6036 if( cps->other->offeredDraw || cps->offeredDraw ) {
\r
6038 if(epStatus[forwardMostMove] == EP_RULE_DRAW)
\r
6039 p = "Draw claim: 50-move rule";
\r
6040 if(epStatus[forwardMostMove] == EP_REP_DRAW)
\r
6041 p = "Draw claim: 3-fold repetition";
\r
6042 if(epStatus[forwardMostMove] == EP_INSUF_DRAW)
\r
6043 p = "Draw claim: insufficient mating material";
\r
6045 SendToProgram("force\n", cps->other); // suppress reply
\r
6046 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
\r
6047 GameEnds( GameIsDrawn, p, GE_XBOARD );
\r
6048 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
6054 if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
\r
6055 SendToProgram("force\n", cps->other); // suppress reply
\r
6056 SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
\r
6057 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
6059 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
\r
6066 if (gameMode == TwoMachinesPlay) {
\r
6067 /* [HGM] relaying draw offers moved to after reception of move */
\r
6068 /* and interpreting offer as claim if it brings draw condition */
\r
6069 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
\r
6070 SendToProgram("draw\n", cps->other);
\r
6072 if (cps->other->sendTime) {
\r
6073 SendTimeRemaining(cps->other,
\r
6074 cps->other->twoMachinesColor[0] == 'w');
\r
6076 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
\r
6077 if (firstMove && !bookHit) {
\r
6078 firstMove = FALSE;
\r
6079 if (cps->other->useColors) {
\r
6080 SendToProgram(cps->other->twoMachinesColor, cps->other);
\r
6082 SendToProgram("go\n", cps->other);
\r
6084 cps->other->maybeThinking = TRUE;
\r
6087 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
\r
6089 if (!pausing && appData.ringBellAfterMoves) {
\r
6094 * Reenable menu items that were disabled while
\r
6095 * machine was thinking
\r
6097 if (gameMode != TwoMachinesPlay)
\r
6098 SetUserThinkingEnables();
\r
6100 // [HGM] book: after book hit opponent has received move and is now in force mode
\r
6101 // force the book reply into it, and then fake that it outputted this move by jumping
\r
6102 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
\r
6104 static char bookMove[MSG_SIZ]; // a bit generous?
\r
6106 strcpy(bookMove, "move ");
\r
6107 strcat(bookMove, bookHit);
\r
6108 message = bookMove;
\r
6110 programStats.depth = programStats.nodes = programStats.time =
\r
6111 programStats.score = programStats.got_only_move = 0;
\r
6112 sprintf(programStats.movelist, "%s (xbook)", bookHit);
\r
6114 if(cps->lastPing != cps->lastPong) {
\r
6115 savedMessage = message; // args for deferred call
\r
6117 ScheduleDelayedEvent(DeferredBookMove, 10);
\r
6120 goto FakeBookMove;
\r
6126 /* Set special modes for chess engines. Later something general
\r
6127 * could be added here; for now there is just one kludge feature,
\r
6128 * needed because Crafty 15.10 and earlier don't ignore SIGINT
\r
6129 * when "xboard" is given as an interactive command.
\r
6131 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
\r
6132 cps->useSigint = FALSE;
\r
6133 cps->useSigterm = FALSE;
\r
6136 /* [HGM] Allow engine to set up a position. Don't ask me why one would
\r
6137 * want this, I was asked to put it in, and obliged.
\r
6139 if (!strncmp(message, "setboard ", 9)) {
\r
6140 Board initial_position; int i;
\r
6142 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
\r
6144 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
\r
6145 DisplayError(_("Bad FEN received from engine"), 0);
\r
6148 Reset(FALSE, FALSE);
\r
6149 CopyBoard(boards[0], initial_position);
\r
6150 initialRulePlies = FENrulePlies;
\r
6151 epStatus[0] = FENepStatus;
\r
6152 for( i=0; i<nrCastlingRights; i++ )
\r
6153 castlingRights[0][i] = FENcastlingRights[i];
\r
6154 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
\r
6155 else gameMode = MachinePlaysBlack;
\r
6156 DrawPosition(FALSE, boards[currentMove]);
\r
6162 * Look for communication commands
\r
6164 if (!strncmp(message, "telluser ", 9)) {
\r
6165 DisplayNote(message + 9);
\r
6168 if (!strncmp(message, "tellusererror ", 14)) {
\r
6169 DisplayError(message + 14, 0);
\r
6172 if (!strncmp(message, "tellopponent ", 13)) {
\r
6173 if (appData.icsActive) {
\r
6175 sprintf(buf1, "%ssay %s\n", ics_prefix, message + 13);
\r
6179 DisplayNote(message + 13);
\r
6183 if (!strncmp(message, "tellothers ", 11)) {
\r
6184 if (appData.icsActive) {
\r
6186 sprintf(buf1, "%swhisper %s\n", ics_prefix, message + 11);
\r
6192 if (!strncmp(message, "tellall ", 8)) {
\r
6193 if (appData.icsActive) {
\r
6195 sprintf(buf1, "%skibitz %s\n", ics_prefix, message + 8);
\r
6199 DisplayNote(message + 8);
\r
6203 if (strncmp(message, "warning", 7) == 0) {
\r
6204 /* Undocumented feature, use tellusererror in new code */
\r
6205 DisplayError(message, 0);
\r
6208 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
\r
6209 strcpy(realname, cps->tidy);
\r
6210 strcat(realname, " query");
\r
6211 AskQuestion(realname, buf2, buf1, cps->pr);
\r
6214 /* Commands from the engine directly to ICS. We don't allow these to be
\r
6215 * sent until we are logged on. Crafty kibitzes have been known to
\r
6216 * interfere with the login process.
\r
6219 if (!strncmp(message, "tellics ", 8)) {
\r
6220 SendToICS(message + 8);
\r
6224 if (!strncmp(message, "tellicsnoalias ", 15)) {
\r
6225 SendToICS(ics_prefix);
\r
6226 SendToICS(message + 15);
\r
6230 /* The following are for backward compatibility only */
\r
6231 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
\r
6232 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
\r
6233 SendToICS(ics_prefix);
\r
6234 SendToICS(message);
\r
6239 if (strncmp(message, "feature ", 8) == 0) {
\r
6240 ParseFeatures(message+8, cps);
\r
6242 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
\r
6246 * If the move is illegal, cancel it and redraw the board.
\r
6247 * Also deal with other error cases. Matching is rather loose
\r
6248 * here to accommodate engines written before the spec.
\r
6250 if (strncmp(message + 1, "llegal move", 11) == 0 ||
\r
6251 strncmp(message, "Error", 5) == 0) {
\r
6252 if (StrStr(message, "name") ||
\r
6253 StrStr(message, "rating") || StrStr(message, "?") ||
\r
6254 StrStr(message, "result") || StrStr(message, "board") ||
\r
6255 StrStr(message, "bk") || StrStr(message, "computer") ||
\r
6256 StrStr(message, "variant") || StrStr(message, "hint") ||
\r
6257 StrStr(message, "random") || StrStr(message, "depth") ||
\r
6258 StrStr(message, "accepted")) {
\r
6261 if (StrStr(message, "protover")) {
\r
6262 /* Program is responding to input, so it's apparently done
\r
6263 initializing, and this error message indicates it is
\r
6264 protocol version 1. So we don't need to wait any longer
\r
6265 for it to initialize and send feature commands. */
\r
6266 FeatureDone(cps, 1);
\r
6267 cps->protocolVersion = 1;
\r
6270 cps->maybeThinking = FALSE;
\r
6272 if (StrStr(message, "draw")) {
\r
6273 /* Program doesn't have "draw" command */
\r
6274 cps->sendDrawOffers = 0;
\r
6277 if (cps->sendTime != 1 &&
\r
6278 (StrStr(message, "time") || StrStr(message, "otim"))) {
\r
6279 /* Program apparently doesn't have "time" or "otim" command */
\r
6280 cps->sendTime = 0;
\r
6283 if (StrStr(message, "analyze")) {
\r
6284 cps->analysisSupport = FALSE;
\r
6285 cps->analyzing = FALSE;
\r
6286 Reset(FALSE, TRUE);
\r
6287 sprintf(buf2, _("%s does not support analysis"), cps->tidy);
\r
6288 DisplayError(buf2, 0);
\r
6291 if (StrStr(message, "(no matching move)st")) {
\r
6292 /* Special kludge for GNU Chess 4 only */
\r
6293 cps->stKludge = TRUE;
\r
6294 SendTimeControl(cps, movesPerSession, timeControl,
\r
6295 timeIncrement, appData.searchDepth,
\r
6299 if (StrStr(message, "(no matching move)sd")) {
\r
6300 /* Special kludge for GNU Chess 4 only */
\r
6301 cps->sdKludge = TRUE;
\r
6302 SendTimeControl(cps, movesPerSession, timeControl,
\r
6303 timeIncrement, appData.searchDepth,
\r
6307 if (!StrStr(message, "llegal")) {
\r
6310 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
\r
6311 gameMode == IcsIdle) return;
\r
6312 if (forwardMostMove <= backwardMostMove) return;
\r
6314 /* Following removed: it caused a bug where a real illegal move
\r
6315 message in analyze mored would be ignored. */
\r
6316 if (cps == &first && programStats.ok_to_send == 0) {
\r
6317 /* Bogus message from Crafty responding to "." This filtering
\r
6318 can miss some of the bad messages, but fortunately the bug
\r
6319 is fixed in current Crafty versions, so it doesn't matter. */
\r
6323 if (pausing) PauseEvent();
\r
6324 if (gameMode == PlayFromGameFile) {
\r
6325 /* Stop reading this game file */
\r
6326 gameMode = EditGame;
\r
6329 currentMove = --forwardMostMove;
\r
6330 DisplayMove(currentMove-1); /* before DisplayMoveError */
\r
6332 DisplayBothClocks();
\r
6333 sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
\r
6334 parseList[currentMove], cps->which);
\r
6335 DisplayMoveError(buf1);
\r
6336 DrawPosition(FALSE, boards[currentMove]);
\r
6338 /* [HGM] illegal-move claim should forfeit game when Xboard */
\r
6339 /* only passes fully legal moves */
\r
6340 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
\r
6341 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
\r
6342 "False illegal-move claim", GE_XBOARD );
\r
6346 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
\r
6347 /* Program has a broken "time" command that
\r
6348 outputs a string not ending in newline.
\r
6350 cps->sendTime = 0;
\r
6354 * If chess program startup fails, exit with an error message.
\r
6355 * Attempts to recover here are futile.
\r
6357 if ((StrStr(message, "unknown host") != NULL)
\r
6358 || (StrStr(message, "No remote directory") != NULL)
\r
6359 || (StrStr(message, "not found") != NULL)
\r
6360 || (StrStr(message, "No such file") != NULL)
\r
6361 || (StrStr(message, "can't alloc") != NULL)
\r
6362 || (StrStr(message, "Permission denied") != NULL)) {
\r
6364 cps->maybeThinking = FALSE;
\r
6365 sprintf(buf1, _("Failed to start %s chess program %s on %s: %s\n"),
\r
6366 cps->which, cps->program, cps->host, message);
\r
6367 RemoveInputSource(cps->isr);
\r
6368 DisplayFatalError(buf1, 0, 1);
\r
6373 * Look for hint output
\r
6375 if (sscanf(message, "Hint: %s", buf1) == 1) {
\r
6376 if (cps == &first && hintRequested) {
\r
6377 hintRequested = FALSE;
\r
6378 if (ParseOneMove(buf1, forwardMostMove, &moveType,
\r
6379 &fromX, &fromY, &toX, &toY, &promoChar)) {
\r
6380 (void) CoordsToAlgebraic(boards[forwardMostMove],
\r
6381 PosFlags(forwardMostMove), EP_UNKNOWN,
\r
6382 fromY, fromX, toY, toX, promoChar, buf1);
\r
6383 sprintf(buf2, _("Hint: %s"), buf1);
\r
6384 DisplayInformation(buf2);
\r
6386 /* Hint move could not be parsed!? */
\r
6388 _("Illegal hint move \"%s\"\nfrom %s chess program"),
\r
6389 buf1, cps->which);
\r
6390 DisplayError(buf2, 0);
\r
6393 strcpy(lastHint, buf1);
\r
6399 * Ignore other messages if game is not in progress
\r
6401 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
\r
6402 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
\r
6405 * look for win, lose, draw, or draw offer
\r
6407 if (strncmp(message, "1-0", 3) == 0) {
\r
6408 char *p, *q, *r = "";
\r
6409 p = strchr(message, '{');
\r
6411 q = strchr(p, '}');
\r
6417 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
\r
6419 } else if (strncmp(message, "0-1", 3) == 0) {
\r
6420 char *p, *q, *r = "";
\r
6421 p = strchr(message, '{');
\r
6423 q = strchr(p, '}');
\r
6429 /* Kludge for Arasan 4.1 bug */
\r
6430 if (strcmp(r, "Black resigns") == 0) {
\r
6431 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
\r
6434 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
\r
6436 } else if (strncmp(message, "1/2", 3) == 0) {
\r
6437 char *p, *q, *r = "";
\r
6438 p = strchr(message, '{');
\r
6440 q = strchr(p, '}');
\r
6447 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
\r
6450 } else if (strncmp(message, "White resign", 12) == 0) {
\r
6451 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
\r
6453 } else if (strncmp(message, "Black resign", 12) == 0) {
\r
6454 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
\r
6456 } else if (strncmp(message, "White matches", 13) == 0 ||
\r
6457 strncmp(message, "Black matches", 13) == 0 ) {
\r
6458 /* [HGM] ignore GNUShogi noises */
\r
6460 } else if (strncmp(message, "White", 5) == 0 &&
\r
6461 message[5] != '(' &&
\r
6462 StrStr(message, "Black") == NULL) {
\r
6463 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
\r
6465 } else if (strncmp(message, "Black", 5) == 0 &&
\r
6466 message[5] != '(') {
\r
6467 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
\r
6469 } else if (strcmp(message, "resign") == 0 ||
\r
6470 strcmp(message, "computer resigns") == 0) {
\r
6471 switch (gameMode) {
\r
6472 case MachinePlaysBlack:
\r
6473 case IcsPlayingBlack:
\r
6474 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
\r
6476 case MachinePlaysWhite:
\r
6477 case IcsPlayingWhite:
\r
6478 GameEnds(BlackWins, "White resigns", GE_ENGINE);
\r
6480 case TwoMachinesPlay:
\r
6481 if (cps->twoMachinesColor[0] == 'w')
\r
6482 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
\r
6484 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
\r
6487 /* can't happen */
\r
6491 } else if (strncmp(message, "opponent mates", 14) == 0) {
\r
6492 switch (gameMode) {
\r
6493 case MachinePlaysBlack:
\r
6494 case IcsPlayingBlack:
\r
6495 GameEnds(WhiteWins, "White mates", GE_ENGINE);
\r
6497 case MachinePlaysWhite:
\r
6498 case IcsPlayingWhite:
\r
6499 GameEnds(BlackWins, "Black mates", GE_ENGINE);
\r
6501 case TwoMachinesPlay:
\r
6502 if (cps->twoMachinesColor[0] == 'w')
\r
6503 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
\r
6505 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
\r
6508 /* can't happen */
\r
6512 } else if (strncmp(message, "computer mates", 14) == 0) {
\r
6513 switch (gameMode) {
\r
6514 case MachinePlaysBlack:
\r
6515 case IcsPlayingBlack:
\r
6516 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
\r
6518 case MachinePlaysWhite:
\r
6519 case IcsPlayingWhite:
\r
6520 GameEnds(WhiteWins, "White mates", GE_ENGINE);
\r
6522 case TwoMachinesPlay:
\r
6523 if (cps->twoMachinesColor[0] == 'w')
\r
6524 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
\r
6526 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
\r
6529 /* can't happen */
\r
6533 } else if (strncmp(message, "checkmate", 9) == 0) {
\r
6534 if (WhiteOnMove(forwardMostMove)) {
\r
6535 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
\r
6537 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
\r
6540 } else if (strstr(message, "Draw") != NULL ||
\r
6541 strstr(message, "game is a draw") != NULL) {
\r
6542 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
\r
6544 } else if (strstr(message, "offer") != NULL &&
\r
6545 strstr(message, "draw") != NULL) {
\r
6547 if (appData.zippyPlay && first.initDone) {
\r
6548 /* Relay offer to ICS */
\r
6549 SendToICS(ics_prefix);
\r
6550 SendToICS("draw\n");
\r
6553 cps->offeredDraw = 2; /* valid until this engine moves twice */
\r
6554 if (gameMode == TwoMachinesPlay) {
\r
6555 if (cps->other->offeredDraw) {
\r
6556 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
\r
6557 /* [HGM] in two-machine mode we delay relaying draw offer */
\r
6558 /* until after we also have move, to see if it is really claim */
\r
6562 if (cps->other->sendDrawOffers) {
\r
6563 SendToProgram("draw\n", cps->other);
\r
6567 } else if (gameMode == MachinePlaysWhite ||
\r
6568 gameMode == MachinePlaysBlack) {
\r
6569 if (userOfferedDraw) {
\r
6570 DisplayInformation(_("Machine accepts your draw offer"));
\r
6571 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
\r
6573 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
\r
6580 * Look for thinking output
\r
6582 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
\r
6583 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
\r
6585 int plylev, mvleft, mvtot, curscore, time;
\r
6586 char mvname[MOVE_LEN];
\r
6587 u64 nodes; // [DM]
\r
6589 int ignore = FALSE;
\r
6590 int prefixHint = FALSE;
\r
6591 mvname[0] = NULLCHAR;
\r
6593 switch (gameMode) {
\r
6594 case MachinePlaysBlack:
\r
6595 case IcsPlayingBlack:
\r
6596 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
\r
6598 case MachinePlaysWhite:
\r
6599 case IcsPlayingWhite:
\r
6600 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
\r
6605 case IcsObserving: /* [DM] icsEngineAnalyze */
\r
6606 if (!appData.icsEngineAnalyze) ignore = TRUE;
\r
6608 case TwoMachinesPlay:
\r
6609 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
\r
6619 buf1[0] = NULLCHAR;
\r
6620 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
\r
6621 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
\r
6623 if (plyext != ' ' && plyext != '\t') {
\r
6627 /* [AS] Negate score if machine is playing black and reporting absolute scores */
\r
6628 if( cps->scoreIsAbsolute &&
\r
6629 ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
\r
6631 curscore = -curscore;
\r
6635 programStats.depth = plylev;
\r
6636 programStats.nodes = nodes;
\r
6637 programStats.time = time;
\r
6638 programStats.score = curscore;
\r
6639 programStats.got_only_move = 0;
\r
6641 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
\r
6644 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
\r
6645 else ticklen = (1000. * nodes) / cps->nps; // convert node count to time
\r
6646 if(WhiteOnMove(forwardMostMove))
\r
6647 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
\r
6648 else blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
\r
6651 /* Buffer overflow protection */
\r
6652 if (buf1[0] != NULLCHAR) {
\r
6653 if (strlen(buf1) >= sizeof(programStats.movelist)
\r
6654 && appData.debugMode) {
\r
6656 "PV is too long; using the first %d bytes.\n",
\r
6657 sizeof(programStats.movelist) - 1);
\r
6660 safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
\r
6662 sprintf(programStats.movelist, " no PV\n");
\r
6665 if (programStats.seen_stat) {
\r
6666 programStats.ok_to_send = 1;
\r
6669 if (strchr(programStats.movelist, '(') != NULL) {
\r
6670 programStats.line_is_book = 1;
\r
6671 programStats.nr_moves = 0;
\r
6672 programStats.moves_left = 0;
\r
6674 programStats.line_is_book = 0;
\r
6677 SendProgramStatsToFrontend( cps, &programStats );
\r
6680 [AS] Protect the thinkOutput buffer from overflow... this
\r
6681 is only useful if buf1 hasn't overflowed first!
\r
6683 sprintf(thinkOutput, "[%d]%c%+.2f %s%s",
\r
6685 (gameMode == TwoMachinesPlay ?
\r
6686 ToUpper(cps->twoMachinesColor[0]) : ' '),
\r
6687 ((double) curscore) / 100.0,
\r
6688 prefixHint ? lastHint : "",
\r
6689 prefixHint ? " " : "" );
\r
6691 if( buf1[0] != NULLCHAR ) {
\r
6692 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
\r
6694 if( strlen(buf1) > max_len ) {
\r
6695 if( appData.debugMode) {
\r
6696 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
\r
6698 buf1[max_len+1] = '\0';
\r
6701 strcat( thinkOutput, buf1 );
\r
6704 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
\r
6705 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
\r
6706 DisplayMove(currentMove - 1);
\r
6707 DisplayAnalysis();
\r
6711 } else if ((p=StrStr(message, "(only move)")) != NULL) {
\r
6712 /* crafty (9.25+) says "(only move) <move>"
\r
6713 * if there is only 1 legal move
\r
6715 sscanf(p, "(only move) %s", buf1);
\r
6716 sprintf(thinkOutput, "%s (only move)", buf1);
\r
6717 sprintf(programStats.movelist, "%s (only move)", buf1);
\r
6718 programStats.depth = 1;
\r
6719 programStats.nr_moves = 1;
\r
6720 programStats.moves_left = 1;
\r
6721 programStats.nodes = 1;
\r
6722 programStats.time = 1;
\r
6723 programStats.got_only_move = 1;
\r
6725 /* Not really, but we also use this member to
\r
6726 mean "line isn't going to change" (Crafty
\r
6727 isn't searching, so stats won't change) */
\r
6728 programStats.line_is_book = 1;
\r
6730 SendProgramStatsToFrontend( cps, &programStats );
\r
6732 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
\r
6733 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
\r
6734 DisplayMove(currentMove - 1);
\r
6735 DisplayAnalysis();
\r
6738 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
\r
6739 &time, &nodes, &plylev, &mvleft,
\r
6740 &mvtot, mvname) >= 5) {
\r
6741 /* The stat01: line is from Crafty (9.29+) in response
\r
6742 to the "." command */
\r
6743 programStats.seen_stat = 1;
\r
6744 cps->maybeThinking = TRUE;
\r
6746 if (programStats.got_only_move || !appData.periodicUpdates)
\r
6749 programStats.depth = plylev;
\r
6750 programStats.time = time;
\r
6751 programStats.nodes = nodes;
\r
6752 programStats.moves_left = mvleft;
\r
6753 programStats.nr_moves = mvtot;
\r
6754 strcpy(programStats.move_name, mvname);
\r
6755 programStats.ok_to_send = 1;
\r
6756 programStats.movelist[0] = '\0';
\r
6758 SendProgramStatsToFrontend( cps, &programStats );
\r
6760 DisplayAnalysis();
\r
6763 } else if (strncmp(message,"++",2) == 0) {
\r
6764 /* Crafty 9.29+ outputs this */
\r
6765 programStats.got_fail = 2;
\r
6768 } else if (strncmp(message,"--",2) == 0) {
\r
6769 /* Crafty 9.29+ outputs this */
\r
6770 programStats.got_fail = 1;
\r
6773 } else if (thinkOutput[0] != NULLCHAR &&
\r
6774 strncmp(message, " ", 4) == 0) {
\r
6775 unsigned message_len;
\r
6778 while (*p && *p == ' ') p++;
\r
6780 message_len = strlen( p );
\r
6782 /* [AS] Avoid buffer overflow */
\r
6783 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
\r
6784 strcat(thinkOutput, " ");
\r
6785 strcat(thinkOutput, p);
\r
6788 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
\r
6789 strcat(programStats.movelist, " ");
\r
6790 strcat(programStats.movelist, p);
\r
6793 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
\r
6794 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
\r
6795 DisplayMove(currentMove - 1);
\r
6796 DisplayAnalysis();
\r
6802 buf1[0] = NULLCHAR;
\r
6804 if (sscanf(message, "%d%c %d %d %lu %[^\n]\n",
\r
6805 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
\r
6807 ChessProgramStats cpstats;
\r
6809 if (plyext != ' ' && plyext != '\t') {
\r
6813 /* [AS] Negate score if machine is playing black and reporting absolute scores */
\r
6814 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
\r
6815 curscore = -curscore;
\r
6818 cpstats.depth = plylev;
\r
6819 cpstats.nodes = nodes;
\r
6820 cpstats.time = time;
\r
6821 cpstats.score = curscore;
\r
6822 cpstats.got_only_move = 0;
\r
6823 cpstats.movelist[0] = '\0';
\r
6825 if (buf1[0] != NULLCHAR) {
\r
6826 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist) );
\r
6829 cpstats.ok_to_send = 0;
\r
6830 cpstats.line_is_book = 0;
\r
6831 cpstats.nr_moves = 0;
\r
6832 cpstats.moves_left = 0;
\r
6834 SendProgramStatsToFrontend( cps, &cpstats );
\r
6841 /* Parse a game score from the character string "game", and
\r
6842 record it as the history of the current game. The game
\r
6843 score is NOT assumed to start from the standard position.
\r
6844 The display is not updated in any way.
\r
6847 ParseGameHistory(game)
\r
6850 ChessMove moveType;
\r
6851 int fromX, fromY, toX, toY, boardIndex;
\r
6854 char buf[MSG_SIZ];
\r
6856 if (appData.debugMode)
\r
6857 fprintf(debugFP, "Parsing game history: %s\n", game);
\r
6859 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
\r
6860 gameInfo.site = StrSave(appData.icsHost);
\r
6861 gameInfo.date = PGNDate();
\r
6862 gameInfo.round = StrSave("-");
\r
6864 /* Parse out names of players */
\r
6865 while (*game == ' ') game++;
\r
6867 while (*game != ' ') *p++ = *game++;
\r
6869 gameInfo.white = StrSave(buf);
\r
6870 while (*game == ' ') game++;
\r
6872 while (*game != ' ' && *game != '\n') *p++ = *game++;
\r
6874 gameInfo.black = StrSave(buf);
\r
6877 boardIndex = blackPlaysFirst ? 1 : 0;
\r
6880 yyboardindex = boardIndex;
\r
6881 moveType = (ChessMove) yylex();
\r
6882 switch (moveType) {
\r
6883 case IllegalMove: /* maybe suicide chess, etc. */
\r
6884 if (appData.debugMode) {
\r
6885 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
\r
6886 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
\r
6887 setbuf(debugFP, NULL);
\r
6889 case WhitePromotionChancellor:
\r
6890 case BlackPromotionChancellor:
\r
6891 case WhitePromotionArchbishop:
\r
6892 case BlackPromotionArchbishop:
\r
6893 case WhitePromotionQueen:
\r
6894 case BlackPromotionQueen:
\r
6895 case WhitePromotionRook:
\r
6896 case BlackPromotionRook:
\r
6897 case WhitePromotionBishop:
\r
6898 case BlackPromotionBishop:
\r
6899 case WhitePromotionKnight:
\r
6900 case BlackPromotionKnight:
\r
6901 case WhitePromotionKing:
\r
6902 case BlackPromotionKing:
\r
6904 case WhiteCapturesEnPassant:
\r
6905 case BlackCapturesEnPassant:
\r
6906 case WhiteKingSideCastle:
\r
6907 case WhiteQueenSideCastle:
\r
6908 case BlackKingSideCastle:
\r
6909 case BlackQueenSideCastle:
\r
6910 case WhiteKingSideCastleWild:
\r
6911 case WhiteQueenSideCastleWild:
\r
6912 case BlackKingSideCastleWild:
\r
6913 case BlackQueenSideCastleWild:
\r
6915 case WhiteHSideCastleFR:
\r
6916 case WhiteASideCastleFR:
\r
6917 case BlackHSideCastleFR:
\r
6918 case BlackASideCastleFR:
\r
6920 fromX = currentMoveString[0] - AAA;
\r
6921 fromY = currentMoveString[1] - ONE;
\r
6922 toX = currentMoveString[2] - AAA;
\r
6923 toY = currentMoveString[3] - ONE;
\r
6924 promoChar = currentMoveString[4];
\r
6928 fromX = moveType == WhiteDrop ?
\r
6929 (int) CharToPiece(ToUpper(currentMoveString[0])) :
\r
6930 (int) CharToPiece(ToLower(currentMoveString[0]));
\r
6931 fromY = DROP_RANK;
\r
6932 toX = currentMoveString[2] - AAA;
\r
6933 toY = currentMoveString[3] - ONE;
\r
6934 promoChar = NULLCHAR;
\r
6936 case AmbiguousMove:
\r
6938 sprintf(buf, _("Ambiguous move in ICS output: \"%s\""), yy_text);
\r
6939 if (appData.debugMode) {
\r
6940 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
\r
6941 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
\r
6942 setbuf(debugFP, NULL);
\r
6944 DisplayError(buf, 0);
\r
6946 case ImpossibleMove:
\r
6948 sprintf(buf, _("Illegal move in ICS output: \"%s\""), yy_text);
\r
6949 if (appData.debugMode) {
\r
6950 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
\r
6951 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
\r
6952 setbuf(debugFP, NULL);
\r
6954 DisplayError(buf, 0);
\r
6956 case (ChessMove) 0: /* end of file */
\r
6957 if (boardIndex < backwardMostMove) {
\r
6958 /* Oops, gap. How did that happen? */
\r
6959 DisplayError(_("Gap in move list"), 0);
\r
6962 backwardMostMove = blackPlaysFirst ? 1 : 0;
\r
6963 if (boardIndex > forwardMostMove) {
\r
6964 forwardMostMove = boardIndex;
\r
6968 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
\r
6969 strcat(parseList[boardIndex-1], " ");
\r
6970 strcat(parseList[boardIndex-1], yy_text);
\r
6982 case GameUnfinished:
\r
6983 if (gameMode == IcsExamining) {
\r
6984 if (boardIndex < backwardMostMove) {
\r
6985 /* Oops, gap. How did that happen? */
\r
6988 backwardMostMove = blackPlaysFirst ? 1 : 0;
\r
6991 gameInfo.result = moveType;
\r
6992 p = strchr(yy_text, '{');
\r
6993 if (p == NULL) p = strchr(yy_text, '(');
\r
6996 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
\r
6998 q = strchr(p, *p == '{' ? '}' : ')');
\r
6999 if (q != NULL) *q = NULLCHAR;
\r
7002 gameInfo.resultDetails = StrSave(p);
\r
7005 if (boardIndex >= forwardMostMove &&
\r
7006 !(gameMode == IcsObserving && ics_gamenum == -1)) {
\r
7007 backwardMostMove = blackPlaysFirst ? 1 : 0;
\r
7010 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
\r
7011 EP_UNKNOWN, fromY, fromX, toY, toX, promoChar,
\r
7012 parseList[boardIndex]);
\r
7013 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
\r
7014 /* currentMoveString is set as a side-effect of yylex */
\r
7015 strcpy(moveList[boardIndex], currentMoveString);
\r
7016 strcat(moveList[boardIndex], "\n");
\r
7018 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
\r
7019 switch (MateTest(boards[boardIndex], PosFlags(boardIndex),
\r
7020 EP_UNKNOWN, castlingRights[boardIndex]) ) {
\r
7022 case MT_STALEMATE:
\r
7026 if(gameInfo.variant != VariantShogi)
\r
7027 strcat(parseList[boardIndex - 1], "+");
\r
7029 case MT_CHECKMATE:
\r
7030 strcat(parseList[boardIndex - 1], "#");
\r
7037 /* Apply a move to the given board */
\r
7039 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
\r
7040 int fromX, fromY, toX, toY;
\r
7044 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
\r
7046 /* [HGM] compute & store e.p. status and castling rights for new position */
\r
7047 /* if we are updating a board for which those exist (i.e. in boards[]) */
\r
7048 if((p = ((int)board - (int)boards[0])/((int)boards[1]-(int)boards[0])) < MAX_MOVES && p > 0)
\r
7051 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
\r
7052 oldEP = epStatus[p-1];
\r
7053 epStatus[p] = EP_NONE;
\r
7055 if( board[toY][toX] != EmptySquare )
\r
7056 epStatus[p] = EP_CAPTURE;
\r
7058 if( board[fromY][fromX] == WhitePawn ) {
\r
7059 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
\r
7060 epStatus[p] = EP_PAWN_MOVE;
\r
7061 if( toY-fromY==2) {
\r
7062 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
\r
7063 gameInfo.variant != VariantBerolina || toX < fromX)
\r
7064 epStatus[p] = toX | berolina;
\r
7065 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
\r
7066 gameInfo.variant != VariantBerolina || toX > fromX)
\r
7067 epStatus[p] = toX;
\r
7070 if( board[fromY][fromX] == BlackPawn ) {
\r
7071 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
\r
7072 epStatus[p] = EP_PAWN_MOVE;
\r
7073 if( toY-fromY== -2) {
\r
7074 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
\r
7075 gameInfo.variant != VariantBerolina || toX < fromX)
\r
7076 epStatus[p] = toX | berolina;
\r
7077 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
\r
7078 gameInfo.variant != VariantBerolina || toX > fromX)
\r
7079 epStatus[p] = toX;
\r
7083 for(i=0; i<nrCastlingRights; i++) {
\r
7084 castlingRights[p][i] = castlingRights[p-1][i];
\r
7085 if(castlingRights[p][i] == fromX && castlingRank[i] == fromY ||
\r
7086 castlingRights[p][i] == toX && castlingRank[i] == toY
\r
7087 ) castlingRights[p][i] = -1; // revoke for moved or captured piece
\r
7092 /* [HGM] In Shatranj and Courier all promotions are to Ferz */
\r
7093 if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
\r
7094 && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
\r
7096 if (fromX == toX && fromY == toY) return;
\r
7098 if (fromY == DROP_RANK) {
\r
7099 /* must be first */
\r
7100 piece = board[toY][toX] = (ChessSquare) fromX;
\r
7102 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
\r
7103 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
\r
7104 if(gameInfo.variant == VariantKnightmate)
\r
7105 king += (int) WhiteUnicorn - (int) WhiteKing;
\r
7107 /* Code added by Tord: */
\r
7108 /* FRC castling assumed when king captures friendly rook. */
\r
7109 if (board[fromY][fromX] == WhiteKing &&
\r
7110 board[toY][toX] == WhiteRook) {
\r
7111 board[fromY][fromX] = EmptySquare;
\r
7112 board[toY][toX] = EmptySquare;
\r
7114 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
\r
7116 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
\r
7118 } else if (board[fromY][fromX] == BlackKing &&
\r
7119 board[toY][toX] == BlackRook) {
\r
7120 board[fromY][fromX] = EmptySquare;
\r
7121 board[toY][toX] = EmptySquare;
\r
7123 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
\r
7125 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
\r
7127 /* End of code added by Tord */
\r
7129 } else if (board[fromY][fromX] == king
\r
7130 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
\r
7131 && toY == fromY && toX > fromX+1) {
\r
7132 board[fromY][fromX] = EmptySquare;
\r
7133 board[toY][toX] = king;
\r
7134 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
\r
7135 board[fromY][BOARD_RGHT-1] = EmptySquare;
\r
7136 } else if (board[fromY][fromX] == king
\r
7137 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
\r
7138 && toY == fromY && toX < fromX-1) {
\r
7139 board[fromY][fromX] = EmptySquare;
\r
7140 board[toY][toX] = king;
\r
7141 board[toY][toX+1] = board[fromY][BOARD_LEFT];
\r
7142 board[fromY][BOARD_LEFT] = EmptySquare;
\r
7143 } else if (board[fromY][fromX] == WhitePawn
\r
7144 && toY == BOARD_HEIGHT-1
\r
7145 && gameInfo.variant != VariantXiangqi
\r
7147 /* white pawn promotion */
\r
7148 board[toY][toX] = CharToPiece(ToUpper(promoChar));
\r
7149 if (board[toY][toX] == EmptySquare) {
\r
7150 board[toY][toX] = WhiteQueen;
\r
7152 if(gameInfo.variant==VariantBughouse ||
\r
7153 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
\r
7154 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
\r
7155 board[fromY][fromX] = EmptySquare;
\r
7156 } else if ((fromY == BOARD_HEIGHT-4)
\r
7158 && gameInfo.variant != VariantXiangqi
\r
7159 && gameInfo.variant != VariantBerolina
\r
7160 && (board[fromY][fromX] == WhitePawn)
\r
7161 && (board[toY][toX] == EmptySquare)) {
\r
7162 board[fromY][fromX] = EmptySquare;
\r
7163 board[toY][toX] = WhitePawn;
\r
7164 captured = board[toY - 1][toX];
\r
7165 board[toY - 1][toX] = EmptySquare;
\r
7166 } else if ((fromY == BOARD_HEIGHT-4)
\r
7168 && gameInfo.variant == VariantBerolina
\r
7169 && (board[fromY][fromX] == WhitePawn)
\r
7170 && (board[toY][toX] == EmptySquare)) {
\r
7171 board[fromY][fromX] = EmptySquare;
\r
7172 board[toY][toX] = WhitePawn;
\r
7173 if(oldEP & EP_BEROLIN_A) {
\r
7174 captured = board[fromY][fromX-1];
\r
7175 board[fromY][fromX-1] = EmptySquare;
\r
7176 }else{ captured = board[fromY][fromX+1];
\r
7177 board[fromY][fromX+1] = EmptySquare;
\r
7179 } else if (board[fromY][fromX] == king
\r
7180 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
\r
7181 && toY == fromY && toX > fromX+1) {
\r
7182 board[fromY][fromX] = EmptySquare;
\r
7183 board[toY][toX] = king;
\r
7184 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
\r
7185 board[fromY][BOARD_RGHT-1] = EmptySquare;
\r
7186 } else if (board[fromY][fromX] == king
\r
7187 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
\r
7188 && toY == fromY && toX < fromX-1) {
\r
7189 board[fromY][fromX] = EmptySquare;
\r
7190 board[toY][toX] = king;
\r
7191 board[toY][toX+1] = board[fromY][BOARD_LEFT];
\r
7192 board[fromY][BOARD_LEFT] = EmptySquare;
\r
7193 } else if (fromY == 7 && fromX == 3
\r
7194 && board[fromY][fromX] == BlackKing
\r
7195 && toY == 7 && toX == 5) {
\r
7196 board[fromY][fromX] = EmptySquare;
\r
7197 board[toY][toX] = BlackKing;
\r
7198 board[fromY][7] = EmptySquare;
\r
7199 board[toY][4] = BlackRook;
\r
7200 } else if (fromY == 7 && fromX == 3
\r
7201 && board[fromY][fromX] == BlackKing
\r
7202 && toY == 7 && toX == 1) {
\r
7203 board[fromY][fromX] = EmptySquare;
\r
7204 board[toY][toX] = BlackKing;
\r
7205 board[fromY][0] = EmptySquare;
\r
7206 board[toY][2] = BlackRook;
\r
7207 } else if (board[fromY][fromX] == BlackPawn
\r
7209 && gameInfo.variant != VariantXiangqi
\r
7211 /* black pawn promotion */
\r
7212 board[0][toX] = CharToPiece(ToLower(promoChar));
\r
7213 if (board[0][toX] == EmptySquare) {
\r
7214 board[0][toX] = BlackQueen;
\r
7216 if(gameInfo.variant==VariantBughouse ||
\r
7217 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
\r
7218 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
\r
7219 board[fromY][fromX] = EmptySquare;
\r
7220 } else if ((fromY == 3)
\r
7222 && gameInfo.variant != VariantXiangqi
\r
7223 && gameInfo.variant != VariantBerolina
\r
7224 && (board[fromY][fromX] == BlackPawn)
\r
7225 && (board[toY][toX] == EmptySquare)) {
\r
7226 board[fromY][fromX] = EmptySquare;
\r
7227 board[toY][toX] = BlackPawn;
\r
7228 captured = board[toY + 1][toX];
\r
7229 board[toY + 1][toX] = EmptySquare;
\r
7230 } else if ((fromY == 3)
\r
7232 && gameInfo.variant == VariantBerolina
\r
7233 && (board[fromY][fromX] == BlackPawn)
\r
7234 && (board[toY][toX] == EmptySquare)) {
\r
7235 board[fromY][fromX] = EmptySquare;
\r
7236 board[toY][toX] = BlackPawn;
\r
7237 if(oldEP & EP_BEROLIN_A) {
\r
7238 captured = board[fromY][fromX-1];
\r
7239 board[fromY][fromX-1] = EmptySquare;
\r
7240 }else{ captured = board[fromY][fromX+1];
\r
7241 board[fromY][fromX+1] = EmptySquare;
\r
7244 board[toY][toX] = board[fromY][fromX];
\r
7245 board[fromY][fromX] = EmptySquare;
\r
7248 /* [HGM] now we promote for Shogi, if needed */
\r
7249 if(gameInfo.variant == VariantShogi && promoChar == 'q')
\r
7250 board[toY][toX] = (ChessSquare) (PROMOTED piece);
\r
7253 if (gameInfo.holdingsWidth != 0) {
\r
7255 /* !!A lot more code needs to be written to support holdings */
\r
7256 /* [HGM] OK, so I have written it. Holdings are stored in the */
\r
7257 /* penultimate board files, so they are automaticlly stored */
\r
7258 /* in the game history. */
\r
7259 if (fromY == DROP_RANK) {
\r
7260 /* Delete from holdings, by decreasing count */
\r
7261 /* and erasing image if necessary */
\r
7263 if(p < (int) BlackPawn) { /* white drop */
\r
7264 p -= (int)WhitePawn;
\r
7265 if(p >= gameInfo.holdingsSize) p = 0;
\r
7266 if(--board[p][BOARD_WIDTH-2] == 0)
\r
7267 board[p][BOARD_WIDTH-1] = EmptySquare;
\r
7268 } else { /* black drop */
\r
7269 p -= (int)BlackPawn;
\r
7270 if(p >= gameInfo.holdingsSize) p = 0;
\r
7271 if(--board[BOARD_HEIGHT-1-p][1] == 0)
\r
7272 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
\r
7275 if (captured != EmptySquare && gameInfo.holdingsSize > 0
\r
7276 && gameInfo.variant != VariantBughouse ) {
\r
7277 /* [HGM] holdings: Add to holdings, if holdings exist */
\r
7278 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
\r
7279 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
\r
7280 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
\r
7282 p = (int) captured;
\r
7283 if (p >= (int) BlackPawn) {
\r
7284 p -= (int)BlackPawn;
\r
7285 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
\r
7286 /* in Shogi restore piece to its original first */
\r
7287 captured = (ChessSquare) (DEMOTED captured);
\r
7290 p = PieceToNumber((ChessSquare)p);
\r
7291 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
\r
7292 board[p][BOARD_WIDTH-2]++;
\r
7293 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
\r
7295 p -= (int)WhitePawn;
\r
7296 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
\r
7297 captured = (ChessSquare) (DEMOTED captured);
\r
7300 p = PieceToNumber((ChessSquare)p);
\r
7301 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
\r
7302 board[BOARD_HEIGHT-1-p][1]++;
\r
7303 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
\r
7307 } else if (gameInfo.variant == VariantAtomic) {
\r
7308 if (captured != EmptySquare) {
\r
7310 for (y = toY-1; y <= toY+1; y++) {
\r
7311 for (x = toX-1; x <= toX+1; x++) {
\r
7312 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
\r
7313 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
\r
7314 board[y][x] = EmptySquare;
\r
7318 board[toY][toX] = EmptySquare;
\r
7321 if(gameInfo.variant == VariantShogi && promoChar != NULLCHAR && promoChar != '=') {
\r
7322 /* [HGM] Shogi promotions */
\r
7323 board[toY][toX] = (ChessSquare) (PROMOTED piece);
\r
7328 /* Updates forwardMostMove */
\r
7330 MakeMove(fromX, fromY, toX, toY, promoChar)
\r
7331 int fromX, fromY, toX, toY;
\r
7334 forwardMostMove++;
\r
7336 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting */
\r
7337 int timeLeft; static int lastLoadFlag=0; int king, piece;
\r
7338 piece = boards[forwardMostMove-1][fromY][fromX];
\r
7339 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
\r
7340 if(gameInfo.variant == VariantKnightmate)
\r
7341 king += (int) WhiteUnicorn - (int) WhiteKing;
\r
7342 if(forwardMostMove == 1) {
\r
7343 if(blackPlaysFirst)
\r
7344 fprintf(serverMoves, "%s;", second.tidy);
\r
7345 fprintf(serverMoves, "%s;", first.tidy);
\r
7346 if(!blackPlaysFirst)
\r
7347 fprintf(serverMoves, "%s;", second.tidy);
\r
7348 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
\r
7349 lastLoadFlag = loadFlag;
\r
7350 // print base move
\r
7351 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
\r
7352 // print castling suffix
\r
7353 if( toY == fromY && piece == king ) {
\r
7355 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
\r
7357 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
\r
7360 if( (boards[forwardMostMove-1][fromY][fromX] == WhitePawn ||
\r
7361 boards[forwardMostMove-1][fromY][fromX] == BlackPawn ) &&
\r
7362 boards[forwardMostMove-1][toY][toX] == EmptySquare
\r
7364 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
\r
7365 // promotion suffix
\r
7366 if(promoChar != NULLCHAR)
\r
7367 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
\r
7369 fprintf(serverMoves, "/%d/%d",
\r
7370 pvInfoList[forwardMostMove-1].depth, pvInfoList[forwardMostMove-1].score);
\r
7371 if(forwardMostMove & 1) timeLeft = whiteTimeRemaining/1000;
\r
7372 else timeLeft = blackTimeRemaining/1000;
\r
7373 fprintf(serverMoves, "/%d", timeLeft);
\r
7375 fflush(serverMoves);
\r
7378 if (forwardMostMove >= MAX_MOVES) {
\r
7379 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
\r
7384 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
\r
7385 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
\r
7386 if (commentList[forwardMostMove] != NULL) {
\r
7387 free(commentList[forwardMostMove]);
\r
7388 commentList[forwardMostMove] = NULL;
\r
7390 CopyBoard(boards[forwardMostMove], boards[forwardMostMove - 1]);
\r
7391 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove]);
\r
7392 gameInfo.result = GameUnfinished;
\r
7393 if (gameInfo.resultDetails != NULL) {
\r
7394 free(gameInfo.resultDetails);
\r
7395 gameInfo.resultDetails = NULL;
\r
7397 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
\r
7398 moveList[forwardMostMove - 1]);
\r
7399 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
\r
7400 PosFlags(forwardMostMove - 1), EP_UNKNOWN,
\r
7401 fromY, fromX, toY, toX, promoChar,
\r
7402 parseList[forwardMostMove - 1]);
\r
7403 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove),
\r
7404 epStatus[forwardMostMove], /* [HGM] use true e.p. */
\r
7405 castlingRights[forwardMostMove]) ) {
\r
7407 case MT_STALEMATE:
\r
7411 if(gameInfo.variant != VariantShogi)
\r
7412 strcat(parseList[forwardMostMove - 1], "+");
\r
7414 case MT_CHECKMATE:
\r
7415 strcat(parseList[forwardMostMove - 1], "#");
\r
7418 if (appData.debugMode) {
\r
7419 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
\r
7424 /* Updates currentMove if not pausing */
\r
7426 ShowMove(fromX, fromY, toX, toY)
\r
7428 int instant = (gameMode == PlayFromGameFile) ?
\r
7429 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
\r
7430 if(appData.noGUI) return;
\r
7431 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
\r
7433 if (forwardMostMove == currentMove + 1) {
\r
7434 AnimateMove(boards[forwardMostMove - 1],
\r
7435 fromX, fromY, toX, toY);
\r
7437 if (appData.highlightLastMove) {
\r
7438 SetHighlights(fromX, fromY, toX, toY);
\r
7441 currentMove = forwardMostMove;
\r
7444 if (instant) return;
\r
7446 DisplayMove(currentMove - 1);
\r
7447 DrawPosition(FALSE, boards[currentMove]);
\r
7448 DisplayBothClocks();
\r
7449 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
\r
7452 void SendEgtPath(ChessProgramState *cps)
\r
7453 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
\r
7454 char buf[MSG_SIZ], name[MSG_SIZ], *p;
\r
7456 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
\r
7459 char c, *q = name+1, *r, *s;
\r
7461 name[0] = ','; // extract next format name from feature and copy with prefixed ','
\r
7462 while(*p && *p != ',') *q++ = *p++;
\r
7463 *q++ = ':'; *q = 0;
\r
7464 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
\r
7465 strcmp(name, ",nalimov:") == 0 ) {
\r
7466 // take nalimov path from the menu-changeable option first, if it is defined
\r
7467 sprintf(buf, "egtpath nalimov %s\n", appData.defaultPathEGTB);
\r
7468 SendToProgram(buf,cps); // send egtbpath command for nalimov
\r
7470 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
\r
7471 (s = StrStr(appData.egtFormats, name)) != NULL) {
\r
7472 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
\r
7473 s = r = StrStr(s, ":") + 1; // beginning of path info
\r
7474 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
\r
7475 c = *r; *r = 0; // temporarily null-terminate path info
\r
7476 *--q = 0; // strip of trailig ':' from name
\r
7477 sprintf(buf, "egtbpath %s %s\n", name+1, s);
\r
7479 SendToProgram(buf,cps); // send egtbpath command for this format
\r
7481 if(*p == ',') p++; // read away comma to position for next format name
\r
7486 InitChessProgram(cps, setup)
\r
7487 ChessProgramState *cps;
\r
7488 int setup; /* [HGM] needed to setup FRC opening position */
\r
7490 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
\r
7491 if (appData.noChessProgram) return;
\r
7492 hintRequested = FALSE;
\r
7493 bookRequested = FALSE;
\r
7495 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
\r
7496 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
\r
7497 if(cps->memSize) { /* [HGM] memory */
\r
7498 sprintf(buf, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
\r
7499 SendToProgram(buf, cps);
\r
7501 SendEgtPath(cps); /* [HGM] EGT */
\r
7502 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
\r
7503 sprintf(buf, "cores %d\n", appData.smpCores);
\r
7504 SendToProgram(buf, cps);
\r
7507 SendToProgram(cps->initString, cps);
\r
7508 if (gameInfo.variant != VariantNormal &&
\r
7509 gameInfo.variant != VariantLoadable
\r
7510 /* [HGM] also send variant if board size non-standard */
\r
7511 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
\r
7513 char *v = VariantName(gameInfo.variant);
\r
7514 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
\r
7515 /* [HGM] in protocol 1 we have to assume all variants valid */
\r
7516 sprintf(buf, _("Variant %s not supported by %s"), v, cps->tidy);
\r
7517 DisplayFatalError(buf, 0, 1);
\r
7521 /* [HGM] make prefix for non-standard board size. Awkward testing... */
\r
7522 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
\r
7523 if( gameInfo.variant == VariantXiangqi )
\r
7524 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
\r
7525 if( gameInfo.variant == VariantShogi )
\r
7526 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
\r
7527 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
\r
7528 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
\r
7529 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
\r
7530 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon )
\r
7531 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
\r
7532 if( gameInfo.variant == VariantCourier )
\r
7533 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
\r
7534 if( gameInfo.variant == VariantSuper )
\r
7535 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
\r
7536 if( gameInfo.variant == VariantGreat )
\r
7537 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
\r
7540 sprintf(b, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
\r
7541 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
\r
7542 /* [HGM] varsize: try first if this defiant size variant is specifically known */
\r
7543 if(StrStr(cps->variants, b) == NULL) {
\r
7544 // specific sized variant not known, check if general sizing allowed
\r
7545 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
\r
7546 if(StrStr(cps->variants, "boardsize") == NULL) {
\r
7547 sprintf(buf, "Board size %dx%d+%d not supported by %s",
\r
7548 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
\r
7549 DisplayFatalError(buf, 0, 1);
\r
7552 /* [HGM] here we really should compare with the maximum supported board size */
\r
7555 } else sprintf(b, "%s", VariantName(gameInfo.variant));
\r
7556 sprintf(buf, "variant %s\n", b);
\r
7557 SendToProgram(buf, cps);
\r
7559 currentlyInitializedVariant = gameInfo.variant;
\r
7561 /* [HGM] send opening position in FRC to first engine */
\r
7563 SendToProgram("force\n", cps);
\r
7564 SendBoard(cps, 0);
\r
7565 /* engine is now in force mode! Set flag to wake it up after first move. */
\r
7566 setboardSpoiledMachineBlack = 1;
\r
7569 if (cps->sendICS) {
\r
7570 sprintf(buf, "ics %s\n", appData.icsActive ? appData.icsHost : "-");
\r
7571 SendToProgram(buf, cps);
\r
7573 cps->maybeThinking = FALSE;
\r
7574 cps->offeredDraw = 0;
\r
7575 if (!appData.icsActive) {
\r
7576 SendTimeControl(cps, movesPerSession, timeControl,
\r
7577 timeIncrement, appData.searchDepth,
\r
7580 if (appData.showThinking
\r
7581 // [HGM] thinking: four options require thinking output to be sent
\r
7582 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
\r
7584 SendToProgram("post\n", cps);
\r
7586 SendToProgram("hard\n", cps);
\r
7587 if (!appData.ponderNextMove) {
\r
7588 /* Warning: "easy" is a toggle in GNU Chess, so don't send
\r
7589 it without being sure what state we are in first. "hard"
\r
7590 is not a toggle, so that one is OK.
\r
7592 SendToProgram("easy\n", cps);
\r
7594 if (cps->usePing) {
\r
7595 sprintf(buf, "ping %d\n", ++cps->lastPing);
\r
7596 SendToProgram(buf, cps);
\r
7598 cps->initDone = TRUE;
\r
7603 StartChessProgram(cps)
\r
7604 ChessProgramState *cps;
\r
7606 char buf[MSG_SIZ];
\r
7609 if (appData.noChessProgram) return;
\r
7610 cps->initDone = FALSE;
\r
7612 if (strcmp(cps->host, "localhost") == 0) {
\r
7613 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
\r
7614 } else if (*appData.remoteShell == NULLCHAR) {
\r
7615 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
\r
7617 if (*appData.remoteUser == NULLCHAR) {
\r
7618 sprintf(buf, "%s %s %s", appData.remoteShell, cps->host,
\r
7621 sprintf(buf, "%s %s -l %s %s", appData.remoteShell,
\r
7622 cps->host, appData.remoteUser, cps->program);
\r
7624 err = StartChildProcess(buf, "", &cps->pr);
\r
7628 sprintf(buf, _("Startup failure on '%s'"), cps->program);
\r
7629 DisplayFatalError(buf, err, 1);
\r
7635 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
\r
7636 if (cps->protocolVersion > 1) {
\r
7637 sprintf(buf, "xboard\nprotover %d\n", cps->protocolVersion);
\r
7638 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
\r
7639 cps->comboCnt = 0; // and values of combo boxes
\r
7640 SendToProgram(buf, cps);
\r
7642 SendToProgram("xboard\n", cps);
\r
7648 TwoMachinesEventIfReady P((void))
\r
7650 if (first.lastPing != first.lastPong) {
\r
7651 DisplayMessage("", _("Waiting for first chess program"));
\r
7652 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
\r
7655 if (second.lastPing != second.lastPong) {
\r
7656 DisplayMessage("", _("Waiting for second chess program"));
\r
7657 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
\r
7661 TwoMachinesEvent();
\r
7665 NextMatchGame P((void))
\r
7667 int index; /* [HGM] autoinc: step lod index during match */
\r
7668 Reset(FALSE, TRUE);
\r
7669 if (*appData.loadGameFile != NULLCHAR) {
\r
7670 index = appData.loadGameIndex;
\r
7671 if(index < 0) { // [HGM] autoinc
\r
7672 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
\r
7673 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
\r
7675 LoadGameFromFile(appData.loadGameFile,
\r
7677 appData.loadGameFile, FALSE);
\r
7678 } else if (*appData.loadPositionFile != NULLCHAR) {
\r
7679 index = appData.loadPositionIndex;
\r
7680 if(index < 0) { // [HGM] autoinc
\r
7681 lastIndex = index = (index == -2 && first.twoMachinesColor[0] == 'b') ? lastIndex : lastIndex+1;
\r
7682 if(appData.rewindIndex > 0 && index > appData.rewindIndex) lastIndex = index = 1;
\r
7684 LoadPositionFromFile(appData.loadPositionFile,
\r
7686 appData.loadPositionFile);
\r
7688 TwoMachinesEventIfReady();
\r
7691 void UserAdjudicationEvent( int result )
\r
7693 ChessMove gameResult = GameIsDrawn;
\r
7695 if( result > 0 ) {
\r
7696 gameResult = WhiteWins;
\r
7698 else if( result < 0 ) {
\r
7699 gameResult = BlackWins;
\r
7702 if( gameMode == TwoMachinesPlay ) {
\r
7703 GameEnds( gameResult, "User adjudication", GE_XBOARD );
\r
7709 GameEnds(result, resultDetails, whosays)
\r
7711 char *resultDetails;
\r
7714 GameMode nextGameMode;
\r
7716 char buf[MSG_SIZ];
\r
7718 if(endingGame) return; /* [HGM] crash: forbid recursion */
\r
7721 if (appData.debugMode) {
\r
7722 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
\r
7723 result, resultDetails ? resultDetails : "(null)", whosays);
\r
7726 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
\r
7727 /* If we are playing on ICS, the server decides when the
\r
7728 game is over, but the engine can offer to draw, claim
\r
7729 a draw, or resign.
\r
7732 if (appData.zippyPlay && first.initDone) {
\r
7733 if (result == GameIsDrawn) {
\r
7734 /* In case draw still needs to be claimed */
\r
7735 SendToICS(ics_prefix);
\r
7736 SendToICS("draw\n");
\r
7737 } else if (StrCaseStr(resultDetails, "resign")) {
\r
7738 SendToICS(ics_prefix);
\r
7739 SendToICS("resign\n");
\r
7743 endingGame = 0; /* [HGM] crash */
\r
7747 /* If we're loading the game from a file, stop */
\r
7748 if (whosays == GE_FILE) {
\r
7749 (void) StopLoadGameTimer();
\r
7750 gameFileFP = NULL;
\r
7753 /* Cancel draw offers */
\r
7754 first.offeredDraw = second.offeredDraw = 0;
\r
7756 /* If this is an ICS game, only ICS can really say it's done;
\r
7757 if not, anyone can. */
\r
7758 isIcsGame = (gameMode == IcsPlayingWhite ||
\r
7759 gameMode == IcsPlayingBlack ||
\r
7760 gameMode == IcsObserving ||
\r
7761 gameMode == IcsExamining);
\r
7763 if (!isIcsGame || whosays == GE_ICS) {
\r
7764 /* OK -- not an ICS game, or ICS said it was done */
\r
7766 if (!isIcsGame && !appData.noChessProgram)
\r
7767 SetUserThinkingEnables();
\r
7769 /* [HGM] if a machine claims the game end we verify this claim */
\r
7770 if(gameMode == TwoMachinesPlay && appData.testClaims) {
\r
7771 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
\r
7774 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
\r
7775 first.twoMachinesColor[0] :
\r
7776 second.twoMachinesColor[0] ;
\r
7777 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) &&
\r
7778 (result == WhiteWins && claimer == 'w' ||
\r
7779 result == BlackWins && claimer == 'b' ) ) {
\r
7780 if (appData.debugMode) {
\r
7781 fprintf(debugFP, "result=%d sp=%d move=%d\n",
\r
7782 result, epStatus[forwardMostMove], forwardMostMove);
\r
7784 /* [HGM] verify: engine mate claims accepted if they were flagged */
\r
7785 if(epStatus[forwardMostMove] != EP_CHECKMATE &&
\r
7786 result != (WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins)) {
\r
7787 sprintf(buf, "False win claim: '%s'", resultDetails);
\r
7788 result = claimer == 'w' ? BlackWins : WhiteWins;
\r
7789 resultDetails = buf;
\r
7792 if( result == GameIsDrawn && epStatus[forwardMostMove] > EP_DRAWS
\r
7793 && (forwardMostMove <= backwardMostMove ||
\r
7794 epStatus[forwardMostMove-1] > EP_DRAWS ||
\r
7795 (claimer=='b')==(forwardMostMove&1))
\r
7797 /* [HGM] verify: draws that were not flagged are false claims */
\r
7798 sprintf(buf, "False draw claim: '%s'", resultDetails);
\r
7799 result = claimer == 'w' ? BlackWins : WhiteWins;
\r
7800 resultDetails = buf;
\r
7802 /* (Claiming a loss is accepted no questions asked!) */
\r
7804 /* [HGM] bare: don't allow bare King to win */
\r
7805 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
\r
7806 && result != GameIsDrawn)
\r
7807 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
\r
7808 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
\r
7809 int p = (int)boards[forwardMostMove][i][j] - color;
\r
7810 if(p >= 0 && p <= (int)WhiteKing) k++;
\r
7812 if (appData.debugMode) {
\r
7813 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
\r
7814 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
\r
7817 result = GameIsDrawn;
\r
7818 sprintf(buf, "%s but bare king", resultDetails);
\r
7819 resultDetails = buf;
\r
7825 if(serverMoves != NULL && !loadFlag) { char c = '=';
\r
7826 if(result==WhiteWins) c = '+';
\r
7827 if(result==BlackWins) c = '-';
\r
7828 if(resultDetails != NULL)
\r
7829 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
\r
7831 if (resultDetails != NULL) {
\r
7832 gameInfo.result = result;
\r
7833 gameInfo.resultDetails = StrSave(resultDetails);
\r
7835 /* display last move only if game was not loaded from file */
\r
7836 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
\r
7837 DisplayMove(currentMove - 1);
\r
7839 if (forwardMostMove != 0) {
\r
7840 if (gameMode != PlayFromGameFile && gameMode != EditGame) {
\r
7841 if (*appData.saveGameFile != NULLCHAR) {
\r
7842 SaveGameToFile(appData.saveGameFile, TRUE);
\r
7843 } else if (appData.autoSaveGames) {
\r
7846 if (*appData.savePositionFile != NULLCHAR) {
\r
7847 SavePositionToFile(appData.savePositionFile);
\r
7852 /* Tell program how game ended in case it is learning */
\r
7853 /* [HGM] Moved this to after saving the PGN, just in case */
\r
7854 /* engine died and we got here through time loss. In that */
\r
7855 /* case we will get a fatal error writing the pipe, which */
\r
7856 /* would otherwise lose us the PGN. */
\r
7857 /* [HGM] crash: not needed anymore, but doesn't hurt; */
\r
7858 /* output during GameEnds should never be fatal anymore */
\r
7859 if (gameMode == MachinePlaysWhite ||
\r
7860 gameMode == MachinePlaysBlack ||
\r
7861 gameMode == TwoMachinesPlay ||
\r
7862 gameMode == IcsPlayingWhite ||
\r
7863 gameMode == IcsPlayingBlack ||
\r
7864 gameMode == BeginningOfGame) {
\r
7865 char buf[MSG_SIZ];
\r
7866 sprintf(buf, "result %s {%s}\n", PGNResult(result),
\r
7868 if (first.pr != NoProc) {
\r
7869 SendToProgram(buf, &first);
\r
7871 if (second.pr != NoProc &&
\r
7872 gameMode == TwoMachinesPlay) {
\r
7873 SendToProgram(buf, &second);
\r
7878 if (appData.icsActive) {
\r
7879 if (appData.quietPlay &&
\r
7880 (gameMode == IcsPlayingWhite ||
\r
7881 gameMode == IcsPlayingBlack)) {
\r
7882 SendToICS(ics_prefix);
\r
7883 SendToICS("set shout 1\n");
\r
7885 nextGameMode = IcsIdle;
\r
7886 ics_user_moved = FALSE;
\r
7887 /* clean up premove. It's ugly when the game has ended and the
\r
7888 * premove highlights are still on the board.
\r
7891 gotPremove = FALSE;
\r
7892 ClearPremoveHighlights();
\r
7893 DrawPosition(FALSE, boards[currentMove]);
\r
7895 if (whosays == GE_ICS) {
\r
7898 if (gameMode == IcsPlayingWhite)
\r
7899 PlayIcsWinSound();
\r
7900 else if(gameMode == IcsPlayingBlack)
\r
7901 PlayIcsLossSound();
\r
7904 if (gameMode == IcsPlayingBlack)
\r
7905 PlayIcsWinSound();
\r
7906 else if(gameMode == IcsPlayingWhite)
\r
7907 PlayIcsLossSound();
\r
7910 PlayIcsDrawSound();
\r
7913 PlayIcsUnfinishedSound();
\r
7916 } else if (gameMode == EditGame ||
\r
7917 gameMode == PlayFromGameFile ||
\r
7918 gameMode == AnalyzeMode ||
\r
7919 gameMode == AnalyzeFile) {
\r
7920 nextGameMode = gameMode;
\r
7922 nextGameMode = EndOfGame;
\r
7927 nextGameMode = gameMode;
\r
7930 if (appData.noChessProgram) {
\r
7931 gameMode = nextGameMode;
\r
7933 endingGame = 0; /* [HGM] crash */
\r
7937 if (first.reuse) {
\r
7938 /* Put first chess program into idle state */
\r
7939 if (first.pr != NoProc &&
\r
7940 (gameMode == MachinePlaysWhite ||
\r
7941 gameMode == MachinePlaysBlack ||
\r
7942 gameMode == TwoMachinesPlay ||
\r
7943 gameMode == IcsPlayingWhite ||
\r
7944 gameMode == IcsPlayingBlack ||
\r
7945 gameMode == BeginningOfGame)) {
\r
7946 SendToProgram("force\n", &first);
\r
7947 if (first.usePing) {
\r
7948 char buf[MSG_SIZ];
\r
7949 sprintf(buf, "ping %d\n", ++first.lastPing);
\r
7950 SendToProgram(buf, &first);
\r
7953 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
\r
7954 /* Kill off first chess program */
\r
7955 if (first.isr != NULL)
\r
7956 RemoveInputSource(first.isr);
\r
7959 if (first.pr != NoProc) {
\r
7960 ExitAnalyzeMode();
\r
7961 DoSleep( appData.delayBeforeQuit );
\r
7962 SendToProgram("quit\n", &first);
\r
7963 DoSleep( appData.delayAfterQuit );
\r
7964 DestroyChildProcess(first.pr, first.useSigterm);
\r
7966 first.pr = NoProc;
\r
7968 if (second.reuse) {
\r
7969 /* Put second chess program into idle state */
\r
7970 if (second.pr != NoProc &&
\r
7971 gameMode == TwoMachinesPlay) {
\r
7972 SendToProgram("force\n", &second);
\r
7973 if (second.usePing) {
\r
7974 char buf[MSG_SIZ];
\r
7975 sprintf(buf, "ping %d\n", ++second.lastPing);
\r
7976 SendToProgram(buf, &second);
\r
7979 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
\r
7980 /* Kill off second chess program */
\r
7981 if (second.isr != NULL)
\r
7982 RemoveInputSource(second.isr);
\r
7983 second.isr = NULL;
\r
7985 if (second.pr != NoProc) {
\r
7986 DoSleep( appData.delayBeforeQuit );
\r
7987 SendToProgram("quit\n", &second);
\r
7988 DoSleep( appData.delayAfterQuit );
\r
7989 DestroyChildProcess(second.pr, second.useSigterm);
\r
7991 second.pr = NoProc;
\r
7994 if (matchMode && gameMode == TwoMachinesPlay) {
\r
7997 if (first.twoMachinesColor[0] == 'w') {
\r
7998 first.matchWins++;
\r
8000 second.matchWins++;
\r
8004 if (first.twoMachinesColor[0] == 'b') {
\r
8005 first.matchWins++;
\r
8007 second.matchWins++;
\r
8013 if (matchGame < appData.matchGames) {
\r
8015 if(appData.sameColorGames <= 1) { /* [HGM] alternate: suppress color swap */
\r
8016 tmp = first.twoMachinesColor;
\r
8017 first.twoMachinesColor = second.twoMachinesColor;
\r
8018 second.twoMachinesColor = tmp;
\r
8020 gameMode = nextGameMode;
\r
8022 if(appData.matchPause>10000 || appData.matchPause<10)
\r
8023 appData.matchPause = 10000; /* [HGM] make pause adjustable */
\r
8024 ScheduleDelayedEvent(NextMatchGame, appData.matchPause);
\r
8025 endingGame = 0; /* [HGM] crash */
\r
8028 char buf[MSG_SIZ];
\r
8029 gameMode = nextGameMode;
\r
8030 sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
\r
8031 first.tidy, second.tidy,
\r
8032 first.matchWins, second.matchWins,
\r
8033 appData.matchGames - (first.matchWins + second.matchWins));
\r
8034 DisplayFatalError(buf, 0, 0);
\r
8037 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
\r
8038 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
\r
8039 ExitAnalyzeMode();
\r
8040 gameMode = nextGameMode;
\r
8042 endingGame = 0; /* [HGM] crash */
\r
8045 /* Assumes program was just initialized (initString sent).
\r
8046 Leaves program in force mode. */
\r
8048 FeedMovesToProgram(cps, upto)
\r
8049 ChessProgramState *cps;
\r
8054 if (appData.debugMode)
\r
8055 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
\r
8056 startedFromSetupPosition ? "position and " : "",
\r
8057 backwardMostMove, upto, cps->which);
\r
8058 if(currentlyInitializedVariant != gameInfo.variant) { char buf[MSG_SIZ];
\r
8059 // [HGM] variantswitch: make engine aware of new variant
\r
8060 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
\r
8061 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
\r
8062 sprintf(buf, "variant %s\n", VariantName(gameInfo.variant));
\r
8063 SendToProgram(buf, cps);
\r
8064 currentlyInitializedVariant = gameInfo.variant;
\r
8066 SendToProgram("force\n", cps);
\r
8067 if (startedFromSetupPosition) {
\r
8068 SendBoard(cps, backwardMostMove);
\r
8069 if (appData.debugMode) {
\r
8070 fprintf(debugFP, "feedMoves\n");
\r
8073 for (i = backwardMostMove; i < upto; i++) {
\r
8074 SendMoveToProgram(i, cps);
\r
8080 ResurrectChessProgram()
\r
8082 /* The chess program may have exited.
\r
8083 If so, restart it and feed it all the moves made so far. */
\r
8085 if (appData.noChessProgram || first.pr != NoProc) return;
\r
8087 StartChessProgram(&first);
\r
8088 InitChessProgram(&first, FALSE);
\r
8089 FeedMovesToProgram(&first, currentMove);
\r
8091 if (!first.sendTime) {
\r
8092 /* can't tell gnuchess what its clock should read,
\r
8093 so we bow to its notion. */
\r
8095 timeRemaining[0][currentMove] = whiteTimeRemaining;
\r
8096 timeRemaining[1][currentMove] = blackTimeRemaining;
\r
8099 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
\r
8100 appData.icsEngineAnalyze) && first.analysisSupport) {
\r
8101 SendToProgram("analyze\n", &first);
\r
8102 first.analyzing = TRUE;
\r
8107 * Button procedures
\r
8110 Reset(redraw, init)
\r
8115 if (appData.debugMode) {
\r
8116 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
\r
8117 redraw, init, gameMode);
\r
8119 pausing = pauseExamInvalid = FALSE;
\r
8120 startedFromSetupPosition = blackPlaysFirst = FALSE;
\r
8122 whiteFlag = blackFlag = FALSE;
\r
8123 userOfferedDraw = FALSE;
\r
8124 hintRequested = bookRequested = FALSE;
\r
8125 first.maybeThinking = FALSE;
\r
8126 second.maybeThinking = FALSE;
\r
8127 first.bookSuspend = FALSE; // [HGM] book
\r
8128 second.bookSuspend = FALSE;
\r
8129 thinkOutput[0] = NULLCHAR;
\r
8130 lastHint[0] = NULLCHAR;
\r
8131 ClearGameInfo(&gameInfo);
\r
8132 gameInfo.variant = StringToVariant(appData.variant);
\r
8133 ics_user_moved = ics_clock_paused = FALSE;
\r
8134 ics_getting_history = H_FALSE;
\r
8136 white_holding[0] = black_holding[0] = NULLCHAR;
\r
8137 ClearProgramStats();
\r
8138 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
\r
8141 ClearHighlights();
\r
8142 flipView = appData.flipView;
\r
8143 ClearPremoveHighlights();
\r
8144 gotPremove = FALSE;
\r
8145 alarmSounded = FALSE;
\r
8147 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
\r
8148 if(appData.serverMovesName != NULL) {
\r
8149 /* [HGM] prepare to make moves file for broadcasting */
\r
8150 clock_t t = clock();
\r
8151 if(serverMoves != NULL) fclose(serverMoves);
\r
8152 serverMoves = fopen(appData.serverMovesName, "r");
\r
8153 if(serverMoves != NULL) {
\r
8154 fclose(serverMoves);
\r
8155 /* delay 15 sec before overwriting, so all clients can see end */
\r
8156 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
\r
8158 serverMoves = fopen(appData.serverMovesName, "w");
\r
8161 ExitAnalyzeMode();
\r
8162 gameMode = BeginningOfGame;
\r
8164 if(appData.icsActive) gameInfo.variant = VariantNormal;
\r
8165 InitPosition(redraw);
\r
8166 for (i = 0; i < MAX_MOVES; i++) {
\r
8167 if (commentList[i] != NULL) {
\r
8168 free(commentList[i]);
\r
8169 commentList[i] = NULL;
\r
8173 timeRemaining[0][0] = whiteTimeRemaining;
\r
8174 timeRemaining[1][0] = blackTimeRemaining;
\r
8175 if (first.pr == NULL) {
\r
8176 StartChessProgram(&first);
\r
8179 InitChessProgram(&first, startedFromSetupPosition);
\r
8182 DisplayMessage("", "");
\r
8183 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
8187 AutoPlayGameLoop()
\r
8190 if (!AutoPlayOneMove())
\r
8192 if (matchMode || appData.timeDelay == 0)
\r
8194 if (appData.timeDelay < 0 || gameMode == AnalyzeFile)
\r
8196 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
\r
8205 int fromX, fromY, toX, toY;
\r
8207 if (appData.debugMode) {
\r
8208 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
\r
8211 if (gameMode != PlayFromGameFile)
\r
8214 if (currentMove >= forwardMostMove) {
\r
8215 gameMode = EditGame;
\r
8218 /* [AS] Clear current move marker at the end of a game */
\r
8219 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
\r
8224 toX = moveList[currentMove][2] - AAA;
\r
8225 toY = moveList[currentMove][3] - ONE;
\r
8227 if (moveList[currentMove][1] == '@') {
\r
8228 if (appData.highlightLastMove) {
\r
8229 SetHighlights(-1, -1, toX, toY);
\r
8232 fromX = moveList[currentMove][0] - AAA;
\r
8233 fromY = moveList[currentMove][1] - ONE;
\r
8235 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
\r
8237 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
\r
8239 if (appData.highlightLastMove) {
\r
8240 SetHighlights(fromX, fromY, toX, toY);
\r
8243 DisplayMove(currentMove);
\r
8244 SendMoveToProgram(currentMove++, &first);
\r
8245 DisplayBothClocks();
\r
8246 DrawPosition(FALSE, boards[currentMove]);
\r
8247 // [HGM] PV info: always display, routine tests if empty
\r
8248 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
8254 LoadGameOneMove(readAhead)
\r
8255 ChessMove readAhead;
\r
8257 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
\r
8258 char promoChar = NULLCHAR;
\r
8259 ChessMove moveType;
\r
8260 char move[MSG_SIZ];
\r
8263 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
\r
8264 gameMode != AnalyzeMode && gameMode != Training) {
\r
8265 gameFileFP = NULL;
\r
8269 yyboardindex = forwardMostMove;
\r
8270 if (readAhead != (ChessMove)0) {
\r
8271 moveType = readAhead;
\r
8273 if (gameFileFP == NULL)
\r
8275 moveType = (ChessMove) yylex();
\r
8279 switch (moveType) {
\r
8281 if (appData.debugMode)
\r
8282 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
\r
8284 if (*p == '{' || *p == '[' || *p == '(') {
\r
8285 p[strlen(p) - 1] = NULLCHAR;
\r
8289 /* append the comment but don't display it */
\r
8290 while (*p == '\n') p++;
\r
8291 AppendComment(currentMove, p);
\r
8294 case WhiteCapturesEnPassant:
\r
8295 case BlackCapturesEnPassant:
\r
8296 case WhitePromotionChancellor:
\r
8297 case BlackPromotionChancellor:
\r
8298 case WhitePromotionArchbishop:
\r
8299 case BlackPromotionArchbishop:
\r
8300 case WhitePromotionCentaur:
\r
8301 case BlackPromotionCentaur:
\r
8302 case WhitePromotionQueen:
\r
8303 case BlackPromotionQueen:
\r
8304 case WhitePromotionRook:
\r
8305 case BlackPromotionRook:
\r
8306 case WhitePromotionBishop:
\r
8307 case BlackPromotionBishop:
\r
8308 case WhitePromotionKnight:
\r
8309 case BlackPromotionKnight:
\r
8310 case WhitePromotionKing:
\r
8311 case BlackPromotionKing:
\r
8313 case WhiteKingSideCastle:
\r
8314 case WhiteQueenSideCastle:
\r
8315 case BlackKingSideCastle:
\r
8316 case BlackQueenSideCastle:
\r
8317 case WhiteKingSideCastleWild:
\r
8318 case WhiteQueenSideCastleWild:
\r
8319 case BlackKingSideCastleWild:
\r
8320 case BlackQueenSideCastleWild:
\r
8322 case WhiteHSideCastleFR:
\r
8323 case WhiteASideCastleFR:
\r
8324 case BlackHSideCastleFR:
\r
8325 case BlackASideCastleFR:
\r
8327 if (appData.debugMode)
\r
8328 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
\r
8329 fromX = currentMoveString[0] - AAA;
\r
8330 fromY = currentMoveString[1] - ONE;
\r
8331 toX = currentMoveString[2] - AAA;
\r
8332 toY = currentMoveString[3] - ONE;
\r
8333 promoChar = currentMoveString[4];
\r
8338 if (appData.debugMode)
\r
8339 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
\r
8340 fromX = moveType == WhiteDrop ?
\r
8341 (int) CharToPiece(ToUpper(currentMoveString[0])) :
\r
8342 (int) CharToPiece(ToLower(currentMoveString[0]));
\r
8343 fromY = DROP_RANK;
\r
8344 toX = currentMoveString[2] - AAA;
\r
8345 toY = currentMoveString[3] - ONE;
\r
8351 case GameUnfinished:
\r
8352 if (appData.debugMode)
\r
8353 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
\r
8354 p = strchr(yy_text, '{');
\r
8355 if (p == NULL) p = strchr(yy_text, '(');
\r
8358 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
\r
8360 q = strchr(p, *p == '{' ? '}' : ')');
\r
8361 if (q != NULL) *q = NULLCHAR;
\r
8364 GameEnds(moveType, p, GE_FILE);
\r
8366 if (cmailMsgLoaded) {
\r
8367 ClearHighlights();
\r
8368 flipView = WhiteOnMove(currentMove);
\r
8369 if (moveType == GameUnfinished) flipView = !flipView;
\r
8370 if (appData.debugMode)
\r
8371 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
\r
8375 case (ChessMove) 0: /* end of file */
\r
8376 if (appData.debugMode)
\r
8377 fprintf(debugFP, "Parser hit end of file\n");
\r
8378 switch (MateTest(boards[currentMove], PosFlags(currentMove),
\r
8379 EP_UNKNOWN, castlingRights[currentMove]) ) {
\r
8383 case MT_CHECKMATE:
\r
8384 if (WhiteOnMove(currentMove)) {
\r
8385 GameEnds(BlackWins, "Black mates", GE_FILE);
\r
8387 GameEnds(WhiteWins, "White mates", GE_FILE);
\r
8390 case MT_STALEMATE:
\r
8391 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
\r
8397 case MoveNumberOne:
\r
8398 if (lastLoadGameStart == GNUChessGame) {
\r
8399 /* GNUChessGames have numbers, but they aren't move numbers */
\r
8400 if (appData.debugMode)
\r
8401 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
\r
8402 yy_text, (int) moveType);
\r
8403 return LoadGameOneMove((ChessMove)0); /* tail recursion */
\r
8405 /* else fall thru */
\r
8408 case GNUChessGame:
\r
8410 /* Reached start of next game in file */
\r
8411 if (appData.debugMode)
\r
8412 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
\r
8413 switch (MateTest(boards[currentMove], PosFlags(currentMove),
\r
8414 EP_UNKNOWN, castlingRights[currentMove]) ) {
\r
8418 case MT_CHECKMATE:
\r
8419 if (WhiteOnMove(currentMove)) {
\r
8420 GameEnds(BlackWins, "Black mates", GE_FILE);
\r
8422 GameEnds(WhiteWins, "White mates", GE_FILE);
\r
8425 case MT_STALEMATE:
\r
8426 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
\r
8432 case PositionDiagram: /* should not happen; ignore */
\r
8433 case ElapsedTime: /* ignore */
\r
8434 case NAG: /* ignore */
\r
8435 if (appData.debugMode)
\r
8436 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
\r
8437 yy_text, (int) moveType);
\r
8438 return LoadGameOneMove((ChessMove)0); /* tail recursion */
\r
8441 if (appData.testLegality) {
\r
8442 if (appData.debugMode)
\r
8443 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
\r
8444 sprintf(move, _("Illegal move: %d.%s%s"),
\r
8445 (forwardMostMove / 2) + 1,
\r
8446 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
\r
8447 DisplayError(move, 0);
\r
8450 if (appData.debugMode)
\r
8451 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
\r
8452 yy_text, currentMoveString);
\r
8453 fromX = currentMoveString[0] - AAA;
\r
8454 fromY = currentMoveString[1] - ONE;
\r
8455 toX = currentMoveString[2] - AAA;
\r
8456 toY = currentMoveString[3] - ONE;
\r
8457 promoChar = currentMoveString[4];
\r
8461 case AmbiguousMove:
\r
8462 if (appData.debugMode)
\r
8463 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
\r
8464 sprintf(move, _("Ambiguous move: %d.%s%s"),
\r
8465 (forwardMostMove / 2) + 1,
\r
8466 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
\r
8467 DisplayError(move, 0);
\r
8472 case ImpossibleMove:
\r
8473 if (appData.debugMode)
\r
8474 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
\r
8475 sprintf(move, _("Illegal move: %d.%s%s"),
\r
8476 (forwardMostMove / 2) + 1,
\r
8477 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
\r
8478 DisplayError(move, 0);
\r
8484 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
\r
8485 DrawPosition(FALSE, boards[currentMove]);
\r
8486 DisplayBothClocks();
\r
8487 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
\r
8488 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
8490 (void) StopLoadGameTimer();
\r
8491 gameFileFP = NULL;
\r
8492 cmailOldMove = forwardMostMove;
\r
8495 /* currentMoveString is set as a side-effect of yylex */
\r
8496 strcat(currentMoveString, "\n");
\r
8497 strcpy(moveList[forwardMostMove], currentMoveString);
\r
8499 thinkOutput[0] = NULLCHAR;
\r
8500 MakeMove(fromX, fromY, toX, toY, promoChar);
\r
8501 currentMove = forwardMostMove;
\r
8506 /* Load the nth game from the given file */
\r
8508 LoadGameFromFile(filename, n, title, useList)
\r
8512 /*Boolean*/ int useList;
\r
8515 char buf[MSG_SIZ];
\r
8517 if (strcmp(filename, "-") == 0) {
\r
8521 f = fopen(filename, "rb");
\r
8523 sprintf(buf, _("Can't open \"%s\""), filename);
\r
8524 DisplayError(buf, errno);
\r
8528 if (fseek(f, 0, 0) == -1) {
\r
8529 /* f is not seekable; probably a pipe */
\r
8532 if (useList && n == 0) {
\r
8533 int error = GameListBuild(f);
\r
8535 DisplayError(_("Cannot build game list"), error);
\r
8536 } else if (!ListEmpty(&gameList) &&
\r
8537 ((ListGame *) gameList.tailPred)->number > 1) {
\r
8538 GameListPopUp(f, title);
\r
8541 GameListDestroy();
\r
8544 if (n == 0) n = 1;
\r
8545 return LoadGame(f, n, title, FALSE);
\r
8550 MakeRegisteredMove()
\r
8552 int fromX, fromY, toX, toY;
\r
8554 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
\r
8555 switch (cmailMoveType[lastLoadGameNumber - 1]) {
\r
8558 if (appData.debugMode)
\r
8559 fprintf(debugFP, "Restoring %s for game %d\n",
\r
8560 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
\r
8562 thinkOutput[0] = NULLCHAR;
\r
8563 strcpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1]);
\r
8564 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
\r
8565 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
\r
8566 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
\r
8567 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
\r
8568 promoChar = cmailMove[lastLoadGameNumber - 1][4];
\r
8569 MakeMove(fromX, fromY, toX, toY, promoChar);
\r
8570 ShowMove(fromX, fromY, toX, toY);
\r
8572 switch (MateTest(boards[currentMove], PosFlags(currentMove),
\r
8573 EP_UNKNOWN, castlingRights[currentMove]) ) {
\r
8578 case MT_CHECKMATE:
\r
8579 if (WhiteOnMove(currentMove)) {
\r
8580 GameEnds(BlackWins, "Black mates", GE_PLAYER);
\r
8582 GameEnds(WhiteWins, "White mates", GE_PLAYER);
\r
8586 case MT_STALEMATE:
\r
8587 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
\r
8593 case CMAIL_RESIGN:
\r
8594 if (WhiteOnMove(currentMove)) {
\r
8595 GameEnds(BlackWins, "White resigns", GE_PLAYER);
\r
8597 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
\r
8601 case CMAIL_ACCEPT:
\r
8602 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
\r
8613 /* Wrapper around LoadGame for use when a Cmail message is loaded */
\r
8615 CmailLoadGame(f, gameNumber, title, useList)
\r
8623 if (gameNumber > nCmailGames) {
\r
8624 DisplayError(_("No more games in this message"), 0);
\r
8627 if (f == lastLoadGameFP) {
\r
8628 int offset = gameNumber - lastLoadGameNumber;
\r
8629 if (offset == 0) {
\r
8630 cmailMsg[0] = NULLCHAR;
\r
8631 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
\r
8632 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
\r
8633 nCmailMovesRegistered--;
\r
8635 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
\r
8636 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
\r
8637 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
\r
8640 if (! RegisterMove()) return FALSE;
\r
8644 retVal = LoadGame(f, gameNumber, title, useList);
\r
8646 /* Make move registered during previous look at this game, if any */
\r
8647 MakeRegisteredMove();
\r
8649 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
\r
8650 commentList[currentMove]
\r
8651 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
\r
8652 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
8658 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
\r
8660 ReloadGame(offset)
\r
8663 int gameNumber = lastLoadGameNumber + offset;
\r
8664 if (lastLoadGameFP == NULL) {
\r
8665 DisplayError(_("No game has been loaded yet"), 0);
\r
8668 if (gameNumber <= 0) {
\r
8669 DisplayError(_("Can't back up any further"), 0);
\r
8672 if (cmailMsgLoaded) {
\r
8673 return CmailLoadGame(lastLoadGameFP, gameNumber,
\r
8674 lastLoadGameTitle, lastLoadGameUseList);
\r
8676 return LoadGame(lastLoadGameFP, gameNumber,
\r
8677 lastLoadGameTitle, lastLoadGameUseList);
\r
8683 /* Load the nth game from open file f */
\r
8685 LoadGame(f, gameNumber, title, useList)
\r
8692 char buf[MSG_SIZ];
\r
8693 int gn = gameNumber;
\r
8694 ListGame *lg = NULL;
\r
8695 int numPGNTags = 0;
\r
8697 GameMode oldGameMode;
\r
8698 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
\r
8700 if (appData.debugMode)
\r
8701 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
\r
8703 if (gameMode == Training )
\r
8704 SetTrainingModeOff();
\r
8706 oldGameMode = gameMode;
\r
8707 if (gameMode != BeginningOfGame) {
\r
8708 Reset(FALSE, TRUE);
\r
8712 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
\r
8713 fclose(lastLoadGameFP);
\r
8717 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
\r
8720 fseek(f, lg->offset, 0);
\r
8721 GameListHighlight(gameNumber);
\r
8725 DisplayError(_("Game number out of range"), 0);
\r
8729 GameListDestroy();
\r
8730 if (fseek(f, 0, 0) == -1) {
\r
8731 if (f == lastLoadGameFP ?
\r
8732 gameNumber == lastLoadGameNumber + 1 :
\r
8733 gameNumber == 1) {
\r
8736 DisplayError(_("Can't seek on game file"), 0);
\r
8741 lastLoadGameFP = f;
\r
8742 lastLoadGameNumber = gameNumber;
\r
8743 strcpy(lastLoadGameTitle, title);
\r
8744 lastLoadGameUseList = useList;
\r
8748 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
\r
8749 sprintf(buf, "%s vs. %s", lg->gameInfo.white,
\r
8750 lg->gameInfo.black);
\r
8751 DisplayTitle(buf);
\r
8752 } else if (*title != NULLCHAR) {
\r
8753 if (gameNumber > 1) {
\r
8754 sprintf(buf, "%s %d", title, gameNumber);
\r
8755 DisplayTitle(buf);
\r
8757 DisplayTitle(title);
\r
8761 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
\r
8762 gameMode = PlayFromGameFile;
\r
8766 currentMove = forwardMostMove = backwardMostMove = 0;
\r
8767 CopyBoard(boards[0], initialPosition);
\r
8771 * Skip the first gn-1 games in the file.
\r
8772 * Also skip over anything that precedes an identifiable
\r
8773 * start of game marker, to avoid being confused by
\r
8774 * garbage at the start of the file. Currently
\r
8775 * recognized start of game markers are the move number "1",
\r
8776 * the pattern "gnuchess .* game", the pattern
\r
8777 * "^[#;%] [^ ]* game file", and a PGN tag block.
\r
8778 * A game that starts with one of the latter two patterns
\r
8779 * will also have a move number 1, possibly
\r
8780 * following a position diagram.
\r
8781 * 5-4-02: Let's try being more lenient and allowing a game to
\r
8782 * start with an unnumbered move. Does that break anything?
\r
8784 cm = lastLoadGameStart = (ChessMove) 0;
\r
8786 yyboardindex = forwardMostMove;
\r
8787 cm = (ChessMove) yylex();
\r
8789 case (ChessMove) 0:
\r
8790 if (cmailMsgLoaded) {
\r
8791 nCmailGames = CMAIL_MAX_GAMES - gn;
\r
8793 Reset(TRUE, TRUE);
\r
8794 DisplayError(_("Game not found in file"), 0);
\r
8798 case GNUChessGame:
\r
8801 lastLoadGameStart = cm;
\r
8804 case MoveNumberOne:
\r
8805 switch (lastLoadGameStart) {
\r
8806 case GNUChessGame:
\r
8810 case MoveNumberOne:
\r
8811 case (ChessMove) 0:
\r
8812 gn--; /* count this game */
\r
8813 lastLoadGameStart = cm;
\r
8822 switch (lastLoadGameStart) {
\r
8823 case GNUChessGame:
\r
8825 case MoveNumberOne:
\r
8826 case (ChessMove) 0:
\r
8827 gn--; /* count this game */
\r
8828 lastLoadGameStart = cm;
\r
8831 lastLoadGameStart = cm; /* game counted already */
\r
8839 yyboardindex = forwardMostMove;
\r
8840 cm = (ChessMove) yylex();
\r
8841 } while (cm == PGNTag || cm == Comment);
\r
8848 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
\r
8849 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
\r
8850 != CMAIL_OLD_RESULT) {
\r
8851 nCmailResults ++ ;
\r
8852 cmailResult[ CMAIL_MAX_GAMES
\r
8853 - gn - 1] = CMAIL_OLD_RESULT;
\r
8859 /* Only a NormalMove can be at the start of a game
\r
8860 * without a position diagram. */
\r
8861 if (lastLoadGameStart == (ChessMove) 0) {
\r
8863 lastLoadGameStart = MoveNumberOne;
\r
8872 if (appData.debugMode)
\r
8873 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
\r
8875 if (cm == XBoardGame) {
\r
8876 /* Skip any header junk before position diagram and/or move 1 */
\r
8878 yyboardindex = forwardMostMove;
\r
8879 cm = (ChessMove) yylex();
\r
8881 if (cm == (ChessMove) 0 ||
\r
8882 cm == GNUChessGame || cm == XBoardGame) {
\r
8883 /* Empty game; pretend end-of-file and handle later */
\r
8884 cm = (ChessMove) 0;
\r
8888 if (cm == MoveNumberOne || cm == PositionDiagram ||
\r
8889 cm == PGNTag || cm == Comment)
\r
8892 } else if (cm == GNUChessGame) {
\r
8893 if (gameInfo.event != NULL) {
\r
8894 free(gameInfo.event);
\r
8896 gameInfo.event = StrSave(yy_text);
\r
8899 startedFromSetupPosition = FALSE;
\r
8900 while (cm == PGNTag) {
\r
8901 if (appData.debugMode)
\r
8902 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
\r
8903 err = ParsePGNTag(yy_text, &gameInfo);
\r
8904 if (!err) numPGNTags++;
\r
8906 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
\r
8907 if(gameInfo.variant != oldVariant) {
\r
8908 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
\r
8909 InitPosition(TRUE);
\r
8910 oldVariant = gameInfo.variant;
\r
8911 if (appData.debugMode)
\r
8912 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
\r
8916 if (gameInfo.fen != NULL) {
\r
8917 Board initial_position;
\r
8918 startedFromSetupPosition = TRUE;
\r
8919 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
\r
8920 Reset(TRUE, TRUE);
\r
8921 DisplayError(_("Bad FEN position in file"), 0);
\r
8924 CopyBoard(boards[0], initial_position);
\r
8925 if (blackPlaysFirst) {
\r
8926 currentMove = forwardMostMove = backwardMostMove = 1;
\r
8927 CopyBoard(boards[1], initial_position);
\r
8928 strcpy(moveList[0], "");
\r
8929 strcpy(parseList[0], "");
\r
8930 timeRemaining[0][1] = whiteTimeRemaining;
\r
8931 timeRemaining[1][1] = blackTimeRemaining;
\r
8932 if (commentList[0] != NULL) {
\r
8933 commentList[1] = commentList[0];
\r
8934 commentList[0] = NULL;
\r
8937 currentMove = forwardMostMove = backwardMostMove = 0;
\r
8939 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
\r
8941 initialRulePlies = FENrulePlies;
\r
8942 epStatus[forwardMostMove] = FENepStatus;
\r
8943 for( i=0; i< nrCastlingRights; i++ )
\r
8944 initialRights[i] = castlingRights[forwardMostMove][i] = FENcastlingRights[i];
\r
8946 yyboardindex = forwardMostMove;
\r
8947 free(gameInfo.fen);
\r
8948 gameInfo.fen = NULL;
\r
8951 yyboardindex = forwardMostMove;
\r
8952 cm = (ChessMove) yylex();
\r
8954 /* Handle comments interspersed among the tags */
\r
8955 while (cm == Comment) {
\r
8957 if (appData.debugMode)
\r
8958 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
\r
8960 if (*p == '{' || *p == '[' || *p == '(') {
\r
8961 p[strlen(p) - 1] = NULLCHAR;
\r
8964 while (*p == '\n') p++;
\r
8965 AppendComment(currentMove, p);
\r
8966 yyboardindex = forwardMostMove;
\r
8967 cm = (ChessMove) yylex();
\r
8971 /* don't rely on existence of Event tag since if game was
\r
8972 * pasted from clipboard the Event tag may not exist
\r
8974 if (numPGNTags > 0){
\r
8976 if (gameInfo.variant == VariantNormal) {
\r
8977 gameInfo.variant = StringToVariant(gameInfo.event);
\r
8980 if( appData.autoDisplayTags ) {
\r
8981 tags = PGNTags(&gameInfo);
\r
8982 TagsPopUp(tags, CmailMsg());
\r
8987 /* Make something up, but don't display it now */
\r
8992 if (cm == PositionDiagram) {
\r
8995 Board initial_position;
\r
8997 if (appData.debugMode)
\r
8998 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
\r
9000 if (!startedFromSetupPosition) {
\r
9002 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
\r
9003 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
\r
9013 initial_position[i][j++] = CharToPiece(*p);
\r
9016 while (*p == ' ' || *p == '\t' ||
\r
9017 *p == '\n' || *p == '\r') p++;
\r
9019 if (strncmp(p, "black", strlen("black"))==0)
\r
9020 blackPlaysFirst = TRUE;
\r
9022 blackPlaysFirst = FALSE;
\r
9023 startedFromSetupPosition = TRUE;
\r
9025 CopyBoard(boards[0], initial_position);
\r
9026 if (blackPlaysFirst) {
\r
9027 currentMove = forwardMostMove = backwardMostMove = 1;
\r
9028 CopyBoard(boards[1], initial_position);
\r
9029 strcpy(moveList[0], "");
\r
9030 strcpy(parseList[0], "");
\r
9031 timeRemaining[0][1] = whiteTimeRemaining;
\r
9032 timeRemaining[1][1] = blackTimeRemaining;
\r
9033 if (commentList[0] != NULL) {
\r
9034 commentList[1] = commentList[0];
\r
9035 commentList[0] = NULL;
\r
9038 currentMove = forwardMostMove = backwardMostMove = 0;
\r
9041 yyboardindex = forwardMostMove;
\r
9042 cm = (ChessMove) yylex();
\r
9045 if (first.pr == NoProc) {
\r
9046 StartChessProgram(&first);
\r
9048 InitChessProgram(&first, FALSE);
\r
9049 SendToProgram("force\n", &first);
\r
9050 if (startedFromSetupPosition) {
\r
9051 SendBoard(&first, forwardMostMove);
\r
9052 if (appData.debugMode) {
\r
9053 fprintf(debugFP, "Load Game\n");
\r
9055 DisplayBothClocks();
\r
9058 /* [HGM] server: flag to write setup moves in broadcast file as one */
\r
9059 loadFlag = appData.suppressLoadMoves;
\r
9061 while (cm == Comment) {
\r
9063 if (appData.debugMode)
\r
9064 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
\r
9066 if (*p == '{' || *p == '[' || *p == '(') {
\r
9067 p[strlen(p) - 1] = NULLCHAR;
\r
9070 while (*p == '\n') p++;
\r
9071 AppendComment(currentMove, p);
\r
9072 yyboardindex = forwardMostMove;
\r
9073 cm = (ChessMove) yylex();
\r
9076 if ((cm == (ChessMove) 0 && lastLoadGameStart != (ChessMove) 0) ||
\r
9077 cm == WhiteWins || cm == BlackWins ||
\r
9078 cm == GameIsDrawn || cm == GameUnfinished) {
\r
9079 DisplayMessage("", _("No moves in game"));
\r
9080 if (cmailMsgLoaded) {
\r
9081 if (appData.debugMode)
\r
9082 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
\r
9083 ClearHighlights();
\r
9086 DrawPosition(FALSE, boards[currentMove]);
\r
9087 DisplayBothClocks();
\r
9088 gameMode = EditGame;
\r
9090 gameFileFP = NULL;
\r
9095 // [HGM] PV info: routine tests if comment empty
\r
9096 if (!matchMode && (pausing || appData.timeDelay != 0)) {
\r
9097 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
9099 if (!matchMode && appData.timeDelay != 0)
\r
9100 DrawPosition(FALSE, boards[currentMove]);
\r
9102 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
\r
9103 programStats.ok_to_send = 1;
\r
9106 /* if the first token after the PGN tags is a move
\r
9107 * and not move number 1, retrieve it from the parser
\r
9109 if (cm != MoveNumberOne)
\r
9110 LoadGameOneMove(cm);
\r
9112 /* load the remaining moves from the file */
\r
9113 while (LoadGameOneMove((ChessMove)0)) {
\r
9114 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
\r
9115 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
\r
9118 /* rewind to the start of the game */
\r
9119 currentMove = backwardMostMove;
\r
9121 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
9123 if (oldGameMode == AnalyzeFile ||
\r
9124 oldGameMode == AnalyzeMode) {
\r
9125 AnalyzeFileEvent();
\r
9128 if (matchMode || appData.timeDelay == 0) {
\r
9130 gameMode = EditGame;
\r
9132 } else if (appData.timeDelay > 0) {
\r
9133 AutoPlayGameLoop();
\r
9136 if (appData.debugMode)
\r
9137 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
\r
9139 loadFlag = 0; /* [HGM] true game starts */
\r
9143 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
\r
9145 ReloadPosition(offset)
\r
9148 int positionNumber = lastLoadPositionNumber + offset;
\r
9149 if (lastLoadPositionFP == NULL) {
\r
9150 DisplayError(_("No position has been loaded yet"), 0);
\r
9153 if (positionNumber <= 0) {
\r
9154 DisplayError(_("Can't back up any further"), 0);
\r
9157 return LoadPosition(lastLoadPositionFP, positionNumber,
\r
9158 lastLoadPositionTitle);
\r
9161 /* Load the nth position from the given file */
\r
9163 LoadPositionFromFile(filename, n, title)
\r
9169 char buf[MSG_SIZ];
\r
9171 if (strcmp(filename, "-") == 0) {
\r
9172 return LoadPosition(stdin, n, "stdin");
\r
9174 f = fopen(filename, "rb");
\r
9176 sprintf(buf, _("Can't open \"%s\""), filename);
\r
9177 DisplayError(buf, errno);
\r
9180 return LoadPosition(f, n, title);
\r
9185 /* Load the nth position from the given open file, and close it */
\r
9187 LoadPosition(f, positionNumber, title)
\r
9189 int positionNumber;
\r
9192 char *p, line[MSG_SIZ];
\r
9193 Board initial_position;
\r
9194 int i, j, fenMode, pn;
\r
9196 if (gameMode == Training )
\r
9197 SetTrainingModeOff();
\r
9199 if (gameMode != BeginningOfGame) {
\r
9200 Reset(FALSE, TRUE);
\r
9202 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
\r
9203 fclose(lastLoadPositionFP);
\r
9205 if (positionNumber == 0) positionNumber = 1;
\r
9206 lastLoadPositionFP = f;
\r
9207 lastLoadPositionNumber = positionNumber;
\r
9208 strcpy(lastLoadPositionTitle, title);
\r
9209 if (first.pr == NoProc) {
\r
9210 StartChessProgram(&first);
\r
9211 InitChessProgram(&first, FALSE);
\r
9213 pn = positionNumber;
\r
9214 if (positionNumber < 0) {
\r
9215 /* Negative position number means to seek to that byte offset */
\r
9216 if (fseek(f, -positionNumber, 0) == -1) {
\r
9217 DisplayError(_("Can't seek on position file"), 0);
\r
9222 if (fseek(f, 0, 0) == -1) {
\r
9223 if (f == lastLoadPositionFP ?
\r
9224 positionNumber == lastLoadPositionNumber + 1 :
\r
9225 positionNumber == 1) {
\r
9228 DisplayError(_("Can't seek on position file"), 0);
\r
9233 /* See if this file is FEN or old-style xboard */
\r
9234 if (fgets(line, MSG_SIZ, f) == NULL) {
\r
9235 DisplayError(_("Position not found in file"), 0);
\r
9239 switch (line[0]) {
\r
9240 case '#': case 'x':
\r
9244 case 'p': case 'n': case 'b': case 'r': case 'q': case 'k':
\r
9245 case 'P': case 'N': case 'B': case 'R': case 'Q': case 'K':
\r
9246 case '1': case '2': case '3': case '4': case '5': case '6':
\r
9247 case '7': case '8': case '9':
\r
9248 case 'H': case 'A': case 'M': case 'h': case 'a': case 'm':
\r
9249 case 'E': case 'F': case 'G': case 'e': case 'f': case 'g':
\r
9250 case 'C': case 'W': case 'c': case 'w':
\r
9255 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
\r
9256 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
\r
9260 if (fenMode || line[0] == '#') pn--;
\r
9262 /* skip positions before number pn */
\r
9263 if (fgets(line, MSG_SIZ, f) == NULL) {
\r
9264 Reset(TRUE, TRUE);
\r
9265 DisplayError(_("Position not found in file"), 0);
\r
9268 if (fenMode || line[0] == '#') pn--;
\r
9273 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
\r
9274 DisplayError(_("Bad FEN position in file"), 0);
\r
9278 (void) fgets(line, MSG_SIZ, f);
\r
9279 (void) fgets(line, MSG_SIZ, f);
\r
9281 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
9282 (void) fgets(line, MSG_SIZ, f);
\r
9283 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
\r
9286 initial_position[i][j++] = CharToPiece(*p);
\r
9290 blackPlaysFirst = FALSE;
\r
9292 (void) fgets(line, MSG_SIZ, f);
\r
9293 if (strncmp(line, "black", strlen("black"))==0)
\r
9294 blackPlaysFirst = TRUE;
\r
9297 startedFromSetupPosition = TRUE;
\r
9299 SendToProgram("force\n", &first);
\r
9300 CopyBoard(boards[0], initial_position);
\r
9301 if (blackPlaysFirst) {
\r
9302 currentMove = forwardMostMove = backwardMostMove = 1;
\r
9303 strcpy(moveList[0], "");
\r
9304 strcpy(parseList[0], "");
\r
9305 CopyBoard(boards[1], initial_position);
\r
9306 DisplayMessage("", _("Black to play"));
\r
9308 currentMove = forwardMostMove = backwardMostMove = 0;
\r
9309 DisplayMessage("", _("White to play"));
\r
9311 /* [HGM] copy FEN attributes as well */
\r
9313 initialRulePlies = FENrulePlies;
\r
9314 epStatus[forwardMostMove] = FENepStatus;
\r
9315 for( i=0; i< nrCastlingRights; i++ )
\r
9316 castlingRights[forwardMostMove][i] = FENcastlingRights[i];
\r
9318 SendBoard(&first, forwardMostMove);
\r
9319 if (appData.debugMode) {
\r
9321 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", castlingRights[i][j]);fprintf(debugFP,"\n");}
\r
9322 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
\r
9323 fprintf(debugFP, "Load Position\n");
\r
9326 if (positionNumber > 1) {
\r
9327 sprintf(line, "%s %d", title, positionNumber);
\r
9328 DisplayTitle(line);
\r
9330 DisplayTitle(title);
\r
9332 gameMode = EditGame;
\r
9335 timeRemaining[0][1] = whiteTimeRemaining;
\r
9336 timeRemaining[1][1] = blackTimeRemaining;
\r
9337 DrawPosition(FALSE, boards[currentMove]);
\r
9344 CopyPlayerNameIntoFileName(dest, src)
\r
9345 char **dest, *src;
\r
9347 while (*src != NULLCHAR && *src != ',') {
\r
9348 if (*src == ' ') {
\r
9352 *(*dest)++ = *src++;
\r
9357 char *DefaultFileName(ext)
\r
9360 static char def[MSG_SIZ];
\r
9363 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
\r
9365 CopyPlayerNameIntoFileName(&p, gameInfo.white);
\r
9367 CopyPlayerNameIntoFileName(&p, gameInfo.black);
\r
9371 def[0] = NULLCHAR;
\r
9376 /* Save the current game to the given file */
\r
9378 SaveGameToFile(filename, append)
\r
9383 char buf[MSG_SIZ];
\r
9385 if (strcmp(filename, "-") == 0) {
\r
9386 return SaveGame(stdout, 0, NULL);
\r
9388 f = fopen(filename, append ? "a" : "w");
\r
9390 sprintf(buf, _("Can't open \"%s\""), filename);
\r
9391 DisplayError(buf, errno);
\r
9394 return SaveGame(f, 0, NULL);
\r
9403 static char buf[MSG_SIZ];
\r
9406 p = strchr(str, ' ');
\r
9407 if (p == NULL) return str;
\r
9408 strncpy(buf, str, p - str);
\r
9409 buf[p - str] = NULLCHAR;
\r
9413 #define PGN_MAX_LINE 75
\r
9415 #define PGN_SIDE_WHITE 0
\r
9416 #define PGN_SIDE_BLACK 1
\r
9419 static int FindFirstMoveOutOfBook( int side )
\r
9423 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
\r
9424 int index = backwardMostMove;
\r
9425 int has_book_hit = 0;
\r
9427 if( (index % 2) != side ) {
\r
9431 while( index < forwardMostMove ) {
\r
9432 /* Check to see if engine is in book */
\r
9433 int depth = pvInfoList[index].depth;
\r
9434 int score = pvInfoList[index].score;
\r
9437 if( depth <= 2 ) {
\r
9440 else if( score == 0 && depth == 63 ) {
\r
9441 in_book = 1; /* Zappa */
\r
9443 else if( score == 2 && depth == 99 ) {
\r
9444 in_book = 1; /* Abrok */
\r
9447 has_book_hit += in_book;
\r
9463 void GetOutOfBookInfo( char * buf )
\r
9467 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
\r
9469 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
\r
9470 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
\r
9474 if( oob[0] >= 0 || oob[1] >= 0 ) {
\r
9475 for( i=0; i<2; i++ ) {
\r
9479 if( i > 0 && oob[0] >= 0 ) {
\r
9480 strcat( buf, " " );
\r
9483 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
\r
9484 sprintf( buf+strlen(buf), "%s%.2f",
\r
9485 pvInfoList[idx].score >= 0 ? "+" : "",
\r
9486 pvInfoList[idx].score / 100.0 );
\r
9492 /* Save game in PGN style and close the file */
\r
9497 int i, offset, linelen, newblock;
\r
9501 int movelen, numlen, blank;
\r
9502 char move_buffer[100]; /* [AS] Buffer for move+PV info */
\r
9504 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
\r
9506 tm = time((time_t *) NULL);
\r
9508 PrintPGNTags(f, &gameInfo);
\r
9510 if (backwardMostMove > 0 || startedFromSetupPosition) {
\r
9511 char *fen = PositionToFEN(backwardMostMove, 1);
\r
9512 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
\r
9513 fprintf(f, "\n{--------------\n");
\r
9514 PrintPosition(f, backwardMostMove);
\r
9515 fprintf(f, "--------------}\n");
\r
9519 /* [AS] Out of book annotation */
\r
9520 if( appData.saveOutOfBookInfo ) {
\r
9523 GetOutOfBookInfo( buf );
\r
9525 if( buf[0] != '\0' ) {
\r
9526 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
\r
9533 i = backwardMostMove;
\r
9537 while (i < forwardMostMove) {
\r
9538 /* Print comments preceding this move */
\r
9539 if (commentList[i] != NULL) {
\r
9540 if (linelen > 0) fprintf(f, "\n");
\r
9541 fprintf(f, "{\n%s}\n", commentList[i]);
\r
9546 /* Format move number */
\r
9547 if ((i % 2) == 0) {
\r
9548 sprintf(numtext, "%d.", (i - offset)/2 + 1);
\r
9551 sprintf(numtext, "%d...", (i - offset)/2 + 1);
\r
9553 numtext[0] = NULLCHAR;
\r
9556 numlen = strlen(numtext);
\r
9559 /* Print move number */
\r
9560 blank = linelen > 0 && numlen > 0;
\r
9561 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
\r
9570 fprintf(f, numtext);
\r
9571 linelen += numlen;
\r
9574 movelen = strlen(parseList[i]); /* [HGM] pgn: line-break point before move */
\r
9577 blank = linelen > 0 && movelen > 0;
\r
9578 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
\r
9587 fprintf(f, parseList[i]);
\r
9588 linelen += movelen;
\r
9590 /* [AS] Add PV info if present */
\r
9591 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
\r
9592 /* [HGM] add time */
\r
9593 char buf[MSG_SIZ]; int seconds = 0;
\r
9596 if(i >= backwardMostMove) {
\r
9597 if(WhiteOnMove(i))
\r
9598 seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
\r
9599 + GetTimeQuota(i/2) / WhitePlayer()->timeOdds;
\r
9601 seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
\r
9602 + GetTimeQuota(i/2) / WhitePlayer()->other->timeOdds;
\r
9604 seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
\r
9606 seconds = (pvInfoList[i].time + 5)/10; // [HGM] PVtime: use engine time
\r
9608 if (appData.debugMode,0) {
\r
9609 fprintf(debugFP, "times = %d %d %d %d, seconds=%d\n",
\r
9610 timeRemaining[0][i+1], timeRemaining[0][i],
\r
9611 timeRemaining[1][i+1], timeRemaining[1][i], seconds
\r
9615 if( seconds <= 0) buf[0] = 0; else
\r
9616 if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
\r
9617 seconds = (seconds + 4)/10; // round to full seconds
\r
9618 if( seconds < 60 ) sprintf(buf, " %d%c", seconds, 0); else
\r
9619 sprintf(buf, " %d:%02d%c", seconds/60, seconds%60, 0);
\r
9622 sprintf( move_buffer, "{%s%.2f/%d%s}",
\r
9623 pvInfoList[i].score >= 0 ? "+" : "",
\r
9624 pvInfoList[i].score / 100.0,
\r
9625 pvInfoList[i].depth,
\r
9628 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
\r
9630 /* Print score/depth */
\r
9631 blank = linelen > 0 && movelen > 0;
\r
9632 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
\r
9641 fprintf(f, move_buffer);
\r
9642 linelen += movelen;
\r
9648 /* Start a new line */
\r
9649 if (linelen > 0) fprintf(f, "\n");
\r
9651 /* Print comments after last move */
\r
9652 if (commentList[i] != NULL) {
\r
9653 fprintf(f, "{\n%s}\n", commentList[i]);
\r
9656 /* Print result */
\r
9657 if (gameInfo.resultDetails != NULL &&
\r
9658 gameInfo.resultDetails[0] != NULLCHAR) {
\r
9659 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
\r
9660 PGNResult(gameInfo.result));
\r
9662 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
\r
9669 /* Save game in old style and close the file */
\r
9671 SaveGameOldStyle(f)
\r
9677 tm = time((time_t *) NULL);
\r
9679 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
\r
9680 PrintOpponents(f);
\r
9682 if (backwardMostMove > 0 || startedFromSetupPosition) {
\r
9683 fprintf(f, "\n[--------------\n");
\r
9684 PrintPosition(f, backwardMostMove);
\r
9685 fprintf(f, "--------------]\n");
\r
9690 i = backwardMostMove;
\r
9691 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
\r
9693 while (i < forwardMostMove) {
\r
9694 if (commentList[i] != NULL) {
\r
9695 fprintf(f, "[%s]\n", commentList[i]);
\r
9698 if ((i % 2) == 1) {
\r
9699 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
\r
9702 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
\r
9704 if (commentList[i] != NULL) {
\r
9708 if (i >= forwardMostMove) {
\r
9712 fprintf(f, "%s\n", parseList[i]);
\r
9717 if (commentList[i] != NULL) {
\r
9718 fprintf(f, "[%s]\n", commentList[i]);
\r
9721 /* This isn't really the old style, but it's close enough */
\r
9722 if (gameInfo.resultDetails != NULL &&
\r
9723 gameInfo.resultDetails[0] != NULLCHAR) {
\r
9724 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
\r
9725 gameInfo.resultDetails);
\r
9727 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
\r
9734 /* Save the current game to open file f and close the file */
\r
9736 SaveGame(f, dummy, dummy2)
\r
9741 if (gameMode == EditPosition) EditPositionDone();
\r
9742 if (appData.oldSaveStyle)
\r
9743 return SaveGameOldStyle(f);
\r
9745 return SaveGamePGN(f);
\r
9748 /* Save the current position to the given file */
\r
9750 SavePositionToFile(filename)
\r
9754 char buf[MSG_SIZ];
\r
9756 if (strcmp(filename, "-") == 0) {
\r
9757 return SavePosition(stdout, 0, NULL);
\r
9759 f = fopen(filename, "a");
\r
9761 sprintf(buf, _("Can't open \"%s\""), filename);
\r
9762 DisplayError(buf, errno);
\r
9765 SavePosition(f, 0, NULL);
\r
9771 /* Save the current position to the given open file and close the file */
\r
9773 SavePosition(f, dummy, dummy2)
\r
9781 if (appData.oldSaveStyle) {
\r
9782 tm = time((time_t *) NULL);
\r
9784 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
\r
9785 PrintOpponents(f);
\r
9786 fprintf(f, "[--------------\n");
\r
9787 PrintPosition(f, currentMove);
\r
9788 fprintf(f, "--------------]\n");
\r
9790 fen = PositionToFEN(currentMove, 1);
\r
9791 fprintf(f, "%s\n", fen);
\r
9799 ReloadCmailMsgEvent(unregister)
\r
9803 static char *inFilename = NULL;
\r
9804 static char *outFilename;
\r
9806 struct stat inbuf, outbuf;
\r
9809 /* Any registered moves are unregistered if unregister is set, */
\r
9810 /* i.e. invoked by the signal handler */
\r
9812 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
\r
9813 cmailMoveRegistered[i] = FALSE;
\r
9814 if (cmailCommentList[i] != NULL) {
\r
9815 free(cmailCommentList[i]);
\r
9816 cmailCommentList[i] = NULL;
\r
9819 nCmailMovesRegistered = 0;
\r
9822 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
\r
9823 cmailResult[i] = CMAIL_NOT_RESULT;
\r
9825 nCmailResults = 0;
\r
9827 if (inFilename == NULL) {
\r
9828 /* Because the filenames are static they only get malloced once */
\r
9829 /* and they never get freed */
\r
9830 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
\r
9831 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
\r
9833 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
\r
9834 sprintf(outFilename, "%s.out", appData.cmailGameName);
\r
9837 status = stat(outFilename, &outbuf);
\r
9839 cmailMailedMove = FALSE;
\r
9841 status = stat(inFilename, &inbuf);
\r
9842 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
\r
9845 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
\r
9846 counts the games, notes how each one terminated, etc.
\r
9848 It would be nice to remove this kludge and instead gather all
\r
9849 the information while building the game list. (And to keep it
\r
9850 in the game list nodes instead of having a bunch of fixed-size
\r
9851 parallel arrays.) Note this will require getting each game's
\r
9852 termination from the PGN tags, as the game list builder does
\r
9853 not process the game moves. --mann
\r
9855 cmailMsgLoaded = TRUE;
\r
9856 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
\r
9858 /* Load first game in the file or popup game menu */
\r
9859 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
\r
9861 #endif /* !WIN32 */
\r
9869 char string[MSG_SIZ];
\r
9871 if ( cmailMailedMove
\r
9872 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
\r
9873 return TRUE; /* Allow free viewing */
\r
9876 /* Unregister move to ensure that we don't leave RegisterMove */
\r
9877 /* with the move registered when the conditions for registering no */
\r
9879 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
\r
9880 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
\r
9881 nCmailMovesRegistered --;
\r
9883 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
\r
9885 free(cmailCommentList[lastLoadGameNumber - 1]);
\r
9886 cmailCommentList[lastLoadGameNumber - 1] = NULL;
\r
9890 if (cmailOldMove == -1) {
\r
9891 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
\r
9895 if (currentMove > cmailOldMove + 1) {
\r
9896 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
\r
9900 if (currentMove < cmailOldMove) {
\r
9901 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
\r
9905 if (forwardMostMove > currentMove) {
\r
9906 /* Silently truncate extra moves */
\r
9910 if ( (currentMove == cmailOldMove + 1)
\r
9911 || ( (currentMove == cmailOldMove)
\r
9912 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
\r
9913 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
\r
9914 if (gameInfo.result != GameUnfinished) {
\r
9915 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
\r
9918 if (commentList[currentMove] != NULL) {
\r
9919 cmailCommentList[lastLoadGameNumber - 1]
\r
9920 = StrSave(commentList[currentMove]);
\r
9922 strcpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1]);
\r
9924 if (appData.debugMode)
\r
9925 fprintf(debugFP, "Saving %s for game %d\n",
\r
9926 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
\r
9929 "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
\r
9931 f = fopen(string, "w");
\r
9932 if (appData.oldSaveStyle) {
\r
9933 SaveGameOldStyle(f); /* also closes the file */
\r
9935 sprintf(string, "%s.pos.out", appData.cmailGameName);
\r
9936 f = fopen(string, "w");
\r
9937 SavePosition(f, 0, NULL); /* also closes the file */
\r
9939 fprintf(f, "{--------------\n");
\r
9940 PrintPosition(f, currentMove);
\r
9941 fprintf(f, "--------------}\n\n");
\r
9943 SaveGame(f, 0, NULL); /* also closes the file*/
\r
9946 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
\r
9947 nCmailMovesRegistered ++;
\r
9948 } else if (nCmailGames == 1) {
\r
9949 DisplayError(_("You have not made a move yet"), 0);
\r
9960 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
\r
9961 FILE *commandOutput;
\r
9962 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
\r
9963 int nBytes = 0; /* Suppress warnings on uninitialized variables */
\r
9969 if (! cmailMsgLoaded) {
\r
9970 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
\r
9974 if (nCmailGames == nCmailResults) {
\r
9975 DisplayError(_("No unfinished games"), 0);
\r
9979 #if CMAIL_PROHIBIT_REMAIL
\r
9980 if (cmailMailedMove) {
\r
9981 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
9982 DisplayError(msg, 0);
\r
9987 if (! (cmailMailedMove || RegisterMove())) return;
\r
9989 if ( cmailMailedMove
\r
9990 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
\r
9991 sprintf(string, partCommandString,
\r
9992 appData.debugMode ? " -v" : "", appData.cmailGameName);
\r
9993 commandOutput = popen(string, "r");
\r
9995 if (commandOutput == NULL) {
\r
9996 DisplayError(_("Failed to invoke cmail"), 0);
\r
9998 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
\r
9999 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
\r
10001 if (nBuffers > 1) {
\r
10002 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
\r
10003 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
\r
10004 nBytes = MSG_SIZ - 1;
\r
10006 (void) memcpy(msg, buffer, nBytes);
\r
10008 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
\r
10010 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
\r
10011 cmailMailedMove = TRUE; /* Prevent >1 moves */
\r
10014 for (i = 0; i < nCmailGames; i ++) {
\r
10015 if (cmailResult[i] == CMAIL_NOT_RESULT) {
\r
10016 archived = FALSE;
\r
10020 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
\r
10022 sprintf(buffer, "%s/%s.%s.archive",
\r
10024 appData.cmailGameName,
\r
10026 LoadGameFromFile(buffer, 1, buffer, FALSE);
\r
10027 cmailMsgLoaded = FALSE;
\r
10031 DisplayInformation(msg);
\r
10032 pclose(commandOutput);
\r
10035 if ((*cmailMsg) != '\0') {
\r
10036 DisplayInformation(cmailMsg);
\r
10041 #endif /* !WIN32 */
\r
10050 int prependComma = 0;
\r
10052 char string[MSG_SIZ]; /* Space for game-list */
\r
10055 if (!cmailMsgLoaded) return "";
\r
10057 if (cmailMailedMove) {
\r
10058 sprintf(cmailMsg, _("Waiting for reply from opponent\n"));
\r
10060 /* Create a list of games left */
\r
10061 sprintf(string, "[");
\r
10062 for (i = 0; i < nCmailGames; i ++) {
\r
10063 if (! ( cmailMoveRegistered[i]
\r
10064 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
\r
10065 if (prependComma) {
\r
10066 sprintf(number, ",%d", i + 1);
\r
10068 sprintf(number, "%d", i + 1);
\r
10069 prependComma = 1;
\r
10072 strcat(string, number);
\r
10075 strcat(string, "]");
\r
10077 if (nCmailMovesRegistered + nCmailResults == 0) {
\r
10078 switch (nCmailGames) {
\r
10080 sprintf(cmailMsg,
\r
10081 _("Still need to make move for game\n"));
\r
10085 sprintf(cmailMsg,
\r
10086 _("Still need to make moves for both games\n"));
\r
10090 sprintf(cmailMsg,
\r
10091 _("Still need to make moves for all %d games\n"),
\r
10096 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
\r
10098 sprintf(cmailMsg,
\r
10099 _("Still need to make a move for game %s\n"),
\r
10104 if (nCmailResults == nCmailGames) {
\r
10105 sprintf(cmailMsg, _("No unfinished games\n"));
\r
10107 sprintf(cmailMsg, _("Ready to send mail\n"));
\r
10112 sprintf(cmailMsg,
\r
10113 _("Still need to make moves for games %s\n"),
\r
10119 #endif /* WIN32 */
\r
10125 if (gameMode == Training)
\r
10126 SetTrainingModeOff();
\r
10128 Reset(TRUE, TRUE);
\r
10129 cmailMsgLoaded = FALSE;
\r
10130 if (appData.icsActive) {
\r
10131 SendToICS(ics_prefix);
\r
10132 SendToICS("refresh\n");
\r
10137 ExitEvent(status)
\r
10141 if (exiting > 2) {
\r
10142 /* Give up on clean exit */
\r
10145 if (exiting > 1) {
\r
10146 /* Keep trying for clean exit */
\r
10150 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
\r
10152 if (telnetISR != NULL) {
\r
10153 RemoveInputSource(telnetISR);
\r
10155 if (icsPR != NoProc) {
\r
10156 DestroyChildProcess(icsPR, TRUE);
\r
10159 /* Save game if resource set and not already saved by GameEnds() */
\r
10160 if ((gameInfo.resultDetails == NULL || errorExitFlag )
\r
10161 && forwardMostMove > 0) {
\r
10162 if (*appData.saveGameFile != NULLCHAR) {
\r
10163 SaveGameToFile(appData.saveGameFile, TRUE);
\r
10164 } else if (appData.autoSaveGames) {
\r
10167 if (*appData.savePositionFile != NULLCHAR) {
\r
10168 SavePositionToFile(appData.savePositionFile);
\r
10171 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
\r
10173 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
\r
10174 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
\r
10176 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
\r
10177 /* make sure this other one finishes before killing it! */
\r
10178 if(endingGame) { int count = 0;
\r
10179 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
\r
10180 while(endingGame && count++ < 10) DoSleep(1);
\r
10181 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
\r
10184 /* Kill off chess programs */
\r
10185 if (first.pr != NoProc) {
\r
10186 ExitAnalyzeMode();
\r
10188 DoSleep( appData.delayBeforeQuit );
\r
10189 SendToProgram("quit\n", &first);
\r
10190 DoSleep( appData.delayAfterQuit );
\r
10191 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
\r
10193 if (second.pr != NoProc) {
\r
10194 DoSleep( appData.delayBeforeQuit );
\r
10195 SendToProgram("quit\n", &second);
\r
10196 DoSleep( appData.delayAfterQuit );
\r
10197 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
\r
10199 if (first.isr != NULL) {
\r
10200 RemoveInputSource(first.isr);
\r
10202 if (second.isr != NULL) {
\r
10203 RemoveInputSource(second.isr);
\r
10206 ShutDownFrontEnd();
\r
10213 if (appData.debugMode)
\r
10214 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
\r
10218 if (gameMode == MachinePlaysWhite ||
\r
10219 gameMode == MachinePlaysBlack) {
\r
10222 DisplayBothClocks();
\r
10224 if (gameMode == PlayFromGameFile) {
\r
10225 if (appData.timeDelay >= 0)
\r
10226 AutoPlayGameLoop();
\r
10227 } else if (gameMode == IcsExamining && pauseExamInvalid) {
\r
10228 Reset(FALSE, TRUE);
\r
10229 SendToICS(ics_prefix);
\r
10230 SendToICS("refresh\n");
\r
10231 } else if (currentMove < forwardMostMove) {
\r
10232 ForwardInner(forwardMostMove);
\r
10234 pauseExamInvalid = FALSE;
\r
10236 switch (gameMode) {
\r
10239 case IcsExamining:
\r
10240 pauseExamForwardMostMove = forwardMostMove;
\r
10241 pauseExamInvalid = FALSE;
\r
10242 /* fall through */
\r
10243 case IcsObserving:
\r
10244 case IcsPlayingWhite:
\r
10245 case IcsPlayingBlack:
\r
10249 case PlayFromGameFile:
\r
10250 (void) StopLoadGameTimer();
\r
10254 case BeginningOfGame:
\r
10255 if (appData.icsActive) return;
\r
10256 /* else fall through */
\r
10257 case MachinePlaysWhite:
\r
10258 case MachinePlaysBlack:
\r
10259 case TwoMachinesPlay:
\r
10260 if (forwardMostMove == 0)
\r
10261 return; /* don't pause if no one has moved */
\r
10262 if ((gameMode == MachinePlaysWhite &&
\r
10263 !WhiteOnMove(forwardMostMove)) ||
\r
10264 (gameMode == MachinePlaysBlack &&
\r
10265 WhiteOnMove(forwardMostMove))) {
\r
10276 EditCommentEvent()
\r
10278 char title[MSG_SIZ];
\r
10280 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
\r
10281 strcpy(title, _("Edit comment"));
\r
10283 sprintf(title, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
\r
10284 WhiteOnMove(currentMove - 1) ? " " : ".. ",
\r
10285 parseList[currentMove - 1]);
\r
10288 EditCommentPopUp(currentMove, title, commentList[currentMove]);
\r
10295 char *tags = PGNTags(&gameInfo);
\r
10296 EditTagsPopUp(tags);
\r
10301 AnalyzeModeEvent()
\r
10303 if (appData.noChessProgram || gameMode == AnalyzeMode)
\r
10306 if (gameMode != AnalyzeFile) {
\r
10307 if (!appData.icsEngineAnalyze) {
\r
10309 if (gameMode != EditGame) return;
\r
10311 ResurrectChessProgram();
\r
10312 SendToProgram("analyze\n", &first);
\r
10313 first.analyzing = TRUE;
\r
10314 /*first.maybeThinking = TRUE;*/
\r
10315 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
\r
10316 AnalysisPopUp(_("Analysis"),
\r
10317 _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
\r
10319 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
\r
10324 StartAnalysisClock();
\r
10325 GetTimeMark(&lastNodeCountTime);
\r
10326 lastNodeCount = 0;
\r
10330 AnalyzeFileEvent()
\r
10332 if (appData.noChessProgram || gameMode == AnalyzeFile)
\r
10335 if (gameMode != AnalyzeMode) {
\r
10337 if (gameMode != EditGame) return;
\r
10338 ResurrectChessProgram();
\r
10339 SendToProgram("analyze\n", &first);
\r
10340 first.analyzing = TRUE;
\r
10341 /*first.maybeThinking = TRUE;*/
\r
10342 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
\r
10343 AnalysisPopUp(_("Analysis"),
\r
10344 _("Starting analysis mode...\nIf this message stays up, your chess program does not support analysis."));
\r
10346 gameMode = AnalyzeFile;
\r
10351 StartAnalysisClock();
\r
10352 GetTimeMark(&lastNodeCountTime);
\r
10353 lastNodeCount = 0;
\r
10357 MachineWhiteEvent()
\r
10359 char buf[MSG_SIZ];
\r
10360 char *bookHit = NULL;
\r
10362 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
\r
10366 if (gameMode == PlayFromGameFile ||
\r
10367 gameMode == TwoMachinesPlay ||
\r
10368 gameMode == Training ||
\r
10369 gameMode == AnalyzeMode ||
\r
10370 gameMode == EndOfGame)
\r
10373 if (gameMode == EditPosition)
\r
10374 EditPositionDone();
\r
10376 if (!WhiteOnMove(currentMove)) {
\r
10377 DisplayError(_("It is not White's turn"), 0);
\r
10381 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
\r
10382 ExitAnalyzeMode();
\r
10384 if (gameMode == EditGame || gameMode == AnalyzeMode ||
\r
10385 gameMode == AnalyzeFile)
\r
10388 ResurrectChessProgram(); /* in case it isn't running */
\r
10389 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
\r
10390 gameMode = MachinePlaysWhite;
\r
10393 gameMode = MachinePlaysWhite;
\r
10397 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
\r
10398 DisplayTitle(buf);
\r
10399 if (first.sendName) {
\r
10400 sprintf(buf, "name %s\n", gameInfo.black);
\r
10401 SendToProgram(buf, &first);
\r
10403 if (first.sendTime) {
\r
10404 if (first.useColors) {
\r
10405 SendToProgram("black\n", &first); /*gnu kludge*/
\r
10407 SendTimeRemaining(&first, TRUE);
\r
10409 if (first.useColors) {
\r
10410 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
\r
10412 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
\r
10413 SetMachineThinkingEnables();
\r
10414 first.maybeThinking = TRUE;
\r
10417 if (appData.autoFlipView && !flipView) {
\r
10418 flipView = !flipView;
\r
10419 DrawPosition(FALSE, NULL);
\r
10420 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
\r
10423 if(bookHit) { // [HGM] book: simulate book reply
\r
10424 static char bookMove[MSG_SIZ]; // a bit generous?
\r
10426 programStats.depth = programStats.nodes = programStats.time =
\r
10427 programStats.score = programStats.got_only_move = 0;
\r
10428 sprintf(programStats.movelist, "%s (xbook)", bookHit);
\r
10430 strcpy(bookMove, "move ");
\r
10431 strcat(bookMove, bookHit);
\r
10432 HandleMachineMove(bookMove, &first);
\r
10437 MachineBlackEvent()
\r
10439 char buf[MSG_SIZ];
\r
10440 char *bookHit = NULL;
\r
10442 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
\r
10446 if (gameMode == PlayFromGameFile ||
\r
10447 gameMode == TwoMachinesPlay ||
\r
10448 gameMode == Training ||
\r
10449 gameMode == AnalyzeMode ||
\r
10450 gameMode == EndOfGame)
\r
10453 if (gameMode == EditPosition)
\r
10454 EditPositionDone();
\r
10456 if (WhiteOnMove(currentMove)) {
\r
10457 DisplayError(_("It is not Black's turn"), 0);
\r
10461 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
\r
10462 ExitAnalyzeMode();
\r
10464 if (gameMode == EditGame || gameMode == AnalyzeMode ||
\r
10465 gameMode == AnalyzeFile)
\r
10468 ResurrectChessProgram(); /* in case it isn't running */
\r
10469 gameMode = MachinePlaysBlack;
\r
10473 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
\r
10474 DisplayTitle(buf);
\r
10475 if (first.sendName) {
\r
10476 sprintf(buf, "name %s\n", gameInfo.white);
\r
10477 SendToProgram(buf, &first);
\r
10479 if (first.sendTime) {
\r
10480 if (first.useColors) {
\r
10481 SendToProgram("white\n", &first); /*gnu kludge*/
\r
10483 SendTimeRemaining(&first, FALSE);
\r
10485 if (first.useColors) {
\r
10486 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
\r
10488 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
\r
10489 SetMachineThinkingEnables();
\r
10490 first.maybeThinking = TRUE;
\r
10493 if (appData.autoFlipView && flipView) {
\r
10494 flipView = !flipView;
\r
10495 DrawPosition(FALSE, NULL);
\r
10496 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
\r
10498 if(bookHit) { // [HGM] book: simulate book reply
\r
10499 static char bookMove[MSG_SIZ]; // a bit generous?
\r
10501 programStats.depth = programStats.nodes = programStats.time =
\r
10502 programStats.score = programStats.got_only_move = 0;
\r
10503 sprintf(programStats.movelist, "%s (xbook)", bookHit);
\r
10505 strcpy(bookMove, "move ");
\r
10506 strcat(bookMove, bookHit);
\r
10507 HandleMachineMove(bookMove, &first);
\r
10513 DisplayTwoMachinesTitle()
\r
10515 char buf[MSG_SIZ];
\r
10516 if (appData.matchGames > 0) {
\r
10517 if (first.twoMachinesColor[0] == 'w') {
\r
10518 sprintf(buf, "%s vs. %s (%d-%d-%d)",
\r
10519 gameInfo.white, gameInfo.black,
\r
10520 first.matchWins, second.matchWins,
\r
10521 matchGame - 1 - (first.matchWins + second.matchWins));
\r
10523 sprintf(buf, "%s vs. %s (%d-%d-%d)",
\r
10524 gameInfo.white, gameInfo.black,
\r
10525 second.matchWins, first.matchWins,
\r
10526 matchGame - 1 - (first.matchWins + second.matchWins));
\r
10529 sprintf(buf, "%s vs. %s", gameInfo.white, gameInfo.black);
\r
10531 DisplayTitle(buf);
\r
10535 TwoMachinesEvent P((void))
\r
10538 char buf[MSG_SIZ];
\r
10539 ChessProgramState *onmove;
\r
10540 char *bookHit = NULL;
\r
10542 if (appData.noChessProgram) return;
\r
10544 switch (gameMode) {
\r
10545 case TwoMachinesPlay:
\r
10547 case MachinePlaysWhite:
\r
10548 case MachinePlaysBlack:
\r
10549 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
\r
10550 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
\r
10553 /* fall through */
\r
10554 case BeginningOfGame:
\r
10555 case PlayFromGameFile:
\r
10558 if (gameMode != EditGame) return;
\r
10560 case EditPosition:
\r
10561 EditPositionDone();
\r
10563 case AnalyzeMode:
\r
10564 case AnalyzeFile:
\r
10565 ExitAnalyzeMode();
\r
10572 forwardMostMove = currentMove;
\r
10573 ResurrectChessProgram(); /* in case first program isn't running */
\r
10575 if (second.pr == NULL) {
\r
10576 StartChessProgram(&second);
\r
10577 if (second.protocolVersion == 1) {
\r
10578 TwoMachinesEventIfReady();
\r
10580 /* kludge: allow timeout for initial "feature" command */
\r
10582 DisplayMessage("", _("Starting second chess program"));
\r
10583 ScheduleDelayedEvent(TwoMachinesEventIfReady, FEATURE_TIMEOUT);
\r
10587 DisplayMessage("", "");
\r
10588 InitChessProgram(&second, FALSE);
\r
10589 SendToProgram("force\n", &second);
\r
10590 if (startedFromSetupPosition) {
\r
10591 SendBoard(&second, backwardMostMove);
\r
10592 if (appData.debugMode) {
\r
10593 fprintf(debugFP, "Two Machines\n");
\r
10596 for (i = backwardMostMove; i < forwardMostMove; i++) {
\r
10597 SendMoveToProgram(i, &second);
\r
10600 gameMode = TwoMachinesPlay;
\r
10604 DisplayTwoMachinesTitle();
\r
10605 firstMove = TRUE;
\r
10606 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
\r
10609 onmove = &second;
\r
10612 SendToProgram(first.computerString, &first);
\r
10613 if (first.sendName) {
\r
10614 sprintf(buf, "name %s\n", second.tidy);
\r
10615 SendToProgram(buf, &first);
\r
10617 SendToProgram(second.computerString, &second);
\r
10618 if (second.sendName) {
\r
10619 sprintf(buf, "name %s\n", first.tidy);
\r
10620 SendToProgram(buf, &second);
\r
10624 if (!first.sendTime || !second.sendTime) {
\r
10625 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
\r
10626 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
\r
10628 if (onmove->sendTime) {
\r
10629 if (onmove->useColors) {
\r
10630 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
\r
10632 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
\r
10634 if (onmove->useColors) {
\r
10635 SendToProgram(onmove->twoMachinesColor, onmove);
\r
10637 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
\r
10638 // SendToProgram("go\n", onmove);
\r
10639 onmove->maybeThinking = TRUE;
\r
10640 SetMachineThinkingEnables();
\r
10644 if(bookHit) { // [HGM] book: simulate book reply
\r
10645 static char bookMove[MSG_SIZ]; // a bit generous?
\r
10647 programStats.depth = programStats.nodes = programStats.time =
\r
10648 programStats.score = programStats.got_only_move = 0;
\r
10649 sprintf(programStats.movelist, "%s (xbook)", bookHit);
\r
10651 strcpy(bookMove, "move ");
\r
10652 strcat(bookMove, bookHit);
\r
10653 HandleMachineMove(bookMove, &first);
\r
10660 if (gameMode == Training) {
\r
10661 SetTrainingModeOff();
\r
10662 gameMode = PlayFromGameFile;
\r
10663 DisplayMessage("", _("Training mode off"));
\r
10665 gameMode = Training;
\r
10666 animateTraining = appData.animate;
\r
10668 /* make sure we are not already at the end of the game */
\r
10669 if (currentMove < forwardMostMove) {
\r
10670 SetTrainingModeOn();
\r
10671 DisplayMessage("", _("Training mode on"));
\r
10673 gameMode = PlayFromGameFile;
\r
10674 DisplayError(_("Already at end of game"), 0);
\r
10683 if (!appData.icsActive) return;
\r
10684 switch (gameMode) {
\r
10685 case IcsPlayingWhite:
\r
10686 case IcsPlayingBlack:
\r
10687 case IcsObserving:
\r
10689 case BeginningOfGame:
\r
10690 case IcsExamining:
\r
10696 case EditPosition:
\r
10697 EditPositionDone();
\r
10700 case AnalyzeMode:
\r
10701 case AnalyzeFile:
\r
10702 ExitAnalyzeMode();
\r
10710 gameMode = IcsIdle;
\r
10721 switch (gameMode) {
\r
10723 SetTrainingModeOff();
\r
10725 case MachinePlaysWhite:
\r
10726 case MachinePlaysBlack:
\r
10727 case BeginningOfGame:
\r
10728 SendToProgram("force\n", &first);
\r
10729 SetUserThinkingEnables();
\r
10731 case PlayFromGameFile:
\r
10732 (void) StopLoadGameTimer();
\r
10733 if (gameFileFP != NULL) {
\r
10734 gameFileFP = NULL;
\r
10737 case EditPosition:
\r
10738 EditPositionDone();
\r
10740 case AnalyzeMode:
\r
10741 case AnalyzeFile:
\r
10742 ExitAnalyzeMode();
\r
10743 SendToProgram("force\n", &first);
\r
10745 case TwoMachinesPlay:
\r
10746 GameEnds((ChessMove) 0, NULL, GE_PLAYER);
\r
10747 ResurrectChessProgram();
\r
10748 SetUserThinkingEnables();
\r
10751 ResurrectChessProgram();
\r
10753 case IcsPlayingBlack:
\r
10754 case IcsPlayingWhite:
\r
10755 DisplayError(_("Warning: You are still playing a game"), 0);
\r
10757 case IcsObserving:
\r
10758 DisplayError(_("Warning: You are still observing a game"), 0);
\r
10760 case IcsExamining:
\r
10761 DisplayError(_("Warning: You are still examining a game"), 0);
\r
10772 first.offeredDraw = second.offeredDraw = 0;
\r
10774 if (gameMode == PlayFromGameFile) {
\r
10775 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
10776 blackTimeRemaining = timeRemaining[1][currentMove];
\r
10777 DisplayTitle("");
\r
10780 if (gameMode == MachinePlaysWhite ||
\r
10781 gameMode == MachinePlaysBlack ||
\r
10782 gameMode == TwoMachinesPlay ||
\r
10783 gameMode == EndOfGame) {
\r
10784 i = forwardMostMove;
\r
10785 while (i > currentMove) {
\r
10786 SendToProgram("undo\n", &first);
\r
10789 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
10790 blackTimeRemaining = timeRemaining[1][currentMove];
\r
10791 DisplayBothClocks();
\r
10792 if (whiteFlag || blackFlag) {
\r
10793 whiteFlag = blackFlag = 0;
\r
10795 DisplayTitle("");
\r
10798 gameMode = EditGame;
\r
10805 EditPositionEvent()
\r
10807 if (gameMode == EditPosition) {
\r
10813 if (gameMode != EditGame) return;
\r
10815 gameMode = EditPosition;
\r
10818 if (currentMove > 0)
\r
10819 CopyBoard(boards[0], boards[currentMove]);
\r
10821 blackPlaysFirst = !WhiteOnMove(currentMove);
\r
10823 currentMove = forwardMostMove = backwardMostMove = 0;
\r
10824 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
10829 ExitAnalyzeMode()
\r
10831 /* [DM] icsEngineAnalyze - possible call from other functions */
\r
10832 if (appData.icsEngineAnalyze) {
\r
10833 appData.icsEngineAnalyze = FALSE;
\r
10835 DisplayMessage("",_("Close ICS engine analyze..."));
\r
10837 if (first.analysisSupport && first.analyzing) {
\r
10838 SendToProgram("exit\n", &first);
\r
10839 first.analyzing = FALSE;
\r
10841 AnalysisPopDown();
\r
10842 thinkOutput[0] = NULLCHAR;
\r
10846 EditPositionDone()
\r
10848 startedFromSetupPosition = TRUE;
\r
10849 InitChessProgram(&first, FALSE);
\r
10850 SendToProgram("force\n", &first);
\r
10851 if (blackPlaysFirst) {
\r
10852 strcpy(moveList[0], "");
\r
10853 strcpy(parseList[0], "");
\r
10854 currentMove = forwardMostMove = backwardMostMove = 1;
\r
10855 CopyBoard(boards[1], boards[0]);
\r
10856 /* [HGM] copy rights as well, as this code is also used after pasting a FEN */
\r
10858 epStatus[1] = epStatus[0];
\r
10859 for(i=0; i<nrCastlingRights; i++) castlingRights[1][i] = castlingRights[0][i];
\r
10862 currentMove = forwardMostMove = backwardMostMove = 0;
\r
10864 SendBoard(&first, forwardMostMove);
\r
10865 if (appData.debugMode) {
\r
10866 fprintf(debugFP, "EditPosDone\n");
\r
10868 DisplayTitle("");
\r
10869 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
\r
10870 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
\r
10871 gameMode = EditGame;
\r
10873 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
10874 ClearHighlights(); /* [AS] */
\r
10877 /* Pause for `ms' milliseconds */
\r
10878 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
\r
10885 GetTimeMark(&m1);
\r
10887 GetTimeMark(&m2);
\r
10888 } while (SubtractTimeMarks(&m2, &m1) < ms);
\r
10891 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
\r
10893 SendMultiLineToICS(buf)
\r
10896 char temp[MSG_SIZ+1], *p;
\r
10899 len = strlen(buf);
\r
10900 if (len > MSG_SIZ)
\r
10903 strncpy(temp, buf, len);
\r
10908 if (*p == '\n' || *p == '\r')
\r
10913 strcat(temp, "\n");
\r
10915 SendToPlayer(temp, strlen(temp));
\r
10919 SetWhiteToPlayEvent()
\r
10921 if (gameMode == EditPosition) {
\r
10922 blackPlaysFirst = FALSE;
\r
10923 DisplayBothClocks(); /* works because currentMove is 0 */
\r
10924 } else if (gameMode == IcsExamining) {
\r
10925 SendToICS(ics_prefix);
\r
10926 SendToICS("tomove white\n");
\r
10931 SetBlackToPlayEvent()
\r
10933 if (gameMode == EditPosition) {
\r
10934 blackPlaysFirst = TRUE;
\r
10935 currentMove = 1; /* kludge */
\r
10936 DisplayBothClocks();
\r
10938 } else if (gameMode == IcsExamining) {
\r
10939 SendToICS(ics_prefix);
\r
10940 SendToICS("tomove black\n");
\r
10945 EditPositionMenuEvent(selection, x, y)
\r
10946 ChessSquare selection;
\r
10949 char buf[MSG_SIZ];
\r
10950 ChessSquare piece = boards[0][y][x];
\r
10952 if (gameMode != EditPosition && gameMode != IcsExamining) return;
\r
10954 switch (selection) {
\r
10956 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
\r
10957 SendToICS(ics_prefix);
\r
10958 SendToICS("bsetup clear\n");
\r
10959 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
\r
10960 SendToICS(ics_prefix);
\r
10961 SendToICS("clearboard\n");
\r
10963 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
\r
10964 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
\r
10965 for (y = 0; y < BOARD_HEIGHT; y++) {
\r
10966 if (gameMode == IcsExamining) {
\r
10967 if (boards[currentMove][y][x] != EmptySquare) {
\r
10968 sprintf(buf, "%sx@%c%c\n", ics_prefix,
\r
10969 AAA + x, ONE + y);
\r
10973 boards[0][y][x] = p;
\r
10978 if (gameMode == EditPosition) {
\r
10979 DrawPosition(FALSE, boards[0]);
\r
10984 SetWhiteToPlayEvent();
\r
10988 SetBlackToPlayEvent();
\r
10991 case EmptySquare:
\r
10992 if (gameMode == IcsExamining) {
\r
10993 sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
\r
10996 boards[0][y][x] = EmptySquare;
\r
10997 DrawPosition(FALSE, boards[0]);
\r
11001 case PromotePiece:
\r
11002 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
\r
11003 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
\r
11004 selection = (ChessSquare) (PROMOTED piece);
\r
11005 } else if(piece == EmptySquare) selection = WhiteSilver;
\r
11006 else selection = (ChessSquare)((int)piece - 1);
\r
11007 goto defaultlabel;
\r
11009 case DemotePiece:
\r
11010 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
\r
11011 piece > (int)BlackMan && piece <= (int)BlackKing ) {
\r
11012 selection = (ChessSquare) (DEMOTED piece);
\r
11013 } else if(piece == EmptySquare) selection = BlackSilver;
\r
11014 else selection = (ChessSquare)((int)piece + 1);
\r
11015 goto defaultlabel;
\r
11019 if(gameInfo.variant == VariantShatranj ||
\r
11020 gameInfo.variant == VariantXiangqi ||
\r
11021 gameInfo.variant == VariantCourier )
\r
11022 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
\r
11023 goto defaultlabel;
\r
11027 if(gameInfo.variant == VariantXiangqi)
\r
11028 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
\r
11029 if(gameInfo.variant == VariantKnightmate)
\r
11030 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
\r
11033 if (gameMode == IcsExamining) {
\r
11034 sprintf(buf, "%s%c@%c%c\n", ics_prefix,
\r
11035 PieceToChar(selection), AAA + x, ONE + y);
\r
11038 boards[0][y][x] = selection;
\r
11039 DrawPosition(FALSE, boards[0]);
\r
11047 DropMenuEvent(selection, x, y)
\r
11048 ChessSquare selection;
\r
11051 ChessMove moveType;
\r
11053 switch (gameMode) {
\r
11054 case IcsPlayingWhite:
\r
11055 case MachinePlaysBlack:
\r
11056 if (!WhiteOnMove(currentMove)) {
\r
11057 DisplayMoveError(_("It is Black's turn"));
\r
11060 moveType = WhiteDrop;
\r
11062 case IcsPlayingBlack:
\r
11063 case MachinePlaysWhite:
\r
11064 if (WhiteOnMove(currentMove)) {
\r
11065 DisplayMoveError(_("It is White's turn"));
\r
11068 moveType = BlackDrop;
\r
11071 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
\r
11077 if (moveType == BlackDrop && selection < BlackPawn) {
\r
11078 selection = (ChessSquare) ((int) selection
\r
11079 + (int) BlackPawn - (int) WhitePawn);
\r
11081 if (boards[currentMove][y][x] != EmptySquare) {
\r
11082 DisplayMoveError(_("That square is occupied"));
\r
11086 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
\r
11092 /* Accept a pending offer of any kind from opponent */
\r
11094 if (appData.icsActive) {
\r
11095 SendToICS(ics_prefix);
\r
11096 SendToICS("accept\n");
\r
11097 } else if (cmailMsgLoaded) {
\r
11098 if (currentMove == cmailOldMove &&
\r
11099 commentList[cmailOldMove] != NULL &&
\r
11100 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
\r
11101 "Black offers a draw" : "White offers a draw")) {
\r
11103 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
\r
11104 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
\r
11106 DisplayError(_("There is no pending offer on this move"), 0);
\r
11107 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
\r
11110 /* Not used for offers from chess program */
\r
11117 /* Decline a pending offer of any kind from opponent */
\r
11119 if (appData.icsActive) {
\r
11120 SendToICS(ics_prefix);
\r
11121 SendToICS("decline\n");
\r
11122 } else if (cmailMsgLoaded) {
\r
11123 if (currentMove == cmailOldMove &&
\r
11124 commentList[cmailOldMove] != NULL &&
\r
11125 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
\r
11126 "Black offers a draw" : "White offers a draw")) {
\r
11128 AppendComment(cmailOldMove, "Draw declined");
\r
11129 DisplayComment(cmailOldMove - 1, "Draw declined");
\r
11130 #endif /*NOTDEF*/
\r
11132 DisplayError(_("There is no pending offer on this move"), 0);
\r
11135 /* Not used for offers from chess program */
\r
11142 /* Issue ICS rematch command */
\r
11143 if (appData.icsActive) {
\r
11144 SendToICS(ics_prefix);
\r
11145 SendToICS("rematch\n");
\r
11152 /* Call your opponent's flag (claim a win on time) */
\r
11153 if (appData.icsActive) {
\r
11154 SendToICS(ics_prefix);
\r
11155 SendToICS("flag\n");
\r
11157 switch (gameMode) {
\r
11160 case MachinePlaysWhite:
\r
11163 GameEnds(GameIsDrawn, "Both players ran out of time",
\r
11166 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
\r
11168 DisplayError(_("Your opponent is not out of time"), 0);
\r
11171 case MachinePlaysBlack:
\r
11174 GameEnds(GameIsDrawn, "Both players ran out of time",
\r
11177 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
\r
11179 DisplayError(_("Your opponent is not out of time"), 0);
\r
11189 /* Offer draw or accept pending draw offer from opponent */
\r
11191 if (appData.icsActive) {
\r
11192 /* Note: tournament rules require draw offers to be
\r
11193 made after you make your move but before you punch
\r
11194 your clock. Currently ICS doesn't let you do that;
\r
11195 instead, you immediately punch your clock after making
\r
11196 a move, but you can offer a draw at any time. */
\r
11198 SendToICS(ics_prefix);
\r
11199 SendToICS("draw\n");
\r
11200 } else if (cmailMsgLoaded) {
\r
11201 if (currentMove == cmailOldMove &&
\r
11202 commentList[cmailOldMove] != NULL &&
\r
11203 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
\r
11204 "Black offers a draw" : "White offers a draw")) {
\r
11205 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
\r
11206 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
\r
11207 } else if (currentMove == cmailOldMove + 1) {
\r
11208 char *offer = WhiteOnMove(cmailOldMove) ?
\r
11209 "White offers a draw" : "Black offers a draw";
\r
11210 AppendComment(currentMove, offer);
\r
11211 DisplayComment(currentMove - 1, offer);
\r
11212 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
\r
11214 DisplayError(_("You must make your move before offering a draw"), 0);
\r
11215 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
\r
11217 } else if (first.offeredDraw) {
\r
11218 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
\r
11220 if (first.sendDrawOffers) {
\r
11221 SendToProgram("draw\n", &first);
\r
11222 userOfferedDraw = TRUE;
\r
11230 /* Offer Adjourn or accept pending Adjourn offer from opponent */
\r
11232 if (appData.icsActive) {
\r
11233 SendToICS(ics_prefix);
\r
11234 SendToICS("adjourn\n");
\r
11236 /* Currently GNU Chess doesn't offer or accept Adjourns */
\r
11244 /* Offer Abort or accept pending Abort offer from opponent */
\r
11246 if (appData.icsActive) {
\r
11247 SendToICS(ics_prefix);
\r
11248 SendToICS("abort\n");
\r
11250 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
\r
11257 /* Resign. You can do this even if it's not your turn. */
\r
11259 if (appData.icsActive) {
\r
11260 SendToICS(ics_prefix);
\r
11261 SendToICS("resign\n");
\r
11263 switch (gameMode) {
\r
11264 case MachinePlaysWhite:
\r
11265 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
\r
11267 case MachinePlaysBlack:
\r
11268 GameEnds(BlackWins, "White resigns", GE_PLAYER);
\r
11271 if (cmailMsgLoaded) {
\r
11273 if (WhiteOnMove(cmailOldMove)) {
\r
11274 GameEnds(BlackWins, "White resigns", GE_PLAYER);
\r
11276 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
\r
11278 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
\r
11289 StopObservingEvent()
\r
11291 /* Stop observing current games */
\r
11292 SendToICS(ics_prefix);
\r
11293 SendToICS("unobserve\n");
\r
11297 StopExaminingEvent()
\r
11299 /* Stop observing current game */
\r
11300 SendToICS(ics_prefix);
\r
11301 SendToICS("unexamine\n");
\r
11305 ForwardInner(target)
\r
11310 if (appData.debugMode)
\r
11311 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
\r
11312 target, currentMove, forwardMostMove);
\r
11314 if (gameMode == EditPosition)
\r
11317 if (gameMode == PlayFromGameFile && !pausing)
\r
11320 if (gameMode == IcsExamining && pausing)
\r
11321 limit = pauseExamForwardMostMove;
\r
11323 limit = forwardMostMove;
\r
11325 if (target > limit) target = limit;
\r
11327 if (target > 0 && moveList[target - 1][0]) {
\r
11328 int fromX, fromY, toX, toY;
\r
11329 toX = moveList[target - 1][2] - AAA;
\r
11330 toY = moveList[target - 1][3] - ONE;
\r
11331 if (moveList[target - 1][1] == '@') {
\r
11332 if (appData.highlightLastMove) {
\r
11333 SetHighlights(-1, -1, toX, toY);
\r
11336 fromX = moveList[target - 1][0] - AAA;
\r
11337 fromY = moveList[target - 1][1] - ONE;
\r
11338 if (target == currentMove + 1) {
\r
11339 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
\r
11341 if (appData.highlightLastMove) {
\r
11342 SetHighlights(fromX, fromY, toX, toY);
\r
11346 if (gameMode == EditGame || gameMode == AnalyzeMode ||
\r
11347 gameMode == Training || gameMode == PlayFromGameFile ||
\r
11348 gameMode == AnalyzeFile) {
\r
11349 while (currentMove < target) {
\r
11350 SendMoveToProgram(currentMove++, &first);
\r
11353 currentMove = target;
\r
11356 if (gameMode == EditGame || gameMode == EndOfGame) {
\r
11357 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
11358 blackTimeRemaining = timeRemaining[1][currentMove];
\r
11360 DisplayBothClocks();
\r
11361 DisplayMove(currentMove - 1);
\r
11362 DrawPosition(FALSE, boards[currentMove]);
\r
11363 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
\r
11364 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
\r
11365 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
11373 if (gameMode == IcsExamining && !pausing) {
\r
11374 SendToICS(ics_prefix);
\r
11375 SendToICS("forward\n");
\r
11377 ForwardInner(currentMove + 1);
\r
11384 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
11385 /* to optimze, we temporarily turn off analysis mode while we feed
\r
11386 * the remaining moves to the engine. Otherwise we get analysis output
\r
11387 * after each move.
\r
11389 if (first.analysisSupport) {
\r
11390 SendToProgram("exit\nforce\n", &first);
\r
11391 first.analyzing = FALSE;
\r
11395 if (gameMode == IcsExamining && !pausing) {
\r
11396 SendToICS(ics_prefix);
\r
11397 SendToICS("forward 999999\n");
\r
11399 ForwardInner(forwardMostMove);
\r
11402 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
11403 /* we have fed all the moves, so reactivate analysis mode */
\r
11404 SendToProgram("analyze\n", &first);
\r
11405 first.analyzing = TRUE;
\r
11406 /*first.maybeThinking = TRUE;*/
\r
11407 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
\r
11412 BackwardInner(target)
\r
11415 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
\r
11417 if (appData.debugMode)
\r
11418 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
\r
11419 target, currentMove, forwardMostMove);
\r
11421 if (gameMode == EditPosition) return;
\r
11422 if (currentMove <= backwardMostMove) {
\r
11423 ClearHighlights();
\r
11424 DrawPosition(full_redraw, boards[currentMove]);
\r
11427 if (gameMode == PlayFromGameFile && !pausing)
\r
11430 if (moveList[target][0]) {
\r
11431 int fromX, fromY, toX, toY;
\r
11432 toX = moveList[target][2] - AAA;
\r
11433 toY = moveList[target][3] - ONE;
\r
11434 if (moveList[target][1] == '@') {
\r
11435 if (appData.highlightLastMove) {
\r
11436 SetHighlights(-1, -1, toX, toY);
\r
11439 fromX = moveList[target][0] - AAA;
\r
11440 fromY = moveList[target][1] - ONE;
\r
11441 if (target == currentMove - 1) {
\r
11442 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
\r
11444 if (appData.highlightLastMove) {
\r
11445 SetHighlights(fromX, fromY, toX, toY);
\r
11449 if (gameMode == EditGame || gameMode==AnalyzeMode ||
\r
11450 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
\r
11451 while (currentMove > target) {
\r
11452 SendToProgram("undo\n", &first);
\r
11456 currentMove = target;
\r
11459 if (gameMode == EditGame || gameMode == EndOfGame) {
\r
11460 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
11461 blackTimeRemaining = timeRemaining[1][currentMove];
\r
11463 DisplayBothClocks();
\r
11464 DisplayMove(currentMove - 1);
\r
11465 DrawPosition(full_redraw, boards[currentMove]);
\r
11466 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
\r
11467 // [HGM] PV info: routine tests if comment empty
\r
11468 DisplayComment(currentMove - 1, commentList[currentMove]);
\r
11474 if (gameMode == IcsExamining && !pausing) {
\r
11475 SendToICS(ics_prefix);
\r
11476 SendToICS("backward\n");
\r
11478 BackwardInner(currentMove - 1);
\r
11485 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
11486 /* to optimze, we temporarily turn off analysis mode while we undo
\r
11487 * all the moves. Otherwise we get analysis output after each undo.
\r
11489 if (first.analysisSupport) {
\r
11490 SendToProgram("exit\nforce\n", &first);
\r
11491 first.analyzing = FALSE;
\r
11495 if (gameMode == IcsExamining && !pausing) {
\r
11496 SendToICS(ics_prefix);
\r
11497 SendToICS("backward 999999\n");
\r
11499 BackwardInner(backwardMostMove);
\r
11502 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
11503 /* we have fed all the moves, so reactivate analysis mode */
\r
11504 SendToProgram("analyze\n", &first);
\r
11505 first.analyzing = TRUE;
\r
11506 /*first.maybeThinking = TRUE;*/
\r
11507 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
\r
11512 ToNrEvent(int to)
\r
11514 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
\r
11515 if (to >= forwardMostMove) to = forwardMostMove;
\r
11516 if (to <= backwardMostMove) to = backwardMostMove;
\r
11517 if (to < currentMove) {
\r
11518 BackwardInner(to);
\r
11520 ForwardInner(to);
\r
11527 if (gameMode != IcsExamining) {
\r
11528 DisplayError(_("You are not examining a game"), 0);
\r
11532 DisplayError(_("You can't revert while pausing"), 0);
\r
11535 SendToICS(ics_prefix);
\r
11536 SendToICS("revert\n");
\r
11540 RetractMoveEvent()
\r
11542 switch (gameMode) {
\r
11543 case MachinePlaysWhite:
\r
11544 case MachinePlaysBlack:
\r
11545 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
\r
11546 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
\r
11549 if (forwardMostMove < 2) return;
\r
11550 currentMove = forwardMostMove = forwardMostMove - 2;
\r
11551 whiteTimeRemaining = timeRemaining[0][currentMove];
\r
11552 blackTimeRemaining = timeRemaining[1][currentMove];
\r
11553 DisplayBothClocks();
\r
11554 DisplayMove(currentMove - 1);
\r
11555 ClearHighlights();/*!! could figure this out*/
\r
11556 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
\r
11557 SendToProgram("remove\n", &first);
\r
11558 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
\r
11561 case BeginningOfGame:
\r
11565 case IcsPlayingWhite:
\r
11566 case IcsPlayingBlack:
\r
11567 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
\r
11568 SendToICS(ics_prefix);
\r
11569 SendToICS("takeback 2\n");
\r
11571 SendToICS(ics_prefix);
\r
11572 SendToICS("takeback 1\n");
\r
11581 ChessProgramState *cps;
\r
11583 switch (gameMode) {
\r
11584 case MachinePlaysWhite:
\r
11585 if (!WhiteOnMove(forwardMostMove)) {
\r
11586 DisplayError(_("It is your turn"), 0);
\r
11591 case MachinePlaysBlack:
\r
11592 if (WhiteOnMove(forwardMostMove)) {
\r
11593 DisplayError(_("It is your turn"), 0);
\r
11598 case TwoMachinesPlay:
\r
11599 if (WhiteOnMove(forwardMostMove) ==
\r
11600 (first.twoMachinesColor[0] == 'w')) {
\r
11606 case BeginningOfGame:
\r
11610 SendToProgram("?\n", cps);
\r
11614 TruncateGameEvent()
\r
11617 if (gameMode != EditGame) return;
\r
11624 if (forwardMostMove > currentMove) {
\r
11625 if (gameInfo.resultDetails != NULL) {
\r
11626 free(gameInfo.resultDetails);
\r
11627 gameInfo.resultDetails = NULL;
\r
11628 gameInfo.result = GameUnfinished;
\r
11630 forwardMostMove = currentMove;
\r
11631 HistorySet(parseList, backwardMostMove, forwardMostMove,
\r
11639 if (appData.noChessProgram) return;
\r
11640 switch (gameMode) {
\r
11641 case MachinePlaysWhite:
\r
11642 if (WhiteOnMove(forwardMostMove)) {
\r
11643 DisplayError(_("Wait until your turn"), 0);
\r
11647 case BeginningOfGame:
\r
11648 case MachinePlaysBlack:
\r
11649 if (!WhiteOnMove(forwardMostMove)) {
\r
11650 DisplayError(_("Wait until your turn"), 0);
\r
11655 DisplayError(_("No hint available"), 0);
\r
11658 SendToProgram("hint\n", &first);
\r
11659 hintRequested = TRUE;
\r
11665 if (appData.noChessProgram) return;
\r
11666 switch (gameMode) {
\r
11667 case MachinePlaysWhite:
\r
11668 if (WhiteOnMove(forwardMostMove)) {
\r
11669 DisplayError(_("Wait until your turn"), 0);
\r
11673 case BeginningOfGame:
\r
11674 case MachinePlaysBlack:
\r
11675 if (!WhiteOnMove(forwardMostMove)) {
\r
11676 DisplayError(_("Wait until your turn"), 0);
\r
11680 case EditPosition:
\r
11681 EditPositionDone();
\r
11683 case TwoMachinesPlay:
\r
11688 SendToProgram("bk\n", &first);
\r
11689 bookOutput[0] = NULLCHAR;
\r
11690 bookRequested = TRUE;
\r
11696 char *tags = PGNTags(&gameInfo);
\r
11697 TagsPopUp(tags, CmailMsg());
\r
11701 /* end button procedures */
\r
11704 PrintPosition(fp, move)
\r
11710 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
11711 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
\r
11712 char c = PieceToChar(boards[move][i][j]);
\r
11713 fputc(c == 'x' ? '.' : c, fp);
\r
11714 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
\r
11717 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
\r
11718 fprintf(fp, "white to play\n");
\r
11720 fprintf(fp, "black to play\n");
\r
11724 PrintOpponents(fp)
\r
11727 if (gameInfo.white != NULL) {
\r
11728 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
\r
11730 fprintf(fp, "\n");
\r
11734 /* Find last component of program's own name, using some heuristics */
\r
11736 TidyProgramName(prog, host, buf)
\r
11737 char *prog, *host, buf[MSG_SIZ];
\r
11740 int local = (strcmp(host, "localhost") == 0);
\r
11741 while (!local && (p = strchr(prog, ';')) != NULL) {
\r
11743 while (*p == ' ') p++;
\r
11746 if (*prog == '"' || *prog == '\'') {
\r
11747 q = strchr(prog + 1, *prog);
\r
11749 q = strchr(prog, ' ');
\r
11751 if (q == NULL) q = prog + strlen(prog);
\r
11753 while (p >= prog && *p != '/' && *p != '\\') p--;
\r
11755 if(p == prog && *p == '"') p++;
\r
11756 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
\r
11757 memcpy(buf, p, q - p);
\r
11758 buf[q - p] = NULLCHAR;
\r
11760 strcat(buf, "@");
\r
11761 strcat(buf, host);
\r
11766 TimeControlTagValue()
\r
11768 char buf[MSG_SIZ];
\r
11769 if (!appData.clockMode) {
\r
11770 strcpy(buf, "-");
\r
11771 } else if (movesPerSession > 0) {
\r
11772 sprintf(buf, "%d/%ld", movesPerSession, timeControl/1000);
\r
11773 } else if (timeIncrement == 0) {
\r
11774 sprintf(buf, "%ld", timeControl/1000);
\r
11776 sprintf(buf, "%ld+%ld", timeControl/1000, timeIncrement/1000);
\r
11778 return StrSave(buf);
\r
11784 /* This routine is used only for certain modes */
\r
11785 VariantClass v = gameInfo.variant;
\r
11786 ClearGameInfo(&gameInfo);
\r
11787 gameInfo.variant = v;
\r
11789 switch (gameMode) {
\r
11790 case MachinePlaysWhite:
\r
11791 gameInfo.event = StrSave( appData.pgnEventHeader );
\r
11792 gameInfo.site = StrSave(HostName());
\r
11793 gameInfo.date = PGNDate();
\r
11794 gameInfo.round = StrSave("-");
\r
11795 gameInfo.white = StrSave(first.tidy);
\r
11796 gameInfo.black = StrSave(UserName());
\r
11797 gameInfo.timeControl = TimeControlTagValue();
\r
11800 case MachinePlaysBlack:
\r
11801 gameInfo.event = StrSave( appData.pgnEventHeader );
\r
11802 gameInfo.site = StrSave(HostName());
\r
11803 gameInfo.date = PGNDate();
\r
11804 gameInfo.round = StrSave("-");
\r
11805 gameInfo.white = StrSave(UserName());
\r
11806 gameInfo.black = StrSave(first.tidy);
\r
11807 gameInfo.timeControl = TimeControlTagValue();
\r
11810 case TwoMachinesPlay:
\r
11811 gameInfo.event = StrSave( appData.pgnEventHeader );
\r
11812 gameInfo.site = StrSave(HostName());
\r
11813 gameInfo.date = PGNDate();
\r
11814 if (matchGame > 0) {
\r
11815 char buf[MSG_SIZ];
\r
11816 sprintf(buf, "%d", matchGame);
\r
11817 gameInfo.round = StrSave(buf);
\r
11819 gameInfo.round = StrSave("-");
\r
11821 if (first.twoMachinesColor[0] == 'w') {
\r
11822 gameInfo.white = StrSave(first.tidy);
\r
11823 gameInfo.black = StrSave(second.tidy);
\r
11825 gameInfo.white = StrSave(second.tidy);
\r
11826 gameInfo.black = StrSave(first.tidy);
\r
11828 gameInfo.timeControl = TimeControlTagValue();
\r
11832 gameInfo.event = StrSave("Edited game");
\r
11833 gameInfo.site = StrSave(HostName());
\r
11834 gameInfo.date = PGNDate();
\r
11835 gameInfo.round = StrSave("-");
\r
11836 gameInfo.white = StrSave("-");
\r
11837 gameInfo.black = StrSave("-");
\r
11840 case EditPosition:
\r
11841 gameInfo.event = StrSave("Edited position");
\r
11842 gameInfo.site = StrSave(HostName());
\r
11843 gameInfo.date = PGNDate();
\r
11844 gameInfo.round = StrSave("-");
\r
11845 gameInfo.white = StrSave("-");
\r
11846 gameInfo.black = StrSave("-");
\r
11849 case IcsPlayingWhite:
\r
11850 case IcsPlayingBlack:
\r
11851 case IcsObserving:
\r
11852 case IcsExamining:
\r
11855 case PlayFromGameFile:
\r
11856 gameInfo.event = StrSave("Game from non-PGN file");
\r
11857 gameInfo.site = StrSave(HostName());
\r
11858 gameInfo.date = PGNDate();
\r
11859 gameInfo.round = StrSave("-");
\r
11860 gameInfo.white = StrSave("?");
\r
11861 gameInfo.black = StrSave("?");
\r
11870 ReplaceComment(index, text)
\r
11876 while (*text == '\n') text++;
\r
11877 len = strlen(text);
\r
11878 while (len > 0 && text[len - 1] == '\n') len--;
\r
11880 if (commentList[index] != NULL)
\r
11881 free(commentList[index]);
\r
11884 commentList[index] = NULL;
\r
11887 commentList[index] = (char *) malloc(len + 2);
\r
11888 strncpy(commentList[index], text, len);
\r
11889 commentList[index][len] = '\n';
\r
11890 commentList[index][len + 1] = NULLCHAR;
\r
11903 if (ch == '\r') continue;
\r
11905 } while (ch != '\0');
\r
11909 AppendComment(index, text)
\r
11916 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
\r
11919 while (*text == '\n') text++;
\r
11920 len = strlen(text);
\r
11921 while (len > 0 && text[len - 1] == '\n') len--;
\r
11923 if (len == 0) return;
\r
11925 if (commentList[index] != NULL) {
\r
11926 old = commentList[index];
\r
11927 oldlen = strlen(old);
\r
11928 commentList[index] = (char *) malloc(oldlen + len + 2);
\r
11929 strcpy(commentList[index], old);
\r
11931 strncpy(&commentList[index][oldlen], text, len);
\r
11932 commentList[index][oldlen + len] = '\n';
\r
11933 commentList[index][oldlen + len + 1] = NULLCHAR;
\r
11935 commentList[index] = (char *) malloc(len + 2);
\r
11936 strncpy(commentList[index], text, len);
\r
11937 commentList[index][len] = '\n';
\r
11938 commentList[index][len + 1] = NULLCHAR;
\r
11942 static char * FindStr( char * text, char * sub_text )
\r
11944 char * result = strstr( text, sub_text );
\r
11946 if( result != NULL ) {
\r
11947 result += strlen( sub_text );
\r
11953 /* [AS] Try to extract PV info from PGN comment */
\r
11954 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
\r
11955 char *GetInfoFromComment( int index, char * text )
\r
11957 char * sep = text;
\r
11959 if( text != NULL && index > 0 ) {
\r
11962 int time = -1, sec = 0, deci;
\r
11963 char * s_eval = FindStr( text, "[%eval " );
\r
11964 char * s_emt = FindStr( text, "[%emt " );
\r
11966 if( s_eval != NULL || s_emt != NULL ) {
\r
11970 if( s_eval != NULL ) {
\r
11971 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
\r
11975 if( delim != ']' ) {
\r
11980 if( s_emt != NULL ) {
\r
11984 /* We expect something like: [+|-]nnn.nn/dd */
\r
11985 int score_lo = 0;
\r
11987 sep = strchr( text, '/' );
\r
11988 if( sep == NULL || sep < (text+4) ) {
\r
11992 time = -1; sec = -1; deci = -1;
\r
11993 if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
\r
11994 sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
\r
11995 sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
\r
11996 sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
\r
12000 if( score_lo < 0 || score_lo >= 100 ) {
\r
12004 if(sec >= 0) time = 600*time + 10*sec; else
\r
12005 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
\r
12007 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
\r
12009 /* [HGM] PV time: now locate end of PV info */
\r
12010 while( *++sep >= '0' && *sep <= '9'); // strip depth
\r
12012 while( *++sep >= '0' && *sep <= '9'); // strip time
\r
12014 while( *++sep >= '0' && *sep <= '9'); // strip seconds
\r
12016 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
\r
12017 while(*sep == ' ') sep++;
\r
12020 if( depth <= 0 ) {
\r
12028 pvInfoList[index-1].depth = depth;
\r
12029 pvInfoList[index-1].score = score;
\r
12030 pvInfoList[index-1].time = 10*time; // centi-sec
\r
12036 SendToProgram(message, cps)
\r
12038 ChessProgramState *cps;
\r
12040 int count, outCount, error;
\r
12041 char buf[MSG_SIZ];
\r
12043 if (cps->pr == NULL) return;
\r
12046 if (appData.debugMode) {
\r
12048 GetTimeMark(&now);
\r
12049 fprintf(debugFP, "%ld >%-6s: %s",
\r
12050 SubtractTimeMarks(&now, &programStartTime),
\r
12051 cps->which, message);
\r
12054 count = strlen(message);
\r
12055 outCount = OutputToProcess(cps->pr, message, count, &error);
\r
12056 if (outCount < count && !exiting
\r
12057 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
\r
12058 sprintf(buf, _("Error writing to %s chess program"), cps->which);
\r
12059 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
\r
12060 if(epStatus[forwardMostMove] <= EP_DRAWS) {
\r
12061 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
\r
12062 sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
\r
12064 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
\r
12066 gameInfo.resultDetails = buf;
\r
12068 DisplayFatalError(buf, error, 1);
\r
12073 ReceiveFromProgram(isr, closure, message, count, error)
\r
12074 InputSourceRef isr;
\r
12075 VOIDSTAR closure;
\r
12081 char buf[MSG_SIZ];
\r
12082 ChessProgramState *cps = (ChessProgramState *)closure;
\r
12084 if (isr != cps->isr) return; /* Killed intentionally */
\r
12085 if (count <= 0) {
\r
12086 if (count == 0) {
\r
12088 _("Error: %s chess program (%s) exited unexpectedly"),
\r
12089 cps->which, cps->program);
\r
12090 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
\r
12091 if(epStatus[forwardMostMove] <= EP_DRAWS) {
\r
12092 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
\r
12093 sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
\r
12095 gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
\r
12097 gameInfo.resultDetails = buf;
\r
12099 RemoveInputSource(cps->isr);
\r
12100 DisplayFatalError(buf, 0, 1);
\r
12103 _("Error reading from %s chess program (%s)"),
\r
12104 cps->which, cps->program);
\r
12105 RemoveInputSource(cps->isr);
\r
12107 /* [AS] Program is misbehaving badly... kill it */
\r
12108 if( count == -2 ) {
\r
12109 DestroyChildProcess( cps->pr, 9 );
\r
12110 cps->pr = NoProc;
\r
12113 DisplayFatalError(buf, error, 1);
\r
12118 if ((end_str = strchr(message, '\r')) != NULL)
\r
12119 *end_str = NULLCHAR;
\r
12120 if ((end_str = strchr(message, '\n')) != NULL)
\r
12121 *end_str = NULLCHAR;
\r
12123 if (appData.debugMode) {
\r
12124 TimeMark now; int print = 1;
\r
12125 char *quote = ""; char c; int i;
\r
12127 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
\r
12128 char start = message[0];
\r
12129 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
\r
12130 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
\r
12131 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
\r
12132 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
\r
12133 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
\r
12134 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
\r
12135 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 && start != '#')
\r
12136 { quote = "# "; print = (appData.engineComments == 2); }
\r
12137 message[0] = start; // restore original message
\r
12140 GetTimeMark(&now);
\r
12141 fprintf(debugFP, "%ld <%-6s: %s%s\n",
\r
12142 SubtractTimeMarks(&now, &programStartTime), cps->which,
\r
12148 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
\r
12149 if (appData.icsEngineAnalyze) {
\r
12150 if (strstr(message, "whisper") != NULL ||
\r
12151 strstr(message, "kibitz") != NULL ||
\r
12152 strstr(message, "tellics") != NULL) return;
\r
12155 HandleMachineMove(message, cps);
\r
12160 SendTimeControl(cps, mps, tc, inc, sd, st)
\r
12161 ChessProgramState *cps;
\r
12162 int mps, inc, sd, st;
\r
12165 char buf[MSG_SIZ];
\r
12168 if( timeControl_2 > 0 ) {
\r
12169 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
\r
12170 tc = timeControl_2;
\r
12173 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
\r
12174 inc /= cps->timeOdds;
\r
12175 st /= cps->timeOdds;
\r
12177 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
\r
12180 /* Set exact time per move, normally using st command */
\r
12181 if (cps->stKludge) {
\r
12182 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
\r
12183 seconds = st % 60;
\r
12184 if (seconds == 0) {
\r
12185 sprintf(buf, "level 1 %d\n", st/60);
\r
12187 sprintf(buf, "level 1 %d:%02d\n", st/60, seconds);
\r
12190 sprintf(buf, "st %d\n", st);
\r
12193 /* Set conventional or incremental time control, using level command */
\r
12194 if (seconds == 0) {
\r
12195 /* Note old gnuchess bug -- minutes:seconds used to not work.
\r
12196 Fixed in later versions, but still avoid :seconds
\r
12197 when seconds is 0. */
\r
12198 sprintf(buf, "level %d %ld %d\n", mps, tc/60000, inc/1000);
\r
12200 sprintf(buf, "level %d %ld:%02d %d\n", mps, tc/60000,
\r
12201 seconds, inc/1000);
\r
12204 SendToProgram(buf, cps);
\r
12206 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
\r
12207 /* Orthogonally, limit search to given depth */
\r
12209 if (cps->sdKludge) {
\r
12210 sprintf(buf, "depth\n%d\n", sd);
\r
12212 sprintf(buf, "sd %d\n", sd);
\r
12214 SendToProgram(buf, cps);
\r
12217 if(cps->nps > 0) { /* [HGM] nps */
\r
12218 if(cps->supportsNPS == FALSE) cps->nps = -1; // don't use if engine explicitly says not supported!
\r
12220 sprintf(buf, "nps %d\n", cps->nps);
\r
12221 SendToProgram(buf, cps);
\r
12226 ChessProgramState *WhitePlayer()
\r
12227 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
\r
12229 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
\r
12230 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
\r
12236 SendTimeRemaining(cps, machineWhite)
\r
12237 ChessProgramState *cps;
\r
12238 int /*boolean*/ machineWhite;
\r
12240 char message[MSG_SIZ];
\r
12241 long time, otime;
\r
12243 /* Note: this routine must be called when the clocks are stopped
\r
12244 or when they have *just* been set or switched; otherwise
\r
12245 it will be off by the time since the current tick started.
\r
12247 if (machineWhite) {
\r
12248 time = whiteTimeRemaining / 10;
\r
12249 otime = blackTimeRemaining / 10;
\r
12251 time = blackTimeRemaining / 10;
\r
12252 otime = whiteTimeRemaining / 10;
\r
12254 /* [HGM] translate opponent's time by time-odds factor */
\r
12255 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
\r
12256 if (appData.debugMode) {
\r
12257 fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
\r
12260 if (time <= 0) time = 1;
\r
12261 if (otime <= 0) otime = 1;
\r
12263 sprintf(message, "time %ld\n", time);
\r
12264 SendToProgram(message, cps);
\r
12266 sprintf(message, "otim %ld\n", otime);
\r
12267 SendToProgram(message, cps);
\r
12271 BoolFeature(p, name, loc, cps)
\r
12275 ChessProgramState *cps;
\r
12277 char buf[MSG_SIZ];
\r
12278 int len = strlen(name);
\r
12280 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
\r
12282 sscanf(*p, "%d", &val);
\r
12283 *loc = (val != 0);
\r
12284 while (**p && **p != ' ') (*p)++;
\r
12285 sprintf(buf, "accepted %s\n", name);
\r
12286 SendToProgram(buf, cps);
\r
12293 IntFeature(p, name, loc, cps)
\r
12297 ChessProgramState *cps;
\r
12299 char buf[MSG_SIZ];
\r
12300 int len = strlen(name);
\r
12301 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
\r
12303 sscanf(*p, "%d", loc);
\r
12304 while (**p && **p != ' ') (*p)++;
\r
12305 sprintf(buf, "accepted %s\n", name);
\r
12306 SendToProgram(buf, cps);
\r
12313 StringFeature(p, name, loc, cps)
\r
12317 ChessProgramState *cps;
\r
12319 char buf[MSG_SIZ];
\r
12320 int len = strlen(name);
\r
12321 if (strncmp((*p), name, len) == 0
\r
12322 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
\r
12324 sscanf(*p, "%[^\"]", loc);
\r
12325 while (**p && **p != '\"') (*p)++;
\r
12326 if (**p == '\"') (*p)++;
\r
12327 sprintf(buf, "accepted %s\n", name);
\r
12328 SendToProgram(buf, cps);
\r
12335 ParseOption(Option *opt, ChessProgramState *cps)
\r
12336 // [HGM] options: process the string that defines an engine option, and determine
\r
12337 // name, type, default value, and allowed value range
\r
12339 char *p, *q, buf[MSG_SIZ];
\r
12340 int n, min = (-1)<<31, max = 1<<31, def;
\r
12342 if(p = strstr(opt->name, " -spin ")) {
\r
12343 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
\r
12344 if(max < min) max = min; // enforce consistency
\r
12345 if(def < min) def = min;
\r
12346 if(def > max) def = max;
\r
12347 opt->value = def;
\r
12350 opt->type = Spin;
\r
12351 } else if(p = strstr(opt->name, " -string ")) {
\r
12352 opt->textValue = p+9;
\r
12353 opt->type = TextBox;
\r
12354 } else if(p = strstr(opt->name, " -check ")) {
\r
12355 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
\r
12356 opt->value = (def != 0);
\r
12357 opt->type = CheckBox;
\r
12358 } else if(p = strstr(opt->name, " -combo ")) {
\r
12359 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
\r
12360 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
\r
12361 opt->value = n = 0;
\r
12362 while(q = StrStr(q, " /// ")) {
\r
12363 n++; *q = 0; // count choices, and null-terminate each of them
\r
12365 if(*q == '*') { // remember default, which is marked with * prefix
\r
12369 cps->comboList[cps->comboCnt++] = q;
\r
12371 cps->comboList[cps->comboCnt++] = NULL;
\r
12372 opt->max = n + 1;
\r
12373 opt->type = ComboBox;
\r
12374 } else if(p = strstr(opt->name, " -button")) {
\r
12375 opt->type = Button;
\r
12376 } else if(p = strstr(opt->name, " -save")) {
\r
12377 opt->type = SaveButton;
\r
12378 } else return FALSE;
\r
12379 *p = 0; // terminate option name
\r
12384 FeatureDone(cps, val)
\r
12385 ChessProgramState* cps;
\r
12388 DelayedEventCallback cb = GetDelayedEvent();
\r
12389 if ((cb == InitBackEnd3 && cps == &first) ||
\r
12390 (cb == TwoMachinesEventIfReady && cps == &second)) {
\r
12391 CancelDelayedEvent();
\r
12392 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
\r
12394 cps->initDone = val;
\r
12397 /* Parse feature command from engine */
\r
12399 ParseFeatures(args, cps)
\r
12401 ChessProgramState *cps;
\r
12406 char buf[MSG_SIZ];
\r
12409 while (*p == ' ') p++;
\r
12410 if (*p == NULLCHAR) return;
\r
12412 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
\r
12413 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
\r
12414 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
\r
12415 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
\r
12416 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
\r
12417 if (BoolFeature(&p, "reuse", &val, cps)) {
\r
12418 /* Engine can disable reuse, but can't enable it if user said no */
\r
12419 if (!val) cps->reuse = FALSE;
\r
12422 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
\r
12423 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
\r
12424 if (gameMode == TwoMachinesPlay) {
\r
12425 DisplayTwoMachinesTitle();
\r
12427 DisplayTitle("");
\r
12431 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
\r
12432 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
\r
12433 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
\r
12434 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
\r
12435 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
\r
12436 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
\r
12437 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
\r
12438 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
\r
12439 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
\r
12440 if (IntFeature(&p, "done", &val, cps)) {
\r
12441 FeatureDone(cps, val);
\r
12444 /* Added by Tord: */
\r
12445 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
\r
12446 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
\r
12447 /* End of additions by Tord */
\r
12449 /* [HGM] added features: */
\r
12450 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
\r
12451 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
\r
12452 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
\r
12453 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
\r
12454 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
\r
12455 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
\r
12456 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
\r
12457 ParseOption(&(cps->option[cps->nrOptions++]), cps); // [HGM] options: add option feature
\r
12458 if(cps->nrOptions >= MAX_OPTIONS) {
\r
12459 cps->nrOptions--;
\r
12460 sprintf(buf, "%s engine has too many options\n", cps->which);
\r
12461 DisplayError(buf, 0);
\r
12465 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
\r
12466 /* End of additions by HGM */
\r
12468 /* unknown feature: complain and skip */
\r
12470 while (*q && *q != '=') q++;
\r
12471 sprintf(buf, "rejected %.*s\n", q-p, p);
\r
12472 SendToProgram(buf, cps);
\r
12476 if (*p == '\"') {
\r
12478 while (*p && *p != '\"') p++;
\r
12479 if (*p == '\"') p++;
\r
12481 while (*p && *p != ' ') p++;
\r
12489 PeriodicUpdatesEvent(newState)
\r
12492 if (newState == appData.periodicUpdates)
\r
12495 appData.periodicUpdates=newState;
\r
12497 /* Display type changes, so update it now */
\r
12498 DisplayAnalysis();
\r
12500 /* Get the ball rolling again... */
\r
12502 AnalysisPeriodicEvent(1);
\r
12503 StartAnalysisClock();
\r
12508 PonderNextMoveEvent(newState)
\r
12511 if (newState == appData.ponderNextMove) return;
\r
12512 if (gameMode == EditPosition) EditPositionDone();
\r
12514 SendToProgram("hard\n", &first);
\r
12515 if (gameMode == TwoMachinesPlay) {
\r
12516 SendToProgram("hard\n", &second);
\r
12519 SendToProgram("easy\n", &first);
\r
12520 thinkOutput[0] = NULLCHAR;
\r
12521 if (gameMode == TwoMachinesPlay) {
\r
12522 SendToProgram("easy\n", &second);
\r
12525 appData.ponderNextMove = newState;
\r
12529 NewSettingEvent(option, command, value)
\r
12531 int option, value;
\r
12533 char buf[MSG_SIZ];
\r
12535 if (gameMode == EditPosition) EditPositionDone();
\r
12536 sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
\r
12537 SendToProgram(buf, &first);
\r
12538 if (gameMode == TwoMachinesPlay) {
\r
12539 SendToProgram(buf, &second);
\r
12544 ShowThinkingEvent()
\r
12545 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
\r
12547 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
\r
12548 int newState = appData.showThinking
\r
12549 // [HGM] thinking: other features now need thinking output as well
\r
12550 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
\r
12552 if (oldState == newState) return;
\r
12553 oldState = newState;
\r
12554 if (gameMode == EditPosition) EditPositionDone();
\r
12556 SendToProgram("post\n", &first);
\r
12557 if (gameMode == TwoMachinesPlay) {
\r
12558 SendToProgram("post\n", &second);
\r
12561 SendToProgram("nopost\n", &first);
\r
12562 thinkOutput[0] = NULLCHAR;
\r
12563 if (gameMode == TwoMachinesPlay) {
\r
12564 SendToProgram("nopost\n", &second);
\r
12567 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
\r
12571 AskQuestionEvent(title, question, replyPrefix, which)
\r
12572 char *title; char *question; char *replyPrefix; char *which;
\r
12574 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
\r
12575 if (pr == NoProc) return;
\r
12576 AskQuestion(title, question, replyPrefix, pr);
\r
12580 DisplayMove(moveNumber)
\r
12583 char message[MSG_SIZ];
\r
12584 char res[MSG_SIZ];
\r
12585 char cpThinkOutput[MSG_SIZ];
\r
12587 if (moveNumber == forwardMostMove - 1 ||
\r
12588 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
\r
12590 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput));
\r
12592 if (strchr(cpThinkOutput, '\n')) {
\r
12593 *strchr(cpThinkOutput, '\n') = NULLCHAR;
\r
12596 *cpThinkOutput = NULLCHAR;
\r
12599 /* [AS] Hide thinking from human user */
\r
12600 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
\r
12601 *cpThinkOutput = NULLCHAR;
\r
12602 if( thinkOutput[0] != NULLCHAR ) {
\r
12605 for( i=0; i<=hiddenThinkOutputState; i++ ) {
\r
12606 cpThinkOutput[i] = '.';
\r
12608 cpThinkOutput[i] = NULLCHAR;
\r
12609 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
\r
12613 if (moveNumber == forwardMostMove - 1 &&
\r
12614 gameInfo.resultDetails != NULL) {
\r
12615 if (gameInfo.resultDetails[0] == NULLCHAR) {
\r
12616 sprintf(res, " %s", PGNResult(gameInfo.result));
\r
12618 sprintf(res, " {%s} %s",
\r
12619 gameInfo.resultDetails, PGNResult(gameInfo.result));
\r
12622 res[0] = NULLCHAR;
\r
12625 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
\r
12626 DisplayMessage(res, cpThinkOutput);
\r
12628 sprintf(message, "%d.%s%s%s", moveNumber / 2 + 1,
\r
12629 WhiteOnMove(moveNumber) ? " " : ".. ",
\r
12630 parseList[moveNumber], res);
\r
12631 DisplayMessage(message, cpThinkOutput);
\r
12636 DisplayAnalysisText(text)
\r
12639 char buf[MSG_SIZ];
\r
12641 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile
\r
12642 || appData.icsEngineAnalyze) {
\r
12643 sprintf(buf, "Analysis (%s)", first.tidy);
\r
12644 AnalysisPopUp(buf, text);
\r
12649 only_one_move(str)
\r
12652 while (*str && isspace(*str)) ++str;
\r
12653 while (*str && !isspace(*str)) ++str;
\r
12654 if (!*str) return 1;
\r
12655 while (*str && isspace(*str)) ++str;
\r
12656 if (!*str) return 1;
\r
12661 DisplayAnalysis()
\r
12663 char buf[MSG_SIZ];
\r
12664 char lst[MSG_SIZ / 2];
\r
12666 static char *xtra[] = { "", " (--)", " (++)" };
\r
12669 if (programStats.time == 0) {
\r
12670 programStats.time = 1;
\r
12673 if (programStats.got_only_move) {
\r
12674 safeStrCpy(buf, programStats.movelist, sizeof(buf));
\r
12676 safeStrCpy( lst, programStats.movelist, sizeof(lst));
\r
12678 nps = (u64ToDouble(programStats.nodes) /
\r
12679 ((double)programStats.time /100.0));
\r
12681 cs = programStats.time % 100;
\r
12682 s = programStats.time / 100;
\r
12683 h = (s / (60*60));
\r
12688 if (programStats.moves_left > 0 && appData.periodicUpdates) {
\r
12689 if (programStats.move_name[0] != NULLCHAR) {
\r
12690 sprintf(buf, "depth=%d %d/%d(%s) %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
\r
12691 programStats.depth,
\r
12692 programStats.nr_moves-programStats.moves_left,
\r
12693 programStats.nr_moves, programStats.move_name,
\r
12694 ((float)programStats.score)/100.0, lst,
\r
12695 only_one_move(lst)?
\r
12696 xtra[programStats.got_fail] : "",
\r
12697 (u64)programStats.nodes, (int)nps, h, m, s, cs);
\r
12699 sprintf(buf, "depth=%d %d/%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
\r
12700 programStats.depth,
\r
12701 programStats.nr_moves-programStats.moves_left,
\r
12702 programStats.nr_moves, ((float)programStats.score)/100.0,
\r
12704 only_one_move(lst)?
\r
12705 xtra[programStats.got_fail] : "",
\r
12706 (u64)programStats.nodes, (int)nps, h, m, s, cs);
\r
12709 sprintf(buf, "depth=%d %+.2f %s%s\nNodes: " u64Display " NPS: %d\nTime: %02d:%02d:%02d.%02d",
\r
12710 programStats.depth,
\r
12711 ((float)programStats.score)/100.0,
\r
12713 only_one_move(lst)?
\r
12714 xtra[programStats.got_fail] : "",
\r
12715 (u64)programStats.nodes, (int)nps, h, m, s, cs);
\r
12718 DisplayAnalysisText(buf);
\r
12722 DisplayComment(moveNumber, text)
\r
12726 char title[MSG_SIZ];
\r
12727 char buf[8000]; // comment can be long!
\r
12728 int score, depth;
\r
12730 if( appData.autoDisplayComment ) {
\r
12731 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
\r
12732 strcpy(title, "Comment");
\r
12734 sprintf(title, "Comment on %d.%s%s", moveNumber / 2 + 1,
\r
12735 WhiteOnMove(moveNumber) ? " " : ".. ",
\r
12736 parseList[moveNumber]);
\r
12738 } else title[0] = 0;
\r
12740 // [HGM] PV info: display PV info together with (or as) comment
\r
12741 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
\r
12742 if(text == NULL) text = "";
\r
12743 score = pvInfoList[moveNumber].score;
\r
12744 sprintf(buf, "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
\r
12745 depth, (pvInfoList[moveNumber].time+50)/100, text);
\r
12746 CommentPopUp(title, buf);
\r
12748 if (text != NULL)
\r
12749 CommentPopUp(title, text);
\r
12752 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
\r
12753 * might be busy thinking or pondering. It can be omitted if your
\r
12754 * gnuchess is configured to stop thinking immediately on any user
\r
12755 * input. However, that gnuchess feature depends on the FIONREAD
\r
12756 * ioctl, which does not work properly on some flavors of Unix.
\r
12760 ChessProgramState *cps;
\r
12763 if (!cps->useSigint) return;
\r
12764 if (appData.noChessProgram || (cps->pr == NoProc)) return;
\r
12765 switch (gameMode) {
\r
12766 case MachinePlaysWhite:
\r
12767 case MachinePlaysBlack:
\r
12768 case TwoMachinesPlay:
\r
12769 case IcsPlayingWhite:
\r
12770 case IcsPlayingBlack:
\r
12771 case AnalyzeMode:
\r
12772 case AnalyzeFile:
\r
12773 /* Skip if we know it isn't thinking */
\r
12774 if (!cps->maybeThinking) return;
\r
12775 if (appData.debugMode)
\r
12776 fprintf(debugFP, "Interrupting %s\n", cps->which);
\r
12777 InterruptChildProcess(cps->pr);
\r
12778 cps->maybeThinking = FALSE;
\r
12783 #endif /*ATTENTION*/
\r
12789 if (whiteTimeRemaining <= 0) {
\r
12790 if (!whiteFlag) {
\r
12791 whiteFlag = TRUE;
\r
12792 if (appData.icsActive) {
\r
12793 if (appData.autoCallFlag &&
\r
12794 gameMode == IcsPlayingBlack && !blackFlag) {
\r
12795 SendToICS(ics_prefix);
\r
12796 SendToICS("flag\n");
\r
12800 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
\r
12802 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
\r
12803 if (appData.autoCallFlag) {
\r
12804 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
\r
12811 if (blackTimeRemaining <= 0) {
\r
12812 if (!blackFlag) {
\r
12813 blackFlag = TRUE;
\r
12814 if (appData.icsActive) {
\r
12815 if (appData.autoCallFlag &&
\r
12816 gameMode == IcsPlayingWhite && !whiteFlag) {
\r
12817 SendToICS(ics_prefix);
\r
12818 SendToICS("flag\n");
\r
12822 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
\r
12824 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
\r
12825 if (appData.autoCallFlag) {
\r
12826 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
\r
12837 CheckTimeControl()
\r
12839 if (!appData.clockMode || appData.icsActive ||
\r
12840 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
\r
12843 * add time to clocks when time control is achieved ([HGM] now also used for increment)
\r
12845 if ( !WhiteOnMove(forwardMostMove) )
\r
12846 /* White made time control */
\r
12847 whiteTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
\r
12848 /* [HGM] time odds: correct new time quota for time odds! */
\r
12849 / WhitePlayer()->timeOdds;
\r
12851 /* Black made time control */
\r
12852 blackTimeRemaining += GetTimeQuota((forwardMostMove-1)/2)
\r
12853 / WhitePlayer()->other->timeOdds;
\r
12857 DisplayBothClocks()
\r
12859 int wom = gameMode == EditPosition ?
\r
12860 !blackPlaysFirst : WhiteOnMove(currentMove);
\r
12861 DisplayWhiteClock(whiteTimeRemaining, wom);
\r
12862 DisplayBlackClock(blackTimeRemaining, !wom);
\r
12866 /* Timekeeping seems to be a portability nightmare. I think everyone
\r
12867 has ftime(), but I'm really not sure, so I'm including some ifdefs
\r
12868 to use other calls if you don't. Clocks will be less accurate if
\r
12869 you have neither ftime nor gettimeofday.
\r
12872 /* Get the current time as a TimeMark */
\r
12877 #if HAVE_GETTIMEOFDAY
\r
12879 struct timeval timeVal;
\r
12880 struct timezone timeZone;
\r
12882 gettimeofday(&timeVal, &timeZone);
\r
12883 tm->sec = (long) timeVal.tv_sec;
\r
12884 tm->ms = (int) (timeVal.tv_usec / 1000L);
\r
12886 #else /*!HAVE_GETTIMEOFDAY*/
\r
12889 #include <sys/timeb.h>
\r
12890 struct timeb timeB;
\r
12893 tm->sec = (long) timeB.time;
\r
12894 tm->ms = (int) timeB.millitm;
\r
12896 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
\r
12897 tm->sec = (long) time(NULL);
\r
12903 /* Return the difference in milliseconds between two
\r
12904 time marks. We assume the difference will fit in a long!
\r
12907 SubtractTimeMarks(tm2, tm1)
\r
12908 TimeMark *tm2, *tm1;
\r
12910 return 1000L*(tm2->sec - tm1->sec) +
\r
12911 (long) (tm2->ms - tm1->ms);
\r
12916 * Code to manage the game clocks.
\r
12918 * In tournament play, black starts the clock and then white makes a move.
\r
12919 * We give the human user a slight advantage if he is playing white---the
\r
12920 * clocks don't run until he makes his first move, so it takes zero time.
\r
12921 * Also, we don't account for network lag, so we could get out of sync
\r
12922 * with GNU Chess's clock -- but then, referees are always right.
\r
12925 static TimeMark tickStartTM;
\r
12926 static long intendedTickLength;
\r
12929 NextTickLength(timeRemaining)
\r
12930 long timeRemaining;
\r
12932 long nominalTickLength, nextTickLength;
\r
12934 if (timeRemaining > 0L && timeRemaining <= 10000L)
\r
12935 nominalTickLength = 100L;
\r
12937 nominalTickLength = 1000L;
\r
12938 nextTickLength = timeRemaining % nominalTickLength;
\r
12939 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
\r
12941 return nextTickLength;
\r
12944 /* Adjust clock one minute up or down */
\r
12946 AdjustClock(Boolean which, int dir)
\r
12948 if(which) blackTimeRemaining += 60000*dir;
\r
12949 else whiteTimeRemaining += 60000*dir;
\r
12950 DisplayBothClocks();
\r
12953 /* Stop clocks and reset to a fresh time control */
\r
12957 (void) StopClockTimer();
\r
12958 if (appData.icsActive) {
\r
12959 whiteTimeRemaining = blackTimeRemaining = 0;
\r
12960 } else { /* [HGM] correct new time quote for time odds */
\r
12961 whiteTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->timeOdds;
\r
12962 blackTimeRemaining = GetTimeQuota(-1) / WhitePlayer()->other->timeOdds;
\r
12964 if (whiteFlag || blackFlag) {
\r
12965 DisplayTitle("");
\r
12966 whiteFlag = blackFlag = FALSE;
\r
12968 DisplayBothClocks();
\r
12971 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
\r
12973 /* Decrement running clock by amount of time that has passed */
\r
12975 DecrementClocks()
\r
12977 long timeRemaining;
\r
12978 long lastTickLength, fudge;
\r
12981 if (!appData.clockMode) return;
\r
12982 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
\r
12984 GetTimeMark(&now);
\r
12986 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
\r
12988 /* Fudge if we woke up a little too soon */
\r
12989 fudge = intendedTickLength - lastTickLength;
\r
12990 if (fudge < 0 || fudge > FUDGE) fudge = 0;
\r
12992 if (WhiteOnMove(forwardMostMove)) {
\r
12993 if(whiteNPS >= 0) lastTickLength = 0;
\r
12994 timeRemaining = whiteTimeRemaining -= lastTickLength;
\r
12995 DisplayWhiteClock(whiteTimeRemaining - fudge,
\r
12996 WhiteOnMove(currentMove));
\r
12998 if(blackNPS >= 0) lastTickLength = 0;
\r
12999 timeRemaining = blackTimeRemaining -= lastTickLength;
\r
13000 DisplayBlackClock(blackTimeRemaining - fudge,
\r
13001 !WhiteOnMove(currentMove));
\r
13004 if (CheckFlags()) return;
\r
13006 tickStartTM = now;
\r
13007 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
\r
13008 StartClockTimer(intendedTickLength);
\r
13010 /* if the time remaining has fallen below the alarm threshold, sound the
\r
13011 * alarm. if the alarm has sounded and (due to a takeback or time control
\r
13012 * with increment) the time remaining has increased to a level above the
\r
13013 * threshold, reset the alarm so it can sound again.
\r
13016 if (appData.icsActive && appData.icsAlarm) {
\r
13018 /* make sure we are dealing with the user's clock */
\r
13019 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
\r
13020 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
\r
13023 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
\r
13024 alarmSounded = FALSE;
\r
13025 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
\r
13026 PlayAlarmSound();
\r
13027 alarmSounded = TRUE;
\r
13033 /* A player has just moved, so stop the previously running
\r
13034 clock and (if in clock mode) start the other one.
\r
13035 We redisplay both clocks in case we're in ICS mode, because
\r
13036 ICS gives us an update to both clocks after every move.
\r
13037 Note that this routine is called *after* forwardMostMove
\r
13038 is updated, so the last fractional tick must be subtracted
\r
13039 from the color that is *not* on move now.
\r
13044 long lastTickLength;
\r
13046 int flagged = FALSE;
\r
13048 GetTimeMark(&now);
\r
13050 if (StopClockTimer() && appData.clockMode) {
\r
13051 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
\r
13052 if (WhiteOnMove(forwardMostMove)) {
\r
13053 if(blackNPS >= 0) lastTickLength = 0;
\r
13054 blackTimeRemaining -= lastTickLength;
\r
13055 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
\r
13056 // if(pvInfoList[forwardMostMove-1].time == -1)
\r
13057 pvInfoList[forwardMostMove-1].time = // use GUI time
\r
13058 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
\r
13060 if(whiteNPS >= 0) lastTickLength = 0;
\r
13061 whiteTimeRemaining -= lastTickLength;
\r
13062 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
\r
13063 // if(pvInfoList[forwardMostMove-1].time == -1)
\r
13064 pvInfoList[forwardMostMove-1].time =
\r
13065 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
\r
13067 flagged = CheckFlags();
\r
13069 CheckTimeControl();
\r
13071 if (flagged || !appData.clockMode) return;
\r
13073 switch (gameMode) {
\r
13074 case MachinePlaysBlack:
\r
13075 case MachinePlaysWhite:
\r
13076 case BeginningOfGame:
\r
13077 if (pausing) return;
\r
13081 case PlayFromGameFile:
\r
13082 case IcsExamining:
\r
13089 tickStartTM = now;
\r
13090 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
\r
13091 whiteTimeRemaining : blackTimeRemaining);
\r
13092 StartClockTimer(intendedTickLength);
\r
13096 /* Stop both clocks */
\r
13100 long lastTickLength;
\r
13103 if (!StopClockTimer()) return;
\r
13104 if (!appData.clockMode) return;
\r
13106 GetTimeMark(&now);
\r
13108 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
\r
13109 if (WhiteOnMove(forwardMostMove)) {
\r
13110 if(whiteNPS >= 0) lastTickLength = 0;
\r
13111 whiteTimeRemaining -= lastTickLength;
\r
13112 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
\r
13114 if(blackNPS >= 0) lastTickLength = 0;
\r
13115 blackTimeRemaining -= lastTickLength;
\r
13116 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
\r
13121 /* Start clock of player on move. Time may have been reset, so
\r
13122 if clock is already running, stop and restart it. */
\r
13126 (void) StopClockTimer(); /* in case it was running already */
\r
13127 DisplayBothClocks();
\r
13128 if (CheckFlags()) return;
\r
13130 if (!appData.clockMode) return;
\r
13131 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
\r
13133 GetTimeMark(&tickStartTM);
\r
13134 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
\r
13135 whiteTimeRemaining : blackTimeRemaining);
\r
13137 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
\r
13138 whiteNPS = blackNPS = -1;
\r
13139 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
\r
13140 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
\r
13141 whiteNPS = first.nps;
\r
13142 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
\r
13143 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
\r
13144 blackNPS = first.nps;
\r
13145 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
\r
13146 whiteNPS = second.nps;
\r
13147 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
\r
13148 blackNPS = second.nps;
\r
13149 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
\r
13151 StartClockTimer(intendedTickLength);
\r
13158 long second, minute, hour, day;
\r
13160 static char buf[32];
\r
13162 if (ms > 0 && ms <= 9900) {
\r
13163 /* convert milliseconds to tenths, rounding up */
\r
13164 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
\r
13166 sprintf(buf, " %03.1f ", tenths/10.0);
\r
13170 /* convert milliseconds to seconds, rounding up */
\r
13171 /* use floating point to avoid strangeness of integer division
\r
13172 with negative dividends on many machines */
\r
13173 second = (long) floor(((double) (ms + 999L)) / 1000.0);
\r
13175 if (second < 0) {
\r
13177 second = -second;
\r
13180 day = second / (60 * 60 * 24);
\r
13181 second = second % (60 * 60 * 24);
\r
13182 hour = second / (60 * 60);
\r
13183 second = second % (60 * 60);
\r
13184 minute = second / 60;
\r
13185 second = second % 60;
\r
13188 sprintf(buf, " %s%ld:%02ld:%02ld:%02ld ",
\r
13189 sign, day, hour, minute, second);
\r
13190 else if (hour > 0)
\r
13191 sprintf(buf, " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
\r
13193 sprintf(buf, " %s%2ld:%02ld ", sign, minute, second);
\r
13200 * This is necessary because some C libraries aren't ANSI C compliant yet.
\r
13203 StrStr(string, match)
\r
13204 char *string, *match;
\r
13208 length = strlen(match);
\r
13210 for (i = strlen(string) - length; i >= 0; i--, string++)
\r
13211 if (!strncmp(match, string, length))
\r
13218 StrCaseStr(string, match)
\r
13219 char *string, *match;
\r
13221 int i, j, length;
\r
13223 length = strlen(match);
\r
13225 for (i = strlen(string) - length; i >= 0; i--, string++) {
\r
13226 for (j = 0; j < length; j++) {
\r
13227 if (ToLower(match[j]) != ToLower(string[j]))
\r
13230 if (j == length) return string;
\r
13236 #ifndef _amigados
\r
13238 StrCaseCmp(s1, s2)
\r
13244 c1 = ToLower(*s1++);
\r
13245 c2 = ToLower(*s2++);
\r
13246 if (c1 > c2) return 1;
\r
13247 if (c1 < c2) return -1;
\r
13248 if (c1 == NULLCHAR) return 0;
\r
13257 return isupper(c) ? tolower(c) : c;
\r
13265 return islower(c) ? toupper(c) : c;
\r
13267 #endif /* !_amigados */
\r
13275 if ((ret = (char *) malloc(strlen(s) + 1))) {
\r
13282 StrSavePtr(s, savePtr)
\r
13283 char *s, **savePtr;
\r
13288 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
\r
13289 strcpy(*savePtr, s);
\r
13291 return(*savePtr);
\r
13299 char buf[MSG_SIZ];
\r
13301 clock = time((time_t *)NULL);
\r
13302 tm = localtime(&clock);
\r
13303 sprintf(buf, "%04d.%02d.%02d",
\r
13304 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
\r
13305 return StrSave(buf);
\r
13310 PositionToFEN(move, useFEN960)
\r
13314 int i, j, fromX, fromY, toX, toY;
\r
13319 ChessSquare piece;
\r
13321 whiteToPlay = (gameMode == EditPosition) ?
\r
13322 !blackPlaysFirst : (move % 2 == 0);
\r
13325 /* Piece placement data */
\r
13326 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
13328 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
\r
13329 if (boards[move][i][j] == EmptySquare) {
\r
13331 } else { ChessSquare piece = boards[move][i][j];
\r
13332 if (emptycount > 0) {
\r
13333 if(emptycount<10) /* [HGM] can be >= 10 */
\r
13334 *p++ = '0' + emptycount;
\r
13335 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
\r
13338 if(PieceToChar(piece) == '+') {
\r
13339 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
\r
13341 piece = (ChessSquare)(DEMOTED piece);
\r
13343 *p++ = PieceToChar(piece);
\r
13344 if(p[-1] == '~') {
\r
13345 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
\r
13346 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
\r
13351 if (emptycount > 0) {
\r
13352 if(emptycount<10) /* [HGM] can be >= 10 */
\r
13353 *p++ = '0' + emptycount;
\r
13354 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
\r
13361 /* [HGM] print Crazyhouse or Shogi holdings */
\r
13362 if( gameInfo.holdingsWidth ) {
\r
13363 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
\r
13365 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
\r
13366 piece = boards[move][i][BOARD_WIDTH-1];
\r
13367 if( piece != EmptySquare )
\r
13368 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
\r
13369 *p++ = PieceToChar(piece);
\r
13371 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
\r
13372 piece = boards[move][BOARD_HEIGHT-i-1][0];
\r
13373 if( piece != EmptySquare )
\r
13374 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
\r
13375 *p++ = PieceToChar(piece);
\r
13378 if( q == p ) *p++ = '-';
\r
13383 /* Active color */
\r
13384 *p++ = whiteToPlay ? 'w' : 'b';
\r
13387 if(nrCastlingRights) {
\r
13389 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
\r
13390 /* [HGM] write directly from rights */
\r
13391 if(castlingRights[move][2] >= 0 &&
\r
13392 castlingRights[move][0] >= 0 )
\r
13393 *p++ = castlingRights[move][0] + AAA + 'A' - 'a';
\r
13394 if(castlingRights[move][2] >= 0 &&
\r
13395 castlingRights[move][1] >= 0 )
\r
13396 *p++ = castlingRights[move][1] + AAA + 'A' - 'a';
\r
13397 if(castlingRights[move][5] >= 0 &&
\r
13398 castlingRights[move][3] >= 0 )
\r
13399 *p++ = castlingRights[move][3] + AAA;
\r
13400 if(castlingRights[move][5] >= 0 &&
\r
13401 castlingRights[move][4] >= 0 )
\r
13402 *p++ = castlingRights[move][4] + AAA;
\r
13405 /* [HGM] write true castling rights */
\r
13406 if( nrCastlingRights == 6 ) {
\r
13407 if(castlingRights[move][0] == BOARD_RGHT-1 &&
\r
13408 castlingRights[move][2] >= 0 ) *p++ = 'K';
\r
13409 if(castlingRights[move][1] == BOARD_LEFT &&
\r
13410 castlingRights[move][2] >= 0 ) *p++ = 'Q';
\r
13411 if(castlingRights[move][3] == BOARD_RGHT-1 &&
\r
13412 castlingRights[move][5] >= 0 ) *p++ = 'k';
\r
13413 if(castlingRights[move][4] == BOARD_LEFT &&
\r
13414 castlingRights[move][5] >= 0 ) *p++ = 'q';
\r
13417 if (q == p) *p++ = '-'; /* No castling rights */
\r
13421 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
\r
13422 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
\r
13423 /* En passant target square */
\r
13424 if (move > backwardMostMove) {
\r
13425 fromX = moveList[move - 1][0] - AAA;
\r
13426 fromY = moveList[move - 1][1] - ONE;
\r
13427 toX = moveList[move - 1][2] - AAA;
\r
13428 toY = moveList[move - 1][3] - ONE;
\r
13429 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
\r
13430 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
\r
13431 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
\r
13433 /* 2-square pawn move just happened */
\r
13434 *p++ = toX + AAA;
\r
13435 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
\r
13445 /* [HGM] find reversible plies */
\r
13446 { int i = 0, j=move;
\r
13448 if (appData.debugMode) { int k;
\r
13449 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
\r
13450 for(k=backwardMostMove; k<=forwardMostMove; k++)
\r
13451 fprintf(debugFP, "e%d. p=%d\n", k, epStatus[k]);
\r
13455 while(j > backwardMostMove && epStatus[j] <= EP_NONE) j--,i++;
\r
13456 if( j == backwardMostMove ) i += initialRulePlies;
\r
13457 sprintf(p, "%d ", i);
\r
13458 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
\r
13460 /* Fullmove number */
\r
13461 sprintf(p, "%d", (move / 2) + 1);
\r
13463 return StrSave(buf);
\r
13467 ParseFEN(board, blackPlaysFirst, fen)
\r
13469 int *blackPlaysFirst;
\r
13475 ChessSquare piece;
\r
13479 /* [HGM] by default clear Crazyhouse holdings, if present */
\r
13480 if(gameInfo.holdingsWidth) {
\r
13481 for(i=0; i<BOARD_HEIGHT; i++) {
\r
13482 board[i][0] = EmptySquare; /* black holdings */
\r
13483 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
\r
13484 board[i][1] = (ChessSquare) 0; /* black counts */
\r
13485 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
\r
13489 /* Piece placement data */
\r
13490 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
\r
13493 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
\r
13494 if (*p == '/') p++;
\r
13495 emptycount = gameInfo.boardWidth - j;
\r
13496 while (emptycount--)
\r
13497 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
\r
13499 #if(BOARD_SIZE >= 10)
\r
13500 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
\r
13501 p++; emptycount=10;
\r
13502 if (j + emptycount > gameInfo.boardWidth) return FALSE;
\r
13503 while (emptycount--)
\r
13504 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
\r
13506 } else if (isdigit(*p)) {
\r
13507 emptycount = *p++ - '0';
\r
13508 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
\r
13509 if (j + emptycount > gameInfo.boardWidth) return FALSE;
\r
13510 while (emptycount--)
\r
13511 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
\r
13512 } else if (*p == '+' || isalpha(*p)) {
\r
13513 if (j >= gameInfo.boardWidth) return FALSE;
\r
13515 piece = CharToPiece(*++p);
\r
13516 if(piece == EmptySquare) return FALSE; /* unknown piece */
\r
13517 piece = (ChessSquare) (PROMOTED piece ); p++;
\r
13518 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
\r
13519 } else piece = CharToPiece(*p++);
\r
13521 if(piece==EmptySquare) return FALSE; /* unknown piece */
\r
13522 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
\r
13523 piece = (ChessSquare) (PROMOTED piece);
\r
13524 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
\r
13527 board[i][(j++)+gameInfo.holdingsWidth] = piece;
\r
13533 while (*p == '/' || *p == ' ') p++;
\r
13535 /* [HGM] look for Crazyhouse holdings here */
\r
13536 while(*p==' ') p++;
\r
13537 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
\r
13538 if(*p == '[') p++;
\r
13539 if(*p == '-' ) *p++; /* empty holdings */ else {
\r
13540 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
\r
13541 /* if we would allow FEN reading to set board size, we would */
\r
13542 /* have to add holdings and shift the board read so far here */
\r
13543 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
\r
13545 if((int) piece >= (int) BlackPawn ) {
\r
13546 i = (int)piece - (int)BlackPawn;
\r
13547 i = PieceToNumber((ChessSquare)i);
\r
13548 if( i >= gameInfo.holdingsSize ) return FALSE;
\r
13549 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
\r
13550 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
\r
13552 i = (int)piece - (int)WhitePawn;
\r
13553 i = PieceToNumber((ChessSquare)i);
\r
13554 if( i >= gameInfo.holdingsSize ) return FALSE;
\r
13555 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
\r
13556 board[i][BOARD_WIDTH-2]++; /* black holdings */
\r
13560 if(*p == ']') *p++;
\r
13563 while(*p == ' ') p++;
\r
13565 /* Active color */
\r
13568 *blackPlaysFirst = FALSE;
\r
13571 *blackPlaysFirst = TRUE;
\r
13577 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
\r
13578 /* return the extra info in global variiables */
\r
13580 /* set defaults in case FEN is incomplete */
\r
13581 FENepStatus = EP_UNKNOWN;
\r
13582 for(i=0; i<nrCastlingRights; i++ ) {
\r
13583 FENcastlingRights[i] =
\r
13584 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? -1 : initialRights[i];
\r
13585 } /* assume possible unless obviously impossible */
\r
13586 if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) FENcastlingRights[0] = -1;
\r
13587 if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) FENcastlingRights[1] = -1;
\r
13588 if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) FENcastlingRights[2] = -1;
\r
13589 if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) FENcastlingRights[3] = -1;
\r
13590 if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) FENcastlingRights[4] = -1;
\r
13591 if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) FENcastlingRights[5] = -1;
\r
13592 FENrulePlies = 0;
\r
13594 while(*p==' ') p++;
\r
13595 if(nrCastlingRights) {
\r
13596 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
\r
13597 /* castling indicator present, so default becomes no castlings */
\r
13598 for(i=0; i<nrCastlingRights; i++ ) {
\r
13599 FENcastlingRights[i] = -1;
\r
13602 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
\r
13603 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
\r
13604 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
\r
13605 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
\r
13606 char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
\r
13608 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
\r
13609 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
\r
13610 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
\r
13614 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
\r
13615 FENcastlingRights[0] = i != whiteKingFile ? i : -1;
\r
13616 FENcastlingRights[2] = whiteKingFile;
\r
13619 for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
\r
13620 FENcastlingRights[1] = i != whiteKingFile ? i : -1;
\r
13621 FENcastlingRights[2] = whiteKingFile;
\r
13624 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
\r
13625 FENcastlingRights[3] = i != blackKingFile ? i : -1;
\r
13626 FENcastlingRights[5] = blackKingFile;
\r
13629 for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
\r
13630 FENcastlingRights[4] = i != blackKingFile ? i : -1;
\r
13631 FENcastlingRights[5] = blackKingFile;
\r
13634 default: /* FRC castlings */
\r
13635 if(c >= 'a') { /* black rights */
\r
13636 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
\r
13637 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
\r
13638 if(i == BOARD_RGHT) break;
\r
13639 FENcastlingRights[5] = i;
\r
13641 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
\r
13642 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
\r
13644 FENcastlingRights[3] = c;
\r
13646 FENcastlingRights[4] = c;
\r
13647 } else { /* white rights */
\r
13648 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
\r
13649 if(board[0][i] == WhiteKing) break;
\r
13650 if(i == BOARD_RGHT) break;
\r
13651 FENcastlingRights[2] = i;
\r
13652 c -= AAA - 'a' + 'A';
\r
13653 if(board[0][c] >= WhiteKing) break;
\r
13655 FENcastlingRights[0] = c;
\r
13657 FENcastlingRights[1] = c;
\r
13661 if (appData.debugMode) {
\r
13662 fprintf(debugFP, "FEN castling rights:");
\r
13663 for(i=0; i<nrCastlingRights; i++)
\r
13664 fprintf(debugFP, " %d", FENcastlingRights[i]);
\r
13665 fprintf(debugFP, "\n");
\r
13668 while(*p==' ') p++;
\r
13671 /* read e.p. field in games that know e.p. capture */
\r
13672 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
\r
13673 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
\r
13675 p++; FENepStatus = EP_NONE;
\r
13677 char c = *p++ - AAA;
\r
13679 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
\r
13680 if(*p >= '0' && *p <='9') *p++;
\r
13686 if(sscanf(p, "%d", &i) == 1) {
\r
13687 FENrulePlies = i; /* 50-move ply counter */
\r
13688 /* (The move number is still ignored) */
\r
13695 EditPositionPasteFEN(char *fen)
\r
13697 if (fen != NULL) {
\r
13698 Board initial_position;
\r
13700 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
\r
13701 DisplayError(_("Bad FEN position in clipboard"), 0);
\r
13704 int savedBlackPlaysFirst = blackPlaysFirst;
\r
13705 EditPositionEvent();
\r
13706 blackPlaysFirst = savedBlackPlaysFirst;
\r
13707 CopyBoard(boards[0], initial_position);
\r
13708 /* [HGM] copy FEN attributes as well */
\r
13710 initialRulePlies = FENrulePlies;
\r
13711 epStatus[0] = FENepStatus;
\r
13712 for( i=0; i<nrCastlingRights; i++ )
\r
13713 castlingRights[0][i] = FENcastlingRights[i];
\r
13715 EditPositionDone();
\r
13716 DisplayBothClocks();
\r
13717 DrawPosition(FALSE, boards[currentMove]);
\r