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_", &i, &i) == 2 ||
\r
1371 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
\r
1372 while( *e++ != '_');
\r
1375 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
\r
1376 if (StrCaseStr(e, variantNames[i])) {
\r
1377 v = (VariantClass) i;
\r
1384 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
\r
1385 || StrCaseStr(e, "wild/fr")) {
\r
1386 v = VariantFischeRandom;
\r
1387 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
\r
1388 (i = 1, p = StrCaseStr(e, "w"))) {
\r
1390 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
\r
1391 if (isdigit(*p)) {
\r
1397 case 0: /* FICS only, actually */
\r
1399 /* Castling legal even if K starts on d-file */
\r
1400 v = VariantWildCastle;
\r
1405 /* Castling illegal even if K & R happen to start in
\r
1406 normal positions. */
\r
1407 v = VariantNoCastle;
\r
1420 /* Castling legal iff K & R start in normal positions */
\r
1421 v = VariantNormal;
\r
1426 /* Special wilds for position setup; unclear what to do here */
\r
1427 v = VariantLoadable;
\r
1430 /* Bizarre ICC game */
\r
1431 v = VariantTwoKings;
\r
1434 v = VariantKriegspiel;
\r
1437 v = VariantLosers;
\r
1440 v = VariantFischeRandom;
\r
1443 v = VariantCrazyhouse;
\r
1446 v = VariantBughouse;
\r
1449 v = Variant3Check;
\r
1452 /* Not quite the same as FICS suicide! */
\r
1453 v = VariantGiveaway;
\r
1456 v = VariantAtomic;
\r
1459 v = VariantShatranj;
\r
1462 /* Temporary names for future ICC types. The name *will* change in
\r
1463 the next xboard/WinBoard release after ICC defines it. */
\r
1492 v = VariantXiangqi;
\r
1495 v = VariantCourier;
\r
1498 v = VariantGothic;
\r
1501 v = VariantCapablanca;
\r
1504 v = VariantKnightmate;
\r
1510 v = VariantShowgi;
\r
1514 /* Found "wild" or "w" in the string but no number;
\r
1515 must assume it's normal chess. */
\r
1516 v = VariantNormal;
\r
1519 sprintf(buf, "Unknown wild type %d", wnum);
\r
1520 DisplayError(buf, 0);
\r
1521 v = VariantUnknown;
\r
1526 if (appData.debugMode) {
\r
1527 fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
\r
1528 e, wnum, VariantName(v));
\r
1533 static int leftover_start = 0, leftover_len = 0;
\r
1534 char star_match[STAR_MATCH_N][MSG_SIZ];
\r
1536 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
\r
1537 advance *index beyond it, and set leftover_start to the new value of
\r
1538 *index; else return FALSE. If pattern contains the character '*', it
\r
1539 matches any sequence of characters not containing '\r', '\n', or the
\r
1540 character following the '*' (if any), and the matched sequence(s) are
\r
1541 copied into star_match.
\r
1544 looking_at(buf, index, pattern)
\r
1549 char *bufp = &buf[*index], *patternp = pattern;
\r
1550 int star_count = 0;
\r
1551 char *matchp = star_match[0];
\r
1554 if (*patternp == NULLCHAR) {
\r
1555 *index = leftover_start = bufp - buf;
\r
1556 *matchp = NULLCHAR;
\r
1559 if (*bufp == NULLCHAR) return FALSE;
\r
1560 if (*patternp == '*') {
\r
1561 if (*bufp == *(patternp + 1)) {
\r
1562 *matchp = NULLCHAR;
\r
1563 matchp = star_match[++star_count];
\r
1567 } else if (*bufp == '\n' || *bufp == '\r') {
\r
1569 if (*patternp == NULLCHAR)
\r
1574 *matchp++ = *bufp++;
\r
1578 if (*patternp != *bufp) return FALSE;
\r
1585 SendToPlayer(data, length)
\r
1589 int error, outCount;
\r
1590 outCount = OutputToProcess(NoProc, data, length, &error);
\r
1591 if (outCount < length) {
\r
1592 DisplayFatalError("Error writing to display", error, 1);
\r
1597 PackHolding(packed, holding)
\r
1601 char *p = holding;
\r
1603 int runlength = 0;
\r
1609 switch (runlength) {
\r
1620 sprintf(q, "%d", runlength);
\r
1632 /* Telnet protocol requests from the front end */
\r
1634 TelnetRequest(ddww, option)
\r
1635 unsigned char ddww, option;
\r
1637 unsigned char msg[3];
\r
1638 int outCount, outError;
\r
1640 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
\r
1642 if (appData.debugMode) {
\r
1643 char buf1[8], buf2[8], *ddwwStr, *optionStr;
\r
1659 sprintf(buf1, "%d", ddww);
\r
1664 optionStr = "ECHO";
\r
1668 sprintf(buf2, "%d", option);
\r
1671 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
\r
1676 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
\r
1677 if (outCount < 3) {
\r
1678 DisplayFatalError("Error writing to ICS", outError, 1);
\r
1685 if (!appData.icsActive) return;
\r
1686 TelnetRequest(TN_DO, TN_ECHO);
\r
1692 if (!appData.icsActive) return;
\r
1693 TelnetRequest(TN_DONT, TN_ECHO);
\r
1697 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
\r
1699 /* put the holdings sent to us by the server on the board holdings area */
\r
1700 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
\r
1702 ChessSquare piece;
\r
1704 if(gameInfo.holdingsWidth < 2) return;
\r
1706 if( (int)lowestPiece >= BlackPawn ) {
\r
1707 holdingsColumn = 0;
\r
1709 holdingsStartRow = BOARD_HEIGHT-1;
\r
1712 holdingsColumn = BOARD_WIDTH-1;
\r
1713 countsColumn = BOARD_WIDTH-2;
\r
1714 holdingsStartRow = 0;
\r
1718 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
\r
1719 board[i][holdingsColumn] = EmptySquare;
\r
1720 board[i][countsColumn] = (ChessSquare) 0;
\r
1722 while( (p=*holdings++) != NULLCHAR ) {
\r
1723 piece = CharToPiece( ToUpper(p) );
\r
1724 if(piece == EmptySquare) continue;
\r
1725 /*j = (int) piece - (int) WhitePawn;*/
\r
1726 j = PieceToNumber(piece);
\r
1727 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
\r
1728 if(j < 0) continue; /* should not happen */
\r
1729 piece = (ChessSquare) ( j + (int)lowestPiece );
\r
1730 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
\r
1731 board[holdingsStartRow+j*direction][countsColumn]++;
\r
1737 VariantSwitch(Board board, VariantClass newVariant)
\r
1739 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
\r
1740 if(gameInfo.variant == newVariant) return;
\r
1742 /* [HGM] This routine is called each time an assignment is made to
\r
1743 * gameInfo.variant during a game, to make sure the board sizes
\r
1744 * are set to match the new variant. If that means adding or deleting
\r
1745 * holdings, we shift the playing board accordingly
\r
1748 if (appData.debugMode) {
\r
1749 fprintf(debugFP, "Switch board from %s to %s\n",
\r
1750 VariantName(gameInfo.variant), VariantName(newVariant));
\r
1751 setbuf(debugFP, NULL);
\r
1753 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
\r
1754 switch(newVariant) {
\r
1755 case VariantShogi:
\r
1756 case VariantShowgi:
\r
1757 newWidth = 9; newHeight = 9;
\r
1758 gameInfo.holdingsSize = 7;
\r
1759 case VariantBughouse:
\r
1760 case VariantCrazyhouse:
\r
1761 newHoldingsWidth = 2; break;
\r
1763 newHoldingsWidth = gameInfo.holdingsSize = 0;
\r
1766 if(newWidth != gameInfo.boardWidth ||
\r
1767 newHeight != gameInfo.boardHeight ||
\r
1768 newHoldingsWidth != gameInfo.holdingsWidth ) {
\r
1770 /* shift position to new playing area, if needed */
\r
1771 if(newHoldingsWidth > gameInfo.holdingsWidth) {
\r
1772 for(i=0; i<BOARD_HEIGHT; i++)
\r
1773 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
\r
1774 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
\r
1776 for(i=0; i<newHeight; i++) {
\r
1777 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
\r
1778 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
\r
1780 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
\r
1781 for(i=0; i<BOARD_HEIGHT; i++)
\r
1782 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
\r
1783 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
\r
1787 gameInfo.boardWidth = newWidth;
\r
1788 gameInfo.boardHeight = newHeight;
\r
1789 gameInfo.holdingsWidth = newHoldingsWidth;
\r
1790 gameInfo.variant = newVariant;
\r
1791 InitDrawingSizes(-2, 0);
\r
1792 } else gameInfo.variant = newVariant;
\r
1795 static int loggedOn = FALSE;
\r
1797 /*-- Game start info cache: --*/
\r
1799 char gs_kind[MSG_SIZ];
\r
1800 static char player1Name[128] = "";
\r
1801 static char player2Name[128] = "";
\r
1802 static int player1Rating = -1;
\r
1803 static int player2Rating = -1;
\r
1804 /*----------------------------*/
\r
1806 ColorClass curColor = ColorNormal;
\r
1809 read_from_ics(isr, closure, data, count, error)
\r
1810 InputSourceRef isr;
\r
1816 #define BUF_SIZE 8192
\r
1817 #define STARTED_NONE 0
\r
1818 #define STARTED_MOVES 1
\r
1819 #define STARTED_BOARD 2
\r
1820 #define STARTED_OBSERVE 3
\r
1821 #define STARTED_HOLDINGS 4
\r
1822 #define STARTED_CHATTER 5
\r
1823 #define STARTED_COMMENT 6
\r
1824 #define STARTED_MOVES_NOHIDE 7
\r
1826 static int started = STARTED_NONE;
\r
1827 static char parse[20000];
\r
1828 static int parse_pos = 0;
\r
1829 static char buf[BUF_SIZE + 1];
\r
1830 static int firstTime = TRUE, intfSet = FALSE;
\r
1831 static ColorClass prevColor = ColorNormal;
\r
1832 static int savingComment = FALSE;
\r
1841 if (appData.debugMode) {
\r
1843 fprintf(debugFP, "<ICS: ");
\r
1844 show_bytes(debugFP, data, count);
\r
1845 fprintf(debugFP, "\n");
\r
1851 /* If last read ended with a partial line that we couldn't parse,
\r
1852 prepend it to the new read and try again. */
\r
1853 if (leftover_len > 0) {
\r
1854 for (i=0; i<leftover_len; i++)
\r
1855 buf[i] = buf[leftover_start + i];
\r
1858 /* Copy in new characters, removing nulls and \r's */
\r
1859 buf_len = leftover_len;
\r
1860 for (i = 0; i < count; i++) {
\r
1861 if (data[i] != NULLCHAR && data[i] != '\r')
\r
1862 buf[buf_len++] = data[i];
\r
1865 buf[buf_len] = NULLCHAR;
\r
1866 next_out = leftover_len;
\r
1867 leftover_start = 0;
\r
1870 while (i < buf_len) {
\r
1871 /* Deal with part of the TELNET option negotiation
\r
1872 protocol. We refuse to do anything beyond the
\r
1873 defaults, except that we allow the WILL ECHO option,
\r
1874 which ICS uses to turn off password echoing when we are
\r
1875 directly connected to it. We reject this option
\r
1876 if localLineEditing mode is on (always on in xboard)
\r
1877 and we are talking to port 23, which might be a real
\r
1878 telnet server that will try to keep WILL ECHO on permanently.
\r
1880 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
\r
1881 static int remoteEchoOption = FALSE; /* telnet ECHO option */
\r
1882 unsigned char option;
\r
1884 switch ((unsigned char) buf[++i]) {
\r
1886 if (appData.debugMode)
\r
1887 fprintf(debugFP, "\n<WILL ");
\r
1888 switch (option = (unsigned char) buf[++i]) {
\r
1890 if (appData.debugMode)
\r
1891 fprintf(debugFP, "ECHO ");
\r
1892 /* Reply only if this is a change, according
\r
1893 to the protocol rules. */
\r
1894 if (remoteEchoOption) break;
\r
1895 if (appData.localLineEditing &&
\r
1896 atoi(appData.icsPort) == TN_PORT) {
\r
1897 TelnetRequest(TN_DONT, TN_ECHO);
\r
1900 TelnetRequest(TN_DO, TN_ECHO);
\r
1901 remoteEchoOption = TRUE;
\r
1905 if (appData.debugMode)
\r
1906 fprintf(debugFP, "%d ", option);
\r
1907 /* Whatever this is, we don't want it. */
\r
1908 TelnetRequest(TN_DONT, option);
\r
1913 if (appData.debugMode)
\r
1914 fprintf(debugFP, "\n<WONT ");
\r
1915 switch (option = (unsigned char) buf[++i]) {
\r
1917 if (appData.debugMode)
\r
1918 fprintf(debugFP, "ECHO ");
\r
1919 /* Reply only if this is a change, according
\r
1920 to the protocol rules. */
\r
1921 if (!remoteEchoOption) break;
\r
1923 TelnetRequest(TN_DONT, TN_ECHO);
\r
1924 remoteEchoOption = FALSE;
\r
1927 if (appData.debugMode)
\r
1928 fprintf(debugFP, "%d ", (unsigned char) option);
\r
1929 /* Whatever this is, it must already be turned
\r
1930 off, because we never agree to turn on
\r
1931 anything non-default, so according to the
\r
1932 protocol rules, we don't reply. */
\r
1937 if (appData.debugMode)
\r
1938 fprintf(debugFP, "\n<DO ");
\r
1939 switch (option = (unsigned char) buf[++i]) {
\r
1941 /* Whatever this is, we refuse to do it. */
\r
1942 if (appData.debugMode)
\r
1943 fprintf(debugFP, "%d ", option);
\r
1944 TelnetRequest(TN_WONT, option);
\r
1949 if (appData.debugMode)
\r
1950 fprintf(debugFP, "\n<DONT ");
\r
1951 switch (option = (unsigned char) buf[++i]) {
\r
1953 if (appData.debugMode)
\r
1954 fprintf(debugFP, "%d ", option);
\r
1955 /* Whatever this is, we are already not doing
\r
1956 it, because we never agree to do anything
\r
1957 non-default, so according to the protocol
\r
1958 rules, we don't reply. */
\r
1963 if (appData.debugMode)
\r
1964 fprintf(debugFP, "\n<IAC ");
\r
1965 /* Doubled IAC; pass it through */
\r
1969 if (appData.debugMode)
\r
1970 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
\r
1971 /* Drop all other telnet commands on the floor */
\r
1974 if (oldi > next_out)
\r
1975 SendToPlayer(&buf[next_out], oldi - next_out);
\r
1976 if (++i > next_out)
\r
1981 /* OK, this at least will *usually* work */
\r
1982 if (!loggedOn && looking_at(buf, &i, "ics%")) {
\r
1986 if (loggedOn && !intfSet) {
\r
1987 if (ics_type == ICS_ICC) {
\r
1989 "/set-quietly interface %s\n/set-quietly style 12\n",
\r
1992 } else if (ics_type == ICS_CHESSNET) {
\r
1993 sprintf(str, "/style 12\n");
\r
1995 strcpy(str, "alias $ @\n$set interface ");
\r
1996 strcat(str, programVersion);
\r
1997 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
\r
1999 strcat(str, "$iset nohighlight 1\n");
\r
2001 strcat(str, "$iset lock 1\n$style 12\n");
\r
2007 if (started == STARTED_COMMENT) {
\r
2008 /* Accumulate characters in comment */
\r
2009 parse[parse_pos++] = buf[i];
\r
2010 if (buf[i] == '\n') {
\r
2011 parse[parse_pos] = NULLCHAR;
\r
2012 AppendComment(forwardMostMove, StripHighlight(parse));
\r
2013 started = STARTED_NONE;
\r
2015 /* Don't match patterns against characters in chatter */
\r
2020 if (started == STARTED_CHATTER) {
\r
2021 if (buf[i] != '\n') {
\r
2022 /* Don't match patterns against characters in chatter */
\r
2026 started = STARTED_NONE;
\r
2029 /* Kludge to deal with rcmd protocol */
\r
2030 if (firstTime && looking_at(buf, &i, "\001*")) {
\r
2031 DisplayFatalError(&buf[1], 0, 1);
\r
2034 firstTime = FALSE;
\r
2037 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
\r
2038 ics_type = ICS_ICC;
\r
2040 if (appData.debugMode)
\r
2041 fprintf(debugFP, "ics_type %d\n", ics_type);
\r
2044 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
\r
2045 ics_type = ICS_FICS;
\r
2047 if (appData.debugMode)
\r
2048 fprintf(debugFP, "ics_type %d\n", ics_type);
\r
2051 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
\r
2052 ics_type = ICS_CHESSNET;
\r
2054 if (appData.debugMode)
\r
2055 fprintf(debugFP, "ics_type %d\n", ics_type);
\r
2060 (looking_at(buf, &i, "\"*\" is *a registered name") ||
\r
2061 looking_at(buf, &i, "Logging you in as \"*\"") ||
\r
2062 looking_at(buf, &i, "will be \"*\""))) {
\r
2063 strcpy(ics_handle, star_match[0]);
\r
2067 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
\r
2068 char buf[MSG_SIZ];
\r
2069 sprintf(buf, "%s@%s", ics_handle, appData.icsHost);
\r
2070 DisplayIcsInteractionTitle(buf);
\r
2071 have_set_title = TRUE;
\r
2074 /* skip finger notes */
\r
2075 if (started == STARTED_NONE &&
\r
2076 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
\r
2077 (buf[i] == '1' && buf[i+1] == '0')) &&
\r
2078 buf[i+2] == ':' && buf[i+3] == ' ') {
\r
2079 started = STARTED_CHATTER;
\r
2084 /* skip formula vars */
\r
2085 if (started == STARTED_NONE &&
\r
2086 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
\r
2087 started = STARTED_CHATTER;
\r
2093 if (appData.zippyTalk || appData.zippyPlay) {
\r
2095 if (ZippyControl(buf, &i) ||
\r
2096 ZippyConverse(buf, &i) ||
\r
2097 (appData.zippyPlay && ZippyMatch(buf, &i))) {
\r
2103 if (/* Don't color "message" or "messages" output */
\r
2104 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
\r
2105 looking_at(buf, &i, "*. * at *:*: ") ||
\r
2106 looking_at(buf, &i, "--* (*:*): ") ||
\r
2107 /* Regular tells and says */
\r
2108 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
\r
2109 looking_at(buf, &i, "* (your partner) tells you: ") ||
\r
2110 looking_at(buf, &i, "* says: ") ||
\r
2111 /* Message notifications (same color as tells) */
\r
2112 looking_at(buf, &i, "* has left a message ") ||
\r
2113 looking_at(buf, &i, "* just sent you a message:\n") ||
\r
2114 /* Whispers and kibitzes */
\r
2115 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
\r
2116 looking_at(buf, &i, "* kibitzes: ") ||
\r
2117 /* Channel tells */
\r
2118 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
\r
2120 if (tkind == 1 && strchr(star_match[0], ':')) {
\r
2121 /* Avoid "tells you:" spoofs in channels */
\r
2124 if (star_match[0][0] == NULLCHAR ||
\r
2125 strchr(star_match[0], ' ') ||
\r
2126 (tkind == 3 && strchr(star_match[1], ' '))) {
\r
2127 /* Reject bogus matches */
\r
2130 if (appData.colorize) {
\r
2131 if (oldi > next_out) {
\r
2132 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2137 Colorize(ColorTell, FALSE);
\r
2138 curColor = ColorTell;
\r
2141 Colorize(ColorKibitz, FALSE);
\r
2142 curColor = ColorKibitz;
\r
2145 p = strrchr(star_match[1], '(');
\r
2147 p = star_match[1];
\r
2151 if (atoi(p) == 1) {
\r
2152 Colorize(ColorChannel1, FALSE);
\r
2153 curColor = ColorChannel1;
\r
2155 Colorize(ColorChannel, FALSE);
\r
2156 curColor = ColorChannel;
\r
2160 curColor = ColorNormal;
\r
2164 if (started == STARTED_NONE && appData.autoComment &&
\r
2165 (gameMode == IcsObserving ||
\r
2166 gameMode == IcsPlayingWhite ||
\r
2167 gameMode == IcsPlayingBlack)) {
\r
2168 parse_pos = i - oldi;
\r
2169 memcpy(parse, &buf[oldi], parse_pos);
\r
2170 parse[parse_pos] = NULLCHAR;
\r
2171 started = STARTED_COMMENT;
\r
2172 savingComment = TRUE;
\r
2174 started = STARTED_CHATTER;
\r
2175 savingComment = FALSE;
\r
2182 if (looking_at(buf, &i, "* s-shouts: ") ||
\r
2183 looking_at(buf, &i, "* c-shouts: ")) {
\r
2184 if (appData.colorize) {
\r
2185 if (oldi > next_out) {
\r
2186 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2189 Colorize(ColorSShout, FALSE);
\r
2190 curColor = ColorSShout;
\r
2193 started = STARTED_CHATTER;
\r
2197 if (looking_at(buf, &i, "--->")) {
\r
2202 if (looking_at(buf, &i, "* shouts: ") ||
\r
2203 looking_at(buf, &i, "--> ")) {
\r
2204 if (appData.colorize) {
\r
2205 if (oldi > next_out) {
\r
2206 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2209 Colorize(ColorShout, FALSE);
\r
2210 curColor = ColorShout;
\r
2213 started = STARTED_CHATTER;
\r
2217 if (looking_at( buf, &i, "Challenge:")) {
\r
2218 if (appData.colorize) {
\r
2219 if (oldi > next_out) {
\r
2220 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2223 Colorize(ColorChallenge, FALSE);
\r
2224 curColor = ColorChallenge;
\r
2230 if (looking_at(buf, &i, "* offers you") ||
\r
2231 looking_at(buf, &i, "* offers to be") ||
\r
2232 looking_at(buf, &i, "* would like to") ||
\r
2233 looking_at(buf, &i, "* requests to") ||
\r
2234 looking_at(buf, &i, "Your opponent offers") ||
\r
2235 looking_at(buf, &i, "Your opponent requests")) {
\r
2237 if (appData.colorize) {
\r
2238 if (oldi > next_out) {
\r
2239 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2242 Colorize(ColorRequest, FALSE);
\r
2243 curColor = ColorRequest;
\r
2248 if (looking_at(buf, &i, "* (*) seeking")) {
\r
2249 if (appData.colorize) {
\r
2250 if (oldi > next_out) {
\r
2251 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2254 Colorize(ColorSeek, FALSE);
\r
2255 curColor = ColorSeek;
\r
2261 if (looking_at(buf, &i, "\\ ")) {
\r
2262 if (prevColor != ColorNormal) {
\r
2263 if (oldi > next_out) {
\r
2264 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2267 Colorize(prevColor, TRUE);
\r
2268 curColor = prevColor;
\r
2270 if (savingComment) {
\r
2271 parse_pos = i - oldi;
\r
2272 memcpy(parse, &buf[oldi], parse_pos);
\r
2273 parse[parse_pos] = NULLCHAR;
\r
2274 started = STARTED_COMMENT;
\r
2276 started = STARTED_CHATTER;
\r
2281 if (looking_at(buf, &i, "Black Strength :") ||
\r
2282 looking_at(buf, &i, "<<< style 10 board >>>") ||
\r
2283 looking_at(buf, &i, "<10>") ||
\r
2284 looking_at(buf, &i, "#@#")) {
\r
2285 /* Wrong board style */
\r
2287 SendToICS(ics_prefix);
\r
2288 SendToICS("set style 12\n");
\r
2289 SendToICS(ics_prefix);
\r
2290 SendToICS("refresh\n");
\r
2294 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
\r
2296 have_sent_ICS_logon = 1;
\r
2300 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
\r
2301 (looking_at(buf, &i, "\n<12> ") ||
\r
2302 looking_at(buf, &i, "<12> "))) {
\r
2304 if (oldi > next_out) {
\r
2305 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2308 started = STARTED_BOARD;
\r
2313 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
\r
2314 looking_at(buf, &i, "<b1> ")) {
\r
2315 if (oldi > next_out) {
\r
2316 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2319 started = STARTED_HOLDINGS;
\r
2324 if (looking_at(buf, &i, "* *vs. * *--- *")) {
\r
2326 /* Header for a move list -- first line */
\r
2328 switch (ics_getting_history) {
\r
2330 switch (gameMode) {
\r
2332 case BeginningOfGame:
\r
2333 /* User typed "moves" or "oldmoves" while we
\r
2334 were idle. Pretend we asked for these
\r
2335 moves and soak them up so user can step
\r
2336 through them and/or save them.
\r
2338 Reset(FALSE, TRUE);
\r
2339 gameMode = IcsObserving;
\r
2342 ics_getting_history = H_GOT_UNREQ_HEADER;
\r
2344 case EditGame: /*?*/
\r
2345 case EditPosition: /*?*/
\r
2346 /* Should above feature work in these modes too? */
\r
2347 /* For now it doesn't */
\r
2348 ics_getting_history = H_GOT_UNWANTED_HEADER;
\r
2351 ics_getting_history = H_GOT_UNWANTED_HEADER;
\r
2356 /* Is this the right one? */
\r
2357 if (gameInfo.white && gameInfo.black &&
\r
2358 strcmp(gameInfo.white, star_match[0]) == 0 &&
\r
2359 strcmp(gameInfo.black, star_match[2]) == 0) {
\r
2361 ics_getting_history = H_GOT_REQ_HEADER;
\r
2364 case H_GOT_REQ_HEADER:
\r
2365 case H_GOT_UNREQ_HEADER:
\r
2366 case H_GOT_UNWANTED_HEADER:
\r
2367 case H_GETTING_MOVES:
\r
2368 /* Should not happen */
\r
2369 DisplayError("Error gathering move list: two headers", 0);
\r
2370 ics_getting_history = H_FALSE;
\r
2374 /* Save player ratings into gameInfo if needed */
\r
2375 if ((ics_getting_history == H_GOT_REQ_HEADER ||
\r
2376 ics_getting_history == H_GOT_UNREQ_HEADER) &&
\r
2377 (gameInfo.whiteRating == -1 ||
\r
2378 gameInfo.blackRating == -1)) {
\r
2380 gameInfo.whiteRating = string_to_rating(star_match[1]);
\r
2381 gameInfo.blackRating = string_to_rating(star_match[3]);
\r
2382 if (appData.debugMode)
\r
2383 fprintf(debugFP, "Ratings from header: W %d, B %d\n",
\r
2384 gameInfo.whiteRating, gameInfo.blackRating);
\r
2389 if (looking_at(buf, &i,
\r
2390 "* * match, initial time: * minute*, increment: * second")) {
\r
2391 /* Header for a move list -- second line */
\r
2392 /* Initial board will follow if this is a wild game */
\r
2393 if (gameInfo.event != NULL) free(gameInfo.event);
\r
2394 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
\r
2395 gameInfo.event = StrSave(str);
\r
2396 /* [HGM] we switched variant. Translate boards if needed. */
\r
2397 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
\r
2401 if (looking_at(buf, &i, "Move ")) {
\r
2402 /* Beginning of a move list */
\r
2403 switch (ics_getting_history) {
\r
2405 /* Normally should not happen */
\r
2406 /* Maybe user hit reset while we were parsing */
\r
2409 /* Happens if we are ignoring a move list that is not
\r
2410 * the one we just requested. Common if the user
\r
2411 * tries to observe two games without turning off
\r
2414 case H_GETTING_MOVES:
\r
2415 /* Should not happen */
\r
2416 DisplayError("Error gathering move list: nested", 0);
\r
2417 ics_getting_history = H_FALSE;
\r
2419 case H_GOT_REQ_HEADER:
\r
2420 ics_getting_history = H_GETTING_MOVES;
\r
2421 started = STARTED_MOVES;
\r
2423 if (oldi > next_out) {
\r
2424 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2427 case H_GOT_UNREQ_HEADER:
\r
2428 ics_getting_history = H_GETTING_MOVES;
\r
2429 started = STARTED_MOVES_NOHIDE;
\r
2432 case H_GOT_UNWANTED_HEADER:
\r
2433 ics_getting_history = H_FALSE;
\r
2439 if (looking_at(buf, &i, "% ") ||
\r
2440 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
\r
2441 && looking_at(buf, &i, "}*"))) {
\r
2442 savingComment = FALSE;
\r
2443 switch (started) {
\r
2444 case STARTED_MOVES:
\r
2445 case STARTED_MOVES_NOHIDE:
\r
2446 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
\r
2447 parse[parse_pos + i - oldi] = NULLCHAR;
\r
2448 ParseGameHistory(parse);
\r
2450 if (appData.zippyPlay && first.initDone) {
\r
2451 FeedMovesToProgram(&first, forwardMostMove);
\r
2452 if (gameMode == IcsPlayingWhite) {
\r
2453 if (WhiteOnMove(forwardMostMove)) {
\r
2454 if (first.sendTime) {
\r
2455 if (first.useColors) {
\r
2456 SendToProgram("black\n", &first);
\r
2458 SendTimeRemaining(&first, TRUE);
\r
2460 if (first.useColors) {
\r
2461 SendToProgram("white\ngo\n", &first);
\r
2463 SendToProgram("go\n", &first);
\r
2465 first.maybeThinking = TRUE;
\r
2467 if (first.usePlayother) {
\r
2468 if (first.sendTime) {
\r
2469 SendTimeRemaining(&first, TRUE);
\r
2471 SendToProgram("playother\n", &first);
\r
2472 firstMove = FALSE;
\r
2477 } else if (gameMode == IcsPlayingBlack) {
\r
2478 if (!WhiteOnMove(forwardMostMove)) {
\r
2479 if (first.sendTime) {
\r
2480 if (first.useColors) {
\r
2481 SendToProgram("white\n", &first);
\r
2483 SendTimeRemaining(&first, FALSE);
\r
2485 if (first.useColors) {
\r
2486 SendToProgram("black\ngo\n", &first);
\r
2488 SendToProgram("go\n", &first);
\r
2490 first.maybeThinking = TRUE;
\r
2492 if (first.usePlayother) {
\r
2493 if (first.sendTime) {
\r
2494 SendTimeRemaining(&first, FALSE);
\r
2496 SendToProgram("playother\n", &first);
\r
2497 firstMove = FALSE;
\r
2505 if (gameMode == IcsObserving && ics_gamenum == -1) {
\r
2506 /* Moves came from oldmoves or moves command
\r
2507 while we weren't doing anything else.
\r
2509 currentMove = forwardMostMove;
\r
2510 ClearHighlights();/*!!could figure this out*/
\r
2511 flipView = appData.flipView;
\r
2512 DrawPosition(FALSE, boards[currentMove]);
\r
2513 DisplayBothClocks();
\r
2514 sprintf(str, "%s vs. %s",
\r
2515 gameInfo.white, gameInfo.black);
\r
2516 DisplayTitle(str);
\r
2517 gameMode = IcsIdle;
\r
2519 /* Moves were history of an active game */
\r
2520 if (gameInfo.resultDetails != NULL) {
\r
2521 free(gameInfo.resultDetails);
\r
2522 gameInfo.resultDetails = NULL;
\r
2525 HistorySet(parseList, backwardMostMove,
\r
2526 forwardMostMove, currentMove-1);
\r
2527 DisplayMove(currentMove - 1);
\r
2528 if (started == STARTED_MOVES) next_out = i;
\r
2529 started = STARTED_NONE;
\r
2530 ics_getting_history = H_FALSE;
\r
2533 case STARTED_OBSERVE:
\r
2534 started = STARTED_NONE;
\r
2535 SendToICS(ics_prefix);
\r
2536 SendToICS("refresh\n");
\r
2545 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
\r
2546 started == STARTED_HOLDINGS ||
\r
2547 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
\r
2548 /* Accumulate characters in move list or board */
\r
2549 parse[parse_pos++] = buf[i];
\r
2552 /* Start of game messages. Mostly we detect start of game
\r
2553 when the first board image arrives. On some versions
\r
2554 of the ICS, though, we need to do a "refresh" after starting
\r
2555 to observe in order to get the current board right away. */
\r
2556 if (looking_at(buf, &i, "Adding game * to observation list")) {
\r
2557 started = STARTED_OBSERVE;
\r
2561 /* Handle auto-observe */
\r
2562 if (appData.autoObserve &&
\r
2563 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
\r
2564 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
\r
2566 /* Choose the player that was highlighted, if any. */
\r
2567 if (star_match[0][0] == '\033' ||
\r
2568 star_match[1][0] != '\033') {
\r
2569 player = star_match[0];
\r
2571 player = star_match[2];
\r
2573 sprintf(str, "%sobserve %s\n",
\r
2574 ics_prefix, StripHighlightAndTitle(player));
\r
2577 /* Save ratings from notify string */
\r
2578 strcpy(player1Name, star_match[0]);
\r
2579 player1Rating = string_to_rating(star_match[1]);
\r
2580 strcpy(player2Name, star_match[2]);
\r
2581 player2Rating = string_to_rating(star_match[3]);
\r
2583 if (appData.debugMode)
\r
2585 "Ratings from 'Game notification:' %s %d, %s %d\n",
\r
2586 player1Name, player1Rating,
\r
2587 player2Name, player2Rating);
\r
2592 /* Deal with automatic examine mode after a game,
\r
2593 and with IcsObserving -> IcsExamining transition */
\r
2594 if (looking_at(buf, &i, "Entering examine mode for game *") ||
\r
2595 looking_at(buf, &i, "has made you an examiner of game *")) {
\r
2597 int gamenum = atoi(star_match[0]);
\r
2598 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
\r
2599 gamenum == ics_gamenum) {
\r
2600 /* We were already playing or observing this game;
\r
2601 no need to refetch history */
\r
2602 gameMode = IcsExamining;
\r
2604 pauseExamForwardMostMove = forwardMostMove;
\r
2605 } else if (currentMove < forwardMostMove) {
\r
2606 ForwardInner(forwardMostMove);
\r
2609 /* I don't think this case really can happen */
\r
2610 SendToICS(ics_prefix);
\r
2611 SendToICS("refresh\n");
\r
2616 /* Error messages */
\r
2617 if (ics_user_moved) {
\r
2618 if (looking_at(buf, &i, "Illegal move") ||
\r
2619 looking_at(buf, &i, "Not a legal move") ||
\r
2620 looking_at(buf, &i, "Your king is in check") ||
\r
2621 looking_at(buf, &i, "It isn't your turn") ||
\r
2622 looking_at(buf, &i, "It is not your move")) {
\r
2623 /* Illegal move */
\r
2624 ics_user_moved = 0;
\r
2625 if (forwardMostMove > backwardMostMove) {
\r
2626 currentMove = --forwardMostMove;
\r
2627 DisplayMove(currentMove - 1); /* before DMError */
\r
2628 DisplayMoveError("Illegal move (rejected by ICS)");
\r
2629 DrawPosition(FALSE, boards[currentMove]);
\r
2631 DisplayBothClocks();
\r
2637 if (looking_at(buf, &i, "still have time") ||
\r
2638 looking_at(buf, &i, "not out of time") ||
\r
2639 looking_at(buf, &i, "either player is out of time") ||
\r
2640 looking_at(buf, &i, "has timeseal; checking")) {
\r
2641 /* We must have called his flag a little too soon */
\r
2642 whiteFlag = blackFlag = FALSE;
\r
2646 if (looking_at(buf, &i, "added * seconds to") ||
\r
2647 looking_at(buf, &i, "seconds were added to")) {
\r
2648 /* Update the clocks */
\r
2649 SendToICS(ics_prefix);
\r
2650 SendToICS("refresh\n");
\r
2654 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
\r
2655 ics_clock_paused = TRUE;
\r
2660 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
\r
2661 ics_clock_paused = FALSE;
\r
2666 /* Grab player ratings from the Creating: message.
\r
2667 Note we have to check for the special case when
\r
2668 the ICS inserts things like [white] or [black]. */
\r
2669 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
\r
2670 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
\r
2672 0 player 1 name (not necessarily white)
\r
2674 2 empty, white, or black (IGNORED)
\r
2675 3 player 2 name (not necessarily black)
\r
2678 The names/ratings are sorted out when the game
\r
2679 actually starts (below).
\r
2681 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
\r
2682 player1Rating = string_to_rating(star_match[1]);
\r
2683 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
\r
2684 player2Rating = string_to_rating(star_match[4]);
\r
2686 if (appData.debugMode)
\r
2688 "Ratings from 'Creating:' %s %d, %s %d\n",
\r
2689 player1Name, player1Rating,
\r
2690 player2Name, player2Rating);
\r
2695 /* Improved generic start/end-of-game messages */
\r
2696 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
\r
2697 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
\r
2698 /* If tkind == 0: */
\r
2699 /* star_match[0] is the game number */
\r
2700 /* [1] is the white player's name */
\r
2701 /* [2] is the black player's name */
\r
2702 /* For end-of-game: */
\r
2703 /* [3] is the reason for the game end */
\r
2704 /* [4] is a PGN end game-token, preceded by " " */
\r
2705 /* For start-of-game: */
\r
2706 /* [3] begins with "Creating" or "Continuing" */
\r
2707 /* [4] is " *" or empty (don't care). */
\r
2708 int gamenum = atoi(star_match[0]);
\r
2709 char *whitename, *blackname, *why, *endtoken;
\r
2710 ChessMove endtype = (ChessMove) 0;
\r
2713 whitename = star_match[1];
\r
2714 blackname = star_match[2];
\r
2715 why = star_match[3];
\r
2716 endtoken = star_match[4];
\r
2718 whitename = star_match[1];
\r
2719 blackname = star_match[3];
\r
2720 why = star_match[5];
\r
2721 endtoken = star_match[6];
\r
2724 /* Game start messages */
\r
2725 if (strncmp(why, "Creating ", 9) == 0 ||
\r
2726 strncmp(why, "Continuing ", 11) == 0) {
\r
2727 gs_gamenum = gamenum;
\r
2728 strcpy(gs_kind, strchr(why, ' ') + 1);
\r
2730 if (appData.zippyPlay) {
\r
2731 ZippyGameStart(whitename, blackname);
\r
2737 /* Game end messages */
\r
2738 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
\r
2739 ics_gamenum != gamenum) {
\r
2742 while (endtoken[0] == ' ') endtoken++;
\r
2743 switch (endtoken[0]) {
\r
2746 endtype = GameUnfinished;
\r
2749 endtype = BlackWins;
\r
2752 if (endtoken[1] == '/')
\r
2753 endtype = GameIsDrawn;
\r
2755 endtype = WhiteWins;
\r
2758 GameEnds(endtype, why, GE_ICS);
\r
2760 if (appData.zippyPlay && first.initDone) {
\r
2761 ZippyGameEnd(endtype, why);
\r
2762 if (first.pr == NULL) {
\r
2763 /* Start the next process early so that we'll
\r
2764 be ready for the next challenge */
\r
2765 StartChessProgram(&first);
\r
2767 /* Send "new" early, in case this command takes
\r
2768 a long time to finish, so that we'll be ready
\r
2769 for the next challenge. */
\r
2770 Reset(TRUE, TRUE);
\r
2776 if (looking_at(buf, &i, "Removing game * from observation") ||
\r
2777 looking_at(buf, &i, "no longer observing game *") ||
\r
2778 looking_at(buf, &i, "Game * (*) has no examiners")) {
\r
2779 if (gameMode == IcsObserving &&
\r
2780 atoi(star_match[0]) == ics_gamenum)
\r
2783 gameMode = IcsIdle;
\r
2785 ics_user_moved = FALSE;
\r
2790 if (looking_at(buf, &i, "no longer examining game *")) {
\r
2791 if (gameMode == IcsExamining &&
\r
2792 atoi(star_match[0]) == ics_gamenum)
\r
2794 gameMode = IcsIdle;
\r
2796 ics_user_moved = FALSE;
\r
2801 /* Advance leftover_start past any newlines we find,
\r
2802 so only partial lines can get reparsed */
\r
2803 if (looking_at(buf, &i, "\n")) {
\r
2804 prevColor = curColor;
\r
2805 if (curColor != ColorNormal) {
\r
2806 if (oldi > next_out) {
\r
2807 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2810 Colorize(ColorNormal, FALSE);
\r
2811 curColor = ColorNormal;
\r
2813 if (started == STARTED_BOARD) {
\r
2814 started = STARTED_NONE;
\r
2815 parse[parse_pos] = NULLCHAR;
\r
2816 ParseBoard12(parse);
\r
2817 ics_user_moved = 0;
\r
2819 /* Send premove here */
\r
2820 if (appData.premove) {
\r
2821 char str[MSG_SIZ];
\r
2822 if (currentMove == 0 &&
\r
2823 gameMode == IcsPlayingWhite &&
\r
2824 appData.premoveWhite) {
\r
2825 sprintf(str, "%s%s\n", ics_prefix,
\r
2826 appData.premoveWhiteText);
\r
2827 if (appData.debugMode)
\r
2828 fprintf(debugFP, "Sending premove:\n");
\r
2830 } else if (currentMove == 1 &&
\r
2831 gameMode == IcsPlayingBlack &&
\r
2832 appData.premoveBlack) {
\r
2833 sprintf(str, "%s%s\n", ics_prefix,
\r
2834 appData.premoveBlackText);
\r
2835 if (appData.debugMode)
\r
2836 fprintf(debugFP, "Sending premove:\n");
\r
2838 } else if (gotPremove) {
\r
2840 ClearPremoveHighlights();
\r
2841 if (appData.debugMode)
\r
2842 fprintf(debugFP, "Sending premove:\n");
\r
2843 UserMoveEvent(premoveFromX, premoveFromY,
\r
2844 premoveToX, premoveToY,
\r
2845 premovePromoChar);
\r
2849 /* Usually suppress following prompt */
\r
2850 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
\r
2851 if (looking_at(buf, &i, "*% ")) {
\r
2852 savingComment = FALSE;
\r
2856 } else if (started == STARTED_HOLDINGS) {
\r
2858 char new_piece[MSG_SIZ];
\r
2859 started = STARTED_NONE;
\r
2860 parse[parse_pos] = NULLCHAR;
\r
2861 if (appData.debugMode)
\r
2862 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
\r
2863 parse, currentMove);
\r
2864 if (sscanf(parse, " game %d", &gamenum) == 1 &&
\r
2865 gamenum == ics_gamenum) {
\r
2866 if (gameInfo.variant == VariantNormal) {
\r
2867 /* [HGM] We seem to switch variant during a game!
\r
2868 * Presumably no holdings were displayed, so we have
\r
2869 * to move the position two files to the right to
\r
2870 * create room for them!
\r
2872 VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
\r
2873 /* Get a move list just to see the header, which
\r
2874 will tell us whether this is really bug or zh */
\r
2875 if (ics_getting_history == H_FALSE) {
\r
2876 ics_getting_history = H_REQUESTED;
\r
2877 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
\r
2881 new_piece[0] = NULLCHAR;
\r
2882 sscanf(parse, "game %d white [%s black [%s <- %s",
\r
2883 &gamenum, white_holding, black_holding,
\r
2885 white_holding[strlen(white_holding)-1] = NULLCHAR;
\r
2886 black_holding[strlen(black_holding)-1] = NULLCHAR;
\r
2887 /* [HGM] copy holdings to board holdings area */
\r
2888 CopyHoldings(boards[currentMove], white_holding, WhitePawn);
\r
2889 CopyHoldings(boards[currentMove], black_holding, BlackPawn);
\r
2891 if (appData.zippyPlay && first.initDone) {
\r
2892 ZippyHoldings(white_holding, black_holding,
\r
2896 if (tinyLayout || smallLayout) {
\r
2897 char wh[16], bh[16];
\r
2898 PackHolding(wh, white_holding);
\r
2899 PackHolding(bh, black_holding);
\r
2900 sprintf(str, "[%s-%s] %s-%s", wh, bh,
\r
2901 gameInfo.white, gameInfo.black);
\r
2903 sprintf(str, "%s [%s] vs. %s [%s]",
\r
2904 gameInfo.white, white_holding,
\r
2905 gameInfo.black, black_holding);
\r
2908 DrawPosition(FALSE, boards[currentMove]);
\r
2909 DisplayTitle(str);
\r
2911 /* Suppress following prompt */
\r
2912 if (looking_at(buf, &i, "*% ")) {
\r
2913 savingComment = FALSE;
\r
2920 i++; /* skip unparsed character and loop back */
\r
2923 if (started != STARTED_MOVES && started != STARTED_BOARD &&
\r
2924 started != STARTED_HOLDINGS && i > next_out) {
\r
2925 SendToPlayer(&buf[next_out], i - next_out);
\r
2929 leftover_len = buf_len - leftover_start;
\r
2930 /* if buffer ends with something we couldn't parse,
\r
2931 reparse it after appending the next read */
\r
2933 } else if (count == 0) {
\r
2934 RemoveInputSource(isr);
\r
2935 DisplayFatalError("Connection closed by ICS", 0, 0);
\r
2937 DisplayFatalError("Error reading from ICS", error, 1);
\r
2942 /* Board style 12 looks like this:
\r
2944 <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
2946 * The "<12> " is stripped before it gets to this routine. The two
\r
2947 * trailing 0's (flip state and clock ticking) are later addition, and
\r
2948 * some chess servers may not have them, or may have only the first.
\r
2949 * Additional trailing fields may be added in the future.
\r
2952 #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
2954 #define RELATION_OBSERVING_PLAYED 0
\r
2955 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
\r
2956 #define RELATION_PLAYING_MYMOVE 1
\r
2957 #define RELATION_PLAYING_NOTMYMOVE -1
\r
2958 #define RELATION_EXAMINING 2
\r
2959 #define RELATION_ISOLATED_BOARD -3
\r
2960 #define RELATION_STARTING_POSITION -4 /* FICS only */
\r
2963 ParseBoard12(string)
\r
2966 GameMode newGameMode;
\r
2967 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
\r
2968 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
\r
2969 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
\r
2970 char to_play, board_chars[72];
\r
2971 char move_str[500], str[500], elapsed_time[500];
\r
2972 char black[32], white[32];
\r
2974 int prevMove = currentMove;
\r
2976 ChessMove moveType;
\r
2977 int fromX, fromY, toX, toY;
\r
2980 fromX = fromY = toX = toY = -1;
\r
2984 if (appData.debugMode)
\r
2985 fprintf(debugFP, "Parsing board: %s\n", string);
\r
2987 move_str[0] = NULLCHAR;
\r
2988 elapsed_time[0] = NULLCHAR;
\r
2989 n = sscanf(string, PATTERN, board_chars, &to_play, &double_push,
\r
2990 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
\r
2991 &gamenum, white, black, &relation, &basetime, &increment,
\r
2992 &white_stren, &black_stren, &white_time, &black_time,
\r
2993 &moveNum, str, elapsed_time, move_str, &ics_flip,
\r
2997 sprintf(str, "Failed to parse board string:\n\"%s\"", string);
\r
2998 DisplayError(str, 0);
\r
3002 /* Convert the move number to internal form */
\r
3003 moveNum = (moveNum - 1) * 2;
\r
3004 if (to_play == 'B') moveNum++;
\r
3005 if (moveNum >= MAX_MOVES) {
\r
3006 DisplayFatalError("Game too long; increase MAX_MOVES and recompile",
\r
3011 switch (relation) {
\r
3012 case RELATION_OBSERVING_PLAYED:
\r
3013 case RELATION_OBSERVING_STATIC:
\r
3014 if (gamenum == -1) {
\r
3015 /* Old ICC buglet */
\r
3016 relation = RELATION_OBSERVING_STATIC;
\r
3018 newGameMode = IcsObserving;
\r
3020 case RELATION_PLAYING_MYMOVE:
\r
3021 case RELATION_PLAYING_NOTMYMOVE:
\r
3023 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
\r
3024 IcsPlayingWhite : IcsPlayingBlack;
\r
3026 case RELATION_EXAMINING:
\r
3027 newGameMode = IcsExamining;
\r
3029 case RELATION_ISOLATED_BOARD:
\r
3031 /* Just display this board. If user was doing something else,
\r
3032 we will forget about it until the next board comes. */
\r
3033 newGameMode = IcsIdle;
\r
3035 case RELATION_STARTING_POSITION:
\r
3036 newGameMode = gameMode;
\r
3040 /* Modify behavior for initial board display on move listing
\r
3043 switch (ics_getting_history) {
\r
3047 case H_GOT_REQ_HEADER:
\r
3048 case H_GOT_UNREQ_HEADER:
\r
3049 /* This is the initial position of the current game */
\r
3050 gamenum = ics_gamenum;
\r
3051 moveNum = 0; /* old ICS bug workaround */
\r
3052 if (to_play == 'B') {
\r
3053 startedFromSetupPosition = TRUE;
\r
3054 blackPlaysFirst = TRUE;
\r
3056 if (forwardMostMove == 0) forwardMostMove = 1;
\r
3057 if (backwardMostMove == 0) backwardMostMove = 1;
\r
3058 if (currentMove == 0) currentMove = 1;
\r
3060 newGameMode = gameMode;
\r
3061 relation = RELATION_STARTING_POSITION; /* ICC needs this */
\r
3063 case H_GOT_UNWANTED_HEADER:
\r
3064 /* This is an initial board that we don't want */
\r
3066 case H_GETTING_MOVES:
\r
3067 /* Should not happen */
\r
3068 DisplayError("Error gathering move list: extra board", 0);
\r
3069 ics_getting_history = H_FALSE;
\r
3073 /* Take action if this is the first board of a new game, or of a
\r
3074 different game than is currently being displayed. */
\r
3075 if (gamenum != ics_gamenum || newGameMode != gameMode ||
\r
3076 relation == RELATION_ISOLATED_BOARD) {
\r
3078 /* Forget the old game and get the history (if any) of the new one */
\r
3079 if (gameMode != BeginningOfGame) {
\r
3080 Reset(FALSE, TRUE);
\r
3083 if (appData.autoRaiseBoard) BoardToTop();
\r
3085 if (gamenum == -1) {
\r
3086 newGameMode = IcsIdle;
\r
3087 } else if (moveNum > 0 && newGameMode != IcsIdle &&
\r
3088 appData.getMoveList) {
\r
3089 /* Need to get game history */
\r
3090 ics_getting_history = H_REQUESTED;
\r
3091 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
\r
3095 /* Initially flip the board to have black on the bottom if playing
\r
3096 black or if the ICS flip flag is set, but let the user change
\r
3097 it with the Flip View button. */
\r
3098 flipView = appData.autoFlipView ?
\r
3099 (newGameMode == IcsPlayingBlack) || ics_flip :
\r
3102 /* Done with values from previous mode; copy in new ones */
\r
3103 gameMode = newGameMode;
\r
3105 ics_gamenum = gamenum;
\r
3106 if (gamenum == gs_gamenum) {
\r
3107 int klen = strlen(gs_kind);
\r
3108 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
\r
3109 sprintf(str, "ICS %s", gs_kind);
\r
3110 gameInfo.event = StrSave(str);
\r
3112 gameInfo.event = StrSave("ICS game");
\r
3114 gameInfo.site = StrSave(appData.icsHost);
\r
3115 gameInfo.date = PGNDate();
\r
3116 gameInfo.round = StrSave("-");
\r
3117 gameInfo.white = StrSave(white);
\r
3118 gameInfo.black = StrSave(black);
\r
3119 timeControl = basetime * 60 * 1000;
\r
3120 timeControl_2 = 0;
\r
3121 timeIncrement = increment * 1000;
\r
3122 movesPerSession = 0;
\r
3123 gameInfo.timeControl = TimeControlTagValue();
\r
3124 VariantSwitch(board, StringToVariant(gameInfo.event) );
\r
3125 if (appData.debugMode) {
\r
3126 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
\r
3127 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
\r
3128 setbuf(debugFP, NULL);
\r
3131 gameInfo.outOfBook = NULL;
\r
3133 /* Do we have the ratings? */
\r
3134 if (strcmp(player1Name, white) == 0 &&
\r
3135 strcmp(player2Name, black) == 0) {
\r
3136 if (appData.debugMode)
\r
3137 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
\r
3138 player1Rating, player2Rating);
\r
3139 gameInfo.whiteRating = player1Rating;
\r
3140 gameInfo.blackRating = player2Rating;
\r
3141 } else if (strcmp(player2Name, white) == 0 &&
\r
3142 strcmp(player1Name, black) == 0) {
\r
3143 if (appData.debugMode)
\r
3144 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
\r
3145 player2Rating, player1Rating);
\r
3146 gameInfo.whiteRating = player2Rating;
\r
3147 gameInfo.blackRating = player1Rating;
\r
3149 player1Name[0] = player2Name[0] = NULLCHAR;
\r
3151 /* Silence shouts if requested */
\r
3152 if (appData.quietPlay &&
\r
3153 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
\r
3154 SendToICS(ics_prefix);
\r
3155 SendToICS("set shout 0\n");
\r
3159 /* Deal with midgame name changes */
\r
3161 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
\r
3162 if (gameInfo.white) free(gameInfo.white);
\r
3163 gameInfo.white = StrSave(white);
\r
3165 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
\r
3166 if (gameInfo.black) free(gameInfo.black);
\r
3167 gameInfo.black = StrSave(black);
\r
3171 /* Throw away game result if anything actually changes in examine mode */
\r
3172 if (gameMode == IcsExamining && !newGame) {
\r
3173 gameInfo.result = GameUnfinished;
\r
3174 if (gameInfo.resultDetails != NULL) {
\r
3175 free(gameInfo.resultDetails);
\r
3176 gameInfo.resultDetails = NULL;
\r
3180 /* In pausing && IcsExamining mode, we ignore boards coming
\r
3181 in if they are in a different variation than we are. */
\r
3182 if (pauseExamInvalid) return;
\r
3183 if (pausing && gameMode == IcsExamining) {
\r
3184 if (moveNum <= pauseExamForwardMostMove) {
\r
3185 pauseExamInvalid = TRUE;
\r
3186 forwardMostMove = pauseExamForwardMostMove;
\r
3191 /* Parse the board */
\r
3192 for (k = 0; k < 8; k++) {
\r
3193 for (j = 0; j < 8; j++)
\r
3194 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(7-k)*9 + j]);
\r
3195 if(gameInfo.holdingsWidth > 1) {
\r
3196 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
\r
3197 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
\r
3200 CopyBoard(boards[moveNum], board);
\r
3201 if (moveNum == 0) {
\r
3202 startedFromSetupPosition =
\r
3203 !CompareBoards(board, initialPosition);
\r
3206 if (ics_getting_history == H_GOT_REQ_HEADER ||
\r
3207 ics_getting_history == H_GOT_UNREQ_HEADER) {
\r
3208 /* This was an initial position from a move list, not
\r
3209 the current position */
\r
3213 /* Update currentMove and known move number limits */
\r
3214 newMove = newGame || moveNum > forwardMostMove;
\r
3216 forwardMostMove = backwardMostMove = currentMove = moveNum;
\r
3217 if (gameMode == IcsExamining && moveNum == 0) {
\r
3218 /* Workaround for ICS limitation: we are not told the wild
\r
3219 type when starting to examine a game. But if we ask for
\r
3220 the move list, the move list header will tell us */
\r
3221 ics_getting_history = H_REQUESTED;
\r
3222 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
\r
3225 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
\r
3226 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
\r
3227 forwardMostMove = moveNum;
\r
3228 if (!pausing || currentMove > forwardMostMove)
\r
3229 currentMove = forwardMostMove;
\r
3231 /* New part of history that is not contiguous with old part */
\r
3232 if (pausing && gameMode == IcsExamining) {
\r
3233 pauseExamInvalid = TRUE;
\r
3234 forwardMostMove = pauseExamForwardMostMove;
\r
3237 forwardMostMove = backwardMostMove = currentMove = moveNum;
\r
3238 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
\r
3239 ics_getting_history = H_REQUESTED;
\r
3240 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
\r
3245 /* Update the clocks */
\r
3246 if (strchr(elapsed_time, '.')) {
\r
3247 /* Time is in ms */
\r
3248 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
\r
3249 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
\r
3251 /* Time is in seconds */
\r
3252 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
\r
3253 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
\r
3258 if (appData.zippyPlay && newGame &&
\r
3259 gameMode != IcsObserving && gameMode != IcsIdle &&
\r
3260 gameMode != IcsExamining)
\r
3261 ZippyFirstBoard(moveNum, basetime, increment);
\r
3264 /* Put the move on the move list, first converting
\r
3265 to canonical algebraic form. */
\r
3266 if (moveNum > 0) {
\r
3267 if (appData.debugMode) {
\r
3268 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
\r
3269 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
\r
3270 setbuf(debugFP, NULL);
\r
3272 if (moveNum <= backwardMostMove) {
\r
3273 /* We don't know what the board looked like before
\r
3274 this move. Punt. */
\r
3275 strcpy(parseList[moveNum - 1], move_str);
\r
3276 strcat(parseList[moveNum - 1], " ");
\r
3277 strcat(parseList[moveNum - 1], elapsed_time);
\r
3278 moveList[moveNum - 1][0] = NULLCHAR;
\r
3279 } else if (ParseOneMove(move_str, moveNum - 1, &moveType,
\r
3280 &fromX, &fromY, &toX, &toY, &promoChar)) {
\r
3281 (void) CoordsToAlgebraic(boards[moveNum - 1],
\r
3282 PosFlags(moveNum - 1), EP_UNKNOWN,
\r
3283 fromY, fromX, toY, toX, promoChar,
\r
3284 parseList[moveNum-1]);
\r
3285 switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN,
\r
3286 castlingRights[moveNum]) ) {
\r
3288 case MT_STALEMATE:
\r
3292 if(gameInfo.variant != VariantShogi)
\r
3293 strcat(parseList[moveNum - 1], "+");
\r
3295 case MT_CHECKMATE:
\r
3296 strcat(parseList[moveNum - 1], "#");
\r
3299 strcat(parseList[moveNum - 1], " ");
\r
3300 strcat(parseList[moveNum - 1], elapsed_time);
\r
3301 /* currentMoveString is set as a side-effect of ParseOneMove */
\r
3302 strcpy(moveList[moveNum - 1], currentMoveString);
\r
3303 strcat(moveList[moveNum - 1], "\n");
\r
3304 } else if (strcmp(move_str, "none") == 0) {
\r
3305 /* Again, we don't know what the board looked like;
\r
3306 this is really the start of the game. */
\r
3307 parseList[moveNum - 1][0] = NULLCHAR;
\r
3308 moveList[moveNum - 1][0] = NULLCHAR;
\r
3309 backwardMostMove = moveNum;
\r
3310 startedFromSetupPosition = TRUE;
\r
3311 fromX = fromY = toX = toY = -1;
\r
3313 /* Move from ICS was illegal!? Punt. */
\r
3315 if (appData.testLegality && appData.debugMode) {
\r
3316 sprintf(str, "Illegal move \"%s\" from ICS", move_str);
\r
3317 DisplayError(str, 0);
\r
3320 strcpy(parseList[moveNum - 1], move_str);
\r
3321 strcat(parseList[moveNum - 1], " ");
\r
3322 strcat(parseList[moveNum - 1], elapsed_time);
\r
3323 moveList[moveNum - 1][0] = NULLCHAR;
\r
3324 fromX = fromY = toX = toY = -1;
\r
3326 if (appData.debugMode) {
\r
3327 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
\r
3328 setbuf(debugFP, NULL);
\r
3332 /* Send move to chess program (BEFORE animating it). */
\r
3333 if (appData.zippyPlay && !newGame && newMove &&
\r
3334 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
\r
3336 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
\r
3337 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
\r
3338 if (moveList[moveNum - 1][0] == NULLCHAR) {
\r
3339 sprintf(str, "Couldn't parse move \"%s\" from ICS",
\r
3341 DisplayError(str, 0);
\r
3343 if (first.sendTime) {
\r
3344 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
\r
3346 SendMoveToProgram(moveNum - 1, &first);
\r
3348 firstMove = FALSE;
\r
3349 if (first.useColors) {
\r
3350 SendToProgram(gameMode == IcsPlayingWhite ?
\r
3352 "black\ngo\n", &first);
\r
3354 SendToProgram("go\n", &first);
\r
3356 first.maybeThinking = TRUE;
\r
3359 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
\r
3360 if (moveList[moveNum - 1][0] == NULLCHAR) {
\r
3361 sprintf(str, "Couldn't parse move \"%s\" from ICS", move_str);
\r
3362 DisplayError(str, 0);
\r
3364 SendMoveToProgram(moveNum - 1, &first);
\r
3371 if (moveNum > 0 && !gotPremove) {
\r
3372 /* If move comes from a remote source, animate it. If it
\r
3373 isn't remote, it will have already been animated. */
\r
3374 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
\r
3375 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
\r
3377 if (!pausing && appData.highlightLastMove) {
\r
3378 SetHighlights(fromX, fromY, toX, toY);
\r
3382 /* Start the clocks */
\r
3383 whiteFlag = blackFlag = FALSE;
\r
3384 appData.clockMode = !(basetime == 0 && increment == 0);
\r
3385 if (ticking == 0) {
\r
3386 ics_clock_paused = TRUE;
\r
3388 } else if (ticking == 1) {
\r
3389 ics_clock_paused = FALSE;
\r
3391 if (gameMode == IcsIdle ||
\r
3392 relation == RELATION_OBSERVING_STATIC ||
\r
3393 relation == RELATION_EXAMINING ||
\r
3395 DisplayBothClocks();
\r
3399 /* Display opponents and material strengths */
\r
3400 if (gameInfo.variant != VariantBughouse &&
\r
3401 gameInfo.variant != VariantCrazyhouse) {
\r
3402 if (tinyLayout || smallLayout) {
\r
3403 sprintf(str, "%s(%d) %s(%d) {%d %d}",
\r
3404 gameInfo.white, white_stren, gameInfo.black, black_stren,
\r
3405 basetime, increment);
\r
3407 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
\r
3408 gameInfo.white, white_stren, gameInfo.black, black_stren,
\r
3409 basetime, increment);
\r
3411 DisplayTitle(str);
\r
3415 /* Display the board */
\r
3418 if (appData.premove)
\r
3419 if (!gotPremove ||
\r
3420 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
\r
3421 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
\r
3422 ClearPremoveHighlights();
\r
3424 DrawPosition(FALSE, boards[currentMove]);
\r
3425 DisplayMove(moveNum - 1);
\r
3426 if (appData.ringBellAfterMoves && !ics_user_moved)
\r
3430 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
\r
3434 GetMoveListEvent()
\r
3436 char buf[MSG_SIZ];
\r
3437 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
\r
3438 ics_getting_history = H_REQUESTED;
\r
3439 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
\r
3445 AnalysisPeriodicEvent(force)
\r
3448 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
\r
3449 && !force) || !appData.periodicUpdates)
\r
3452 /* Send . command to Crafty to collect stats */
\r
3453 SendToProgram(".\n", &first);
\r
3455 /* Don't send another until we get a response (this makes
\r
3456 us stop sending to old Crafty's which don't understand
\r
3457 the "." command (sending illegal cmds resets node count & time,
\r
3458 which looks bad)) */
\r
3459 programStats.ok_to_send = 0;
\r
3463 SendMoveToProgram(moveNum, cps)
\r
3465 ChessProgramState *cps;
\r
3467 char buf[MSG_SIZ];
\r
3468 if (cps->useUsermove) {
\r
3469 SendToProgram("usermove ", cps);
\r
3471 if (cps->useSAN) {
\r
3473 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
\r
3474 int len = space - parseList[moveNum];
\r
3475 memcpy(buf, parseList[moveNum], len);
\r
3476 buf[len++] = '\n';
\r
3477 buf[len] = NULLCHAR;
\r
3479 sprintf(buf, "%s\n", parseList[moveNum]);
\r
3481 /* [HGM] decrement all digits to code ranks starting from 0 */
\r
3482 if(BOARD_HEIGHT>9) {
\r
3484 while(*p) { if(*p < 'A') (*p)--; p++; }
\r
3486 SendToProgram(buf, cps);
\r
3488 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
\r
3489 * the engine. It would be nice to have a better way to identify castle
\r
3491 if(gameInfo.variant == VariantFischeRandom && cps->useOOCastle) {
\r
3492 int fromX = moveList[moveNum][0] - AAA;
\r
3493 int fromY = moveList[moveNum][1] - ONE;
\r
3494 int toX = moveList[moveNum][2] - AAA;
\r
3495 int toY = moveList[moveNum][3] - ONE;
\r
3496 if((boards[currentMove][fromY][fromX] == WhiteKing
\r
3497 && boards[currentMove][toY][toX] == WhiteRook)
\r
3498 || (boards[currentMove][fromY][fromX] == BlackKing
\r
3499 && boards[currentMove][toY][toX] == BlackRook)) {
\r
3500 if(toX > fromX) SendToProgram("O-O\n", cps);
\r
3501 else SendToProgram("O-O-O\n", cps);
\r