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,
\r
6 * Massachusetts. Enhancements Copyright
\r
7 * 1992-2001,2002,2003,2004,2005,2006,2007,2008,2009 Free Software
\r
10 * The following terms apply to Digital Equipment Corporation's copyright
\r
11 * interest in XBoard:
\r
12 * ------------------------------------------------------------------------
\r
13 * All Rights Reserved
\r
15 * Permission to use, copy, modify, and distribute this software and its
\r
16 * documentation for any purpose and without fee is hereby granted,
\r
17 * provided that the above copyright notice appear in all copies and that
\r
18 * both that copyright notice and this permission notice appear in
\r
19 * supporting documentation, and that the name of Digital not be
\r
20 * used in advertising or publicity pertaining to distribution of the
\r
21 * software without specific, written prior permission.
\r
23 * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
\r
24 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
\r
25 * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
\r
26 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
\r
27 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
\r
28 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
\r
30 * ------------------------------------------------------------------------
\r
32 * The following terms apply to the enhanced version of XBoard
\r
33 * distributed by the Free Software Foundation:
\r
34 * ------------------------------------------------------------------------
\r
36 * GNU XBoard is free software: you can redistribute it and/or modify
\r
37 * it under the terms of the GNU General Public License as published by
\r
38 * the Free Software Foundation, either version 3 of the License, or (at
\r
39 * your option) any later version.
\r
41 * GNU XBoard is distributed in the hope that it will be useful, but
\r
42 * WITHOUT ANY WARRANTY; without even the implied warranty of
\r
43 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
\r
44 * General Public License for more details.
\r
46 * You should have received a copy of the GNU General Public License
\r
47 * along with this program. If not, see http://www.gnu.org/licenses/. *
\r
49 *------------------------------------------------------------------------
\r
50 ** See the file ChangeLog for a revision history. */
\r
52 /* [AS] Also useful here for debugging */
\r
54 #include <windows.h>
\r
56 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
\r
60 #define DoSleep( n ) if( (n) >= 0) sleep(n)
\r
70 #include <sys/types.h>
\r
71 #include <sys/stat.h>
\r
76 # include <stdlib.h>
\r
77 # include <string.h>
\r
78 #else /* not STDC_HEADERS */
\r
80 # include <string.h>
\r
81 # else /* not HAVE_STRING_H */
\r
82 # include <strings.h>
\r
83 # endif /* not HAVE_STRING_H */
\r
84 #endif /* not STDC_HEADERS */
\r
86 #if HAVE_SYS_FCNTL_H
\r
87 # include <sys/fcntl.h>
\r
88 #else /* not HAVE_SYS_FCNTL_H */
\r
91 # endif /* HAVE_FCNTL_H */
\r
92 #endif /* not HAVE_SYS_FCNTL_H */
\r
94 #if TIME_WITH_SYS_TIME
\r
95 # include <sys/time.h>
\r
98 # if HAVE_SYS_TIME_H
\r
99 # include <sys/time.h>
\r
105 #if defined(_amigados) && !defined(__GNUC__)
\r
107 int tz_minuteswest;
\r
110 extern int gettimeofday(struct timeval *, struct timezone *);
\r
114 # include <unistd.h>
\r
117 #include "common.h"
\r
118 #include "frontend.h"
\r
119 #include "backend.h"
\r
120 #include "parser.h"
\r
123 # include "zippy.h"
\r
125 #include "backendz.h"
\r
126 #include "gettext.h"
\r
129 # define _(s) gettext (s)
\r
130 # define N_(s) gettext_noop (s)
\r
137 /* A point in time */
\r
139 long sec; /* Assuming this is >= 32 bits */
\r
140 int ms; /* Assuming this is >= 16 bits */
\r
143 int establish P((void));
\r
144 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
\r
145 char *buf, int count, int error));
\r
146 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
\r
147 char *buf, int count, int error));
\r
148 void SendToICS P((char *s));
\r
149 void SendToICSDelayed P((char *s, long msdelay));
\r
150 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
\r
151 int toX, int toY));
\r
152 void InitPosition P((int redraw));
\r
153 void HandleMachineMove P((char *message, ChessProgramState *cps));
\r
154 int AutoPlayOneMove P((void));
\r
155 int LoadGameOneMove P((ChessMove readAhead));
\r
156 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
\r
157 int LoadPositionFromFile P((char *filename, int n, char *title));
\r
158 int SavePositionToFile P((char *filename));
\r
159 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
\r
161 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
\r
162 void ShowMove P((int fromX, int fromY, int toX, int toY));
\r
163 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
\r
164 /*char*/int promoChar));
\r
165 void BackwardInner P((int target));
\r
166 void ForwardInner P((int target));
\r
167 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
\r
168 void EditPositionDone P((void));
\r
169 void PrintOpponents P((FILE *fp));
\r
170 void PrintPosition P((FILE *fp, int move));
\r
171 void StartChessProgram P((ChessProgramState *cps));
\r
172 void SendToProgram P((char *message, ChessProgramState *cps));
\r
173 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
\r
174 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
\r
175 char *buf, int count, int error));
\r
176 void SendTimeControl P((ChessProgramState *cps,
\r
177 int mps, long tc, int inc, int sd, int st));
\r
178 char *TimeControlTagValue P((void));
\r
179 void Attention P((ChessProgramState *cps));
\r
180 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
\r
181 void ResurrectChessProgram P((void));
\r
182 void DisplayComment P((int moveNumber, char *text));
\r
183 void DisplayMove P((int moveNumber));
\r
184 void DisplayAnalysis P((void));
\r
186 void ParseGameHistory P((char *game));
\r
187 void ParseBoard12 P((char *string));
\r
188 void StartClocks P((void));
\r
189 void SwitchClocks P((void));
\r
190 void StopClocks P((void));
\r
191 void ResetClocks P((void));
\r
192 char *PGNDate P((void));
\r
193 void SetGameInfo P((void));
\r
194 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
\r
195 int RegisterMove P((void));
\r
196 void MakeRegisteredMove P((void));
\r
197 void TruncateGame P((void));
\r
198 int looking_at P((char *, int *, char *));
\r
199 void CopyPlayerNameIntoFileName P((char **, char *));
\r
200 char *SavePart P((char *));
\r
201 int SaveGameOldStyle P((FILE *));
\r
202 int SaveGamePGN P((FILE *));
\r
203 void GetTimeMark P((TimeMark *));
\r
204 long SubtractTimeMarks P((TimeMark *, TimeMark *));
\r
205 int CheckFlags P((void));
\r
206 long NextTickLength P((long));
\r
207 void CheckTimeControl P((void));
\r
208 void show_bytes P((FILE *, char *, int));
\r
209 int string_to_rating P((char *str));
\r
210 void ParseFeatures P((char* args, ChessProgramState *cps));
\r
211 void InitBackEnd3 P((void));
\r
212 void FeatureDone P((ChessProgramState* cps, int val));
\r
213 void InitChessProgram P((ChessProgramState *cps, int setup));
\r
214 void OutputKibitz(int window, char *text);
\r
215 int PerpetualChase(int first, int last);
\r
216 int EngineOutputIsUp();
\r
217 void InitDrawingSizes(int x, int y);
\r
220 extern void ConsoleCreate();
\r
223 ChessProgramState *WhitePlayer();
\r
224 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
\r
225 int VerifyDisplayMode P(());
\r
227 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
\r
228 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
\r
229 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
\r
230 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
\r
231 extern char installDir[MSG_SIZ];
\r
233 extern int tinyLayout, smallLayout;
\r
234 ChessProgramStats programStats;
\r
235 static int exiting = 0; /* [HGM] moved to top */
\r
236 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
\r
237 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
\r
238 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
\r
239 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
\r
240 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
\r
241 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
\r
242 int opponentKibitzes;
\r
244 /* States for ics_getting_history */
\r
246 #define H_REQUESTED 1
\r
247 #define H_GOT_REQ_HEADER 2
\r
248 #define H_GOT_UNREQ_HEADER 3
\r
249 #define H_GETTING_MOVES 4
\r
250 #define H_GOT_UNWANTED_HEADER 5
\r
252 /* whosays values for GameEnds */
\r
254 #define GE_ENGINE 1
\r
255 #define GE_PLAYER 2
\r
257 #define GE_XBOARD 4
\r
258 #define GE_ENGINE1 5
\r
259 #define GE_ENGINE2 6
\r
261 /* Maximum number of games in a cmail message */
\r
262 #define CMAIL_MAX_GAMES 20
\r
264 /* Different types of move when calling RegisterMove */
\r
265 #define CMAIL_MOVE 0
\r
266 #define CMAIL_RESIGN 1
\r
267 #define CMAIL_DRAW 2
\r
268 #define CMAIL_ACCEPT 3
\r
270 /* Different types of result to remember for each game */
\r
271 #define CMAIL_NOT_RESULT 0
\r
272 #define CMAIL_OLD_RESULT 1
\r
273 #define CMAIL_NEW_RESULT 2
\r
275 /* Telnet protocol constants */
\r
276 #define TN_WILL 0373
\r
277 #define TN_WONT 0374
\r
279 #define TN_DONT 0376
\r
280 #define TN_IAC 0377
\r
281 #define TN_ECHO 0001
\r
282 #define TN_SGA 0003
\r
286 static char * safeStrCpy( char * dst, const char * src, size_t count )
\r
288 assert( dst != NULL );
\r
289 assert( src != NULL );
\r
290 assert( count > 0 );
\r
292 strncpy( dst, src, count );
\r
293 dst[ count-1 ] = '\0';
\r
298 //[HGM] for future use? Conditioned out for now to suppress warning.
\r
299 static char * safeStrCat( char * dst, const char * src, size_t count )
\r
303 assert( dst != NULL );
\r
304 assert( src != NULL );
\r
305 assert( count > 0 );
\r
307 dst_len = strlen(dst);
\r
309 assert( count > dst_len ); /* Buffer size must be greater than current length */
\r
311 safeStrCpy( dst + dst_len, src, count - dst_len );
\r
317 /* Some compiler can't cast u64 to double
\r
318 * This function do the job for us:
\r
320 * We use the highest bit for cast, this only
\r
321 * works if the highest bit is not
\r
322 * in use (This should not happen)
\r
324 * We used this for all compiler
\r
327 u64ToDouble(u64 value)
\r
330 u64 tmp = value & u64Const(0x7fffffffffffffff);
\r
331 r = (double)(s64)tmp;
\r
332 if (value & u64Const(0x8000000000000000))
\r
333 r += 9.2233720368547758080e18; /* 2^63 */
\r
337 /* Fake up flags for now, as we aren't keeping track of castling
\r
338 availability yet. [HGM] Change of logic: the flag now only
\r
339 indicates the type of castlings allowed by the rule of the game.
\r
340 The actual rights themselves are maintained in the array
\r
341 castlingRights, as part of the game history, and are not probed
\r
347 int flags = F_ALL_CASTLE_OK;
\r
348 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
\r
349 switch (gameInfo.variant) {
\r
350 case VariantSuicide:
\r
351 flags &= ~F_ALL_CASTLE_OK;
\r
352 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
\r
353 flags |= F_IGNORE_CHECK;
\r
354 case VariantLosers:
\r
355 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
\r
357 case VariantAtomic:
\r
358 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
\r
360 case VariantKriegspiel:
\r
361 flags |= F_KRIEGSPIEL_CAPTURE;
\r
363 case VariantCapaRandom:
\r
364 case VariantFischeRandom:
\r
365 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
\r
366 case VariantNoCastle:
\r
367 case VariantShatranj:
\r
368 case VariantCourier:
\r
369 flags &= ~F_ALL_CASTLE_OK;
\r
377 FILE *gameFileFP, *debugFP;
\r
380 [AS] Note: sometimes, the sscanf() function is used to parse the input
\r
381 into a fixed-size buffer. Because of this, we must be prepared to
\r
382 receive strings as long as the size of the input buffer, which is currently
\r
383 set to 4K for Windows and 8K for the rest.
\r
384 So, we must either allocate sufficiently large buffers here, or
\r
385 reduce the size of the input buffer in the input reading part.
\r
388 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
\r
389 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
\r
390 char thinkOutput1[MSG_SIZ*10];
\r
392 ChessProgramState first, second;
\r
394 /* premove variables */
\r
395 int premoveToX = 0;
\r
396 int premoveToY = 0;
\r
397 int premoveFromX = 0;
\r
398 int premoveFromY = 0;
\r
399 int premovePromoChar = 0;
\r
400 int gotPremove = 0;
\r
401 Boolean alarmSounded;
\r
402 /* end premove variables */
\r
404 char *ics_prefix = "$";
\r
405 int ics_type = ICS_GENERIC;
\r
407 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
\r
408 int pauseExamForwardMostMove = 0;
\r
409 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
\r
410 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
\r
411 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
\r
412 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
\r
413 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
\r
414 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
\r
415 int whiteFlag = FALSE, blackFlag = FALSE;
\r
416 int userOfferedDraw = FALSE;
\r
417 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
\r
418 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
\r
419 int cmailMoveType[CMAIL_MAX_GAMES];
\r
420 long ics_clock_paused = 0;
\r
421 ProcRef icsPR = NoProc, cmailPR = NoProc;
\r
422 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
\r
423 GameMode gameMode = BeginningOfGame;
\r
424 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
\r
425 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
\r
426 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
\r
427 int hiddenThinkOutputState = 0; /* [AS] */
\r
428 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
\r
429 int adjudicateLossPlies = 6;
\r
430 char white_holding[64], black_holding[64];
\r
431 TimeMark lastNodeCountTime;
\r
432 long lastNodeCount=0;
\r
433 int have_sent_ICS_logon = 0;
\r
434 int movesPerSession;
\r
435 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
\r
436 long timeControl_2; /* [AS] Allow separate time controls */
\r
437 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
\r
438 long timeRemaining[2][MAX_MOVES];
\r
440 TimeMark programStartTime;
\r
441 char ics_handle[MSG_SIZ];
\r
442 int have_set_title = 0;
\r
444 /* animateTraining preserves the state of appData.animate
\r
445 * when Training mode is activated. This allows the
\r
446 * response to be animated when appData.animate == TRUE and
\r
447 * appData.animateDragging == TRUE.
\r
449 Boolean animateTraining;
\r
455 Board boards[MAX_MOVES];
\r
456 /* [HGM] Following 7 needed for accurate legality tests: */
\r
457 char epStatus[MAX_MOVES];
\r
458 char castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1
\r
459 char castlingRank[BOARD_SIZE]; // and corresponding ranks
\r
460 char initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];
\r
461 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
\r
462 int initialRulePlies, FENrulePlies;
\r
464 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
\r
466 int shuffleOpenings;
\r
468 ChessSquare FIDEArray[2][BOARD_SIZE] = {
\r
469 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
\r
470 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
\r
471 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
\r
472 BlackKing, BlackBishop, BlackKnight, BlackRook }
\r
475 ChessSquare twoKingsArray[2][BOARD_SIZE] = {
\r
476 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
\r
477 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
\r
478 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
\r
479 BlackKing, BlackKing, BlackKnight, BlackRook }
\r
482 ChessSquare KnightmateArray[2][BOARD_SIZE] = {
\r
483 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
\r
484 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
\r
485 { BlackRook, BlackMan, BlackBishop, BlackQueen,
\r
486 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
\r
489 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */
\r
490 { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
\r
491 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
\r
492 { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
\r
493 BlackKing, BlackBishop, BlackKnight, BlackRook }
\r
496 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
\r
497 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
\r
498 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
\r
499 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
\r
500 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
\r
504 #if (BOARD_SIZE>=10)
\r
505 ChessSquare ShogiArray[2][BOARD_SIZE] = {
\r
506 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
\r
507 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
\r
508 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
\r
509 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
\r
512 ChessSquare XiangqiArray[2][BOARD_SIZE] = {
\r
513 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
\r
514 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
\r
515 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
\r
516 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
\r
519 ChessSquare CapablancaArray[2][BOARD_SIZE] = {
\r
520 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
\r
521 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
\r
522 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
\r
523 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
\r
526 ChessSquare GreatArray[2][BOARD_SIZE] = {
\r
527 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
\r
528 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
\r
529 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
\r
530 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
\r
533 ChessSquare JanusArray[2][BOARD_SIZE] = {
\r
534 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
\r
535 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
\r
536 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
\r
537 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
\r
541 ChessSquare GothicArray[2][BOARD_SIZE] = {
\r
542 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
\r
543 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
\r
544 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
\r
545 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
\r
548 #define GothicArray CapablancaArray
\r
552 ChessSquare FalconArray[2][BOARD_SIZE] = {
\r
553 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
\r
554 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
\r
555 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
\r
556 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
\r
559 #define FalconArray CapablancaArray
\r
562 #else // !(BOARD_SIZE>=10)
\r
563 #define XiangqiPosition FIDEArray
\r
564 #define CapablancaArray FIDEArray
\r
565 #define GothicArray FIDEArray
\r
566 #define GreatArray FIDEArray
\r
567 #endif // !(BOARD_SIZE>=10)
\r
569 #if (BOARD_SIZE>=12)
\r
570 ChessSquare CourierArray[2][BOARD_SIZE] = {
\r
571 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
\r
572 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
\r
573 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
\r
574 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
\r
576 #else // !(BOARD_SIZE>=12)
\r
577 #define CourierArray CapablancaArray
\r
578 #endif // !(BOARD_SIZE>=12)
\r
581 Board initialPosition;
\r
584 /* Convert str to a rating. Checks for special cases of "----",
\r
586 "++++", etc. Also strips ()'s */
\r
588 string_to_rating(str)
\r
591 while(*str && !isdigit(*str)) ++str;
\r
593 return 0; /* One of the special "no rating" cases */
\r
599 ClearProgramStats()
\r
601 /* Init programStats */
\r
602 programStats.movelist[0] = 0;
\r
603 programStats.depth = 0;
\r
604 programStats.nr_moves = 0;
\r
605 programStats.moves_left = 0;
\r
606 programStats.nodes = 0;
\r
607 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
\r
608 programStats.score = 0;
\r
609 programStats.got_only_move = 0;
\r
610 programStats.got_fail = 0;
\r
611 programStats.line_is_book = 0;
\r
617 int matched, min, sec;
\r
619 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
\r
621 GetTimeMark(&programStartTime);
\r
622 srand(programStartTime.ms); // [HGM] book: makes sure random is unpredictabe to msec level
\r
624 ClearProgramStats();
\r
625 programStats.ok_to_send = 1;
\r
626 programStats.seen_stat = 0;
\r
629 * Initialize game list
\r
631 ListNew(&gameList);
\r
635 * Internet chess server status
\r
637 if (appData.icsActive) {
\r
638 appData.matchMode = FALSE;
\r
639 appData.matchGames = 0;
\r
641 appData.noChessProgram = !appData.zippyPlay;
\r
643 appData.zippyPlay = FALSE;
\r
644 appData.zippyTalk = FALSE;
\r
645 appData.noChessProgram = TRUE;
\r
647 if (*appData.icsHelper != NULLCHAR) {
\r
648 appData.useTelnet = TRUE;
\r
649 appData.telnetProgram = appData.icsHelper;
\r
652 appData.zippyTalk = appData.zippyPlay = FALSE;
\r
655 /* [AS] Initialize pv info list [HGM] and game state */
\r
659 for( i=0; i<MAX_MOVES; i++ ) {
\r
660 pvInfoList[i].depth = -1;
\r
661 epStatus[i]=EP_NONE;
\r
662 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
\r
667 * Parse timeControl resource
\r
669 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
\r
670 appData.movesPerSession)) {
\r
672 sprintf(buf, _("bad timeControl option %s"), appData.timeControl);
\r
673 DisplayFatalError(buf, 0, 2);
\r
677 * Parse searchTime resource
\r
679 if (*appData.searchTime != NULLCHAR) {
\r
680 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
\r
681 if (matched == 1) {
\r
682 searchTime = min * 60;
\r
683 } else if (matched == 2) {
\r
684 searchTime = min * 60 + sec;
\r
687 sprintf(buf, _("bad searchTime option %s"), appData.searchTime);
\r
688 DisplayFatalError(buf, 0, 2);
\r
692 /* [AS] Adjudication threshold */
\r
693 adjudicateLossThreshold = appData.adjudicateLossThreshold;
\r
695 first.which = "first";
\r
696 second.which = "second";
\r
697 first.maybeThinking = second.maybeThinking = FALSE;
\r
698 first.pr = second.pr = NoProc;
\r
699 first.isr = second.isr = NULL;
\r
700 first.sendTime = second.sendTime = 2;
\r
701 first.sendDrawOffers = 1;
\r
702 if (appData.firstPlaysBlack) {
\r
703 first.twoMachinesColor = "black\n";
\r
704 second.twoMachinesColor = "white\n";
\r
706 first.twoMachinesColor = "white\n";
\r
707 second.twoMachinesColor = "black\n";
\r
709 first.program = appData.firstChessProgram;
\r
710 second.program = appData.secondChessProgram;
\r
711 first.host = appData.firstHost;
\r
712 second.host = appData.secondHost;
\r
713 first.dir = appData.firstDirectory;
\r
714 second.dir = appData.secondDirectory;
\r
715 first.other = &second;
\r
716 second.other = &first;
\r
717 first.initString = appData.initString;
\r
718 second.initString = appData.secondInitString;
\r
719 first.computerString = appData.firstComputerString;
\r
720 second.computerString = appData.secondComputerString;
\r
721 first.useSigint = second.useSigint = TRUE;
\r
722 first.useSigterm = second.useSigterm = TRUE;
\r
723 first.reuse = appData.reuseFirst;
\r
724 second.reuse = appData.reuseSecond;
\r
725 first.nps = appData.firstNPS; // [HGM] nps: copy nodes per second
\r
726 second.nps = appData.secondNPS;
\r
727 first.useSetboard = second.useSetboard = FALSE;
\r
728 first.useSAN = second.useSAN = FALSE;
\r
729 first.usePing = second.usePing = FALSE;
\r
730 first.lastPing = second.lastPing = 0;
\r
731 first.lastPong = second.lastPong = 0;
\r
732 first.usePlayother = second.usePlayother = FALSE;
\r
733 first.useColors = second.useColors = TRUE;
\r
734 first.useUsermove = second.useUsermove = FALSE;
\r
735 first.sendICS = second.sendICS = FALSE;
\r
736 first.sendName = second.sendName = appData.icsActive;
\r
737 first.sdKludge = second.sdKludge = FALSE;
\r
738 first.stKludge = second.stKludge = FALSE;
\r
739 TidyProgramName(first.program, first.host, first.tidy);
\r
740 TidyProgramName(second.program, second.host, second.tidy);
\r
741 first.matchWins = second.matchWins = 0;
\r
742 strcpy(first.variants, appData.variant);
\r
743 strcpy(second.variants, appData.variant);
\r
744 first.analysisSupport = second.analysisSupport = 2; /* detect */
\r
745 first.analyzing = second.analyzing = FALSE;
\r
746 first.initDone = second.initDone = FALSE;
\r
748 /* New features added by Tord: */
\r
749 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
\r
750 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
\r
751 /* End of new features added by Tord. */
\r
752 first.fenOverride = appData.fenOverride1;
\r
753 second.fenOverride = appData.fenOverride2;
\r
755 /* [HGM] time odds: set factor for each machine */
\r
756 first.timeOdds = appData.firstTimeOdds;
\r
757 second.timeOdds = appData.secondTimeOdds;
\r
759 if(appData.timeOddsMode) {
\r
760 norm = first.timeOdds;
\r
761 if(norm > second.timeOdds) norm = second.timeOdds;
\r
763 first.timeOdds /= norm;
\r
764 second.timeOdds /= norm;
\r
767 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
\r
768 first.accumulateTC = appData.firstAccumulateTC;
\r
769 second.accumulateTC = appData.secondAccumulateTC;
\r
770 first.maxNrOfSessions = second.maxNrOfSessions = 1;
\r
773 first.debug = second.debug = FALSE;
\r
774 first.supportsNPS = second.supportsNPS = UNKNOWN;
\r
776 /* [HGM] options */
\r
777 first.optionSettings = appData.firstOptions;
\r
778 second.optionSettings = appData.secondOptions;
\r
780 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
\r
781 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
\r
782 first.isUCI = appData.firstIsUCI; /* [AS] */
\r
783 second.isUCI = appData.secondIsUCI; /* [AS] */
\r
784 first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
\r
785 second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
\r
787 if (appData.firstProtocolVersion > PROTOVER ||
\r
788 appData.firstProtocolVersion < 1) {
\r
790 sprintf(buf, _("protocol version %d not supported"),
\r
791 appData.firstProtocolVersion);
\r
792 DisplayFatalError(buf, 0, 2);
\r
794 first.protocolVersion = appData.firstProtocolVersion;
\r
797 if (appData.secondProtocolVersion > PROTOVER ||
\r
798 appData.secondProtocolVersion < 1) {
\r
800 sprintf(buf, _("protocol version %d not supported"),
\r
801 appData.secondProtocolVersion);
\r
802 DisplayFatalError(buf, 0, 2);
\r
804 second.protocolVersion = appData.secondProtocolVersion;
\r
807 if (appData.icsActive) {
\r
808 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
\r
809 } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
\r
810 appData.clockMode = FALSE;
\r
811 first.sendTime = second.sendTime = 0;
\r
815 /* Override some settings from environment variables, for backward
\r
816 compatibility. Unfortunately it's not feasible to have the env
\r
817 vars just set defaults, at least in xboard. Ugh.
\r
819 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
\r
824 if (appData.noChessProgram) {
\r
825 programVersion = (char*) malloc(5 + strlen(PRODUCT) + strlen(VERSION)
\r
826 + strlen(PATCHLEVEL));
\r
827 sprintf(programVersion, "%s %s.%s", PRODUCT, VERSION, PATCHLEVEL);
\r
832 while (*q != ' ' && *q != NULLCHAR) q++;
\r
834 while (p > first.program && *(p-1) != '/' && *(p-1) != '\\') p--; /* [HGM] bckslash added */
\r
835 programVersion = (char*) malloc(8 + strlen(PRODUCT) + strlen(VERSION)
\r
836 + strlen(PATCHLEVEL) + (q - p));
\r
837 sprintf(programVersion, "%s %s.%s + ", PRODUCT, VERSION, PATCHLEVEL);
\r
838 strncat(programVersion, p, q - p);
\r
840 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
\r
841 programVersion = (char*) malloc(8 + strlen(PRODUCT) + strlen(VERSION)
\r
842 + strlen(PATCHLEVEL) + strlen(first.tidy));
\r
843 sprintf(programVersion, "%s %s.%s + %s", PRODUCT, VERSION, PATCHLEVEL, first.tidy);
\r
847 if (!appData.icsActive) {
\r
849 /* Check for variants that are supported only in ICS mode,
\r
850 or not at all. Some that are accepted here nevertheless
\r
851 have bugs; see comments below.
\r
853 VariantClass variant = StringToVariant(appData.variant);
\r
855 case VariantBughouse: /* need four players and two boards */
\r
856 case VariantKriegspiel: /* need to hide pieces and move details */
\r
857 /* case VariantFischeRandom: (Fabien: moved below) */
\r
858 sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
\r
859 DisplayFatalError(buf, 0, 2);
\r
862 case VariantUnknown:
\r
863 case VariantLoadable:
\r
873 sprintf(buf, _("Unknown variant name %s"), appData.variant);
\r
874 DisplayFatalError(buf, 0, 2);
\r
877 case VariantXiangqi: /* [HGM] repetition rules not implemented */
\r
878 case VariantFairy: /* [HGM] TestLegality definitely off! */
\r
879 case VariantGothic: /* [HGM] should work */
\r
880 case VariantCapablanca: /* [HGM] should work */
\r
881 case VariantCourier: /* [HGM] initial forced moves not implemented */
\r
882 case VariantShogi: /* [HGM] drops not tested for legality */
\r
883 case VariantKnightmate: /* [HGM] should work */
\r
884 case VariantCylinder: /* [HGM] untested */
\r
885 case VariantFalcon: /* [HGM] untested */
\r
886 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
\r
887 offboard interposition not understood */
\r
888 case VariantNormal: /* definitely works! */
\r
889 case VariantWildCastle: /* pieces not automatically shuffled */
\r
890 case VariantNoCastle: /* pieces not automatically shuffled */
\r
891 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
\r
892 case VariantLosers: /* should work except for win condition,
\r
893 and doesn't know captures are mandatory */
\r
894 case VariantSuicide: /* should work except for win condition,
\r
895 and doesn't know captures are mandatory */
\r
896 case VariantGiveaway: /* should work except for win condition,
\r
897 and doesn't know captures are mandatory */
\r
898 case VariantTwoKings: /* should work */
\r
899 case VariantAtomic: /* should work except for win condition */
\r
900 case Variant3Check: /* should work except for win condition */
\r
901 case VariantShatranj: /* should work except for all win conditions */
\r
902 case VariantBerolina: /* might work if TestLegality is off */
\r
903 case VariantCapaRandom: /* should work */
\r
904 case VariantJanus: /* should work */
\r
905 case VariantSuper: /* experimental */
\r
906 case VariantGreat: /* experimental, requires legality testing to be off */
\r
911 InitEngineUCI( installDir, &first ); // [HGM] moved here from winboard.c, to make available in xboard
\r
912 InitEngineUCI( installDir, &second );
\r
915 int NextIntegerFromString( char ** str, long * value )
\r
920 while( *s == ' ' || *s == '\t' ) {
\r
926 if( *s >= '0' && *s <= '9' ) {
\r
927 while( *s >= '0' && *s <= '9' ) {
\r
928 *value = *value * 10 + (*s - '0');
\r
940 int NextTimeControlFromString( char ** str, long * value )
\r
943 int result = NextIntegerFromString( str, &temp );
\r
945 if( result == 0 ) {
\r
946 *value = temp * 60; /* Minutes */
\r
947 if( **str == ':' ) {
\r
949 result = NextIntegerFromString( str, &temp );
\r
950 *value += temp; /* Seconds */
\r
957 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
\r
958 { /* [HGM] routine added to read '+moves/time' for secondary time control */
\r
959 int result = -1; long temp, temp2;
\r
961 if(**str != '+') return -1; // old params remain in force!
\r
963 if( NextTimeControlFromString( str, &temp ) ) return -1;
\r
966 /* time only: incremental or sudden-death time control */
\r
967 if(**str == '+') { /* increment follows; read it */
\r
969 if(result = NextIntegerFromString( str, &temp2)) return -1;
\r
970 *inc = temp2 * 1000;
\r
972 *moves = 0; *tc = temp * 1000;
\r
974 } else if(temp % 60 != 0) return -1; /* moves was given as min:sec */
\r
976 (*str)++; /* classical time control */
\r
977 result = NextTimeControlFromString( str, &temp2);
\r
980 *tc = temp2 * 1000;
\r
986 int GetTimeQuota(int movenr)
\r
987 { /* [HGM] get time to add from the multi-session time-control string */
\r
988 int moves=1; /* kludge to force reading of first session */
\r
989 long time, increment;
\r
990 char *s = fullTimeControlString;
\r
992 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
\r
994 if(moves) NextSessionFromString(&s, &moves, &time, &increment);
\r
995 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
\r
996 if(movenr == -1) return time; /* last move before new session */
\r
997 if(!moves) return increment; /* current session is incremental */
\r
998 if(movenr >= 0) movenr -= moves; /* we already finished this session */
\r
999 } while(movenr >= -1); /* try again for next session */
\r
1001 return 0; // no new time quota on this move
\r
1005 ParseTimeControl(tc, ti, mps)
\r
1011 int matched, min, sec;
\r
1013 matched = sscanf(tc, "%d:%d", &min, &sec);
\r
1014 if (matched == 1) {
\r
1015 timeControl = min * 60 * 1000;
\r
1016 } else if (matched == 2) {
\r
1017 timeControl = (min * 60 + sec) * 1000;
\r
1024 char buf[MSG_SIZ];
\r
1026 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
\r
1029 sprintf(buf, "+%d/%s+%d", mps, tc, ti);
\r
1030 else sprintf(buf, "+%s+%d", tc, ti);
\r
1033 sprintf(buf, "+%d/%s", mps, tc);
\r
1034 else sprintf(buf, "+%s", tc);
\r
1036 fullTimeControlString = StrSave(buf);
\r
1038 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
\r
1042 if( *tc == '/' ) {
\r
1043 /* Parse second time control */
\r
1046 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
\r
1054 timeControl_2 = tc2 * 1000;
\r
1057 timeControl_2 = 0;
\r
1064 timeControl = tc1 * 1000;
\r
1068 timeIncrement = ti * 1000; /* convert to ms */
\r
1069 movesPerSession = 0;
\r
1071 timeIncrement = 0;
\r
1072 movesPerSession = mps;
\r
1080 if (appData.debugMode) {
\r
1081 fprintf(debugFP, "%s\n", programVersion);
\r
1084 if (appData.matchGames > 0) {
\r
1085 appData.matchMode = TRUE;
\r
1086 } else if (appData.matchMode) {
\r
1087 appData.matchGames = 1;
\r
1089 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
\r
1090 appData.matchGames = appData.sameColorGames;
\r
1091 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
\r
1092 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
\r
1093 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
\r
1095 Reset(TRUE, FALSE);
\r
1096 if (appData.noChessProgram || first.protocolVersion == 1) {
\r
1099 /* kludge: allow timeout for initial "feature" commands */
\r
1101 DisplayMessage("", _("Starting chess program"));
\r
1102 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
\r
1107 InitBackEnd3 P((void))
\r
1109 GameMode initialMode;
\r
1110 char buf[MSG_SIZ];
\r
1113 InitChessProgram(&first, startedFromSetupPosition);
\r
1116 if (appData.icsActive) {
\r
1118 /* [DM] Make a console window if needed [HGM] merged ifs */
\r
1121 err = establish();
\r
1123 if (*appData.icsCommPort != NULLCHAR) {
\r
1124 sprintf(buf, _("Could not open comm port %s"),
\r
1125 appData.icsCommPort);
\r
1127 sprintf(buf, _("Could not connect to host %s, port %s"),
\r
1128 appData.icsHost, appData.icsPort);
\r
1130 DisplayFatalError(buf, err, 1);
\r
1135 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
\r
1137 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
\r
1138 } else if (appData.noChessProgram) {
\r
1144 if (*appData.cmailGameName != NULLCHAR) {
\r
1146 OpenLoopback(&cmailPR);
\r
1148 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
\r
1152 DisplayMessage("", "");
\r
1153 if (StrCaseCmp(appData.initialMode, "") == 0) {
\r
1154 initialMode = BeginningOfGame;
\r
1155 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
\r
1156 initialMode = TwoMachinesPlay;
\r
1157 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
\r
1158 initialMode = AnalyzeFile;
\r
1159 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
\r
1160 initialMode = AnalyzeMode;
\r
1161 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
\r
1162 initialMode = MachinePlaysWhite;
\r
1163 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
\r
1164 initialMode = MachinePlaysBlack;
\r
1165 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
\r
1166 initialMode = EditGame;
\r
1167 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
\r
1168 initialMode = EditPosition;
\r
1169 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
\r
1170 initialMode = Training;
\r
1172 sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
\r
1173 DisplayFatalError(buf, 0, 2);
\r
1177 if (appData.matchMode) {
\r
1178 /* Set up machine vs. machine match */
\r
1179 if (appData.noChessProgram) {
\r
1180 DisplayFatalError(_("Can't have a match with no chess programs"),
\r
1186 if (*appData.loadGameFile != NULLCHAR) {
\r
1187 int index = appData.loadGameIndex; // [HGM] autoinc
\r
1188 if(index<0) lastIndex = index = 1;
\r
1189 if (!LoadGameFromFile(appData.loadGameFile,
\r
1191 appData.loadGameFile, FALSE)) {
\r
1192 DisplayFatalError(_("Bad game file"), 0, 1);
\r
1195 } else if (*appData.loadPositionFile != NULLCHAR) {
\r
1196 int index = appData.loadPositionIndex; // [HGM] autoinc
\r
1197 if(index<0) lastIndex = index = 1;
\r
1198 if (!LoadPositionFromFile(appData.loadPositionFile,
\r
1200 appData.loadPositionFile)) {
\r
1201 DisplayFatalError(_("Bad position file"), 0, 1);
\r
1205 TwoMachinesEvent();
\r
1206 } else if (*appData.cmailGameName != NULLCHAR) {
\r
1207 /* Set up cmail mode */
\r
1208 ReloadCmailMsgEvent(TRUE);
\r
1210 /* Set up other modes */
\r
1211 if (initialMode == AnalyzeFile) {
\r
1212 if (*appData.loadGameFile == NULLCHAR) {
\r
1213 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
\r
1217 if (*appData.loadGameFile != NULLCHAR) {
\r
1218 (void) LoadGameFromFile(appData.loadGameFile,
\r
1219 appData.loadGameIndex,
\r
1220 appData.loadGameFile, TRUE);
\r
1221 } else if (*appData.loadPositionFile != NULLCHAR) {
\r
1222 (void) LoadPositionFromFile(appData.loadPositionFile,
\r
1223 appData.loadPositionIndex,
\r
1224 appData.loadPositionFile);
\r
1225 /* [HGM] try to make self-starting even after FEN load */
\r
1226 /* to allow automatic setup of fairy variants with wtm */
\r
1227 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
\r
1228 gameMode = BeginningOfGame;
\r
1229 setboardSpoiledMachineBlack = 1;
\r
1231 /* [HGM] loadPos: make that every new game uses the setup */
\r
1232 /* from file as long as we do not switch variant */
\r
1233 if(!blackPlaysFirst) { int i;
\r
1234 startedFromPositionFile = TRUE;
\r
1235 CopyBoard(filePosition, boards[0]);
\r
1236 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
\r
1239 if (initialMode == AnalyzeMode) {
\r
1240 if (appData.noChessProgram) {
\r
1241 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
\r
1244 if (appData.icsActive) {
\r
1245 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
\r
1248 AnalyzeModeEvent();
\r
1249 } else if (initialMode == AnalyzeFile) {
\r
1250 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
\r
1251 ShowThinkingEvent();
\r
1252 AnalyzeFileEvent();
\r
1253 AnalysisPeriodicEvent(1);
\r
1254 } else if (initialMode == MachinePlaysWhite) {
\r
1255 if (appData.noChessProgram) {
\r
1256 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
\r
1260 if (appData.icsActive) {
\r
1261 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
\r
1265 MachineWhiteEvent();
\r
1266 } else if (initialMode == MachinePlaysBlack) {
\r
1267 if (appData.noChessProgram) {
\r
1268 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
\r
1272 if (appData.icsActive) {
\r
1273 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
\r
1277 MachineBlackEvent();
\r
1278 } else if (initialMode == TwoMachinesPlay) {
\r
1279 if (appData.noChessProgram) {
\r
1280 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
\r
1284 if (appData.icsActive) {
\r
1285 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
\r
1289 TwoMachinesEvent();
\r
1290 } else if (initialMode == EditGame) {
\r
1292 } else if (initialMode == EditPosition) {
\r
1293 EditPositionEvent();
\r
1294 } else if (initialMode == Training) {
\r
1295 if (*appData.loadGameFile == NULLCHAR) {
\r
1296 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
\r
1305 * Establish will establish a contact to a remote host.port.
\r
1306 * Sets icsPR to a ProcRef for a process (or pseudo-process)
\r
1307 * used to talk to the host.
\r
1308 * Returns 0 if okay, error code if not.
\r
1313 char buf[MSG_SIZ];
\r
1315 if (*appData.icsCommPort != NULLCHAR) {
\r
1316 /* Talk to the host through a serial comm port */
\r
1317 return OpenCommPort(appData.icsCommPort, &icsPR);
\r
1319 } else if (*appData.gateway != NULLCHAR) {
\r
1320 if (*appData.remoteShell == NULLCHAR) {
\r
1321 /* Use the rcmd protocol to run telnet program on a gateway host */
\r
1322 sprintf(buf, "%s %s %s",
\r
1323 appData.telnetProgram, appData.icsHost, appData.icsPort);
\r
1324 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
\r
1327 /* Use the rsh program to run telnet program on a gateway host */
\r
1328 if (*appData.remoteUser == NULLCHAR) {
\r
1329 sprintf(buf, "%s %s %s %s %s", appData.remoteShell,
\r
1330 appData.gateway, appData.telnetProgram,
\r
1331 appData.icsHost, appData.icsPort);
\r
1333 sprintf(buf, "%s %s -l %s %s %s %s",
\r
1334 appData.remoteShell, appData.gateway,
\r
1335 appData.remoteUser, appData.telnetProgram,
\r
1336 appData.icsHost, appData.icsPort);
\r
1338 return StartChildProcess(buf, "", &icsPR);
\r
1341 } else if (appData.useTelnet) {
\r
1342 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
\r
1345 /* TCP socket interface differs somewhat between
\r
1346 Unix and NT; handle details in the front end.
\r
1348 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
\r
1353 show_bytes(fp, buf, count)
\r
1359 if (*buf < 040 || *(unsigned char *) buf > 0177) {
\r
1360 fprintf(fp, "\\%03o", *buf & 0xff);
\r
1369 /* Returns an errno value */
\r
1371 OutputMaybeTelnet(pr, message, count, outError)
\r
1377 char buf[8192], *p, *q, *buflim;
\r
1378 int left, newcount, outcount;
\r
1380 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
\r
1381 *appData.gateway != NULLCHAR) {
\r
1382 if (appData.debugMode) {
\r
1383 fprintf(debugFP, ">ICS: ");
\r
1384 show_bytes(debugFP, message, count);
\r
1385 fprintf(debugFP, "\n");
\r
1387 return OutputToProcess(pr, message, count, outError);
\r
1390 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
\r
1396 if (q >= buflim) {
\r
1397 if (appData.debugMode) {
\r
1398 fprintf(debugFP, ">ICS: ");
\r
1399 show_bytes(debugFP, buf, newcount);
\r
1400 fprintf(debugFP, "\n");
\r
1402 outcount = OutputToProcess(pr, buf, newcount, outError);
\r
1403 if (outcount < newcount) return -1; /* to be sure */
\r
1410 } else if (((unsigned char) *p) == TN_IAC) {
\r
1411 *q++ = (char) TN_IAC;
\r
1418 if (appData.debugMode) {
\r
1419 fprintf(debugFP, ">ICS: ");
\r
1420 show_bytes(debugFP, buf, newcount);
\r
1421 fprintf(debugFP, "\n");
\r
1423 outcount = OutputToProcess(pr, buf, newcount, outError);
\r
1424 if (outcount < newcount) return -1; /* to be sure */
\r
1429 read_from_player(isr, closure, message, count, error)
\r
1430 InputSourceRef isr;
\r
1436 int outError, outCount;
\r
1437 static int gotEof = 0;
\r
1439 /* Pass data read from player on to ICS */
\r
1442 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
\r
1443 if (outCount < count) {
\r
1444 DisplayFatalError(_("Error writing to ICS"), outError, 1);
\r
1446 } else if (count < 0) {
\r
1447 RemoveInputSource(isr);
\r
1448 DisplayFatalError(_("Error reading from keyboard"), error, 1);
\r
1449 } else if (gotEof++ > 0) {
\r
1450 RemoveInputSource(isr);
\r
1451 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
\r
1459 int count, outCount, outError;
\r
1461 if (icsPR == NULL) return;
\r
1463 count = strlen(s);
\r
1464 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
\r
1465 if (outCount < count) {
\r
1466 DisplayFatalError(_("Error writing to ICS"), outError, 1);
\r
1470 /* This is used for sending logon scripts to the ICS. Sending
\r
1471 without a delay causes problems when using timestamp on ICC
\r
1472 (at least on my machine). */
\r
1474 SendToICSDelayed(s,msdelay)
\r
1478 int count, outCount, outError;
\r
1480 if (icsPR == NULL) return;
\r
1482 count = strlen(s);
\r
1483 if (appData.debugMode) {
\r
1484 fprintf(debugFP, ">ICS: ");
\r
1485 show_bytes(debugFP, s, count);
\r
1486 fprintf(debugFP, "\n");
\r
1488 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
\r
1490 if (outCount < count) {
\r
1491 DisplayFatalError(_("Error writing to ICS"), outError, 1);
\r
1496 /* Remove all highlighting escape sequences in s
\r
1497 Also deletes any suffix starting with '('
\r
1500 StripHighlightAndTitle(s)
\r
1503 static char retbuf[MSG_SIZ];
\r
1506 while (*s != NULLCHAR) {
\r
1507 while (*s == '\033') {
\r
1508 while (*s != NULLCHAR && !isalpha(*s)) s++;
\r
1509 if (*s != NULLCHAR) s++;
\r
1511 while (*s != NULLCHAR && *s != '\033') {
\r
1512 if (*s == '(' || *s == '[') {
\r
1523 /* Remove all highlighting escape sequences in s */
\r
1528 static char retbuf[MSG_SIZ];
\r
1531 while (*s != NULLCHAR) {
\r
1532 while (*s == '\033') {
\r
1533 while (*s != NULLCHAR && !isalpha(*s)) s++;
\r
1534 if (*s != NULLCHAR) s++;
\r
1536 while (*s != NULLCHAR && *s != '\033') {
\r
1544 char *variantNames[] = VARIANT_NAMES;
\r
1549 return variantNames[v];
\r
1553 /* Identify a variant from the strings the chess servers use or the
\r
1554 PGN Variant tag names we use. */
\r
1556 StringToVariant(e)
\r
1561 VariantClass v = VariantNormal;
\r
1562 int i, found = FALSE;
\r
1563 char buf[MSG_SIZ];
\r
1567 /* [HGM] skip over optional board-size prefixes */
\r
1568 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
\r
1569 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
\r
1570 while( *e++ != '_');
\r
1573 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
\r
1574 if (StrCaseStr(e, variantNames[i])) {
\r
1575 v = (VariantClass) i;
\r
1582 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
\r
1583 || StrCaseStr(e, "wild/fr")
\r
1584 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
\r
1585 v = VariantFischeRandom;
\r
1586 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
\r
1587 (i = 1, p = StrCaseStr(e, "w"))) {
\r
1589 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
\r
1590 if (isdigit(*p)) {
\r
1596 case 0: /* FICS only, actually */
\r
1598 /* Castling legal even if K starts on d-file */
\r
1599 v = VariantWildCastle;
\r
1604 /* Castling illegal even if K & R happen to start in
\r
1605 normal positions. */
\r
1606 v = VariantNoCastle;
\r
1619 /* Castling legal iff K & R start in normal positions */
\r
1620 v = VariantNormal;
\r
1625 /* Special wilds for position setup; unclear what to do here */
\r
1626 v = VariantLoadable;
\r
1629 /* Bizarre ICC game */
\r
1630 v = VariantTwoKings;
\r
1633 v = VariantKriegspiel;
\r
1636 v = VariantLosers;
\r
1639 v = VariantFischeRandom;
\r
1642 v = VariantCrazyhouse;
\r
1645 v = VariantBughouse;
\r
1648 v = Variant3Check;
\r
1651 /* Not quite the same as FICS suicide! */
\r
1652 v = VariantGiveaway;
\r
1655 v = VariantAtomic;
\r
1658 v = VariantShatranj;
\r
1661 /* Temporary names for future ICC types. The name *will* change in
\r
1662 the next xboard/WinBoard release after ICC defines it. */
\r
1691 v = VariantXiangqi;
\r
1694 v = VariantCourier;
\r
1697 v = VariantGothic;
\r
1700 v = VariantCapablanca;
\r
1703 v = VariantKnightmate;
\r
1709 v = VariantCylinder;
\r
1712 v = VariantFalcon;
\r
1715 v = VariantCapaRandom;
\r
1718 v = VariantBerolina;
\r
1730 /* Found "wild" or "w" in the string but no number;
\r
1731 must assume it's normal chess. */
\r
1732 v = VariantNormal;
\r
1735 sprintf(buf, _("Unknown wild type %d"), wnum);
\r
1736 DisplayError(buf, 0);
\r
1737 v = VariantUnknown;
\r
1742 if (appData.debugMode) {
\r
1743 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
\r
1744 e, wnum, VariantName(v));
\r
1749 static int leftover_start = 0, leftover_len = 0;
\r
1750 char star_match[STAR_MATCH_N][MSG_SIZ];
\r
1752 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
\r
1753 advance *index beyond it, and set leftover_start to the new value of
\r
1754 *index; else return FALSE. If pattern contains the character '*', it
\r
1755 matches any sequence of characters not containing '\r', '\n', or the
\r
1756 character following the '*' (if any), and the matched sequence(s) are
\r
1757 copied into star_match.
\r
1760 looking_at(buf, index, pattern)
\r
1765 char *bufp = &buf[*index], *patternp = pattern;
\r
1766 int star_count = 0;
\r
1767 char *matchp = star_match[0];
\r
1770 if (*patternp == NULLCHAR) {
\r
1771 *index = leftover_start = bufp - buf;
\r
1772 *matchp = NULLCHAR;
\r
1775 if (*bufp == NULLCHAR) return FALSE;
\r
1776 if (*patternp == '*') {
\r
1777 if (*bufp == *(patternp + 1)) {
\r
1778 *matchp = NULLCHAR;
\r
1779 matchp = star_match[++star_count];
\r
1783 } else if (*bufp == '\n' || *bufp == '\r') {
\r
1785 if (*patternp == NULLCHAR)
\r
1790 *matchp++ = *bufp++;
\r
1794 if (*patternp != *bufp) return FALSE;
\r
1801 SendToPlayer(data, length)
\r
1805 int error, outCount;
\r
1806 outCount = OutputToProcess(NoProc, data, length, &error);
\r
1807 if (outCount < length) {
\r
1808 DisplayFatalError(_("Error writing to display"), error, 1);
\r
1813 PackHolding(packed, holding)
\r
1817 char *p = holding;
\r
1819 int runlength = 0;
\r
1825 switch (runlength) {
\r
1836 sprintf(q, "%d", runlength);
\r
1848 /* Telnet protocol requests from the front end */
\r
1850 TelnetRequest(ddww, option)
\r
1851 unsigned char ddww, option;
\r
1853 unsigned char msg[3];
\r
1854 int outCount, outError;
\r
1856 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
\r
1858 if (appData.debugMode) {
\r
1859 char buf1[8], buf2[8], *ddwwStr, *optionStr;
\r
1875 sprintf(buf1, "%d", ddww);
\r
1880 optionStr = "ECHO";
\r
1884 sprintf(buf2, "%d", option);
\r
1887 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
\r
1892 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
\r
1893 if (outCount < 3) {
\r
1894 DisplayFatalError(_("Error writing to ICS"), outError, 1);
\r
1901 if (!appData.icsActive) return;
\r
1902 TelnetRequest(TN_DO, TN_ECHO);
\r
1908 if (!appData.icsActive) return;
\r
1909 TelnetRequest(TN_DONT, TN_ECHO);
\r
1913 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
\r
1915 /* put the holdings sent to us by the server on the board holdings area */
\r
1916 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
\r
1918 ChessSquare piece;
\r
1920 if(gameInfo.holdingsWidth < 2) return;
\r
1922 if( (int)lowestPiece >= BlackPawn ) {
\r
1923 holdingsColumn = 0;
\r
1925 holdingsStartRow = BOARD_HEIGHT-1;
\r
1928 holdingsColumn = BOARD_WIDTH-1;
\r
1929 countsColumn = BOARD_WIDTH-2;
\r
1930 holdingsStartRow = 0;
\r
1934 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
\r
1935 board[i][holdingsColumn] = EmptySquare;
\r
1936 board[i][countsColumn] = (ChessSquare) 0;
\r
1938 while( (p=*holdings++) != NULLCHAR ) {
\r
1939 piece = CharToPiece( ToUpper(p) );
\r
1940 if(piece == EmptySquare) continue;
\r
1941 /*j = (int) piece - (int) WhitePawn;*/
\r
1942 j = PieceToNumber(piece);
\r
1943 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
\r
1944 if(j < 0) continue; /* should not happen */
\r
1945 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
\r
1946 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
\r
1947 board[holdingsStartRow+j*direction][countsColumn]++;
\r
1954 VariantSwitch(Board board, VariantClass newVariant)
\r
1956 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
\r
1957 int oldCurrentMove = currentMove, oldForwardMostMove = forwardMostMove, oldBackwardMostMove = backwardMostMove;
\r
1958 // Board tempBoard; int saveCastling[BOARD_SIZE], saveEP;
\r
1960 startedFromPositionFile = FALSE;
\r
1961 if(gameInfo.variant == newVariant) return;
\r
1963 /* [HGM] This routine is called each time an assignment is made to
\r
1964 * gameInfo.variant during a game, to make sure the board sizes
\r
1965 * are set to match the new variant. If that means adding or deleting
\r
1966 * holdings, we shift the playing board accordingly
\r
1967 * This kludge is needed because in ICS observe mode, we get boards
\r
1968 * of an ongoing game without knowing the variant, and learn about the
\r
1969 * latter only later. This can be because of the move list we requested,
\r
1970 * in which case the game history is refilled from the beginning anyway,
\r
1971 * but also when receiving holdings of a crazyhouse game. In the latter
\r
1972 * case we want to add those holdings to the already received position.
\r
1976 if (appData.debugMode) {
\r
1977 fprintf(debugFP, "Switch board from %s to %s\n",
\r
1978 VariantName(gameInfo.variant), VariantName(newVariant));
\r
1979 setbuf(debugFP, NULL);
\r
1981 shuffleOpenings = 0; /* [HGM] shuffle */
\r
1982 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
\r
1983 switch(newVariant) {
\r
1984 case VariantShogi:
\r
1985 newWidth = 9; newHeight = 9;
\r
1986 gameInfo.holdingsSize = 7;
\r
1987 case VariantBughouse:
\r
1988 case VariantCrazyhouse:
\r
1989 newHoldingsWidth = 2; break;
\r
1991 newHoldingsWidth = gameInfo.holdingsSize = 0;
\r
1994 if(newWidth != gameInfo.boardWidth ||
\r
1995 newHeight != gameInfo.boardHeight ||
\r
1996 newHoldingsWidth != gameInfo.holdingsWidth ) {
\r
1998 /* shift position to new playing area, if needed */
\r
1999 if(newHoldingsWidth > gameInfo.holdingsWidth) {
\r
2000 for(i=0; i<BOARD_HEIGHT; i++)
\r
2001 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
\r
2002 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
\r
2004 for(i=0; i<newHeight; i++) {
\r
2005 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
\r
2006 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
\r
2008 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
\r
2009 for(i=0; i<BOARD_HEIGHT; i++)
\r
2010 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
\r
2011 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
\r
2015 gameInfo.boardWidth = newWidth;
\r
2016 gameInfo.boardHeight = newHeight;
\r
2017 gameInfo.holdingsWidth = newHoldingsWidth;
\r
2018 gameInfo.variant = newVariant;
\r
2019 InitDrawingSizes(-2, 0);
\r
2021 /* [HGM] The following should definitely be solved in a better way */
\r
2023 CopyBoard(board, tempBoard); /* save position in case it is board[0] */
\r
2024 for(i=0; i<BOARD_SIZE; i++) saveCastling[i] = castlingRights[0][i];
\r
2025 saveEP = epStatus[0];
\r
2027 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
\r
2029 epStatus[0] = saveEP;
\r
2030 for(i=0; i<BOARD_SIZE; i++) castlingRights[0][i] = saveCastling[i];
\r
2031 CopyBoard(tempBoard, board); /* restore position received from ICS */
\r
2033 } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
\r
2035 forwardMostMove = oldForwardMostMove;
\r
2036 backwardMostMove = oldBackwardMostMove;
\r
2037 currentMove = oldCurrentMove; /* InitPos reset these, but we need still to redraw the position */
\r
2040 static int loggedOn = FALSE;
\r
2042 /*-- Game start info cache: --*/
\r
2044 char gs_kind[MSG_SIZ];
\r
2045 static char player1Name[128] = "";
\r
2046 static char player2Name[128] = "";
\r
2047 static int player1Rating = -1;
\r
2048 static int player2Rating = -1;
\r
2049 /*----------------------------*/
\r
2051 ColorClass curColor = ColorNormal;
\r
2052 int suppressKibitz = 0;
\r
2055 read_from_ics(isr, closure, data, count, error)
\r
2056 InputSourceRef isr;
\r
2062 #define BUF_SIZE 8192
\r
2063 #define STARTED_NONE 0
\r
2064 #define STARTED_MOVES 1
\r
2065 #define STARTED_BOARD 2
\r
2066 #define STARTED_OBSERVE 3
\r
2067 #define STARTED_HOLDINGS 4
\r
2068 #define STARTED_CHATTER 5
\r
2069 #define STARTED_COMMENT 6
\r
2070 #define STARTED_MOVES_NOHIDE 7
\r
2072 static int started = STARTED_NONE;
\r
2073 static char parse[20000];
\r
2074 static int parse_pos = 0;
\r
2075 static char buf[BUF_SIZE + 1];
\r
2076 static int firstTime = TRUE, intfSet = FALSE;
\r
2077 static ColorClass prevColor = ColorNormal;
\r
2078 static int savingComment = FALSE;
\r
2084 int backup; /* [DM] For zippy color lines */
\r
2087 if (appData.debugMode) {
\r
2089 fprintf(debugFP, "<ICS: ");
\r
2090 show_bytes(debugFP, data, count);
\r
2091 fprintf(debugFP, "\n");
\r
2095 if (appData.debugMode) { int f = forwardMostMove;
\r
2096 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
\r
2097 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
\r
2100 /* If last read ended with a partial line that we couldn't parse,
\r
2101 prepend it to the new read and try again. */
\r
2102 if (leftover_len > 0) {
\r
2103 for (i=0; i<leftover_len; i++)
\r
2104 buf[i] = buf[leftover_start + i];
\r
2107 /* Copy in new characters, removing nulls and \r's */
\r
2108 buf_len = leftover_len;
\r
2109 for (i = 0; i < count; i++) {
\r
2110 if (data[i] != NULLCHAR && data[i] != '\r')
\r
2111 buf[buf_len++] = data[i];
\r
2112 if(buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' &&
\r
2113 buf[buf_len-3]==' ' && buf[buf_len-2]==' ' && buf[buf_len-1]==' ')
\r
2114 buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous
\r
2117 buf[buf_len] = NULLCHAR;
\r
2118 next_out = leftover_len;
\r
2119 leftover_start = 0;
\r
2122 while (i < buf_len) {
\r
2123 /* Deal with part of the TELNET option negotiation
\r
2124 protocol. We refuse to do anything beyond the
\r
2125 defaults, except that we allow the WILL ECHO option,
\r
2126 which ICS uses to turn off password echoing when we are
\r
2127 directly connected to it. We reject this option
\r
2128 if localLineEditing mode is on (always on in xboard)
\r
2129 and we are talking to port 23, which might be a real
\r
2130 telnet server that will try to keep WILL ECHO on permanently.
\r
2132 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
\r
2133 static int remoteEchoOption = FALSE; /* telnet ECHO option */
\r
2134 unsigned char option;
\r
2136 switch ((unsigned char) buf[++i]) {
\r
2138 if (appData.debugMode)
\r
2139 fprintf(debugFP, "\n<WILL ");
\r
2140 switch (option = (unsigned char) buf[++i]) {
\r
2142 if (appData.debugMode)
\r
2143 fprintf(debugFP, "ECHO ");
\r
2144 /* Reply only if this is a change, according
\r
2145 to the protocol rules. */
\r
2146 if (remoteEchoOption) break;
\r
2147 if (appData.localLineEditing &&
\r
2148 atoi(appData.icsPort) == TN_PORT) {
\r
2149 TelnetRequest(TN_DONT, TN_ECHO);
\r
2152 TelnetRequest(TN_DO, TN_ECHO);
\r
2153 remoteEchoOption = TRUE;
\r
2157 if (appData.debugMode)
\r
2158 fprintf(debugFP, "%d ", option);
\r
2159 /* Whatever this is, we don't want it. */
\r
2160 TelnetRequest(TN_DONT, option);
\r
2165 if (appData.debugMode)
\r
2166 fprintf(debugFP, "\n<WONT ");
\r
2167 switch (option = (unsigned char) buf[++i]) {
\r
2169 if (appData.debugMode)
\r
2170 fprintf(debugFP, "ECHO ");
\r
2171 /* Reply only if this is a change, according
\r
2172 to the protocol rules. */
\r
2173 if (!remoteEchoOption) break;
\r
2175 TelnetRequest(TN_DONT, TN_ECHO);
\r
2176 remoteEchoOption = FALSE;
\r
2179 if (appData.debugMode)
\r
2180 fprintf(debugFP, "%d ", (unsigned char) option);
\r
2181 /* Whatever this is, it must already be turned
\r
2182 off, because we never agree to turn on
\r
2183 anything non-default, so according to the
\r
2184 protocol rules, we don't reply. */
\r
2189 if (appData.debugMode)
\r
2190 fprintf(debugFP, "\n<DO ");
\r
2191 switch (option = (unsigned char) buf[++i]) {
\r
2193 /* Whatever this is, we refuse to do it. */
\r
2194 if (appData.debugMode)
\r
2195 fprintf(debugFP, "%d ", option);
\r
2196 TelnetRequest(TN_WONT, option);
\r
2201 if (appData.debugMode)
\r
2202 fprintf(debugFP, "\n<DONT ");
\r
2203 switch (option = (unsigned char) buf[++i]) {
\r
2205 if (appData.debugMode)
\r
2206 fprintf(debugFP, "%d ", option);
\r
2207 /* Whatever this is, we are already not doing
\r
2208 it, because we never agree to do anything
\r
2209 non-default, so according to the protocol
\r
2210 rules, we don't reply. */
\r
2215 if (appData.debugMode)
\r
2216 fprintf(debugFP, "\n<IAC ");
\r
2217 /* Doubled IAC; pass it through */
\r
2221 if (appData.debugMode)
\r
2222 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
\r
2223 /* Drop all other telnet commands on the floor */
\r
2226 if (oldi > next_out)
\r
2227 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2228 if (++i > next_out)
\r
2233 /* OK, this at least will *usually* work */
\r
2234 if (!loggedOn && looking_at(buf, &i, "ics%")) {
\r
2238 if (loggedOn && !intfSet) {
\r
2239 if (ics_type == ICS_ICC) {
\r
2241 "/set-quietly interface %s\n/set-quietly style 12\n",
\r
2244 } else if (ics_type == ICS_CHESSNET) {
\r
2245 sprintf(str, "/style 12\n");
\r
2247 strcpy(str, "alias $ @\n$set interface ");
\r
2248 strcat(str, programVersion);
\r
2249 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
\r
2251 strcat(str, "$iset nohighlight 1\n");
\r
2253 strcat(str, "$iset lock 1\n$style 12\n");
\r
2259 if (started == STARTED_COMMENT) {
\r
2260 /* Accumulate characters in comment */
\r
2261 parse[parse_pos++] = buf[i];
\r
2262 if (buf[i] == '\n') {
\r
2263 parse[parse_pos] = NULLCHAR;
\r
2264 if(!suppressKibitz) // [HGM] kibitz
\r
2265 AppendComment(forwardMostMove, StripHighlight(parse));
\r
2266 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
\r
2267 int nrDigit = 0, nrAlph = 0, i;
\r
2268 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
\r
2269 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
\r
2270 parse[parse_pos] = NULLCHAR;
\r
2271 // try to be smart: if it does not look like search info, it should go to
\r
2272 // ICS interaction window after all, not to engine-output window.
\r
2273 for(i=0; i<parse_pos; i++) { // count letters and digits
\r
2274 nrDigit += (parse[i] >= '0' && parse[i] <= '9');
\r
2275 nrAlph += (parse[i] >= 'a' && parse[i] <= 'z');
\r
2276 nrAlph += (parse[i] >= 'A' && parse[i] <= 'Z');
\r
2278 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
\r
2279 OutputKibitz(suppressKibitz, parse);
\r
2281 char tmp[MSG_SIZ];
\r
2282 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
\r
2283 SendToPlayer(tmp, strlen(tmp));
\r
2286 started = STARTED_NONE;
\r
2288 /* Don't match patterns against characters in chatter */
\r
2293 if (started == STARTED_CHATTER) {
\r
2294 if (buf[i] != '\n') {
\r
2295 /* Don't match patterns against characters in chatter */
\r
2299 started = STARTED_NONE;
\r
2302 /* Kludge to deal with rcmd protocol */
\r
2303 if (firstTime && looking_at(buf, &i, "\001*")) {
\r
2304 DisplayFatalError(&buf[1], 0, 1);
\r
2307 firstTime = FALSE;
\r
2310 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
\r
2311 ics_type = ICS_ICC;
\r
2313 if (appData.debugMode)
\r
2314 fprintf(debugFP, "ics_type %d\n", ics_type);
\r
2317 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
\r
2318 ics_type = ICS_FICS;
\r
2320 if (appData.debugMode)
\r
2321 fprintf(debugFP, "ics_type %d\n", ics_type);
\r
2324 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
\r
2325 ics_type = ICS_CHESSNET;
\r
2327 if (appData.debugMode)
\r
2328 fprintf(debugFP, "ics_type %d\n", ics_type);
\r
2333 (looking_at(buf, &i, "\"*\" is *a registered name") ||
\r
2334 looking_at(buf, &i, "Logging you in as \"*\"") ||
\r
2335 looking_at(buf, &i, "will be \"*\""))) {
\r
2336 strcpy(ics_handle, star_match[0]);
\r
2340 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
\r
2341 char buf[MSG_SIZ];
\r
2342 sprintf(buf, "%s@%s", ics_handle, appData.icsHost);
\r
2343 DisplayIcsInteractionTitle(buf);
\r
2344 have_set_title = TRUE;
\r
2347 /* skip finger notes */
\r
2348 if (started == STARTED_NONE &&
\r
2349 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
\r
2350 (buf[i] == '1' && buf[i+1] == '0')) &&
\r
2351 buf[i+2] == ':' && buf[i+3] == ' ') {
\r
2352 started = STARTED_CHATTER;
\r
2357 /* skip formula vars */
\r
2358 if (started == STARTED_NONE &&
\r
2359 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
\r
2360 started = STARTED_CHATTER;
\r
2366 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
\r
2367 if (appData.autoKibitz && started == STARTED_NONE &&
\r
2368 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
\r
2369 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
\r
2370 if(looking_at(buf, &i, "* kibitzes: ") &&
\r
2371 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
\r
2372 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
\r
2373 suppressKibitz = TRUE;
\r
2374 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
\r
2375 && (gameMode == IcsPlayingWhite)) ||
\r
2376 (StrStr(star_match[0], gameInfo.black) == star_match[0]
\r
2377 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
\r
2378 started = STARTED_CHATTER; // own kibitz we simply discard
\r
2380 started = STARTED_COMMENT; // make sure it will be collected in parse[]
\r
2381 parse_pos = 0; parse[0] = NULLCHAR;
\r
2382 savingComment = TRUE;
\r
2383 suppressKibitz = gameMode != IcsObserving ? 2 :
\r
2384 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
\r
2388 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
\r
2389 started = STARTED_CHATTER;
\r
2390 suppressKibitz = TRUE;
\r
2392 } // [HGM] kibitz: end of patch
\r
2394 if (appData.zippyTalk || appData.zippyPlay) {
\r
2395 /* [DM] Backup address for color zippy lines */
\r
2399 if (loggedOn == TRUE)
\r
2400 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
\r
2401 (appData.zippyPlay && ZippyMatch(buf, &backup)));
\r
2403 if (ZippyControl(buf, &i) ||
\r
2404 ZippyConverse(buf, &i) ||
\r
2405 (appData.zippyPlay && ZippyMatch(buf, &i))) {
\r
2407 if (!appData.colorize) continue;
\r
2411 } // [DM] 'else { ' deleted
\r
2412 if (/* Don't color "message" or "messages" output */
\r
2413 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
\r
2414 looking_at(buf, &i, "*. * at *:*: ") ||
\r
2415 looking_at(buf, &i, "--* (*:*): ") ||
\r
2416 /* Regular tells and says */
\r
2417 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
\r
2418 looking_at(buf, &i, "* (your partner) tells you: ") ||
\r
2419 looking_at(buf, &i, "* says: ") ||
\r
2420 /* Message notifications (same color as tells) */
\r
2421 looking_at(buf, &i, "* has left a message ") ||
\r
2422 looking_at(buf, &i, "* just sent you a message:\n") ||
\r
2423 /* Whispers and kibitzes */
\r
2424 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
\r
2425 looking_at(buf, &i, "* kibitzes: ") ||
\r
2426 /* Channel tells */
\r
2427 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
\r
2429 if (tkind == 1 && strchr(star_match[0], ':')) {
\r
2430 /* Avoid "tells you:" spoofs in channels */
\r
2433 if (star_match[0][0] == NULLCHAR ||
\r
2434 strchr(star_match[0], ' ') ||
\r
2435 (tkind == 3 && strchr(star_match[1], ' '))) {
\r
2436 /* Reject bogus matches */
\r
2439 if (appData.colorize) {
\r
2440 if (oldi > next_out) {
\r
2441 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2446 Colorize(ColorTell, FALSE);
\r
2447 curColor = ColorTell;
\r
2450 Colorize(ColorKibitz, FALSE);
\r
2451 curColor = ColorKibitz;
\r
2454 p = strrchr(star_match[1], '(');
\r
2456 p = star_match[1];
\r
2460 if (atoi(p) == 1) {
\r
2461 Colorize(ColorChannel1, FALSE);
\r
2462 curColor = ColorChannel1;
\r
2464 Colorize(ColorChannel, FALSE);
\r
2465 curColor = ColorChannel;
\r
2469 curColor = ColorNormal;
\r
2473 if (started == STARTED_NONE && appData.autoComment &&
\r
2474 (gameMode == IcsObserving ||
\r
2475 gameMode == IcsPlayingWhite ||
\r
2476 gameMode == IcsPlayingBlack)) {
\r
2477 parse_pos = i - oldi;
\r
2478 memcpy(parse, &buf[oldi], parse_pos);
\r
2479 parse[parse_pos] = NULLCHAR;
\r
2480 started = STARTED_COMMENT;
\r
2481 savingComment = TRUE;
\r
2483 started = STARTED_CHATTER;
\r
2484 savingComment = FALSE;
\r
2491 if (looking_at(buf, &i, "* s-shouts: ") ||
\r
2492 looking_at(buf, &i, "* c-shouts: ")) {
\r
2493 if (appData.colorize) {
\r
2494 if (oldi > next_out) {
\r
2495 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2498 Colorize(ColorSShout, FALSE);
\r
2499 curColor = ColorSShout;
\r
2502 started = STARTED_CHATTER;
\r
2506 if (looking_at(buf, &i, "--->")) {
\r
2511 if (looking_at(buf, &i, "* shouts: ") ||
\r
2512 looking_at(buf, &i, "--> ")) {
\r
2513 if (appData.colorize) {
\r
2514 if (oldi > next_out) {
\r
2515 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2518 Colorize(ColorShout, FALSE);
\r
2519 curColor = ColorShout;
\r
2522 started = STARTED_CHATTER;
\r
2526 if (looking_at( buf, &i, "Challenge:")) {
\r
2527 if (appData.colorize) {
\r
2528 if (oldi > next_out) {
\r
2529 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2532 Colorize(ColorChallenge, FALSE);
\r
2533 curColor = ColorChallenge;
\r
2539 if (looking_at(buf, &i, "* offers you") ||
\r
2540 looking_at(buf, &i, "* offers to be") ||
\r
2541 looking_at(buf, &i, "* would like to") ||
\r
2542 looking_at(buf, &i, "* requests to") ||
\r
2543 looking_at(buf, &i, "Your opponent offers") ||
\r
2544 looking_at(buf, &i, "Your opponent requests")) {
\r
2546 if (appData.colorize) {
\r
2547 if (oldi > next_out) {
\r
2548 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2551 Colorize(ColorRequest, FALSE);
\r
2552 curColor = ColorRequest;
\r
2557 if (looking_at(buf, &i, "* (*) seeking")) {
\r
2558 if (appData.colorize) {
\r
2559 if (oldi > next_out) {
\r
2560 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2563 Colorize(ColorSeek, FALSE);
\r
2564 curColor = ColorSeek;
\r
2569 if (looking_at(buf, &i, "\\ ")) {
\r
2570 if (prevColor != ColorNormal) {
\r
2571 if (oldi > next_out) {
\r
2572 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2575 Colorize(prevColor, TRUE);
\r
2576 curColor = prevColor;
\r
2578 if (savingComment) {
\r
2579 parse_pos = i - oldi;
\r
2580 memcpy(parse, &buf[oldi], parse_pos);
\r
2581 parse[parse_pos] = NULLCHAR;
\r
2582 started = STARTED_COMMENT;
\r
2584 started = STARTED_CHATTER;
\r
2589 if (looking_at(buf, &i, "Black Strength :") ||
\r
2590 looking_at(buf, &i, "<<< style 10 board >>>") ||
\r
2591 looking_at(buf, &i, "<10>") ||
\r
2592 looking_at(buf, &i, "#@#")) {
\r
2593 /* Wrong board style */
\r
2595 SendToICS(ics_prefix);
\r
2596 SendToICS("set style 12\n");
\r
2597 SendToICS(ics_prefix);
\r
2598 SendToICS("refresh\n");
\r
2602 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
\r
2604 have_sent_ICS_logon = 1;
\r
2608 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
\r
2609 (looking_at(buf, &i, "\n<12> ") ||
\r
2610 looking_at(buf, &i, "<12> "))) {
\r
2612 if (oldi > next_out) {
\r
2613 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2616 started = STARTED_BOARD;
\r
2621 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
\r
2622 looking_at(buf, &i, "<b1> ")) {
\r
2623 if (oldi > next_out) {
\r
2624 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2627 started = STARTED_HOLDINGS;
\r
2632 if (looking_at(buf, &i, "* *vs. * *--- *")) {
\r
2634 /* Header for a move list -- first line */
\r
2636 switch (ics_getting_history) {
\r
2638 switch (gameMode) {
\r
2640 case BeginningOfGame:
\r
2641 /* User typed "moves" or "oldmoves" while we
\r
2642 were idle. Pretend we asked for these
\r
2643 moves and soak them up so user can step
\r
2644 through them and/or save them.
\r
2646 Reset(FALSE, TRUE);
\r
2647 gameMode = IcsObserving;
\r
2650 ics_getting_history = H_GOT_UNREQ_HEADER;
\r
2652 case EditGame: /*?*/
\r
2653 case EditPosition: /*?*/
\r
2654 /* Should above feature work in these modes too? */
\r
2655 /* For now it doesn't */
\r
2656 ics_getting_history = H_GOT_UNWANTED_HEADER;
\r
2659 ics_getting_history = H_GOT_UNWANTED_HEADER;
\r
2664 /* Is this the right one? */
\r
2665 if (gameInfo.white && gameInfo.black &&
\r
2666 strcmp(gameInfo.white, star_match[0]) == 0 &&
\r
2667 strcmp(gameInfo.black, star_match[2]) == 0) {
\r
2669 ics_getting_history = H_GOT_REQ_HEADER;
\r
2672 case H_GOT_REQ_HEADER:
\r
2673 case H_GOT_UNREQ_HEADER:
\r
2674 case H_GOT_UNWANTED_HEADER:
\r
2675 case H_GETTING_MOVES:
\r
2676 /* Should not happen */
\r
2677 DisplayError(_("Error gathering move list: two headers"), 0);
\r
2678 ics_getting_history = H_FALSE;
\r
2682 /* Save player ratings into gameInfo if needed */
\r
2683 if ((ics_getting_history == H_GOT_REQ_HEADER ||
\r
2684 ics_getting_history == H_GOT_UNREQ_HEADER) &&
\r
2685 (gameInfo.whiteRating == -1 ||
\r
2686 gameInfo.blackRating == -1)) {
\r
2688 gameInfo.whiteRating = string_to_rating(star_match[1]);
\r
2689 gameInfo.blackRating = string_to_rating(star_match[3]);
\r
2690 if (appData.debugMode)
\r
2691 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
\r
2692 gameInfo.whiteRating, gameInfo.blackRating);
\r
2697 if (looking_at(buf, &i,
\r
2698 "* * match, initial time: * minute*, increment: * second")) {
\r
2699 /* Header for a move list -- second line */
\r
2700 /* Initial board will follow if this is a wild game */
\r
2701 if (gameInfo.event != NULL) free(gameInfo.event);
\r
2702 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
\r
2703 gameInfo.event = StrSave(str);
\r
2704 /* [HGM] we switched variant. Translate boards if needed. */
\r
2705 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
\r
2709 if (looking_at(buf, &i, "Move ")) {
\r
2710 /* Beginning of a move list */
\r
2711 switch (ics_getting_history) {
\r
2713 /* Normally should not happen */
\r
2714 /* Maybe user hit reset while we were parsing */
\r
2717 /* Happens if we are ignoring a move list that is not
\r
2718 * the one we just requested. Common if the user
\r
2719 * tries to observe two games without turning off
\r
2722 case H_GETTING_MOVES:
\r
2723 /* Should not happen */
\r
2724 DisplayError(_("Error gathering move list: nested"), 0);
\r
2725 ics_getting_history = H_FALSE;
\r
2727 case H_GOT_REQ_HEADER:
\r
2728 ics_getting_history = H_GETTING_MOVES;
\r
2729 started = STARTED_MOVES;
\r
2731 if (oldi > next_out) {
\r
2732 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2735 case H_GOT_UNREQ_HEADER:
\r
2736 ics_getting_history = H_GETTING_MOVES;
\r
2737 started = STARTED_MOVES_NOHIDE;
\r
2740 case H_GOT_UNWANTED_HEADER:
\r
2741 ics_getting_history = H_FALSE;
\r
2747 if (looking_at(buf, &i, "% ") ||
\r
2748 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
\r
2749 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
\r
2750 savingComment = FALSE;
\r
2751 switch (started) {
\r
2752 case STARTED_MOVES:
\r
2753 case STARTED_MOVES_NOHIDE:
\r
2754 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
\r
2755 parse[parse_pos + i - oldi] = NULLCHAR;
\r
2756 ParseGameHistory(parse);
\r
2758 if (appData.zippyPlay && first.initDone) {
\r
2759 FeedMovesToProgram(&first, forwardMostMove);
\r
2760 if (gameMode == IcsPlayingWhite) {
\r
2761 if (WhiteOnMove(forwardMostMove)) {
\r
2762 if (first.sendTime) {
\r
2763 if (first.useColors) {
\r
2764 SendToProgram("black\n", &first);
\r
2766 SendTimeRemaining(&first, TRUE);
\r
2769 if (first.useColors) {
\r
2770 SendToProgram("white\ngo\n", &first);
\r
2772 SendToProgram("go\n", &first);
\r
2775 if (first.useColors) {
\r
2776 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
\r
2778 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
\r
2780 first.maybeThinking = TRUE;
\r
2782 if (first.usePlayother) {
\r
2783 if (first.sendTime) {
\r
2784 SendTimeRemaining(&first, TRUE);
\r
2786 SendToProgram("playother\n", &first);
\r
2787 firstMove = FALSE;
\r
2792 } else if (gameMode == IcsPlayingBlack) {
\r
2793 if (!WhiteOnMove(forwardMostMove)) {
\r
2794 if (first.sendTime) {
\r
2795 if (first.useColors) {
\r
2796 SendToProgram("white\n", &first);
\r
2798 SendTimeRemaining(&first, FALSE);
\r
2801 if (first.useColors) {
\r
2802 SendToProgram("black\ngo\n", &first);
\r
2804 SendToProgram("go\n", &first);
\r
2807 if (first.useColors) {
\r
2808 SendToProgram("black\n", &first);
\r
2810 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
\r
2812 first.maybeThinking = TRUE;
\r
2814 if (first.usePlayother) {
\r
2815 if (first.sendTime) {
\r
2816 SendTimeRemaining(&first, FALSE);
\r
2818 SendToProgram("playother\n", &first);
\r
2819 firstMove = FALSE;
\r
2827 if (gameMode == IcsObserving && ics_gamenum == -1) {
\r
2828 /* Moves came from oldmoves or moves command
\r
2829 while we weren't doing anything else.
\r
2831 currentMove = forwardMostMove;
\r
2832 ClearHighlights();/*!!could figure this out*/
\r
2833 flipView = appData.flipView;
\r
2834 DrawPosition(FALSE, boards[currentMove]);
\r
2835 DisplayBothClocks();
\r
2836 sprintf(str, "%s vs. %s",
\r
2837 gameInfo.white, gameInfo.black);
\r
2838 DisplayTitle(str);
\r
2839 gameMode = IcsIdle;
\r
2841 /* Moves were history of an active game */
\r
2842 if (gameInfo.resultDetails != NULL) {
\r
2843 free(gameInfo.resultDetails);
\r
2844 gameInfo.resultDetails = NULL;
\r
2847 HistorySet(parseList, backwardMostMove,
\r
2848 forwardMostMove, currentMove-1);
\r
2849 DisplayMove(currentMove - 1);
\r
2850 if (started == STARTED_MOVES) next_out = i;
\r
2851 started = STARTED_NONE;
\r
2852 ics_getting_history = H_FALSE;
\r
2855 case STARTED_OBSERVE:
\r
2856 started = STARTED_NONE;
\r
2857 SendToICS(ics_prefix);
\r
2858 SendToICS("refresh\n");
\r
2864 if(bookHit) { // [HGM] book: simulate book reply
\r
2865 static char bookMove[MSG_SIZ]; // a bit generous?
\r
2867 programStats.nodes = programStats.depth = programStats.time =
\r
2868 programStats.score = programStats.got_only_move = 0;
\r
2869 sprintf(programStats.movelist, "%s (xbook)", bookHit);
\r
2871 strcpy(bookMove, "move ");
\r
2872 strcat(bookMove, bookHit);
\r
2873 HandleMachineMove(bookMove, &first);
\r
2878 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
\r
2879 started == STARTED_HOLDINGS ||
\r
2880 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
\r
2881 /* Accumulate characters in move list or board */
\r
2882 parse[parse_pos++] = buf[i];
\r
2885 /* Start of game messages. Mostly we detect start of game
\r
2886 when the first board image arrives. On some versions
\r
2887 of the ICS, though, we need to do a "refresh" after starting
\r
2888 to observe in order to get the current board right away. */
\r
2889 if (looking_at(buf, &i, "Adding game * to observation list")) {
\r
2890 started = STARTED_OBSERVE;
\r
2894 /* Handle auto-observe */
\r
2895 if (appData.autoObserve &&
\r
2896 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
\r
2897 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
\r
2899 /* Choose the player that was highlighted, if any. */
\r
2900 if (star_match[0][0] == '\033' ||
\r
2901 star_match[1][0] != '\033') {
\r
2902 player = star_match[0];
\r
2904 player = star_match[2];
\r
2906 sprintf(str, "%sobserve %s\n",
\r
2907 ics_prefix, StripHighlightAndTitle(player));
\r
2910 /* Save ratings from notify string */
\r
2911 strcpy(player1Name, star_match[0]);
\r
2912 player1Rating = string_to_rating(star_match[1]);
\r
2913 strcpy(player2Name, star_match[2]);
\r
2914 player2Rating = string_to_rating(star_match[3]);
\r
2916 if (appData.debugMode)
\r
2918 "Ratings from 'Game notification:' %s %d, %s %d\n",
\r
2919 player1Name, player1Rating,
\r
2920 player2Name, player2Rating);
\r
2925 /* Deal with automatic examine mode after a game,
\r
2926 and with IcsObserving -> IcsExamining transition */
\r
2927 if (looking_at(buf, &i, "Entering examine mode for game *") ||
\r
2928 looking_at(buf, &i, "has made you an examiner of game *")) {
\r
2930 int gamenum = atoi(star_match[0]);
\r
2931 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
\r
2932 gamenum == ics_gamenum) {
\r
2933 /* We were already playing or observing this game;
\r
2934 no need to refetch history */
\r
2935 gameMode = IcsExamining;
\r
2937 pauseExamForwardMostMove = forwardMostMove;
\r
2938 } else if (currentMove < forwardMostMove) {
\r
2939 ForwardInner(forwardMostMove);
\r
2942 /* I don't think this case really can happen */
\r
2943 SendToICS(ics_prefix);
\r
2944 SendToICS("refresh\n");
\r
2949 /* Error messages */
\r
2950 if (ics_user_moved) {
\r
2951 if (looking_at(buf, &i, "Illegal move") ||
\r
2952 looking_at(buf, &i, "Not a legal move") ||
\r
2953 looking_at(buf, &i, "Your king is in check") ||
\r
2954 looking_at(buf, &i, "It isn't your turn") ||
\r
2955 looking_at(buf, &i, "It is not your move")) {
\r
2956 /* Illegal move */
\r
2957 ics_user_moved = 0;
\r
2958 if (forwardMostMove > backwardMostMove) {
\r
2959 currentMove = --forwardMostMove;
\r
2960 DisplayMove(currentMove - 1); /* before DMError */
\r
2961 DisplayMoveError(_("Illegal move (rejected by ICS)"));
\r
2962 DrawPosition(FALSE, boards[currentMove]);
\r
2964 DisplayBothClocks();
\r
2970 if (looking_at(buf, &i, "still have time") ||
\r
2971 looking_at(buf, &i, "not out of time") ||
\r
2972 looking_at(buf, &i, "either player is out of time") ||
\r
2973 looking_at(buf, &i, "has timeseal; checking")) {
\r
2974 /* We must have called his flag a little too soon */
\r
2975 whiteFlag = blackFlag = FALSE;
\r
2979 if (looking_at(buf, &i, "added * seconds to") ||
\r
2980 looking_at(buf, &i, "seconds were added to")) {
\r
2981 /* Update the clocks */
\r
2982 SendToICS(ics_prefix);
\r
2983 SendToICS("refresh\n");
\r
2987 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
\r
2988 ics_clock_paused = TRUE;
\r
2993 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
\r
2994 ics_clock_paused = FALSE;
\r
2999 /* Grab player ratings from the Creating: message.
\r
3000 Note we have to check for the special case when
\r
3001 the ICS inserts things like [white] or [black]. */
\r
3002 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
\r
3003 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
\r
3005 0 player 1 name (not necessarily white)
\r
3007 2 empty, white, or black (IGNORED)
\r
3008 3 player 2 name (not necessarily black)
\r
3011 The names/ratings are sorted out when the game
\r
3012 actually starts (below).
\r
3014 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
\r
3015 player1Rating = string_to_rating(star_match[1]);
\r
3016 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
\r
3017 player2Rating = string_to_rating(star_match[4]);
\r
3019 if (appData.debugMode)
\r
3021 "Ratings from 'Creating:' %s %d, %s %d\n",
\r
3022 player1Name, player1Rating,
\r
3023 player2Name, player2Rating);
\r
3028 /* Improved generic start/end-of-game messages */
\r
3029 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
\r
3030 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
\r
3031 /* If tkind == 0: */
\r
3032 /* star_match[0] is the game number */
\r
3033 /* [1] is the white player's name */
\r
3034 /* [2] is the black player's name */
\r
3035 /* For end-of-game: */
\r
3036 /* [3] is the reason for the game end */
\r
3037 /* [4] is a PGN end game-token, preceded by " " */
\r
3038 /* For start-of-game: */
\r
3039 /* [3] begins with "Creating" or "Continuing" */
\r
3040 /* [4] is " *" or empty (don't care). */
\r
3041 int gamenum = atoi(star_match[0]);
\r
3042 char *whitename, *blackname, *why, *endtoken;
\r
3043 ChessMove endtype = (ChessMove) 0;
\r
3046 whitename = star_match[1];
\r
3047 blackname = star_match[2];
\r
3048 why = star_match[3];
\r
3049 endtoken = star_match[4];
\r
3051 whitename = star_match[1];
\r
3052 blackname = star_match[3];
\r
3053 why = star_match[5];
\r
3054 endtoken = star_match[6];
\r
3057 /* Game start messages */
\r
3058 if (strncmp(why, "Creating ", 9) == 0 ||
\r
3059 strncmp(why, "Continuing ", 11) == 0) {
\r
3060 gs_gamenum = gamenum;
\r
3061 strcpy(gs_kind, strchr(why, ' ') + 1);
\r
3063 if (appData.zippyPlay) {
\r
3064 ZippyGameStart(whitename, blackname);
\r
3070 /* Game end messages */
\r
3071 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
\r
3072 ics_gamenum != gamenum) {
\r
3075 while (endtoken[0] == ' ') endtoken++;
\r
3076 switch (endtoken[0]) {
\r
3079 endtype = GameUnfinished;
\r
3082 endtype = BlackWins;
\r
3085 if (endtoken[1] == '/')
\r
3086 endtype = GameIsDrawn;
\r
3088 endtype = WhiteWins;
\r
3091 GameEnds(endtype, why, GE_ICS);
\r
3093 if (appData.zippyPlay && first.initDone) {
\r
3094 ZippyGameEnd(endtype, why);
\r
3095 if (first.pr == NULL) {
\r
3096 /* Start the next process early so that we'll
\r
3097 be ready for the next challenge */
\r
3098 StartChessProgram(&first);
\r
3100 /* Send "new" early, in case this command takes
\r
3101 a long time to finish, so that we'll be ready
\r
3102 for the next challenge. */
\r
3103 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
\r
3104 Reset(TRUE, TRUE);
\r
3110 if (looking_at(buf, &i, "Removing game * from observation") ||
\r
3111 looking_at(buf, &i, "no longer observing game *") ||
\r
3112 looking_at(buf, &i, "Game * (*) has no examiners")) {
\r
3113 if (gameMode == IcsObserving &&
\r
3114 atoi(star_match[0]) == ics_gamenum)
\r
3116 /* icsEngineAnalyze */
\r
3117 if (appData.icsEngineAnalyze) {
\r
3118 ExitAnalyzeMode();
\r
3122 gameMode = IcsIdle;
\r
3124 ics_user_moved = FALSE;
\r
3129 if (looking_at(buf, &i, "no longer examining game *")) {
\r
3130 if (gameMode == IcsExamining &&
\r
3131 atoi(star_match[0]) == ics_gamenum)
\r
3133 gameMode = IcsIdle;
\r
3135 ics_user_moved = FALSE;
\r
3140 /* Advance leftover_start past any newlines we find,
\r
3141 so only partial lines can get reparsed */
\r
3142 if (looking_at(buf, &i, "\n")) {
\r
3143 prevColor = curColor;
\r
3144 if (curColor != ColorNormal) {
\r
3145 if (oldi > next_out) {
\r
3146 SendToPlayer(&buf[next_out], oldi - next_out);
\r
3149 Colorize(ColorNormal, FALSE);
\r
3150 curColor = ColorNormal;
\r
3152 if (started == STARTED_BOARD) {
\r
3153 started = STARTED_NONE;
\r
3154 parse[parse_pos] = NULLCHAR;
\r
3155 ParseBoard12(parse);
\r
3156 ics_user_moved = 0;
\r
3158 /* Send premove here */
\r
3159 if (appData.premove) {
\r
3160 char str[MSG_SIZ];
\r
3161 if (currentMove == 0 &&
\r
3162 gameMode == IcsPlayingWhite &&
\r
3163 appData.premoveWhite) {
\r
3164 sprintf(str, "%s%s\n", ics_prefix,
\r
3165 appData.premoveWhiteText);
\r
3166 if (appData.debugMode)
\r
3167 fprintf(debugFP, "Sending premove:\n");
\r
3169 } else if (currentMove == 1 &&
\r
3170 gameMode == IcsPlayingBlack &&
\r
3171 appData.premoveBlack) {
\r
3172 sprintf(str, "%s%s\n", ics_prefix,
\r
3173 appData.premoveBlackText);
\r
3174 if (appData.debugMode)
\r
3175 fprintf(debugFP, "Sending premove:\n");
\r
3177 } else if (gotPremove) {
\r
3179 ClearPremoveHighlights();
\r
3180 if (appData.debugMode)
\r
3181 fprintf(debugFP, "Sending premove:\n");
\r
3182 UserMoveEvent(premoveFromX, premoveFromY,
\r
3183 premoveToX, premoveToY,
\r
3184 premovePromoChar);
\r
3188 /* Usually suppress following prompt */
\r
3189 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
\r
3190 if (looking_at(buf, &i, "*% ")) {
\r
3191 savingComment = FALSE;
\r
3195 } else if (started == STARTED_HOLDINGS) {
\r
3197 char new_piece[MSG_SIZ];
\r
3198 started = STARTED_NONE;
\r
3199 parse[parse_pos] = NULLCHAR;
\r
3200 if (appData.debugMode)
\r
3201 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
\r
3202 parse, currentMove);
\r
3203 if (sscanf(parse, " game %d", &gamenum) == 1 &&
\r
3204 gamenum == ics_gamenum) {
\r
3205 if (gameInfo.variant == VariantNormal) {
\r
3206 /* [HGM] We seem to switch variant during a game!
\r
3207 * Presumably no holdings were displayed, so we have
\r
3208 * to move the position two files to the right to
\r
3209 * create room for them!
\r
3211 VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
\r
3212 /* Get a move list just to see the header, which
\r
3213 will tell us whether this is really bug or zh */
\r
3214 if (ics_getting_history == H_FALSE) {
\r
3215 ics_getting_history = H_REQUESTED;
\r
3216 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
\r
3220 new_piece[0] = NULLCHAR;
\r
3221 sscanf(parse, "game %d white [%s black [%s <- %s",
\r
3222 &gamenum, white_holding, black_holding,
\r
3224 white_holding[strlen(white_holding)-1] = NULLCHAR;
\r
3225 black_holding[strlen(black_holding)-1] = NULLCHAR;
\r
3226 /* [HGM] copy holdings to board holdings area */
\r
3227 CopyHoldings(boards[currentMove], white_holding, WhitePawn);
\r
3228 CopyHoldings(boards[currentMove], black_holding, BlackPawn);
\r
3230 if (appData.zippyPlay && first.initDone) {
\r
3231 ZippyHoldings(white_holding, black_holding,
\r
3235 if (tinyLayout || smallLayout) {
\r
3236 char wh[16], bh[16];
\r
3237 PackHolding(wh, white_holding);
\r
3238 PackHolding(bh, black_holding);
\r
3239 sprintf(str, "[%s-%s] %s-%s", wh, bh,
\r
3240 gameInfo.white, gameInfo.black);
\r
3242 sprintf(str, "%s [%s] vs. %s [%s]",
\r
3243 gameInfo.white, white_holding,
\r
3244 gameInfo.black, black_holding);
\r
3247 DrawPosition(FALSE, boards[currentMove]);
\r
3248 DisplayTitle(str);
\r
3250 /* Suppress following prompt */
\r
3251 if (looking_at(buf, &i, "*% ")) {
\r
3252 savingComment = FALSE;
\r
3259 i++; /* skip unparsed character and loop back */
\r
3262 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
\r
3263 started != STARTED_HOLDINGS && i > next_out) {
\r
3264 SendToPlayer(&buf[next_out], i - next_out);
\r
3267 suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
\r
3269 leftover_len = buf_len - leftover_start;
\r
3270 /* if buffer ends with something we couldn't parse,
\r
3271 reparse it after appending the next read */
\r
3273 } else if (count == 0) {
\r
3274 RemoveInputSource(isr);
\r
3275 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
\r
3277 DisplayFatalError(_("Error reading from ICS"), error, 1);
\r
3282 /* Board style 12 looks like this:
\r
3284 <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
3286 * The "<12> " is stripped before it gets to this routine. The two
\r
3287 * trailing 0's (flip state and clock ticking) are later addition, and
\r
3288 * some chess servers may not have them, or may have only the first.
\r
3289 * Additional trailing fields may be added in the future.
\r
3292 #define PATTERN "%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
\r
3294 #define RELATION_OBSERVING_PLAYED 0
\r
3295 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
\r
3296 #define RELATION_PLAYING_MYMOVE 1
\r
3297 #define RELATION_PLAYING_NOTMYMOVE -1
\r
3298 #define RELATION_EXAMINING 2
\r
3299 #define RELATION_ISOLATED_BOARD -3
\r
3300 #define RELATION_STARTING_POSITION -4 /* FICS only */
\r
3303 ParseBoard12(string)
\r
3306 GameMode newGameMode;
\r
3307 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
\r
3308 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
\r
3309 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
\r
3310 char to_play, board_chars[200];
\r
3311 char move_str[500], str[500], elapsed_time[500];
\r
3312 char black[32], white[32];
\r
3314 int prevMove = currentMove;
\r
3316 ChessMove moveType;
\r
3317 int fromX, fromY, toX, toY;
\r
3319 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
\r
3320 char *bookHit = NULL; // [HGM] book
\r
3322 fromX = fromY = toX = toY = -1;
\r
3326 if (appData.debugMode)
\r
3327 fprintf(debugFP, _("Parsing board: %s\n"), string);
\r
3329 move_str[0] = NULLCHAR;
\r
3330 elapsed_time[0] = NULLCHAR;
\r
3331 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
\r
3333 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
\r
3334 if(string[i] == ' ') { ranks++; files = 0; }
\r
3338 for(j = 0; j <i; j++) board_chars[j] = string[j];
\r
3339 board_chars[i] = '\0';
\r
3342 n = sscanf(string, PATTERN, &to_play, &double_push,
\r
3343 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
\r
3344 &gamenum, white, black, &relation, &basetime, &increment,
\r
3345 &white_stren, &black_stren, &white_time, &black_time,
\r
3346 &moveNum, str, elapsed_time, move_str, &ics_flip,
\r
3350 sprintf(str, _("Failed to parse board string:\n\"%s\""), string);
\r
3351 DisplayError(str, 0);
\r
3355 /* Convert the move number to internal form */
\r
3356 moveNum = (moveNum - 1) * 2;
\r
3357 if (to_play == 'B') moveNum++;
\r
3358 if (moveNum >= MAX_MOVES) {
\r
3359 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
\r
3364 switch (relation) {
\r
3365 case RELATION_OBSERVING_PLAYED:
\r
3366 case RELATION_OBSERVING_STATIC:
\r
3367 if (gamenum == -1) {
\r
3368 /* Old ICC buglet */
\r
3369 relation = RELATION_OBSERVING_STATIC;
\r
3371 newGameMode = IcsObserving;
\r
3373 case RELATION_PLAYING_MYMOVE:
\r
3374 case RELATION_PLAYING_NOTMYMOVE:
\r
3376 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
\r
3377 IcsPlayingWhite : IcsPlayingBlack;
\r
3379 case RELATION_EXAMINING:
\r
3380 newGameMode = IcsExamining;
\r
3382 case RELATION_ISOLATED_BOARD:
\r
3384 /* Just display this board. If user was doing something else,
\r
3385 we will forget about it until the next board comes. */
\r
3386 newGameMode = IcsIdle;
\r
3388 case RELATION_STARTING_POSITION:
\r
3389 newGameMode = gameMode;
\r
3393 /* Modify behavior for initial board display on move listing
\r
3396 switch (ics_getting_history) {
\r
3400 case H_GOT_REQ_HEADER:
\r
3401 case H_GOT_UNREQ_HEADER:
\r
3402 /* This is the initial position of the current game */
\r
3403 gamenum = ics_gamenum;
\r
3404 moveNum = 0; /* old ICS bug workaround */
\r
3405 if (to_play == 'B') {
\r
3406 startedFromSetupPosition = TRUE;
\r
3407 blackPlaysFirst = TRUE;
\r
3409 if (forwardMostMove == 0) forwardMostMove = 1;
\r
3410 if (backwardMostMove == 0) backwardMostMove = 1;
\r
3411 if (currentMove == 0) currentMove = 1;
\r
3413 newGameMode = gameMode;
\r
3414 relation = RELATION_STARTING_POSITION; /* ICC needs this */
\r
3416 case H_GOT_UNWANTED_HEADER:
\r
3417 /* This is an initial board that we don't want */
\r
3419 case H_GETTING_MOVES:
\r
3420 /* Should not happen */
\r
3421 DisplayError(_("Error gathering move list: extra board"), 0);
\r
3422 ics_getting_history = H_FALSE;
\r
3426 /* Take action if this is the first board of a new game, or of a
\r
3427 different game than is currently being displayed. */
\r
3428 if (gamenum != ics_gamenum || newGameMode != gameMode ||
\r
3429 relation == RELATION_ISOLATED_BOARD) {
\r
3431 /* Forget the old game and get the history (if any) of the new one */
\r
3432 if (gameMode != BeginningOfGame) {
\r
3433 Reset(FALSE, TRUE);
\r
3436 if (appData.autoRaiseBoard) BoardToTop();
\r
3438 if (gamenum == -1) {
\r
3439 newGameMode = IcsIdle;
\r
3440 } else if (moveNum > 0 && newGameMode != IcsIdle &&
\r
3441 appData.getMoveList) {
\r
3442 /* Need to get game history */
\r
3443 ics_getting_history = H_REQUESTED;
\r
3444 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
\r
3448 /* Initially flip the board to have black on the bottom if playing
\r
3449 black or if the ICS flip flag is set, but let the user change
\r
3450 it with the Flip View button. */
\r
3451 flipView = appData.autoFlipView ?
\r
3452 (newGameMode == IcsPlayingBlack) || ics_flip :
\r
3455 /* Done with values from previous mode; copy in new ones */
\r
3456 gameMode = newGameMode;
\r
3458 ics_gamenum = gamenum;
\r
3459 if (gamenum == gs_gamenum) {
\r
3460 int klen = strlen(gs_kind);
\r
3461 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
\r
3462 sprintf(str, "ICS %s", gs_kind);
\r
3463 gameInfo.event = StrSave(str);
\r
3465 gameInfo.event = StrSave("ICS game");
\r
3467 gameInfo.site = StrSave(appData.icsHost);
\r
3468 gameInfo.date = PGNDate();
\r
3469 gameInfo.round = StrSave("-");
\r
3470 gameInfo.white = StrSave(white);
\r
3471 gameInfo.black = StrSave(black);
\r
3472 timeControl = basetime * 60 * 1000;
\r
3473 timeControl_2 = 0;
\r
3474 timeIncrement = increment * 1000;
\r
3475 movesPerSession = 0;
\r
3476 gameInfo.timeControl = TimeControlTagValue();
\r
3477 VariantSwitch(board, StringToVariant(gameInfo.event) );
\r