2 * backend.c -- Common back end for X and Windows NT versions of
\r
3 * XBoard $Id: backend.c,v 2.6 2003/11/28 09:37:36 mann Exp $
\r
5 * Copyright 1991 by Digital Equipment Corporation, Maynard, Massachusetts.
\r
6 * Enhancements Copyright 1992-2001 Free Software Foundation, Inc.
\r
8 * The following terms apply to Digital Equipment Corporation's copyright
\r
9 * interest in XBoard:
\r
10 * ------------------------------------------------------------------------
\r
11 * All Rights Reserved
\r
13 * Permission to use, copy, modify, and distribute this software and its
\r
14 * documentation for any purpose and without fee is hereby granted,
\r
15 * provided that the above copyright notice appear in all copies and that
\r
16 * both that copyright notice and this permission notice appear in
\r
17 * supporting documentation, and that the name of Digital not be
\r
18 * used in advertising or publicity pertaining to distribution of the
\r
19 * software without specific, written prior permission.
\r
21 * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
\r
22 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
\r
23 * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
\r
24 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
\r
25 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
\r
26 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
\r
28 * ------------------------------------------------------------------------
\r
30 * The following terms apply to the enhanced version of XBoard distributed
\r
31 * by the Free Software Foundation:
\r
32 * ------------------------------------------------------------------------
\r
33 * This program is free software; you can redistribute it and/or modify
\r
34 * it under the terms of the GNU General Public License as published by
\r
35 * the Free Software Foundation; either version 2 of the License, or
\r
36 * (at your option) any later version.
\r
38 * This program is distributed in the hope that it will be useful,
\r
39 * but WITHOUT ANY WARRANTY; without even the implied warranty of
\r
40 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
\r
41 * GNU General Public License for more details.
\r
43 * You should have received a copy of the GNU General Public License
\r
44 * along with this program; if not, write to the Free Software
\r
45 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
\r
46 * ------------------------------------------------------------------------
\r
48 * See the file ChangeLog for a revision history. */
\r
50 /* [AS] Also useful here for debugging */
\r
52 #include <windows.h>
\r
54 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
\r
58 #define DoSleep( n )
\r
68 #include <sys/types.h>
\r
69 #include <sys/stat.h>
\r
73 # include <stdlib.h>
\r
74 # include <string.h>
\r
75 #else /* not STDC_HEADERS */
\r
77 # include <string.h>
\r
78 # else /* not HAVE_STRING_H */
\r
79 # include <strings.h>
\r
80 # endif /* not HAVE_STRING_H */
\r
81 #endif /* not STDC_HEADERS */
\r
83 #if HAVE_SYS_FCNTL_H
\r
84 # include <sys/fcntl.h>
\r
85 #else /* not HAVE_SYS_FCNTL_H */
\r
88 # endif /* HAVE_FCNTL_H */
\r
89 #endif /* not HAVE_SYS_FCNTL_H */
\r
91 #if TIME_WITH_SYS_TIME
\r
92 # include <sys/time.h>
\r
95 # if HAVE_SYS_TIME_H
\r
96 # include <sys/time.h>
\r
102 #if defined(_amigados) && !defined(__GNUC__)
\r
104 int tz_minuteswest;
\r
107 extern int gettimeofday(struct timeval *, struct timezone *);
\r
111 # include <unistd.h>
\r
114 #include "common.h"
\r
115 #include "frontend.h"
\r
116 #include "backend.h"
\r
117 #include "parser.h"
\r
120 # include "zippy.h"
\r
122 #include "backendz.h"
\r
124 /* A point in time */
\r
126 long sec; /* Assuming this is >= 32 bits */
\r
127 int ms; /* Assuming this is >= 16 bits */
\r
130 /* Search stats from chessprogram */
\r
132 char movelist[2*MSG_SIZ]; /* Last PV we were sent */
\r
133 int depth; /* Current search depth */
\r
134 int nr_moves; /* Total nr of root moves */
\r
135 int moves_left; /* Moves remaining to be searched */
\r
136 char move_name[MOVE_LEN]; /* Current move being searched, if provided */
\r
137 unsigned long nodes; /* # of nodes searched */
\r
138 int time; /* Search time (centiseconds) */
\r
139 int score; /* Score (centipawns) */
\r
140 int got_only_move; /* If last msg was "(only move)" */
\r
141 int got_fail; /* 0 - nothing, 1 - got "--", 2 - got "++" */
\r
142 int ok_to_send; /* handshaking between send & recv */
\r
143 int line_is_book; /* 1 if movelist is book moves */
\r
144 int seen_stat; /* 1 if we've seen the stat01: line */
\r
145 } ChessProgramStats;
\r
147 int establish P((void));
\r
148 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
\r
149 char *buf, int count, int error));
\r
150 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
\r
151 char *buf, int count, int error));
\r
152 void SendToICS P((char *s));
\r
153 void SendToICSDelayed P((char *s, long msdelay));
\r
154 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
\r
155 int toX, int toY));
\r
156 void InitPosition P((int redraw));
\r
157 void HandleMachineMove P((char *message, ChessProgramState *cps));
\r
158 int AutoPlayOneMove P((void));
\r
159 int LoadGameOneMove P((ChessMove readAhead));
\r
160 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
\r
161 int LoadPositionFromFile P((char *filename, int n, char *title));
\r
162 int SavePositionToFile P((char *filename));
\r
163 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
\r
165 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
\r
166 void ShowMove P((int fromX, int fromY, int toX, int toY));
\r
167 void FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
\r
168 /*char*/int promoChar));
\r
169 void BackwardInner P((int target));
\r
170 void ForwardInner P((int target));
\r
171 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
\r
172 void EditPositionDone P((void));
\r
173 void PrintOpponents P((FILE *fp));
\r
174 void PrintPosition P((FILE *fp, int move));
\r
175 void StartChessProgram P((ChessProgramState *cps));
\r
176 void SendToProgram P((char *message, ChessProgramState *cps));
\r
177 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
\r
178 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
\r
179 char *buf, int count, int error));
\r
180 void SendTimeControl P((ChessProgramState *cps,
\r
181 int mps, long tc, int inc, int sd, int st));
\r
182 char *TimeControlTagValue P((void));
\r
183 void Attention P((ChessProgramState *cps));
\r
184 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
\r
185 void ResurrectChessProgram P((void));
\r
186 void DisplayComment P((int moveNumber, char *text));
\r
187 void DisplayMove P((int moveNumber));
\r
188 void DisplayAnalysis P((void));
\r
190 void ParseGameHistory P((char *game));
\r
191 void ParseBoard12 P((char *string));
\r
192 void StartClocks P((void));
\r
193 void SwitchClocks P((void));
\r
194 void StopClocks P((void));
\r
195 void ResetClocks P((void));
\r
196 char *PGNDate P((void));
\r
197 void SetGameInfo P((void));
\r
198 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
\r
199 int RegisterMove P((void));
\r
200 void MakeRegisteredMove P((void));
\r
201 void TruncateGame P((void));
\r
202 int looking_at P((char *, int *, char *));
\r
203 void CopyPlayerNameIntoFileName P((char **, char *));
\r
204 char *SavePart P((char *));
\r
205 int SaveGameOldStyle P((FILE *));
\r
206 int SaveGamePGN P((FILE *));
\r
207 void GetTimeMark P((TimeMark *));
\r
208 long SubtractTimeMarks P((TimeMark *, TimeMark *));
\r
209 int CheckFlags P((void));
\r
210 long NextTickLength P((long));
\r
211 void CheckTimeControl P((void));
\r
212 void show_bytes P((FILE *, char *, int));
\r
213 int string_to_rating P((char *str));
\r
214 void ParseFeatures P((char* args, ChessProgramState *cps));
\r
215 void InitBackEnd3 P((void));
\r
216 void FeatureDone P((ChessProgramState* cps, int val));
\r
217 void InitChessProgram P((ChessProgramState *cps));
\r
219 void GetInfoFromComment( int, char * );
\r
221 extern int tinyLayout, smallLayout;
\r
222 static ChessProgramStats programStats;
\r
224 /* States for ics_getting_history */
\r
226 #define H_REQUESTED 1
\r
227 #define H_GOT_REQ_HEADER 2
\r
228 #define H_GOT_UNREQ_HEADER 3
\r
229 #define H_GETTING_MOVES 4
\r
230 #define H_GOT_UNWANTED_HEADER 5
\r
232 /* whosays values for GameEnds */
\r
234 #define GE_ENGINE 1
\r
235 #define GE_PLAYER 2
\r
237 #define GE_XBOARD 4
\r
238 #define GE_ENGINE1 5
\r
239 #define GE_ENGINE2 6
\r
241 /* Maximum number of games in a cmail message */
\r
242 #define CMAIL_MAX_GAMES 20
\r
244 /* Different types of move when calling RegisterMove */
\r
245 #define CMAIL_MOVE 0
\r
246 #define CMAIL_RESIGN 1
\r
247 #define CMAIL_DRAW 2
\r
248 #define CMAIL_ACCEPT 3
\r
250 /* Different types of result to remember for each game */
\r
251 #define CMAIL_NOT_RESULT 0
\r
252 #define CMAIL_OLD_RESULT 1
\r
253 #define CMAIL_NEW_RESULT 2
\r
255 /* Telnet protocol constants */
\r
256 #define TN_WILL 0373
\r
257 #define TN_WONT 0374
\r
259 #define TN_DONT 0376
\r
260 #define TN_IAC 0377
\r
261 #define TN_ECHO 0001
\r
262 #define TN_SGA 0003
\r
266 static char * safeStrCpy( char * dst, const char * src, size_t count )
\r
268 assert( dst != NULL );
\r
269 assert( src != NULL );
\r
270 assert( count > 0 );
\r
272 strncpy( dst, src, count );
\r
273 dst[ count-1 ] = '\0';
\r
277 static char * safeStrCat( char * dst, const char * src, size_t count )
\r
281 assert( dst != NULL );
\r
282 assert( src != NULL );
\r
283 assert( count > 0 );
\r
285 dst_len = strlen(dst);
\r
287 assert( count > dst_len ); /* Buffer size must be greater than current length */
\r
289 safeStrCpy( dst + dst_len, src, count - dst_len );
\r
294 /* Fake up flags for now, as we aren't keeping track of castling
\r
295 availability yet */
\r
299 int flags = F_ALL_CASTLE_OK;
\r
300 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
\r
301 switch (gameInfo.variant) {
\r
302 case VariantSuicide:
\r
303 case VariantGiveaway:
\r
304 flags |= F_IGNORE_CHECK;
\r
305 flags &= ~F_ALL_CASTLE_OK;
\r
307 case VariantAtomic:
\r
308 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
\r
310 case VariantKriegspiel:
\r
311 flags |= F_KRIEGSPIEL_CAPTURE;
\r
313 case VariantNoCastle:
\r
314 flags &= ~F_ALL_CASTLE_OK;
\r
322 FILE *gameFileFP, *debugFP;
\r
325 [AS] Note: sometimes, the sscanf() function is used to parse the input
\r
326 into a fixed-size buffer. Because of this, we must be prepared to
\r
327 receive strings as long as the size of the input buffer, which is currently
\r
328 set to 4K for Windows and 8K for the rest.
\r
329 So, we must either allocate sufficiently large buffers here, or
\r
330 reduce the size of the input buffer in the input reading part.
\r
333 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
\r
334 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
\r
335 char thinkOutput1[MSG_SIZ*10];
\r
337 ChessProgramState first, second;
\r
339 /* premove variables */
\r
340 int premoveToX = 0;
\r
341 int premoveToY = 0;
\r
342 int premoveFromX = 0;
\r
343 int premoveFromY = 0;
\r
344 int premovePromoChar = 0;
\r
345 int gotPremove = 0;
\r
346 Boolean alarmSounded;
\r
347 /* end premove variables */
\r
349 #define ICS_GENERIC 0
\r
352 #define ICS_CHESSNET 3 /* not really supported */
\r
353 int ics_type = ICS_GENERIC;
\r
354 char *ics_prefix = "$";
\r
356 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
\r
357 int pauseExamForwardMostMove = 0;
\r
358 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
\r
359 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
\r
360 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
\r
361 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
\r
362 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
\r
363 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
\r
364 int whiteFlag = FALSE, blackFlag = FALSE;
\r
365 int userOfferedDraw = FALSE;
\r
366 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
\r
367 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
\r
368 int cmailMoveType[CMAIL_MAX_GAMES];
\r
369 long ics_clock_paused = 0;
\r
370 ProcRef icsPR = NoProc, cmailPR = NoProc;
\r
371 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
\r
372 GameMode gameMode = BeginningOfGame;
\r
373 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
\r
374 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
\r
375 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
\r
376 int hiddenThinkOutputState = 0; /* [AS] */
\r
377 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
\r
378 int adjudicateLossPlies = 6;
\r
379 char white_holding[64], black_holding[64];
\r
380 TimeMark lastNodeCountTime;
\r
381 long lastNodeCount=0;
\r
382 int have_sent_ICS_logon = 0;
\r
383 int movesPerSession;
\r
384 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
\r
385 long timeControl_2; /* [AS] Allow separate time controls */
\r
386 long timeRemaining[2][MAX_MOVES];
\r
388 TimeMark programStartTime;
\r
389 char ics_handle[MSG_SIZ];
\r
390 int have_set_title = 0;
\r
392 /* animateTraining preserves the state of appData.animate
\r
393 * when Training mode is activated. This allows the
\r
394 * response to be animated when appData.animate == TRUE and
\r
395 * appData.animateDragging == TRUE.
\r
397 Boolean animateTraining;
\r
403 Board boards[MAX_MOVES];
\r
404 /* [HGM] Following 7 needed for accurate legality tests: */
\r
405 char epStatus[MAX_MOVES];
\r
406 char castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1
\r
407 char castlingRank[BOARD_SIZE]; // and corresponding ranks
\r
408 char initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE];
\r
409 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
\r
410 int initialRulePlies, FENrulePlies;
\r
413 ChessSquare FIDEArray[2][BOARD_SIZE] = {
\r
414 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
\r
415 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
\r
416 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
\r
417 BlackKing, BlackBishop, BlackKnight, BlackRook }
\r
420 ChessSquare twoKingsArray[2][BOARD_SIZE] = {
\r
421 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
\r
422 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
\r
423 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
\r
424 BlackKing, BlackKing, BlackKnight, BlackRook }
\r
428 ChessSquare KnightmateArray[2][BOARD_SIZE] = {
\r
429 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
\r
430 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
\r
431 { BlackRook, BlackMan, BlackBishop, BlackQueen,
\r
432 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
\r
435 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */
\r
436 { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
\r
437 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
\r
438 { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
\r
439 BlackKing, BlackBishop, BlackKnight, BlackRook }
\r
442 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
\r
443 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
\r
444 WhiteKing, WhiteAlfil, WhiteKnight, WhiteRook },
\r
445 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
\r
446 BlackKing, BlackAlfil, BlackKnight, BlackRook }
\r
450 #if (BOARD_SIZE>=10)
\r
451 ChessSquare ShogiArray[2][BOARD_SIZE] = {
\r
452 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
\r
453 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
\r
454 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
\r
455 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
\r
458 ChessSquare XiangqiArray[2][BOARD_SIZE] = {
\r
459 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
\r
460 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
\r
461 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
\r
462 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
\r
465 ChessSquare CapablancaArray[2][BOARD_SIZE] = {
\r
466 { WhiteRook, WhiteKnight, WhiteCardinal, WhiteBishop, WhiteQueen,
\r
467 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
\r
468 { BlackRook, BlackKnight, BlackCardinal, BlackBishop, BlackQueen,
\r
469 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
\r
473 ChessSquare GothicArray[2][BOARD_SIZE] = {
\r
474 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
\r
475 WhiteKing, WhiteCardinal, WhiteBishop, WhiteKnight, WhiteRook },
\r
476 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
\r
477 BlackKing, BlackCardinal, BlackBishop, BlackKnight, BlackRook }
\r
480 #define GothicArray CapablancaArray
\r
483 #else // !(BOARD_SIZE>=10)
\r
484 #define XiangqiPosition FIDEArray
\r
485 #define CapablancaArray FIDEArray
\r
486 #define GothicArray FIDEArray
\r
487 #endif // !(BOARD_SIZE>=10)
\r
489 #if (BOARD_SIZE>=12)
\r
490 ChessSquare CourierArray[2][BOARD_SIZE] = {
\r
491 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
\r
492 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
\r
493 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
\r
494 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
\r
496 #else // !(BOARD_SIZE>=12)
\r
497 #define CourierArray CapablancaArray
\r
498 #endif // !(BOARD_SIZE>=12)
\r
502 Board initialPosition;
\r
505 /* Convert str to a rating. Checks for special cases of "----",
\r
507 "++++", etc. Also strips ()'s */
\r
509 string_to_rating(str)
\r
512 while(*str && !isdigit(*str)) ++str;
\r
514 return 0; /* One of the special "no rating" cases */
\r
520 ClearProgramStats()
\r
522 /* Init programStats */
\r
523 programStats.movelist[0] = 0;
\r
524 programStats.depth = 0;
\r
525 programStats.nr_moves = 0;
\r
526 programStats.moves_left = 0;
\r
527 programStats.nodes = 0;
\r
528 programStats.time = 100;
\r
529 programStats.score = 0;
\r
530 programStats.got_only_move = 0;
\r
531 programStats.got_fail = 0;
\r
532 programStats.line_is_book = 0;
\r
538 int matched, min, sec;
\r
540 GetTimeMark(&programStartTime);
\r
542 ClearProgramStats();
\r
543 programStats.ok_to_send = 1;
\r
544 programStats.seen_stat = 0;
\r
547 * Initialize game list
\r
549 ListNew(&gameList);
\r
553 * Internet chess server status
\r
555 if (appData.icsActive) {
\r
556 appData.matchMode = FALSE;
\r
557 appData.matchGames = 0;
\r
559 appData.noChessProgram = !appData.zippyPlay;
\r
561 appData.zippyPlay = FALSE;
\r
562 appData.zippyTalk = FALSE;
\r
563 appData.noChessProgram = TRUE;
\r
565 if (*appData.icsHelper != NULLCHAR) {
\r
566 appData.useTelnet = TRUE;
\r
567 appData.telnetProgram = appData.icsHelper;
\r
570 appData.zippyTalk = appData.zippyPlay = FALSE;
\r
573 /* [AS] Initialize pv info list [HGM] and game state */
\r
577 for( i=0; i<MAX_MOVES; i++ ) {
\r
578 pvInfoList[i].depth = -1;
\r
579 epStatus[i]=EP_NONE;
\r
580 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
\r
585 * Parse timeControl resource
\r
587 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
\r
588 appData.movesPerSession)) {
\r
590 sprintf(buf, "bad timeControl option %s", appData.timeControl);
\r
591 DisplayFatalError(buf, 0, 2);
\r
595 * Parse searchTime resource
\r
597 if (*appData.searchTime != NULLCHAR) {
\r
598 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
\r
599 if (matched == 1) {
\r
600 searchTime = min * 60;
\r
601 } else if (matched == 2) {
\r
602 searchTime = min * 60 + sec;
\r
605 sprintf(buf, "bad searchTime option %s", appData.searchTime);
\r
606 DisplayFatalError(buf, 0, 2);
\r
610 /* [AS] Adjudication threshold */
\r
611 adjudicateLossThreshold = appData.adjudicateLossThreshold;
\r
613 first.which = "first";
\r
614 second.which = "second";
\r
615 first.maybeThinking = second.maybeThinking = FALSE;
\r
616 first.pr = second.pr = NoProc;
\r
617 first.isr = second.isr = NULL;
\r
618 first.sendTime = second.sendTime = 2;
\r
619 first.sendDrawOffers = 1;
\r
620 if (appData.firstPlaysBlack) {
\r
621 first.twoMachinesColor = "black\n";
\r
622 second.twoMachinesColor = "white\n";
\r
624 first.twoMachinesColor = "white\n";
\r
625 second.twoMachinesColor = "black\n";
\r
627 first.program = appData.firstChessProgram;
\r
628 second.program = appData.secondChessProgram;
\r
629 first.host = appData.firstHost;
\r
630 second.host = appData.secondHost;
\r
631 first.dir = appData.firstDirectory;
\r
632 second.dir = appData.secondDirectory;
\r
633 first.other = &second;
\r
634 second.other = &first;
\r
635 first.initString = appData.initString;
\r
636 second.initString = appData.secondInitString;
\r
637 first.computerString = appData.firstComputerString;
\r
638 second.computerString = appData.secondComputerString;
\r
639 first.useSigint = second.useSigint = TRUE;
\r
640 first.useSigterm = second.useSigterm = TRUE;
\r
641 first.reuse = appData.reuseFirst;
\r
642 second.reuse = appData.reuseSecond;
\r
643 first.useSetboard = second.useSetboard = FALSE;
\r
644 first.useSAN = second.useSAN = FALSE;
\r
645 first.usePing = second.usePing = FALSE;
\r
646 first.lastPing = second.lastPing = 0;
\r
647 first.lastPong = second.lastPong = 0;
\r
648 first.usePlayother = second.usePlayother = FALSE;
\r
649 first.useColors = second.useColors = TRUE;
\r
650 first.useUsermove = second.useUsermove = FALSE;
\r
651 first.sendICS = second.sendICS = FALSE;
\r
652 first.sendName = second.sendName = appData.icsActive;
\r
653 first.sdKludge = second.sdKludge = FALSE;
\r
654 first.stKludge = second.stKludge = FALSE;
\r
655 TidyProgramName(first.program, first.host, first.tidy);
\r
656 TidyProgramName(second.program, second.host, second.tidy);
\r
657 first.matchWins = second.matchWins = 0;
\r
658 strcpy(first.variants, appData.variant);
\r
659 strcpy(second.variants, appData.variant);
\r
660 first.analysisSupport = second.analysisSupport = 2; /* detect */
\r
661 first.analyzing = second.analyzing = FALSE;
\r
662 first.initDone = second.initDone = FALSE;
\r
664 /* New features added by Tord: */
\r
665 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
\r
666 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
\r
667 /* End of new features added by Tord. */
\r
669 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
\r
670 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
\r
671 first.isUCI = appData.firstIsUCI; /* [AS] */
\r
672 second.isUCI = appData.secondIsUCI; /* [AS] */
\r
673 first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
\r
674 second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
\r
676 if (appData.firstProtocolVersion > PROTOVER ||
\r
677 appData.firstProtocolVersion < 1) {
\r
679 sprintf(buf, "protocol version %d not supported",
\r
680 appData.firstProtocolVersion);
\r
681 DisplayFatalError(buf, 0, 2);
\r
683 first.protocolVersion = appData.firstProtocolVersion;
\r
686 if (appData.secondProtocolVersion > PROTOVER ||
\r
687 appData.secondProtocolVersion < 1) {
\r
689 sprintf(buf, "protocol version %d not supported",
\r
690 appData.secondProtocolVersion);
\r
691 DisplayFatalError(buf, 0, 2);
\r
693 second.protocolVersion = appData.secondProtocolVersion;
\r
696 if (appData.icsActive) {
\r
697 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
\r
698 } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
\r
699 appData.clockMode = FALSE;
\r
700 first.sendTime = second.sendTime = 0;
\r
704 /* Override some settings from environment variables, for backward
\r
705 compatibility. Unfortunately it's not feasible to have the env
\r
706 vars just set defaults, at least in xboard. Ugh.
\r
708 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
\r
713 if (appData.noChessProgram) {
\r
714 programVersion = (char*) malloc(5 + strlen(PRODUCT) + strlen(VERSION)
\r
715 + strlen(PATCHLEVEL));
\r
716 sprintf(programVersion, "%s %s.%s", PRODUCT, VERSION, PATCHLEVEL);
\r
720 while (*q != ' ' && *q != NULLCHAR) q++;
\r
722 while (p > first.program && *(p-1) != '/') p--;
\r
723 programVersion = (char*) malloc(8 + strlen(PRODUCT) + strlen(VERSION)
\r
724 + strlen(PATCHLEVEL) + (q - p));
\r
725 sprintf(programVersion, "%s %s.%s + ", PRODUCT, VERSION, PATCHLEVEL);
\r
726 strncat(programVersion, p, q - p);
\r
729 if (!appData.icsActive) {
\r
731 /* Check for variants that are supported only in ICS mode,
\r
732 or not at all. Some that are accepted here nevertheless
\r
733 have bugs; see comments below.
\r
735 VariantClass variant = StringToVariant(appData.variant);
\r
737 case VariantBughouse: /* need four players and two boards */
\r
738 case VariantKriegspiel: /* need to hide pieces and move details */
\r
739 /* case VariantFischeRandom: (Fabien: moved below) */
\r
740 sprintf(buf, "Variant %s supported only in ICS mode", appData.variant);
\r
741 DisplayFatalError(buf, 0, 2);
\r
744 case VariantUnknown:
\r
745 case VariantLoadable:
\r
755 sprintf(buf, "Unknown variant name %s", appData.variant);
\r
756 DisplayFatalError(buf, 0, 2);
\r
759 case VariantXiangqi: /* [HGM] repetition rules not implemented */
\r
760 case VariantFairy: /* [HGM] TestLegality definitely off! */
\r
761 case VariantGothic: /* [HGM] should work */
\r
762 case VariantCapablanca: /* [HGM] should work */
\r
763 case VariantCourier: /* [HGM] initial forced moves not implemented */
\r
764 case VariantShogi: /* [HGM] drops not tested for legality */
\r
765 case VariantShowgi: /* [HGM] not a valid variant */
\r
766 case VariantKnightmate: /* [HGM] should work */
\r
767 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
\r
768 offboard interposition not understood */
\r
769 case VariantNormal: /* definitely works! */
\r
770 case VariantWildCastle: /* pieces not automatically shuffled */
\r
771 case VariantNoCastle: /* pieces not automatically shuffled */
\r
772 case VariantFischeRandom: /* Fabien: pieces not automatically shuffled */
\r
773 case VariantLosers: /* should work except for win condition,
\r
774 and doesn't know captures are mandatory */
\r
775 case VariantSuicide: /* should work except for win condition,
\r
776 and doesn't know captures are mandatory */
\r
777 case VariantGiveaway: /* should work except for win condition,
\r
778 and doesn't know captures are mandatory */
\r
779 case VariantTwoKings: /* should work */
\r
780 case VariantAtomic: /* should work except for win condition */
\r
781 case Variant3Check: /* should work except for win condition */
\r
782 case VariantShatranj: /* might work if TestLegality is off */
\r
788 int NextIntegerFromString( char ** str, long * value )
\r
793 while( *s == ' ' || *s == '\t' ) {
\r
799 if( *s >= '0' && *s <= '9' ) {
\r
800 while( *s >= '0' && *s <= '9' ) {
\r
801 *value = *value * 10 + (*s - '0');
\r
813 int NextTimeControlFromString( char ** str, long * value )
\r
816 int result = NextIntegerFromString( str, &temp );
\r
818 if( result == 0 ) {
\r
819 *value = temp * 60; /* Minutes */
\r
820 if( **str == ':' ) {
\r
822 result = NextIntegerFromString( str, &temp );
\r
823 *value += temp; /* Seconds */
\r
830 int GetTimeControlForWhite()
\r
832 int result = timeControl;
\r
837 int GetTimeControlForBlack()
\r
839 int result = timeControl;
\r
841 if( timeControl_2 > 0 ) {
\r
842 result = timeControl_2;
\r
849 ParseTimeControl(tc, ti, mps)
\r
855 int matched, min, sec;
\r
857 matched = sscanf(tc, "%d:%d", &min, &sec);
\r
858 if (matched == 1) {
\r
859 timeControl = min * 60 * 1000;
\r
860 } else if (matched == 2) {
\r
861 timeControl = (min * 60 + sec) * 1000;
\r
869 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
\r
874 /* Parse second time control */
\r
877 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
\r
885 timeControl_2 = tc2 * 1000;
\r
895 timeControl = tc1 * 1000;
\r
899 timeIncrement = ti * 1000; /* convert to ms */
\r
900 movesPerSession = 0;
\r
903 movesPerSession = mps;
\r
911 if (appData.debugMode) {
\r
912 fprintf(debugFP, "%s\n", programVersion);
\r
915 if (appData.matchGames > 0) {
\r
916 appData.matchMode = TRUE;
\r
917 } else if (appData.matchMode) {
\r
918 appData.matchGames = 1;
\r
920 Reset(TRUE, FALSE);
\r
921 if (appData.noChessProgram || first.protocolVersion == 1) {
\r
924 /* kludge: allow timeout for initial "feature" commands */
\r
926 DisplayMessage("", "Starting chess program");
\r
927 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
\r
932 InitBackEnd3 P((void))
\r
934 GameMode initialMode;
\r
938 InitChessProgram(&first);
\r
940 if (appData.icsActive) {
\r
943 if (*appData.icsCommPort != NULLCHAR) {
\r
944 sprintf(buf, "Could not open comm port %s",
\r
945 appData.icsCommPort);
\r
947 sprintf(buf, "Could not connect to host %s, port %s",
\r
948 appData.icsHost, appData.icsPort);
\r
950 DisplayFatalError(buf, err, 1);
\r
955 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
\r
957 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
\r
958 } else if (appData.noChessProgram) {
\r
964 if (*appData.cmailGameName != NULLCHAR) {
\r
966 OpenLoopback(&cmailPR);
\r
968 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
\r
972 DisplayMessage("", "");
\r
973 if (StrCaseCmp(appData.initialMode, "") == 0) {
\r
974 initialMode = BeginningOfGame;
\r
975 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
\r
976 initialMode = TwoMachinesPlay;
\r
977 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
\r
978 initialMode = AnalyzeFile;
\r
979 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
\r
980 initialMode = AnalyzeMode;
\r
981 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
\r
982 initialMode = MachinePlaysWhite;
\r
983 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
\r
984 initialMode = MachinePlaysBlack;
\r
985 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
\r
986 initialMode = EditGame;
\r
987 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
\r
988 initialMode = EditPosition;
\r
989 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
\r
990 initialMode = Training;
\r
992 sprintf(buf, "Unknown initialMode %s", appData.initialMode);
\r
993 DisplayFatalError(buf, 0, 2);
\r
997 if (appData.matchMode) {
\r
998 /* Set up machine vs. machine match */
\r
999 if (appData.noChessProgram) {
\r
1000 DisplayFatalError("Can't have a match with no chess programs",
\r
1006 if (*appData.loadGameFile != NULLCHAR) {
\r
1007 if (!LoadGameFromFile(appData.loadGameFile,
\r
1008 appData.loadGameIndex,
\r
1009 appData.loadGameFile, FALSE)) {
\r
1010 DisplayFatalError("Bad game file", 0, 1);
\r
1013 } else if (*appData.loadPositionFile != NULLCHAR) {
\r
1014 if (!LoadPositionFromFile(appData.loadPositionFile,
\r
1015 appData.loadPositionIndex,
\r
1016 appData.loadPositionFile)) {
\r
1017 DisplayFatalError("Bad position file", 0, 1);
\r
1021 TwoMachinesEvent();
\r
1022 } else if (*appData.cmailGameName != NULLCHAR) {
\r
1023 /* Set up cmail mode */
\r
1024 ReloadCmailMsgEvent(TRUE);
\r
1026 /* Set up other modes */
\r
1027 if (initialMode == AnalyzeFile) {
\r
1028 if (*appData.loadGameFile == NULLCHAR) {
\r
1029 DisplayFatalError("AnalyzeFile mode requires a game file", 0, 1);
\r
1033 if (*appData.loadGameFile != NULLCHAR) {
\r
1034 (void) LoadGameFromFile(appData.loadGameFile,
\r
1035 appData.loadGameIndex,
\r
1036 appData.loadGameFile, TRUE);
\r
1037 } else if (*appData.loadPositionFile != NULLCHAR) {
\r
1038 (void) LoadPositionFromFile(appData.loadPositionFile,
\r
1039 appData.loadPositionIndex,
\r
1040 appData.loadPositionFile);
\r
1042 if (initialMode == AnalyzeMode) {
\r
1043 if (appData.noChessProgram) {
\r
1044 DisplayFatalError("Analysis mode requires a chess engine", 0, 2);
\r
1047 if (appData.icsActive) {
\r
1048 DisplayFatalError("Analysis mode does not work with ICS mode",0,2);
\r
1051 AnalyzeModeEvent();
\r
1052 } else if (initialMode == AnalyzeFile) {
\r
1053 ShowThinkingEvent(TRUE);
\r
1054 AnalyzeFileEvent();
\r
1055 AnalysisPeriodicEvent(1);
\r
1056 } else if (initialMode == MachinePlaysWhite) {
\r
1057 if (appData.noChessProgram) {
\r
1058 DisplayFatalError("MachineWhite mode requires a chess engine",
\r
1062 if (appData.icsActive) {
\r
1063 DisplayFatalError("MachineWhite mode does not work with ICS mode",
\r
1067 MachineWhiteEvent();
\r
1068 } else if (initialMode == MachinePlaysBlack) {
\r
1069 if (appData.noChessProgram) {
\r
1070 DisplayFatalError("MachineBlack mode requires a chess engine",
\r
1074 if (appData.icsActive) {
\r
1075 DisplayFatalError("MachineBlack mode does not work with ICS mode",
\r
1079 MachineBlackEvent();
\r
1080 } else if (initialMode == TwoMachinesPlay) {
\r
1081 if (appData.noChessProgram) {
\r
1082 DisplayFatalError("TwoMachines mode requires a chess engine",
\r
1086 if (appData.icsActive) {
\r
1087 DisplayFatalError("TwoMachines mode does not work with ICS mode",
\r
1091 TwoMachinesEvent();
\r
1092 } else if (initialMode == EditGame) {
\r
1094 } else if (initialMode == EditPosition) {
\r
1095 EditPositionEvent();
\r
1096 } else if (initialMode == Training) {
\r
1097 if (*appData.loadGameFile == NULLCHAR) {
\r
1098 DisplayFatalError("Training mode requires a game file", 0, 2);
\r
1107 * Establish will establish a contact to a remote host.port.
\r
1108 * Sets icsPR to a ProcRef for a process (or pseudo-process)
\r
1109 * used to talk to the host.
\r
1110 * Returns 0 if okay, error code if not.
\r
1115 char buf[MSG_SIZ];
\r
1117 if (*appData.icsCommPort != NULLCHAR) {
\r
1118 /* Talk to the host through a serial comm port */
\r
1119 return OpenCommPort(appData.icsCommPort, &icsPR);
\r
1121 } else if (*appData.gateway != NULLCHAR) {
\r
1122 if (*appData.remoteShell == NULLCHAR) {
\r
1123 /* Use the rcmd protocol to run telnet program on a gateway host */
\r
1124 sprintf(buf, "%s %s %s",
\r
1125 appData.telnetProgram, appData.icsHost, appData.icsPort);
\r
1126 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
\r
1129 /* Use the rsh program to run telnet program on a gateway host */
\r
1130 if (*appData.remoteUser == NULLCHAR) {
\r
1131 sprintf(buf, "%s %s %s %s %s", appData.remoteShell,
\r
1132 appData.gateway, appData.telnetProgram,
\r
1133 appData.icsHost, appData.icsPort);
\r
1135 sprintf(buf, "%s %s -l %s %s %s %s",
\r
1136 appData.remoteShell, appData.gateway,
\r
1137 appData.remoteUser, appData.telnetProgram,
\r
1138 appData.icsHost, appData.icsPort);
\r
1140 return StartChildProcess(buf, "", &icsPR);
\r
1143 } else if (appData.useTelnet) {
\r
1144 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
\r
1147 /* TCP socket interface differs somewhat between
\r
1148 Unix and NT; handle details in the front end.
\r
1150 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
\r
1155 show_bytes(fp, buf, count)
\r
1161 if (*buf < 040 || *(unsigned char *) buf > 0177) {
\r
1162 fprintf(fp, "\\%03o", *buf & 0xff);
\r
1171 /* Returns an errno value */
\r
1173 OutputMaybeTelnet(pr, message, count, outError)
\r
1179 char buf[8192], *p, *q, *buflim;
\r
1180 int left, newcount, outcount;
\r
1182 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
\r
1183 *appData.gateway != NULLCHAR) {
\r
1184 if (appData.debugMode) {
\r
1185 fprintf(debugFP, ">ICS: ");
\r
1186 show_bytes(debugFP, message, count);
\r
1187 fprintf(debugFP, "\n");
\r
1189 return OutputToProcess(pr, message, count, outError);
\r
1192 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
\r
1198 if (q >= buflim) {
\r
1199 if (appData.debugMode) {
\r
1200 fprintf(debugFP, ">ICS: ");
\r
1201 show_bytes(debugFP, buf, newcount);
\r
1202 fprintf(debugFP, "\n");
\r
1204 outcount = OutputToProcess(pr, buf, newcount, outError);
\r
1205 if (outcount < newcount) return -1; /* to be sure */
\r
1212 } else if (((unsigned char) *p) == TN_IAC) {
\r
1213 *q++ = (char) TN_IAC;
\r
1220 if (appData.debugMode) {
\r
1221 fprintf(debugFP, ">ICS: ");
\r
1222 show_bytes(debugFP, buf, newcount);
\r
1223 fprintf(debugFP, "\n");
\r
1225 outcount = OutputToProcess(pr, buf, newcount, outError);
\r
1226 if (outcount < newcount) return -1; /* to be sure */
\r
1231 read_from_player(isr, closure, message, count, error)
\r
1232 InputSourceRef isr;
\r
1238 int outError, outCount;
\r
1239 static int gotEof = 0;
\r
1241 /* Pass data read from player on to ICS */
\r
1244 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
\r
1245 if (outCount < count) {
\r
1246 DisplayFatalError("Error writing to ICS", outError, 1);
\r
1248 } else if (count < 0) {
\r
1249 RemoveInputSource(isr);
\r
1250 DisplayFatalError("Error reading from keyboard", error, 1);
\r
1251 } else if (gotEof++ > 0) {
\r
1252 RemoveInputSource(isr);
\r
1253 DisplayFatalError("Got end of file from keyboard", 0, 0);
\r
1261 int count, outCount, outError;
\r
1263 if (icsPR == NULL) return;
\r
1265 count = strlen(s);
\r
1266 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
\r
1267 if (outCount < count) {
\r
1268 DisplayFatalError("Error writing to ICS", outError, 1);
\r
1272 /* This is used for sending logon scripts to the ICS. Sending
\r
1273 without a delay causes problems when using timestamp on ICC
\r
1274 (at least on my machine). */
\r
1276 SendToICSDelayed(s,msdelay)
\r
1280 int count, outCount, outError;
\r
1282 if (icsPR == NULL) return;
\r
1284 count = strlen(s);
\r
1285 if (appData.debugMode) {
\r
1286 fprintf(debugFP, ">ICS: ");
\r
1287 show_bytes(debugFP, s, count);
\r
1288 fprintf(debugFP, "\n");
\r
1290 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
\r
1292 if (outCount < count) {
\r
1293 DisplayFatalError("Error writing to ICS", outError, 1);
\r
1298 /* Remove all highlighting escape sequences in s
\r
1299 Also deletes any suffix starting with '('
\r
1302 StripHighlightAndTitle(s)
\r
1305 static char retbuf[MSG_SIZ];
\r
1308 while (*s != NULLCHAR) {
\r
1309 while (*s == '\033') {
\r
1310 while (*s != NULLCHAR && !isalpha(*s)) s++;
\r
1311 if (*s != NULLCHAR) s++;
\r
1313 while (*s != NULLCHAR && *s != '\033') {
\r
1314 if (*s == '(' || *s == '[') {
\r
1325 /* Remove all highlighting escape sequences in s */
\r
1330 static char retbuf[MSG_SIZ];
\r
1333 while (*s != NULLCHAR) {
\r
1334 while (*s == '\033') {
\r
1335 while (*s != NULLCHAR && !isalpha(*s)) s++;
\r
1336 if (*s != NULLCHAR) s++;
\r
1338 while (*s != NULLCHAR && *s != '\033') {
\r
1346 char *variantNames[] = VARIANT_NAMES;
\r
1351 return variantNames[v];
\r
1355 /* Identify a variant from the strings the chess servers use or the
\r
1356 PGN Variant tag names we use. */
\r
1358 StringToVariant(e)
\r
1363 VariantClass v = VariantNormal;
\r
1364 int i, found = FALSE;
\r
1365 char buf[MSG_SIZ];
\r
1369 /* [HGM] skip over optional board-size prefixes */
\r
1370 if( sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
\r
1371 while( *e++ != '_');
\r
1372 } else if( sscanf(e, "%dx%d_", &i, &i) == 2 ) {
\r
1373 while( *e++ != '_');
\r
1376 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
\r
1377 if (StrCaseStr(e, variantNames[i])) {
\r
1378 v = (VariantClass) i;
\r
1385 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
\r
1386 || StrCaseStr(e, "wild/fr")) {
\r
1387 v = VariantFischeRandom;
\r
1388 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
\r
1389 (i = 1, p = StrCaseStr(e, "w"))) {
\r
1391 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
\r
1392 if (isdigit(*p)) {
\r
1398 case 0: /* FICS only, actually */
\r
1400 /* Castling legal even if K starts on d-file */
\r
1401 v = VariantWildCastle;
\r
1406 /* Castling illegal even if K & R happen to start in
\r
1407 normal positions. */
\r
1408 v = VariantNoCastle;
\r
1421 /* Castling legal iff K & R start in normal positions */
\r
1422 v = VariantNormal;
\r
1427 /* Special wilds for position setup; unclear what to do here */
\r
1428 v = VariantLoadable;
\r
1431 /* Bizarre ICC game */
\r
1432 v = VariantTwoKings;
\r
1435 v = VariantKriegspiel;
\r
1438 v = VariantLosers;
\r
1441 v = VariantFischeRandom;
\r
1444 v = VariantCrazyhouse;
\r
1447 v = VariantBughouse;
\r
1450 v = Variant3Check;
\r
1453 /* Not quite the same as FICS suicide! */
\r
1454 v = VariantGiveaway;
\r
1457 v = VariantAtomic;
\r
1460 v = VariantShatranj;
\r
1463 /* Temporary names for future ICC types. The name *will* change in
\r
1464 the next xboard/WinBoard release after ICC defines it. */
\r
1493 v = VariantXiangqi;
\r
1496 v = VariantCourier;
\r
1499 v = VariantGothic;
\r
1502 v = VariantCapablanca;
\r
1505 v = VariantKnightmate;
\r
1511 v = VariantShowgi;
\r
1515 /* Found "wild" or "w" in the string but no number;
\r
1516 must assume it's normal chess. */
\r
1517 v = VariantNormal;
\r
1520 sprintf(buf, "Unknown wild type %d", wnum);
\r
1521 DisplayError(buf, 0);
\r
1522 v = VariantUnknown;
\r
1527 if (appData.debugMode) {
\r
1528 fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
\r
1529 e, wnum, VariantName(v));
\r
1534 static int leftover_start = 0, leftover_len = 0;
\r
1535 char star_match[STAR_MATCH_N][MSG_SIZ];
\r
1537 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
\r
1538 advance *index beyond it, and set leftover_start to the new value of
\r
1539 *index; else return FALSE. If pattern contains the character '*', it
\r
1540 matches any sequence of characters not containing '\r', '\n', or the
\r
1541 character following the '*' (if any), and the matched sequence(s) are
\r
1542 copied into star_match.
\r
1545 looking_at(buf, index, pattern)
\r
1550 char *bufp = &buf[*index], *patternp = pattern;
\r
1551 int star_count = 0;
\r
1552 char *matchp = star_match[0];
\r
1555 if (*patternp == NULLCHAR) {
\r
1556 *index = leftover_start = bufp - buf;
\r
1557 *matchp = NULLCHAR;
\r
1560 if (*bufp == NULLCHAR) return FALSE;
\r
1561 if (*patternp == '*') {
\r
1562 if (*bufp == *(patternp + 1)) {
\r
1563 *matchp = NULLCHAR;
\r
1564 matchp = star_match[++star_count];
\r
1568 } else if (*bufp == '\n' || *bufp == '\r') {
\r
1570 if (*patternp == NULLCHAR)
\r
1575 *matchp++ = *bufp++;
\r
1579 if (*patternp != *bufp) return FALSE;
\r
1586 SendToPlayer(data, length)
\r
1590 int error, outCount;
\r
1591 outCount = OutputToProcess(NoProc, data, length, &error);
\r
1592 if (outCount < length) {
\r
1593 DisplayFatalError("Error writing to display", error, 1);
\r
1598 PackHolding(packed, holding)
\r
1602 char *p = holding;
\r
1604 int runlength = 0;
\r
1610 switch (runlength) {
\r
1621 sprintf(q, "%d", runlength);
\r
1633 /* Telnet protocol requests from the front end */
\r
1635 TelnetRequest(ddww, option)
\r
1636 unsigned char ddww, option;
\r
1638 unsigned char msg[3];
\r
1639 int outCount, outError;
\r
1641 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
\r
1643 if (appData.debugMode) {
\r
1644 char buf1[8], buf2[8], *ddwwStr, *optionStr;
\r
1660 sprintf(buf1, "%d", ddww);
\r
1665 optionStr = "ECHO";
\r
1669 sprintf(buf2, "%d", option);
\r
1672 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
\r
1677 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
\r
1678 if (outCount < 3) {
\r
1679 DisplayFatalError("Error writing to ICS", outError, 1);
\r
1686 if (!appData.icsActive) return;
\r
1687 TelnetRequest(TN_DO, TN_ECHO);
\r
1693 if (!appData.icsActive) return;
\r
1694 TelnetRequest(TN_DONT, TN_ECHO);
\r
1698 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
\r
1700 /* put the holdings sent to us by the server on the board holdings area */
\r
1701 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
\r
1703 ChessSquare piece;
\r
1705 if(gameInfo.holdingsWidth < 2) return;
\r
1707 if( (int)lowestPiece >= BlackPawn ) {
\r
1708 holdingsColumn = 0;
\r
1710 holdingsStartRow = BOARD_HEIGHT-1;
\r
1713 holdingsColumn = BOARD_WIDTH-1;
\r
1714 countsColumn = BOARD_WIDTH-2;
\r
1715 holdingsStartRow = 0;
\r
1719 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
\r
1720 board[i][holdingsColumn] = EmptySquare;
\r
1721 board[i][countsColumn] = (ChessSquare) 0;
\r
1723 while( (p=*holdings++) != NULLCHAR ) {
\r
1724 piece = CharToPiece( ToUpper(p) );
\r
1725 if(piece == EmptySquare) continue;
\r
1726 /*j = (int) piece - (int) WhitePawn;*/
\r
1727 j = PieceToNumber(piece);
\r
1728 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
\r
1729 if(j < 0) continue; /* should not happen */
\r
1730 piece = (ChessSquare) ( j + (int)lowestPiece );
\r
1731 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
\r
1732 board[holdingsStartRow+j*direction][countsColumn]++;
\r
1737 static int loggedOn = FALSE;
\r
1739 /*-- Game start info cache: --*/
\r
1741 char gs_kind[MSG_SIZ];
\r
1742 static char player1Name[128] = "";
\r
1743 static char player2Name[128] = "";
\r
1744 static int player1Rating = -1;
\r
1745 static int player2Rating = -1;
\r
1746 /*----------------------------*/
\r
1748 ColorClass curColor = ColorNormal;
\r
1751 read_from_ics(isr, closure, data, count, error)
\r
1752 InputSourceRef isr;
\r
1758 #define BUF_SIZE 8192
\r
1759 #define STARTED_NONE 0
\r
1760 #define STARTED_MOVES 1
\r
1761 #define STARTED_BOARD 2
\r
1762 #define STARTED_OBSERVE 3
\r
1763 #define STARTED_HOLDINGS 4
\r
1764 #define STARTED_CHATTER 5
\r
1765 #define STARTED_COMMENT 6
\r
1766 #define STARTED_MOVES_NOHIDE 7
\r
1768 static int started = STARTED_NONE;
\r
1769 static char parse[20000];
\r
1770 static int parse_pos = 0;
\r
1771 static char buf[BUF_SIZE + 1];
\r
1772 static int firstTime = TRUE, intfSet = FALSE;
\r
1773 static ColorClass prevColor = ColorNormal;
\r
1774 static int savingComment = FALSE;
\r
1783 if (appData.debugMode) {
\r
1785 fprintf(debugFP, "<ICS: ");
\r
1786 show_bytes(debugFP, data, count);
\r
1787 fprintf(debugFP, "\n");
\r
1793 /* If last read ended with a partial line that we couldn't parse,
\r
1794 prepend it to the new read and try again. */
\r
1795 if (leftover_len > 0) {
\r
1796 for (i=0; i<leftover_len; i++)
\r
1797 buf[i] = buf[leftover_start + i];
\r
1800 /* Copy in new characters, removing nulls and \r's */
\r
1801 buf_len = leftover_len;
\r
1802 for (i = 0; i < count; i++) {
\r
1803 if (data[i] != NULLCHAR && data[i] != '\r')
\r
1804 buf[buf_len++] = data[i];
\r
1807 buf[buf_len] = NULLCHAR;
\r
1808 next_out = leftover_len;
\r
1809 leftover_start = 0;
\r
1812 while (i < buf_len) {
\r
1813 /* Deal with part of the TELNET option negotiation
\r
1814 protocol. We refuse to do anything beyond the
\r
1815 defaults, except that we allow the WILL ECHO option,
\r
1816 which ICS uses to turn off password echoing when we are
\r
1817 directly connected to it. We reject this option
\r
1818 if localLineEditing mode is on (always on in xboard)
\r
1819 and we are talking to port 23, which might be a real
\r
1820 telnet server that will try to keep WILL ECHO on permanently.
\r
1822 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
\r
1823 static int remoteEchoOption = FALSE; /* telnet ECHO option */
\r
1824 unsigned char option;
\r
1826 switch ((unsigned char) buf[++i]) {
\r
1828 if (appData.debugMode)
\r
1829 fprintf(debugFP, "\n<WILL ");
\r
1830 switch (option = (unsigned char) buf[++i]) {
\r
1832 if (appData.debugMode)
\r
1833 fprintf(debugFP, "ECHO ");
\r
1834 /* Reply only if this is a change, according
\r
1835 to the protocol rules. */
\r
1836 if (remoteEchoOption) break;
\r
1837 if (appData.localLineEditing &&
\r
1838 atoi(appData.icsPort) == TN_PORT) {
\r
1839 TelnetRequest(TN_DONT, TN_ECHO);
\r
1842 TelnetRequest(TN_DO, TN_ECHO);
\r
1843 remoteEchoOption = TRUE;
\r
1847 if (appData.debugMode)
\r
1848 fprintf(debugFP, "%d ", option);
\r
1849 /* Whatever this is, we don't want it. */
\r
1850 TelnetRequest(TN_DONT, option);
\r
1855 if (appData.debugMode)
\r
1856 fprintf(debugFP, "\n<WONT ");
\r
1857 switch (option = (unsigned char) buf[++i]) {
\r
1859 if (appData.debugMode)
\r
1860 fprintf(debugFP, "ECHO ");
\r
1861 /* Reply only if this is a change, according
\r
1862 to the protocol rules. */
\r
1863 if (!remoteEchoOption) break;
\r
1865 TelnetRequest(TN_DONT, TN_ECHO);
\r
1866 remoteEchoOption = FALSE;
\r
1869 if (appData.debugMode)
\r
1870 fprintf(debugFP, "%d ", (unsigned char) option);
\r
1871 /* Whatever this is, it must already be turned
\r
1872 off, because we never agree to turn on
\r
1873 anything non-default, so according to the
\r
1874 protocol rules, we don't reply. */
\r
1879 if (appData.debugMode)
\r
1880 fprintf(debugFP, "\n<DO ");
\r
1881 switch (option = (unsigned char) buf[++i]) {
\r
1883 /* Whatever this is, we refuse to do it. */
\r
1884 if (appData.debugMode)
\r
1885 fprintf(debugFP, "%d ", option);
\r
1886 TelnetRequest(TN_WONT, option);
\r
1891 if (appData.debugMode)
\r
1892 fprintf(debugFP, "\n<DONT ");
\r
1893 switch (option = (unsigned char) buf[++i]) {
\r
1895 if (appData.debugMode)
\r
1896 fprintf(debugFP, "%d ", option);
\r
1897 /* Whatever this is, we are already not doing
\r
1898 it, because we never agree to do anything
\r
1899 non-default, so according to the protocol
\r
1900 rules, we don't reply. */
\r
1905 if (appData.debugMode)
\r
1906 fprintf(debugFP, "\n<IAC ");
\r
1907 /* Doubled IAC; pass it through */
\r
1911 if (appData.debugMode)
\r
1912 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
\r
1913 /* Drop all other telnet commands on the floor */
\r
1916 if (oldi > next_out)
\r
1917 SendToPlayer(&buf[next_out], oldi - next_out);
\r
1918 if (++i > next_out)
\r
1923 /* OK, this at least will *usually* work */
\r
1924 if (!loggedOn && looking_at(buf, &i, "ics%")) {
\r
1928 if (loggedOn && !intfSet) {
\r
1929 if (ics_type == ICS_ICC) {
\r
1931 "/set-quietly interface %s\n/set-quietly style 12\n",
\r
1934 } else if (ics_type == ICS_CHESSNET) {
\r
1935 sprintf(str, "/style 12\n");
\r
1937 strcpy(str, "alias $ @\n$set interface ");
\r
1938 strcat(str, programVersion);
\r
1939 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
\r
1941 strcat(str, "$iset nohighlight 1\n");
\r
1943 strcat(str, "$iset lock 1\n$style 12\n");
\r
1949 if (started == STARTED_COMMENT) {
\r
1950 /* Accumulate characters in comment */
\r
1951 parse[parse_pos++] = buf[i];
\r
1952 if (buf[i] == '\n') {
\r
1953 parse[parse_pos] = NULLCHAR;
\r
1954 AppendComment(forwardMostMove, StripHighlight(parse));
\r
1955 started = STARTED_NONE;
\r
1957 /* Don't match patterns against characters in chatter */
\r
1962 if (started == STARTED_CHATTER) {
\r
1963 if (buf[i] != '\n') {
\r
1964 /* Don't match patterns against characters in chatter */
\r
1968 started = STARTED_NONE;
\r
1971 /* Kludge to deal with rcmd protocol */
\r
1972 if (firstTime && looking_at(buf, &i, "\001*")) {
\r
1973 DisplayFatalError(&buf[1], 0, 1);
\r
1976 firstTime = FALSE;
\r
1979 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
\r
1980 ics_type = ICS_ICC;
\r
1982 if (appData.debugMode)
\r
1983 fprintf(debugFP, "ics_type %d\n", ics_type);
\r
1986 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
\r
1987 ics_type = ICS_FICS;
\r
1989 if (appData.debugMode)
\r
1990 fprintf(debugFP, "ics_type %d\n", ics_type);
\r
1993 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
\r
1994 ics_type = ICS_CHESSNET;
\r
1996 if (appData.debugMode)
\r
1997 fprintf(debugFP, "ics_type %d\n", ics_type);
\r
2002 (looking_at(buf, &i, "\"*\" is *a registered name") ||
\r
2003 looking_at(buf, &i, "Logging you in as \"*\"") ||
\r
2004 looking_at(buf, &i, "will be \"*\""))) {
\r
2005 strcpy(ics_handle, star_match[0]);
\r
2009 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
\r
2010 char buf[MSG_SIZ];
\r
2011 sprintf(buf, "%s@%s", ics_handle, appData.icsHost);
\r
2012 DisplayIcsInteractionTitle(buf);
\r
2013 have_set_title = TRUE;
\r
2016 /* skip finger notes */
\r
2017 if (started == STARTED_NONE &&
\r
2018 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
\r
2019 (buf[i] == '1' && buf[i+1] == '0')) &&
\r
2020 buf[i+2] == ':' && buf[i+3] == ' ') {
\r
2021 started = STARTED_CHATTER;
\r
2026 /* skip formula vars */
\r
2027 if (started == STARTED_NONE &&
\r
2028 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
\r
2029 started = STARTED_CHATTER;
\r
2035 if (appData.zippyTalk || appData.zippyPlay) {
\r
2037 if (ZippyControl(buf, &i) ||
\r
2038 ZippyConverse(buf, &i) ||
\r
2039 (appData.zippyPlay && ZippyMatch(buf, &i))) {
\r
2045 if (/* Don't color "message" or "messages" output */
\r
2046 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
\r
2047 looking_at(buf, &i, "*. * at *:*: ") ||
\r
2048 looking_at(buf, &i, "--* (*:*): ") ||
\r
2049 /* Regular tells and says */
\r
2050 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
\r
2051 looking_at(buf, &i, "* (your partner) tells you: ") ||
\r
2052 looking_at(buf, &i, "* says: ") ||
\r
2053 /* Message notifications (same color as tells) */
\r
2054 looking_at(buf, &i, "* has left a message ") ||
\r
2055 looking_at(buf, &i, "* just sent you a message:\n") ||
\r
2056 /* Whispers and kibitzes */
\r
2057 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
\r
2058 looking_at(buf, &i, "* kibitzes: ") ||
\r
2059 /* Channel tells */
\r
2060 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
\r
2062 if (tkind == 1 && strchr(star_match[0], ':')) {
\r
2063 /* Avoid "tells you:" spoofs in channels */
\r
2066 if (star_match[0][0] == NULLCHAR ||
\r
2067 strchr(star_match[0], ' ') ||
\r
2068 (tkind == 3 && strchr(star_match[1], ' '))) {
\r
2069 /* Reject bogus matches */
\r
2072 if (appData.colorize) {
\r
2073 if (oldi > next_out) {
\r
2074 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2079 Colorize(ColorTell, FALSE);
\r
2080 curColor = ColorTell;
\r
2083 Colorize(ColorKibitz, FALSE);
\r
2084 curColor = ColorKibitz;
\r
2087 p = strrchr(star_match[1], '(');
\r
2089 p = star_match[1];
\r
2093 if (atoi(p) == 1) {
\r
2094 Colorize(ColorChannel1, FALSE);
\r
2095 curColor = ColorChannel1;
\r
2097 Colorize(ColorChannel, FALSE);
\r
2098 curColor = ColorChannel;
\r
2102 curColor = ColorNormal;
\r
2106 if (started == STARTED_NONE && appData.autoComment &&
\r
2107 (gameMode == IcsObserving ||
\r
2108 gameMode == IcsPlayingWhite ||
\r
2109 gameMode == IcsPlayingBlack)) {
\r
2110 parse_pos = i - oldi;
\r
2111 memcpy(parse, &buf[oldi], parse_pos);
\r
2112 parse[parse_pos] = NULLCHAR;
\r
2113 started = STARTED_COMMENT;
\r
2114 savingComment = TRUE;
\r
2116 started = STARTED_CHATTER;
\r
2117 savingComment = FALSE;
\r
2124 if (looking_at(buf, &i, "* s-shouts: ") ||
\r
2125 looking_at(buf, &i, "* c-shouts: ")) {
\r
2126 if (appData.colorize) {
\r
2127 if (oldi > next_out) {
\r
2128 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2131 Colorize(ColorSShout, FALSE);
\r
2132 curColor = ColorSShout;
\r
2135 started = STARTED_CHATTER;
\r
2139 if (looking_at(buf, &i, "--->")) {
\r
2144 if (looking_at(buf, &i, "* shouts: ") ||
\r
2145 looking_at(buf, &i, "--> ")) {
\r
2146 if (appData.colorize) {
\r
2147 if (oldi > next_out) {
\r
2148 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2151 Colorize(ColorShout, FALSE);
\r
2152 curColor = ColorShout;
\r
2155 started = STARTED_CHATTER;
\r
2159 if (looking_at( buf, &i, "Challenge:")) {
\r
2160 if (appData.colorize) {
\r
2161 if (oldi > next_out) {
\r
2162 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2165 Colorize(ColorChallenge, FALSE);
\r
2166 curColor = ColorChallenge;
\r
2172 if (looking_at(buf, &i, "* offers you") ||
\r
2173 looking_at(buf, &i, "* offers to be") ||
\r
2174 looking_at(buf, &i, "* would like to") ||
\r
2175 looking_at(buf, &i, "* requests to") ||
\r
2176 looking_at(buf, &i, "Your opponent offers") ||
\r
2177 looking_at(buf, &i, "Your opponent requests")) {
\r
2179 if (appData.colorize) {
\r
2180 if (oldi > next_out) {
\r
2181 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2184 Colorize(ColorRequest, FALSE);
\r
2185 curColor = ColorRequest;
\r
2190 if (looking_at(buf, &i, "* (*) seeking")) {
\r
2191 if (appData.colorize) {
\r
2192 if (oldi > next_out) {
\r
2193 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2196 Colorize(ColorSeek, FALSE);
\r
2197 curColor = ColorSeek;
\r
2203 if (looking_at(buf, &i, "\\ ")) {
\r
2204 if (prevColor != ColorNormal) {
\r
2205 if (oldi > next_out) {
\r
2206 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2209 Colorize(prevColor, TRUE);
\r
2210 curColor = prevColor;
\r
2212 if (savingComment) {
\r
2213 parse_pos = i - oldi;
\r
2214 memcpy(parse, &buf[oldi], parse_pos);
\r
2215 parse[parse_pos] = NULLCHAR;
\r
2216 started = STARTED_COMMENT;
\r
2218 started = STARTED_CHATTER;
\r
2223 if (looking_at(buf, &i, "Black Strength :") ||
\r
2224 looking_at(buf, &i, "<<< style 10 board >>>") ||
\r
2225 looking_at(buf, &i, "<10>") ||
\r
2226 looking_at(buf, &i, "#@#")) {
\r
2227 /* Wrong board style */
\r
2229 SendToICS(ics_prefix);
\r
2230 SendToICS("set style 12\n");
\r
2231 SendToICS(ics_prefix);
\r
2232 SendToICS("refresh\n");
\r
2236 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
\r
2238 have_sent_ICS_logon = 1;
\r
2242 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
\r
2243 (looking_at(buf, &i, "\n<12> ") ||
\r
2244 looking_at(buf, &i, "<12> "))) {
\r
2246 if (oldi > next_out) {
\r
2247 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2250 started = STARTED_BOARD;
\r
2255 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
\r
2256 looking_at(buf, &i, "<b1> ")) {
\r
2257 if (oldi > next_out) {
\r
2258 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2261 started = STARTED_HOLDINGS;
\r
2266 if (looking_at(buf, &i, "* *vs. * *--- *")) {
\r
2268 /* Header for a move list -- first line */
\r
2270 switch (ics_getting_history) {
\r
2272 switch (gameMode) {
\r
2274 case BeginningOfGame:
\r
2275 /* User typed "moves" or "oldmoves" while we
\r
2276 were idle. Pretend we asked for these
\r
2277 moves and soak them up so user can step
\r
2278 through them and/or save them.
\r
2280 Reset(FALSE, TRUE);
\r
2281 gameMode = IcsObserving;
\r
2284 ics_getting_history = H_GOT_UNREQ_HEADER;
\r
2286 case EditGame: /*?*/
\r
2287 case EditPosition: /*?*/
\r
2288 /* Should above feature work in these modes too? */
\r
2289 /* For now it doesn't */
\r
2290 ics_getting_history = H_GOT_UNWANTED_HEADER;
\r
2293 ics_getting_history = H_GOT_UNWANTED_HEADER;
\r
2298 /* Is this the right one? */
\r
2299 if (gameInfo.white && gameInfo.black &&
\r
2300 strcmp(gameInfo.white, star_match[0]) == 0 &&
\r
2301 strcmp(gameInfo.black, star_match[2]) == 0) {
\r
2303 ics_getting_history = H_GOT_REQ_HEADER;
\r
2306 case H_GOT_REQ_HEADER:
\r
2307 case H_GOT_UNREQ_HEADER:
\r
2308 case H_GOT_UNWANTED_HEADER:
\r
2309 case H_GETTING_MOVES:
\r
2310 /* Should not happen */
\r
2311 DisplayError("Error gathering move list: two headers", 0);
\r
2312 ics_getting_history = H_FALSE;
\r
2316 /* Save player ratings into gameInfo if needed */
\r
2317 if ((ics_getting_history == H_GOT_REQ_HEADER ||
\r
2318 ics_getting_history == H_GOT_UNREQ_HEADER) &&
\r
2319 (gameInfo.whiteRating == -1 ||
\r
2320 gameInfo.blackRating == -1)) {
\r
2322 gameInfo.whiteRating = string_to_rating(star_match[1]);
\r
2323 gameInfo.blackRating = string_to_rating(star_match[3]);
\r
2324 if (appData.debugMode)
\r
2325 fprintf(debugFP, "Ratings from header: W %d, B %d\n",
\r
2326 gameInfo.whiteRating, gameInfo.blackRating);
\r
2331 if (looking_at(buf, &i,
\r
2332 "* * match, initial time: * minute*, increment: * second")) {
\r
2333 /* Header for a move list -- second line */
\r
2334 /* Initial board will follow if this is a wild game */
\r
2336 if (gameInfo.event != NULL) free(gameInfo.event);
\r
2337 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
\r
2338 gameInfo.event = StrSave(str);
\r
2339 gameInfo.variant = StringToVariant(gameInfo.event);
\r
2340 Reset(TRUE,TRUE); /* [HGM] possibly change board or holdings size */
\r
2344 if (looking_at(buf, &i, "Move ")) {
\r
2345 /* Beginning of a move list */
\r
2346 switch (ics_getting_history) {
\r
2348 /* Normally should not happen */
\r
2349 /* Maybe user hit reset while we were parsing */
\r
2352 /* Happens if we are ignoring a move list that is not
\r
2353 * the one we just requested. Common if the user
\r
2354 * tries to observe two games without turning off
\r
2357 case H_GETTING_MOVES:
\r
2358 /* Should not happen */
\r
2359 DisplayError("Error gathering move list: nested", 0);
\r
2360 ics_getting_history = H_FALSE;
\r
2362 case H_GOT_REQ_HEADER:
\r
2363 ics_getting_history = H_GETTING_MOVES;
\r
2364 started = STARTED_MOVES;
\r
2366 if (oldi > next_out) {
\r
2367 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2370 case H_GOT_UNREQ_HEADER:
\r
2371 ics_getting_history = H_GETTING_MOVES;
\r
2372 started = STARTED_MOVES_NOHIDE;
\r
2375 case H_GOT_UNWANTED_HEADER:
\r
2376 ics_getting_history = H_FALSE;
\r
2382 if (looking_at(buf, &i, "% ") ||
\r
2383 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
\r
2384 && looking_at(buf, &i, "}*"))) {
\r
2385 savingComment = FALSE;
\r
2386 switch (started) {
\r
2387 case STARTED_MOVES:
\r
2388 case STARTED_MOVES_NOHIDE:
\r
2389 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
\r
2390 parse[parse_pos + i - oldi] = NULLCHAR;
\r
2391 ParseGameHistory(parse);
\r
2393 if (appData.zippyPlay && first.initDone) {
\r
2394 FeedMovesToProgram(&first, forwardMostMove);
\r
2395 if (gameMode == IcsPlayingWhite) {
\r
2396 if (WhiteOnMove(forwardMostMove)) {
\r
2397 if (first.sendTime) {
\r
2398 if (first.useColors) {
\r
2399 SendToProgram("black\n", &first);
\r
2401 SendTimeRemaining(&first, TRUE);
\r
2403 if (first.useColors) {
\r
2404 SendToProgram("white\ngo\n", &first);
\r
2406 SendToProgram("go\n", &first);
\r
2408 first.maybeThinking = TRUE;
\r
2410 if (first.usePlayother) {
\r
2411 if (first.sendTime) {
\r
2412 SendTimeRemaining(&first, TRUE);
\r
2414 SendToProgram("playother\n", &first);
\r
2415 firstMove = FALSE;
\r
2420 } else if (gameMode == IcsPlayingBlack) {
\r
2421 if (!WhiteOnMove(forwardMostMove)) {
\r
2422 if (first.sendTime) {
\r
2423 if (first.useColors) {
\r
2424 SendToProgram("white\n", &first);
\r
2426 SendTimeRemaining(&first, FALSE);
\r
2428 if (first.useColors) {
\r
2429 SendToProgram("black\ngo\n", &first);
\r
2431 SendToProgram("go\n", &first);
\r
2433 first.maybeThinking = TRUE;
\r
2435 if (first.usePlayother) {
\r
2436 if (first.sendTime) {
\r
2437 SendTimeRemaining(&first, FALSE);
\r
2439 SendToProgram("playother\n", &first);
\r
2440 firstMove = FALSE;
\r
2448 if (gameMode == IcsObserving && ics_gamenum == -1) {
\r
2449 /* Moves came from oldmoves or moves command
\r
2450 while we weren't doing anything else.
\r
2452 currentMove = forwardMostMove;
\r
2453 ClearHighlights();/*!!could figure this out*/
\r
2454 flipView = appData.flipView;
\r
2455 DrawPosition(FALSE, boards[currentMove]);
\r
2456 DisplayBothClocks();
\r
2457 sprintf(str, "%s vs. %s",
\r
2458 gameInfo.white, gameInfo.black);
\r
2459 DisplayTitle(str);
\r
2460 gameMode = IcsIdle;
\r
2462 /* Moves were history of an active game */
\r
2463 if (gameInfo.resultDetails != NULL) {
\r
2464 free(gameInfo.resultDetails);
\r
2465 gameInfo.resultDetails = NULL;
\r
2468 HistorySet(parseList, backwardMostMove,
\r
2469 forwardMostMove, currentMove-1);
\r
2470 DisplayMove(currentMove - 1);
\r
2471 if (started == STARTED_MOVES) next_out = i;
\r
2472 started = STARTED_NONE;
\r
2473 ics_getting_history = H_FALSE;
\r
2476 case STARTED_OBSERVE:
\r
2477 started = STARTED_NONE;
\r
2478 SendToICS(ics_prefix);
\r
2479 SendToICS("refresh\n");
\r
2488 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
\r
2489 started == STARTED_HOLDINGS ||
\r
2490 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
\r
2491 /* Accumulate characters in move list or board */
\r
2492 parse[parse_pos++] = buf[i];
\r
2495 /* Start of game messages. Mostly we detect start of game
\r
2496 when the first board image arrives. On some versions
\r
2497 of the ICS, though, we need to do a "refresh" after starting
\r
2498 to observe in order to get the current board right away. */
\r
2499 if (looking_at(buf, &i, "Adding game * to observation list")) {
\r
2500 started = STARTED_OBSERVE;
\r
2504 /* Handle auto-observe */
\r
2505 if (appData.autoObserve &&
\r
2506 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
\r
2507 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
\r
2509 /* Choose the player that was highlighted, if any. */
\r
2510 if (star_match[0][0] == '\033' ||
\r
2511 star_match[1][0] != '\033') {
\r
2512 player = star_match[0];
\r
2514 player = star_match[2];
\r
2516 sprintf(str, "%sobserve %s\n",
\r
2517 ics_prefix, StripHighlightAndTitle(player));
\r
2520 /* Save ratings from notify string */
\r
2521 strcpy(player1Name, star_match[0]);
\r
2522 player1Rating = string_to_rating(star_match[1]);
\r
2523 strcpy(player2Name, star_match[2]);
\r
2524 player2Rating = string_to_rating(star_match[3]);
\r
2526 if (appData.debugMode)
\r
2528 "Ratings from 'Game notification:' %s %d, %s %d\n",
\r
2529 player1Name, player1Rating,
\r
2530 player2Name, player2Rating);
\r
2535 /* Deal with automatic examine mode after a game,
\r
2536 and with IcsObserving -> IcsExamining transition */
\r
2537 if (looking_at(buf, &i, "Entering examine mode for game *") ||
\r
2538 looking_at(buf, &i, "has made you an examiner of game *")) {
\r
2540 int gamenum = atoi(star_match[0]);
\r
2541 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
\r
2542 gamenum == ics_gamenum) {
\r
2543 /* We were already playing or observing this game;
\r
2544 no need to refetch history */
\r
2545 gameMode = IcsExamining;
\r
2547 pauseExamForwardMostMove = forwardMostMove;
\r
2548 } else if (currentMove < forwardMostMove) {
\r
2549 ForwardInner(forwardMostMove);
\r
2552 /* I don't think this case really can happen */
\r
2553 SendToICS(ics_prefix);
\r
2554 SendToICS("refresh\n");
\r
2559 /* Error messages */
\r
2560 if (ics_user_moved) {
\r
2561 if (looking_at(buf, &i, "Illegal move") ||
\r
2562 looking_at(buf, &i, "Not a legal move") ||
\r
2563 looking_at(buf, &i, "Your king is in check") ||
\r
2564 looking_at(buf, &i, "It isn't your turn") ||
\r
2565 looking_at(buf, &i, "It is not your move")) {
\r
2566 /* Illegal move */
\r
2567 ics_user_moved = 0;
\r
2568 if (forwardMostMove > backwardMostMove) {
\r
2569 currentMove = --forwardMostMove;
\r
2570 DisplayMove(currentMove - 1); /* before DMError */
\r
2571 DisplayMoveError("Illegal move (rejected by ICS)");
\r
2572 DrawPosition(FALSE, boards[currentMove]);
\r
2574 DisplayBothClocks();
\r
2580 if (looking_at(buf, &i, "still have time") ||
\r
2581 looking_at(buf, &i, "not out of time") ||
\r
2582 looking_at(buf, &i, "either player is out of time") ||
\r
2583 looking_at(buf, &i, "has timeseal; checking")) {
\r
2584 /* We must have called his flag a little too soon */
\r
2585 whiteFlag = blackFlag = FALSE;
\r
2589 if (looking_at(buf, &i, "added * seconds to") ||
\r
2590 looking_at(buf, &i, "seconds were added to")) {
\r
2591 /* Update the clocks */
\r
2592 SendToICS(ics_prefix);
\r
2593 SendToICS("refresh\n");
\r
2597 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
\r
2598 ics_clock_paused = TRUE;
\r
2603 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
\r
2604 ics_clock_paused = FALSE;
\r
2609 /* Grab player ratings from the Creating: message.
\r
2610 Note we have to check for the special case when
\r
2611 the ICS inserts things like [white] or [black]. */
\r
2612 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
\r
2613 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
\r
2615 0 player 1 name (not necessarily white)
\r
2617 2 empty, white, or black (IGNORED)
\r
2618 3 player 2 name (not necessarily black)
\r
2621 The names/ratings are sorted out when the game
\r
2622 actually starts (below).
\r
2624 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
\r
2625 player1Rating = string_to_rating(star_match[1]);
\r
2626 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
\r
2627 player2Rating = string_to_rating(star_match[4]);
\r
2629 if (appData.debugMode)
\r
2631 "Ratings from 'Creating:' %s %d, %s %d\n",
\r
2632 player1Name, player1Rating,
\r
2633 player2Name, player2Rating);
\r
2638 /* Improved generic start/end-of-game messages */
\r
2639 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
\r
2640 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
\r
2641 /* If tkind == 0: */
\r
2642 /* star_match[0] is the game number */
\r
2643 /* [1] is the white player's name */
\r
2644 /* [2] is the black player's name */
\r
2645 /* For end-of-game: */
\r
2646 /* [3] is the reason for the game end */
\r
2647 /* [4] is a PGN end game-token, preceded by " " */
\r
2648 /* For start-of-game: */
\r
2649 /* [3] begins with "Creating" or "Continuing" */
\r
2650 /* [4] is " *" or empty (don't care). */
\r
2651 int gamenum = atoi(star_match[0]);
\r
2652 char *whitename, *blackname, *why, *endtoken;
\r
2653 ChessMove endtype = (ChessMove) 0;
\r
2656 whitename = star_match[1];
\r
2657 blackname = star_match[2];
\r
2658 why = star_match[3];
\r
2659 endtoken = star_match[4];
\r
2661 whitename = star_match[1];
\r
2662 blackname = star_match[3];
\r
2663 why = star_match[5];
\r
2664 endtoken = star_match[6];
\r
2667 /* Game start messages */
\r
2668 if (strncmp(why, "Creating ", 9) == 0 ||
\r
2669 strncmp(why, "Continuing ", 11) == 0) {
\r
2670 gs_gamenum = gamenum;
\r
2671 strcpy(gs_kind, strchr(why, ' ') + 1);
\r
2673 if (appData.zippyPlay) {
\r
2674 ZippyGameStart(whitename, blackname);
\r
2680 /* Game end messages */
\r
2681 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
\r
2682 ics_gamenum != gamenum) {
\r
2685 while (endtoken[0] == ' ') endtoken++;
\r
2686 switch (endtoken[0]) {
\r
2689 endtype = GameUnfinished;
\r
2692 endtype = BlackWins;
\r
2695 if (endtoken[1] == '/')
\r
2696 endtype = GameIsDrawn;
\r
2698 endtype = WhiteWins;
\r
2701 GameEnds(endtype, why, GE_ICS);
\r
2703 if (appData.zippyPlay && first.initDone) {
\r
2704 ZippyGameEnd(endtype, why);
\r
2705 if (first.pr == NULL) {
\r
2706 /* Start the next process early so that we'll
\r
2707 be ready for the next challenge */
\r
2708 StartChessProgram(&first);
\r
2710 /* Send "new" early, in case this command takes
\r
2711 a long time to finish, so that we'll be ready
\r
2712 for the next challenge. */
\r
2713 Reset(TRUE, TRUE);
\r
2719 if (looking_at(buf, &i, "Removing game * from observation") ||
\r
2720 looking_at(buf, &i, "no longer observing game *") ||
\r
2721 looking_at(buf, &i, "Game * (*) has no examiners")) {
\r
2722 if (gameMode == IcsObserving &&
\r
2723 atoi(star_match[0]) == ics_gamenum)
\r
2726 gameMode = IcsIdle;
\r
2728 ics_user_moved = FALSE;
\r
2733 if (looking_at(buf, &i, "no longer examining game *")) {
\r
2734 if (gameMode == IcsExamining &&
\r
2735 atoi(star_match[0]) == ics_gamenum)
\r
2737 gameMode = IcsIdle;
\r
2739 ics_user_moved = FALSE;
\r
2744 /* Advance leftover_start past any newlines we find,
\r
2745 so only partial lines can get reparsed */
\r
2746 if (looking_at(buf, &i, "\n")) {
\r
2747 prevColor = curColor;
\r
2748 if (curColor != ColorNormal) {
\r
2749 if (oldi > next_out) {
\r
2750 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2753 Colorize(ColorNormal, FALSE);
\r
2754 curColor = ColorNormal;
\r
2756 if (started == STARTED_BOARD) {
\r
2757 started = STARTED_NONE;
\r
2758 parse[parse_pos] = NULLCHAR;
\r
2759 ParseBoard12(parse);
\r
2760 ics_user_moved = 0;
\r
2762 /* Send premove here */
\r
2763 if (appData.premove) {
\r
2764 char str[MSG_SIZ];
\r
2765 if (currentMove == 0 &&
\r
2766 gameMode == IcsPlayingWhite &&
\r
2767 appData.premoveWhite) {
\r
2768 sprintf(str, "%s%s\n", ics_prefix,
\r
2769 appData.premoveWhiteText);
\r
2770 if (appData.debugMode)
\r
2771 fprintf(debugFP, "Sending premove:\n");
\r
2773 } else if (currentMove == 1 &&
\r
2774 gameMode == IcsPlayingBlack &&
\r
2775 appData.premoveBlack) {
\r
2776 sprintf(str, "%s%s\n", ics_prefix,
\r
2777 appData.premoveBlackText);
\r
2778 if (appData.debugMode)
\r
2779 fprintf(debugFP, "Sending premove:\n");
\r
2781 } else if (gotPremove) {
\r
2783 ClearPremoveHighlights();
\r
2784 if (appData.debugMode)
\r
2785 fprintf(debugFP, "Sending premove:\n");
\r
2786 UserMoveEvent(premoveFromX, premoveFromY,
\r
2787 premoveToX, premoveToY,
\r
2788 premovePromoChar);
\r
2792 /* Usually suppress following prompt */
\r
2793 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
\r
2794 if (looking_at(buf, &i, "*% ")) {
\r
2795 savingComment = FALSE;
\r
2799 } else if (started == STARTED_HOLDINGS) {
\r
2801 char new_piece[MSG_SIZ];
\r
2802 started = STARTED_NONE;
\r
2803 parse[parse_pos] = NULLCHAR;
\r
2804 if (appData.debugMode)
\r
2805 fprintf(debugFP, "Parsing holdings: %s\n", parse);
\r
2806 if (sscanf(parse, " game %d", &gamenum) == 1 &&
\r
2807 gamenum == ics_gamenum) {
\r
2808 if (gameInfo.variant == VariantNormal) {
\r
2809 /* [HGM] We seem to switch variant during a game!
\r
2810 * Presumably no holdings were displayed, so we have
\r
2811 * to move the position two files to the right to
\r
2812 * create room for them!
\r
2815 if(gameInfo.holdingsWidth == 0) /* to be sure */
\r
2816 for(i=0; i<BOARD_HEIGHT; i++)
\r
2817 for(j=BOARD_RGHT-1; j>=0; j--)
\r
2818 boards[currentMove][i][j+2] = boards[currentMove][i][j];
\r
2820 if (appData.debugMode) {
\r
2821 fprintf(debugFP, "Switch board to Crazy\n");
\r
2822 setbuf(debugFP, NULL);
\r
2824 gameInfo.variant = VariantCrazyhouse; /*temp guess*/
\r
2825 gameInfo.boardWidth = 8; /* [HGM] guess board size as well */
\r
2826 gameInfo.boardHeight = 8;
\r
2827 gameInfo.holdingsSize = 5;
\r
2828 gameInfo.holdingsWidth = 2;
\r
2829 InitDrawingSizes(-2, 0);
\r
2830 /* Get a move list just to see the header, which
\r
2831 will tell us whether this is really bug or zh */
\r
2832 if (ics_getting_history == H_FALSE) {
\r
2833 ics_getting_history = H_REQUESTED;
\r
2834 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
\r
2838 new_piece[0] = NULLCHAR;
\r
2839 sscanf(parse, "game %d white [%s black [%s <- %s",
\r
2840 &gamenum, white_holding, black_holding,
\r
2842 white_holding[strlen(white_holding)-1] = NULLCHAR;
\r
2843 black_holding[strlen(black_holding)-1] = NULLCHAR;
\r
2844 /* [HGM] copy holdings to board holdings area */
\r
2845 CopyHoldings(boards[currentMove], white_holding, WhitePawn);
\r
2846 CopyHoldings(boards[currentMove], black_holding, BlackPawn);
\r
2848 if (appData.zippyPlay && first.initDone) {
\r
2849 ZippyHoldings(white_holding, black_holding,
\r
2853 if (tinyLayout || smallLayout) {
\r
2854 char wh[16], bh[16];
\r
2855 PackHolding(wh, white_holding);
\r
2856 PackHolding(bh, black_holding);
\r
2857 sprintf(str, "[%s-%s] %s-%s", wh, bh,
\r
2858 gameInfo.white, gameInfo.black);
\r
2860 sprintf(str, "%s [%s] vs. %s [%s]",
\r
2861 gameInfo.white, white_holding,
\r
2862 gameInfo.black, black_holding);
\r
2865 DrawPosition(FALSE, NULL);
\r
2866 DisplayTitle(str);
\r
2868 /* Suppress following prompt */
\r
2869 if (looking_at(buf, &i, "*% ")) {
\r
2870 savingComment = FALSE;
\r
2877 i++; /* skip unparsed character and loop back */
\r
2880 if (started != STARTED_MOVES && started != STARTED_BOARD &&
\r
2881 started != STARTED_HOLDINGS && i > next_out) {
\r
2882 SendToPlayer(&buf[next_out], i - next_out);
\r
2886 leftover_len = buf_len - leftover_start;
\r
2887 /* if buffer ends with something we couldn't parse,
\r
2888 reparse it after appending the next read */
\r
2890 } else if (count == 0) {
\r
2891 RemoveInputSource(isr);
\r
2892 DisplayFatalError("Connection closed by ICS", 0, 0);
\r
2894 DisplayFatalError("Error reading from ICS", error, 1);
\r
2899 /* Board style 12 looks like this:
\r
2901 <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
2903 * The "<12> " is stripped before it gets to this routine. The two
\r
2904 * trailing 0's (flip state and clock ticking) are later addition, and
\r
2905 * some chess servers may not have them, or may have only the first.
\r
2906 * Additional trailing fields may be added in the future.
\r
2909 #define PATTERN "%72c%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
\r
2911 #define RELATION_OBSERVING_PLAYED 0
\r
2912 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
\r
2913 #define RELATION_PLAYING_MYMOVE 1
\r
2914 #define RELATION_PLAYING_NOTMYMOVE -1
\r
2915 #define RELATION_EXAMINING 2
\r
2916 #define RELATION_ISOLATED_BOARD -3
\r
2917 #define RELATION_STARTING_POSITION -4 /* FICS only */
\r
2920 ParseBoard12(string)
\r
2923 GameMode newGameMode;
\r
2924 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
\r
2925 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
\r
2926 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
\r
2927 char to_play, board_chars[72];
\r
2928 char move_str[500], str[500], elapsed_time[500];
\r
2929 char black[32], white[32];
\r
2931 int prevMove = currentMove;
\r
2933 ChessMove moveType;
\r
2934 int fromX, fromY, toX, toY;
\r
2937 fromX = fromY = toX = toY = -1;
\r
2941 if (appData.debugMode)
\r
2942 fprintf(debugFP, "Parsing board: %s\n", string);
\r
2944 move_str[0] = NULLCHAR;
\r
2945 elapsed_time[0] = NULLCHAR;
\r
2946 n = sscanf(string, PATTERN, board_chars, &to_play, &double_push,
\r
2947 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
\r
2948 &gamenum, white, black, &relation, &basetime, &increment,
\r
2949 &white_stren, &black_stren, &white_time, &black_time,
\r
2950 &moveNum, str, elapsed_time, move_str, &ics_flip,
\r
2954 sprintf(str, "Failed to parse board string:\n\"%s\"", string);
\r
2955 DisplayError(str, 0);
\r
2959 /* Convert the move number to internal form */
\r
2960 moveNum = (moveNum - 1) * 2;
\r
2961 if (to_play == 'B') moveNum++;
\r
2962 if (moveNum >= MAX_MOVES) {
\r
2963 DisplayFatalError("Game too long; increase MAX_MOVES and recompile",
\r
2968 switch (relation) {
\r
2969 case RELATION_OBSERVING_PLAYED:
\r
2970 case RELATION_OBSERVING_STATIC:
\r
2971 if (gamenum == -1) {
\r
2972 /* Old ICC buglet */
\r
2973 relation = RELATION_OBSERVING_STATIC;
\r
2975 newGameMode = IcsObserving;
\r
2977 case RELATION_PLAYING_MYMOVE:
\r
2978 case RELATION_PLAYING_NOTMYMOVE:
\r
2980 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
\r
2981 IcsPlayingWhite : IcsPlayingBlack;
\r
2983 case RELATION_EXAMINING:
\r
2984 newGameMode = IcsExamining;
\r
2986 case RELATION_ISOLATED_BOARD:
\r
2988 /* Just display this board. If user was doing something else,
\r
2989 we will forget about it until the next board comes. */
\r
2990 newGameMode = IcsIdle;
\r
2992 case RELATION_STARTING_POSITION:
\r
2993 newGameMode = gameMode;
\r
2997 /* Modify behavior for initial board display on move listing
\r
3000 switch (ics_getting_history) {
\r
3004 case H_GOT_REQ_HEADER:
\r
3005 case H_GOT_UNREQ_HEADER:
\r
3006 /* This is the initial position of the current game */
\r
3007 gamenum = ics_gamenum;
\r
3008 moveNum = 0; /* old ICS bug workaround */
\r
3009 if (to_play == 'B') {
\r
3010 startedFromSetupPosition = TRUE;
\r
3011 blackPlaysFirst = TRUE;
\r
3013 if (forwardMostMove == 0) forwardMostMove = 1;
\r
3014 if (backwardMostMove == 0) backwardMostMove = 1;
\r
3015 if (currentMove == 0) currentMove = 1;
\r
3017 newGameMode = gameMode;
\r
3018 relation = RELATION_STARTING_POSITION; /* ICC needs this */
\r
3020 case H_GOT_UNWANTED_HEADER:
\r
3021 /* This is an initial board that we don't want */
\r
3023 case H_GETTING_MOVES:
\r
3024 /* Should not happen */
\r
3025 DisplayError("Error gathering move list: extra board", 0);
\r
3026 ics_getting_history = H_FALSE;
\r
3030 /* Take action if this is the first board of a new game, or of a
\r
3031 different game than is currently being displayed. */
\r
3032 if (gamenum != ics_gamenum || newGameMode != gameMode ||
\r
3033 relation == RELATION_ISOLATED_BOARD) {
\r
3035 /* Forget the old game and get the history (if any) of the new one */
\r
3036 if (gameMode != BeginningOfGame) {
\r
3037 Reset(FALSE, TRUE);
\r
3040 if (appData.autoRaiseBoard) BoardToTop();
\r
3042 if (gamenum == -1) {
\r
3043 newGameMode = IcsIdle;
\r
3044 } else if (moveNum > 0 && newGameMode != IcsIdle &&
\r
3045 appData.getMoveList) {
\r
3046 /* Need to get game history */
\r
3047 ics_getting_history = H_REQUESTED;
\r
3048 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
\r
3052 /* Initially flip the board to have black on the bottom if playing
\r
3053 black or if the ICS flip flag is set, but let the user change
\r
3054 it with the Flip View button. */
\r
3055 flipView = appData.autoFlipView ?
\r
3056 (newGameMode == IcsPlayingBlack) || ics_flip :
\r
3059 /* Done with values from previous mode; copy in new ones */
\r
3060 gameMode = newGameMode;
\r
3062 ics_gamenum = gamenum;
\r
3063 if (gamenum == gs_gamenum) {
\r
3064 int klen = strlen(gs_kind);
\r
3065 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
\r
3066 sprintf(str, "ICS %s", gs_kind);
\r
3067 gameInfo.event = StrSave(str);
\r
3069 gameInfo.event = StrSave("ICS game");
\r
3071 gameInfo.site = StrSave(appData.icsHost);
\r
3072 gameInfo.date = PGNDate();
\r
3073 gameInfo.round = StrSave("-");
\r
3074 gameInfo.white = StrSave(white);
\r
3075 gameInfo.black = StrSave(black);
\r
3076 timeControl = basetime * 60 * 1000;
\r
3077 timeControl_2 = 0;
\r
3078 timeIncrement = increment * 1000;
\r
3079 movesPerSession = 0;
\r
3080 gameInfo.timeControl = TimeControlTagValue();
\r
3081 gameInfo.variant = StringToVariant(gameInfo.event);
\r
3082 if (appData.debugMode) {
\r
3083 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
\r
3084 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
\r
3085 setbuf(debugFP, NULL);
\r
3088 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
\r
3089 gameInfo.boardWidth = gameInfo.boardHeight = 8;
\r
3090 switch(gameInfo.variant) {
\r
3091 case VariantShogi:
\r
3092 case VariantShowgi:
\r
3093 gameInfo.boardWidth = 9; gameInfo.boardHeight = 9;
\r
3094 gameInfo.holdingsSize = 7;
\r
3095 case VariantBughouse:
\r
3096 case VariantCrazyhouse:
\r
3097 gameInfo.holdingsWidth = 2; break;
\r
3099 gameInfo.holdingsWidth = gameInfo.holdingsSize = 0;
\r
3101 InitDrawingSizes(-2, 0);
\r
3102 gameInfo.outOfBook = NULL;
\r
3104 /* Do we have the ratings? */
\r
3105 if (strcmp(player1Name, white) == 0 &&
\r
3106 strcmp(player2Name, black) == 0) {
\r
3107 if (appData.debugMode)
\r
3108 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
\r
3109 player1Rating, player2Rating);
\r
3110 gameInfo.whiteRating = player1Rating;
\r
3111 gameInfo.blackRating = player2Rating;
\r
3112 } else if (strcmp(player2Name, white) == 0 &&
\r
3113 strcmp(player1Name, black) == 0) {
\r
3114 if (appData.debugMode)
\r
3115 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
\r
3116 player2Rating, player1Rating);
\r
3117 gameInfo.whiteRating = player2Rating;
\r
3118 gameInfo.blackRating = player1Rating;
\r
3120 player1Name[0] = player2Name[0] = NULLCHAR;
\r
3122 /* Silence shouts if requested */
\r
3123 if (appData.quietPlay &&
\r
3124 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
\r
3125 SendToICS(ics_prefix);
\r
3126 SendToICS("set shout 0\n");
\r
3130 /* Deal with midgame name changes */
\r
3132 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
\r
3133 if (gameInfo.white) free(gameInfo.white);
\r
3134 gameInfo.white = StrSave(white);
\r
3136 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
\r
3137 if (gameInfo.black) free(gameInfo.black);
\r
3138 gameInfo.black = StrSave(black);
\r
3142 /* Throw away game result if anything actually changes in examine mode */
\r
3143 if (gameMode == IcsExamining && !newGame) {
\r
3144 gameInfo.result = GameUnfinished;
\r
3145 if (gameInfo.resultDetails != NULL) {
\r
3146 free(gameInfo.resultDetails);
\r
3147 gameInfo.resultDetails = NULL;
\r
3151 /* In pausing && IcsExamining mode, we ignore boards coming
\r
3152 in if they are in a different variation than we are. */
\r
3153 if (pauseExamInvalid) return;
\r
3154 if (pausing && gameMode == IcsExamining) {
\r
3155 if (moveNum <= pauseExamForwardMostMove) {
\r
3156 pauseExamInvalid = TRUE;
\r
3157 forwardMostMove = pauseExamForwardMostMove;
\r
3162 /* Parse the board */
\r
3163 for (k = 0; k < 8; k++) {
\r
3164 for (j = 0; j < 8; j++)
\r
3165 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(7-k)*9 + j]);
\r
3166 if(gameInfo.holdingsWidth > 1) {
\r
3167 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
\r
3168 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
\r
3171 CopyBoard(boards[moveNum], board);
\r
3172 if (moveNum == 0) {
\r
3173 startedFromSetupPosition =
\r
3174 !CompareBoards(board, initialPosition);
\r
3177 if (ics_getting_history == H_GOT_REQ_HEADER ||
\r
3178 ics_getting_history == H_GOT_UNREQ_HEADER) {
\r
3179 /* This was an initial position from a move list, not
\r
3180 the current position */
\r
3184 /* Update currentMove and known move number limits */
\r
3185 newMove = newGame || moveNum > forwardMostMove;
\r
3187 forwardMostMove = backwardMostMove = currentMove = moveNum;
\r
3188 if (gameMode == IcsExamining && moveNum == 0) {
\r
3189 /* Workaround for ICS limitation: we are not told the wild
\r
3190 type when starting to examine a game. But if we ask for
\r
3191 the move list, the move list header will tell us */
\r
3192 ics_getting_history = H_REQUESTED;
\r
3193 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
\r
3196 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
\r
3197 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
\r
3198 forwardMostMove = moveNum;
\r
3199 if (!pausing || currentMove > forwardMostMove)
\r
3200 currentMove = forwardMostMove;
\r
3202 /* New part of history that is not contiguous with old part */
\r
3203 if (pausing && gameMode == IcsExamining) {
\r
3204 pauseExamInvalid = TRUE;
\r
3205 forwardMostMove = pauseExamForwardMostMove;
\r
3208 forwardMostMove = backwardMostMove = currentMove = moveNum;
\r
3209 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
\r
3210 ics_getting_history = H_REQUESTED;
\r
3211 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
\r
3216 /* Update the clocks */
\r
3217 if (strchr(elapsed_time, '.')) {
\r
3218 /* Time is in ms */
\r
3219 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
\r
3220 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
\r
3222 /* Time is in seconds */
\r
3223 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
\r
3224 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
\r
3229 if (appData.zippyPlay && newGame &&
\r
3230 gameMode != IcsObserving && gameMode != IcsIdle &&
\r
3231 gameMode != IcsExamining)
\r
3232 ZippyFirstBoard(moveNum, basetime, increment);
\r
3235 /* Put the move on the move list, first converting
\r
3236 to canonical algebraic form. */
\r
3237 if (moveNum > 0) {
\r
3238 if (appData.debugMode) {
\r
3239 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
\r
3240 fprintf(debugFP, "board = %d-d x%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
\r
3241 setbuf(debugFP, NULL);
\r
3243 if (moveNum <= backwardMostMove) {
\r
3244 /* We don't know what the board looked like before
\r
3245 this move. Punt. */
\r
3246 strcpy(parseList[moveNum - 1], move_str);
\r
3247 strcat(parseList[moveNum - 1], " ");
\r
3248 strcat(parseList[moveNum - 1], elapsed_time);
\r
3249 moveList[moveNum - 1][0] = NULLCHAR;
\r
3250 } else if (ParseOneMove(move_str, moveNum - 1, &moveType,
\r
3251 &fromX, &fromY, &toX, &toY, &promoChar)) {
\r
3252 (void) CoordsToAlgebraic(boards[moveNum - 1],
\r
3253 PosFlags(moveNum - 1), EP_UNKNOWN,
\r
3254 fromY, fromX, toY, toX, promoChar,
\r
3255 parseList[moveNum-1]);
\r
3256 switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
\r
3257 castlingRights[moveNum]) ) {
\r
3259 case MT_STALEMATE:
\r
3263 if(gameInfo.variant != VariantShogi)
\r
3264 strcat(parseList[moveNum - 1], "+");
\r
3266 case MT_CHECKMATE:
\r
3267 strcat(parseList[moveNum - 1], "#");
\r
3270 strcat(parseList[moveNum - 1], " ");
\r
3271 strcat(parseList[moveNum - 1], elapsed_time);
\r
3272 /* currentMoveString is set as a side-effect of ParseOneMove */
\r
3273 strcpy(moveList[moveNum - 1], currentMoveString);
\r
3274 strcat(moveList[moveNum - 1], "\n");
\r
3275 } else if (strcmp(move_str, "none") == 0) {
\r
3276 /* Again, we don't know what the board looked like;
\r
3277 this is really the start of the game. */
\r
3278 parseList[moveNum - 1][0] = NULLCHAR;
\r
3279 moveList[moveNum - 1][0] = NULLCHAR;
\r
3280 backwardMostMove = moveNum;
\r
3281 startedFromSetupPosition = TRUE;
\r
3282 fromX = fromY = toX = toY = -1;
\r
3284 /* Move from ICS was illegal!? Punt. */
\r
3286 if (appData.testLegality && appData.debugMode) {
\r
3287 sprintf(str, "Illegal move \"%s\" from ICS", move_str);
\r
3288 DisplayError(str, 0);
\r
3291 strcpy(parseList[moveNum - 1], move_str);
\r
3292 strcat(parseList[moveNum - 1], " ");
\r
3293 strcat(parseList[moveNum - 1], elapsed_time);
\r
3294 moveList[moveNum - 1][0] = NULLCHAR;
\r
3295 fromX = fromY = toX = toY = -1;
\r
3297 if (appData.debugMode) {
\r
3298 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
\r
3299 setbuf(debugFP, NULL);
\r
3303 /* Send move to chess program (BEFORE animating it). */
\r
3304 if (appData.zippyPlay && !newGame && newMove &&
\r
3305 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
\r
3307 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
\r
3308 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
\r
3309 if (moveList[moveNum - 1][0] == NULLCHAR) {
\r
3310 sprintf(str, "Couldn't parse move \"%s\" from ICS",
\r
3312 DisplayError(str, 0);
\r
3314 if (first.sendTime) {
\r
3315 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
\r
3317 SendMoveToProgram(moveNum - 1, &first);
\r
3319 firstMove = FALSE;
\r
3320 if (first.useColors) {
\r
3321 SendToProgram(gameMode == IcsPlayingWhite ?
\r
3323 "black\ngo\n", &first);
\r
3325 SendToProgram("go\n", &first);
\r
3327 first.maybeThinking = TRUE;
\r
3330 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
\r
3331 if (moveList[moveNum - 1][0] == NULLCHAR) {
\r
3332 sprintf(str, "Couldn't parse move \"%s\" from ICS", move_str);
\r
3333 DisplayError(str, 0);
\r
3335 SendMoveToProgram(moveNum - 1, &first);
\r
3342 if (moveNum > 0 && !gotPremove) {
\r
3343 /* If move comes from a remote source, animate it. If it
\r
3344 isn't remote, it will have already been animated. */
\r
3345 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
\r
3346 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
\r
3348 if (!pausing && appData.highlightLastMove) {
\r
3349 SetHighlights(fromX, fromY, toX, toY);
\r
3353 /* Start the clocks */
\r
3354 whiteFlag = blackFlag = FALSE;
\r
3355 appData.clockMode = !(basetime == 0 && increment == 0);
\r
3356 if (ticking == 0) {
\r
3357 ics_clock_paused = TRUE;
\r
3359 } else if (ticking == 1) {
\r
3360 ics_clock_paused = FALSE;
\r
3362 if (gameMode == IcsIdle ||
\r
3363 relation == RELATION_OBSERVING_STATIC ||
\r
3364 relation == RELATION_EXAMINING ||
\r
3366 DisplayBothClocks();
\r
3370 /* Display opponents and material strengths */
\r
3371 if (gameInfo.variant != VariantBughouse &&
\r
3372 gameInfo.variant != VariantCrazyhouse) {
\r
3373 if (tinyLayout || smallLayout) {
\r
3374 sprintf(str, "%s(%d) %s(%d) {%d %d}",
\r
3375 gameInfo.white, white_stren, gameInfo.black, black_stren,
\r
3376 basetime, increment);
\r
3378 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
\r
3379 gameInfo.white, white_stren, gameInfo.black, black_stren,
\r
3380 basetime, increment);
\r
3382 DisplayTitle(str);
\r
3386 /* Display the board */
\r
3389 if (appData.premove)
\r
3390 if (!gotPremove ||
\r
3391 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
\r
3392 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
\r
3393 ClearPremoveHighlights();
\r
3395 DrawPosition(FALSE, boards[currentMove]);
\r
3396 DisplayMove(moveNum - 1);
\r
3397 if (appData.ringBellAfterMoves && !ics_user_moved)
\r
3401 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
3405 GetMoveListEvent()
\r
3407 char buf[MSG_SIZ];
\r
3408 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
\r
3409 ics_getting_history = H_REQUESTED;
\r
3410 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
\r
3416 AnalysisPeriodicEvent(force)
\r
3419 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
\r
3420 && !force) || !appData.periodicUpdates)
\r
3423 /* Send . command to Crafty to collect stats */
\r
3424 SendToProgram(".\n", &first);
\r
3426 /* Don't send another until we get a response (this makes
\r
3427 us stop sending to old Crafty's which don't understand
\r
3428 the "." command (sending illegal cmds resets node count & time,
\r
3429 which looks bad)) */
\r
3430 programStats.ok_to_send = 0;
\r
3434 SendMoveToProgram(moveNum, cps)
\r
3436 ChessProgramState *cps;
\r
3438 char buf[MSG_SIZ];
\r
3439 if (cps->useUsermove) {
\r
3440 SendToProgram("usermove ", cps);
\r
3442 if (cps->useSAN) {
\r
3444 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
\r
3445 int len = space - parseList[moveNum];
\r
3446 memcpy(buf, parseList[moveNum], len);
\r
3447 buf[len++] = '\n';
\r
3448 buf[len] = NULLCHAR;
\r
3450 sprintf(buf, "%s\n", parseList[moveNum]);
\r
3452 /* [HGM] decrement all digits to code ranks starting from 0 */
\r
3453 if(BOARD_HEIGHT>9) {
\r
3455 while(*p) { if(*p < 'A') (*p)--; p++; }
\r
3457 SendToProgram(buf, cps);
\r
3459 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
\r
3460 * the engine. It would be nice to have a better way to identify castle
\r
3462 if(gameInfo.variant == VariantFischeRandom && cps->useOOCastle) {
\r
3463 int fromX = moveList[moveNum][0] - AAA;
\r
3464 int fromY = moveList[moveNum][1] - ONE;
\r
3465 int toX = moveList[moveNum][2] - AAA;
\r
3466 int toY = moveList[moveNum][3] - ONE;
\r
3467 if((boards[currentMove][fromY][fromX] == WhiteKing
\r
3468 && boards[currentMove][toY][toX] == WhiteRook)
\r
3469 || (boards[currentMove][fromY][fromX] == BlackKing
\r
3470 && boards[currentMove][toY][toX] == BlackRook)) {
\r
3471 if(toX > fromX) SendToProgram("O-O\n", cps);
\r
3472 else SendToProgram("O-O-O\n", cps);
\r
3474 else SendToProgram(moveList[moveNum], cps);
\r
3476 else SendToProgram(moveList[moveNum], cps);
\r
3477 /* End of additions by Tord */
\r
3482 SendMoveToICS(moveType, fromX, fromY, toX, toY)
\r
3483 ChessMove moveType;
\r
3484 int fromX, fromY, toX, toY;
\r
3486 char user_move[MSG_SIZ];
\r
3488 switch (moveType) {
\r
3490 sprintf(user_move, "say Internal error; bad moveType %d (%d,%d-%d,%d)",
\r
3491 (int)moveType, fromX, fromY, toX, toY);
\r
3492 DisplayError(user_move + strlen("say "), 0);
\r
3494 case WhiteKingSideCastle:
\r
3495 case BlackKingSideCastle:
\r
3496 case WhiteQueenSideCastleWild:
\r
3497 case BlackQueenSideCastleWild:
\r
3499 case WhiteHSideCastleFR:
\r
3500 case BlackHSideCastleFR:
\r
3502 sprintf(user_move, "o-o\n");
\r