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
355 case VariantAtomic:
\r
356 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
\r
358 case VariantKriegspiel:
\r
359 flags |= F_KRIEGSPIEL_CAPTURE;
\r
361 case VariantCapaRandom:
\r
362 case VariantFischeRandom:
\r
363 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
\r
364 case VariantNoCastle:
\r
365 case VariantShatranj:
\r
366 case VariantCourier:
\r
367 flags &= ~F_ALL_CASTLE_OK;
\r
375 FILE *gameFileFP, *debugFP;
\r
378 [AS] Note: sometimes, the sscanf() function is used to parse the input
\r
379 into a fixed-size buffer. Because of this, we must be prepared to
\r
380 receive strings as long as the size of the input buffer, which is currently
\r
381 set to 4K for Windows and 8K for the rest.
\r
382 So, we must either allocate sufficiently large buffers here, or
\r
383 reduce the size of the input buffer in the input reading part.
\r
386 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
\r
387 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
\r
388 char thinkOutput1[MSG_SIZ*10];
\r
390 ChessProgramState first, second;
\r
392 /* premove variables */
\r
393 int premoveToX = 0;
\r
394 int premoveToY = 0;
\r
395 int premoveFromX = 0;
\r
396 int premoveFromY = 0;
\r
397 int premovePromoChar = 0;
\r
398 int gotPremove = 0;
\r
399 Boolean alarmSounded;
\r
400 /* end premove variables */
\r
402 char *ics_prefix = "$";
\r
403 int ics_type = ICS_GENERIC;
\r
405 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
\r
406 int pauseExamForwardMostMove = 0;
\r
407 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
\r
408 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
\r
409 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
\r
410 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
\r
411 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
\r
412 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
\r
413 int whiteFlag = FALSE, blackFlag = FALSE;
\r
414 int userOfferedDraw = FALSE;
\r
415 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
\r
416 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
\r
417 int cmailMoveType[CMAIL_MAX_GAMES];
\r
418 long ics_clock_paused = 0;
\r
419 ProcRef icsPR = NoProc, cmailPR = NoProc;
\r
420 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
\r
421 GameMode gameMode = BeginningOfGame;
\r
422 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
\r
423 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
\r
424 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
\r
425 int hiddenThinkOutputState = 0; /* [AS] */
\r
426 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
\r
427 int adjudicateLossPlies = 6;
\r
428 char white_holding[64], black_holding[64];
\r
429 TimeMark lastNodeCountTime;
\r
430 long lastNodeCount=0;
\r
431 int have_sent_ICS_logon = 0;
\r
432 int movesPerSession;
\r
433 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
\r
434 long timeControl_2; /* [AS] Allow separate time controls */
\r
435 char *fullTimeControlString = NULL; /* [HGM] secondary TC: merge of MPS, TC and inc */
\r
436 long timeRemaining[2][MAX_MOVES];
\r
438 TimeMark programStartTime;
\r
439 char ics_handle[MSG_SIZ];
\r
440 int have_set_title = 0;
\r
442 /* animateTraining preserves the state of appData.animate
\r
443 * when Training mode is activated. This allows the
\r
444 * response to be animated when appData.animate == TRUE and
\r
445 * appData.animateDragging == TRUE.
\r
447 Boolean animateTraining;
\r
453 Board boards[MAX_MOVES];
\r
454 /* [HGM] Following 7 needed for accurate legality tests: */
\r
455 char epStatus[MAX_MOVES];
\r
456 char castlingRights[MAX_MOVES][BOARD_SIZE]; // stores files for pieces with castling rights or -1
\r
457 char castlingRank[BOARD_SIZE]; // and corresponding ranks
\r
458 char initialRights[BOARD_SIZE], FENcastlingRights[BOARD_SIZE], fileRights[BOARD_SIZE];
\r
459 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
\r
460 int initialRulePlies, FENrulePlies;
\r
462 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
\r
464 int shuffleOpenings;
\r
466 ChessSquare FIDEArray[2][BOARD_SIZE] = {
\r
467 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
\r
468 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
\r
469 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
\r
470 BlackKing, BlackBishop, BlackKnight, BlackRook }
\r
473 ChessSquare twoKingsArray[2][BOARD_SIZE] = {
\r
474 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
\r
475 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
\r
476 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
\r
477 BlackKing, BlackKing, BlackKnight, BlackRook }
\r
480 ChessSquare KnightmateArray[2][BOARD_SIZE] = {
\r
481 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
\r
482 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
\r
483 { BlackRook, BlackMan, BlackBishop, BlackQueen,
\r
484 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
\r
487 ChessSquare fairyArray[2][BOARD_SIZE] = { /* [HGM] Queen side differs from King side */
\r
488 { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
\r
489 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
\r
490 { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
\r
491 BlackKing, BlackBishop, BlackKnight, BlackRook }
\r
494 ChessSquare ShatranjArray[2][BOARD_SIZE] = { /* [HGM] (movGen knows about Shatranj Q and P) */
\r
495 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
\r
496 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
\r
497 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
\r
498 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
\r
502 #if (BOARD_SIZE>=10)
\r
503 ChessSquare ShogiArray[2][BOARD_SIZE] = {
\r
504 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
\r
505 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
\r
506 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
\r
507 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
\r
510 ChessSquare XiangqiArray[2][BOARD_SIZE] = {
\r
511 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
\r
512 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
\r
513 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
\r
514 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
\r
517 ChessSquare CapablancaArray[2][BOARD_SIZE] = {
\r
518 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
\r
519 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
\r
520 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
\r
521 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
\r
524 ChessSquare GreatArray[2][BOARD_SIZE] = {
\r
525 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
\r
526 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
\r
527 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
\r
528 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
\r
531 ChessSquare JanusArray[2][BOARD_SIZE] = {
\r
532 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
\r
533 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
\r
534 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
\r
535 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
\r
539 ChessSquare GothicArray[2][BOARD_SIZE] = {
\r
540 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
\r
541 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
\r
542 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
\r
543 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
\r
546 #define GothicArray CapablancaArray
\r
550 ChessSquare FalconArray[2][BOARD_SIZE] = {
\r
551 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
\r
552 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
\r
553 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
\r
554 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
\r
557 #define FalconArray CapablancaArray
\r
560 #else // !(BOARD_SIZE>=10)
\r
561 #define XiangqiPosition FIDEArray
\r
562 #define CapablancaArray FIDEArray
\r
563 #define GothicArray FIDEArray
\r
564 #define GreatArray FIDEArray
\r
565 #endif // !(BOARD_SIZE>=10)
\r
567 #if (BOARD_SIZE>=12)
\r
568 ChessSquare CourierArray[2][BOARD_SIZE] = {
\r
569 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
\r
570 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
\r
571 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
\r
572 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
\r
574 #else // !(BOARD_SIZE>=12)
\r
575 #define CourierArray CapablancaArray
\r
576 #endif // !(BOARD_SIZE>=12)
\r
579 Board initialPosition;
\r
582 /* Convert str to a rating. Checks for special cases of "----",
\r
584 "++++", etc. Also strips ()'s */
\r
586 string_to_rating(str)
\r
589 while(*str && !isdigit(*str)) ++str;
\r
591 return 0; /* One of the special "no rating" cases */
\r
597 ClearProgramStats()
\r
599 /* Init programStats */
\r
600 programStats.movelist[0] = 0;
\r
601 programStats.depth = 0;
\r
602 programStats.nr_moves = 0;
\r
603 programStats.moves_left = 0;
\r
604 programStats.nodes = 0;
\r
605 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
\r
606 programStats.score = 0;
\r
607 programStats.got_only_move = 0;
\r
608 programStats.got_fail = 0;
\r
609 programStats.line_is_book = 0;
\r
615 int matched, min, sec;
\r
617 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
\r
619 GetTimeMark(&programStartTime);
\r
621 ClearProgramStats();
\r
622 programStats.ok_to_send = 1;
\r
623 programStats.seen_stat = 0;
\r
626 * Initialize game list
\r
628 ListNew(&gameList);
\r
632 * Internet chess server status
\r
634 if (appData.icsActive) {
\r
635 appData.matchMode = FALSE;
\r
636 appData.matchGames = 0;
\r
638 appData.noChessProgram = !appData.zippyPlay;
\r
640 appData.zippyPlay = FALSE;
\r
641 appData.zippyTalk = FALSE;
\r
642 appData.noChessProgram = TRUE;
\r
644 if (*appData.icsHelper != NULLCHAR) {
\r
645 appData.useTelnet = TRUE;
\r
646 appData.telnetProgram = appData.icsHelper;
\r
649 appData.zippyTalk = appData.zippyPlay = FALSE;
\r
652 /* [AS] Initialize pv info list [HGM] and game state */
\r
656 for( i=0; i<MAX_MOVES; i++ ) {
\r
657 pvInfoList[i].depth = -1;
\r
658 epStatus[i]=EP_NONE;
\r
659 for( j=0; j<BOARD_SIZE; j++ ) castlingRights[i][j] = -1;
\r
664 * Parse timeControl resource
\r
666 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
\r
667 appData.movesPerSession)) {
\r
669 sprintf(buf, _("bad timeControl option %s"), appData.timeControl);
\r
670 DisplayFatalError(buf, 0, 2);
\r
674 * Parse searchTime resource
\r
676 if (*appData.searchTime != NULLCHAR) {
\r
677 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
\r
678 if (matched == 1) {
\r
679 searchTime = min * 60;
\r
680 } else if (matched == 2) {
\r
681 searchTime = min * 60 + sec;
\r
684 sprintf(buf, _("bad searchTime option %s"), appData.searchTime);
\r
685 DisplayFatalError(buf, 0, 2);
\r
689 /* [AS] Adjudication threshold */
\r
690 adjudicateLossThreshold = appData.adjudicateLossThreshold;
\r
692 first.which = "first";
\r
693 second.which = "second";
\r
694 first.maybeThinking = second.maybeThinking = FALSE;
\r
695 first.pr = second.pr = NoProc;
\r
696 first.isr = second.isr = NULL;
\r
697 first.sendTime = second.sendTime = 2;
\r
698 first.sendDrawOffers = 1;
\r
699 if (appData.firstPlaysBlack) {
\r
700 first.twoMachinesColor = "black\n";
\r
701 second.twoMachinesColor = "white\n";
\r
703 first.twoMachinesColor = "white\n";
\r
704 second.twoMachinesColor = "black\n";
\r
706 first.program = appData.firstChessProgram;
\r
707 second.program = appData.secondChessProgram;
\r
708 first.host = appData.firstHost;
\r
709 second.host = appData.secondHost;
\r
710 first.dir = appData.firstDirectory;
\r
711 second.dir = appData.secondDirectory;
\r
712 first.other = &second;
\r
713 second.other = &first;
\r
714 first.initString = appData.initString;
\r
715 second.initString = appData.secondInitString;
\r
716 first.computerString = appData.firstComputerString;
\r
717 second.computerString = appData.secondComputerString;
\r
718 first.useSigint = second.useSigint = TRUE;
\r
719 first.useSigterm = second.useSigterm = TRUE;
\r
720 first.reuse = appData.reuseFirst;
\r
721 second.reuse = appData.reuseSecond;
\r
722 first.nps = appData.firstNPS; // [HGM] nps: copy nodes per second
\r
723 second.nps = appData.secondNPS;
\r
724 first.useSetboard = second.useSetboard = FALSE;
\r
725 first.useSAN = second.useSAN = FALSE;
\r
726 first.usePing = second.usePing = FALSE;
\r
727 first.lastPing = second.lastPing = 0;
\r
728 first.lastPong = second.lastPong = 0;
\r
729 first.usePlayother = second.usePlayother = FALSE;
\r
730 first.useColors = second.useColors = TRUE;
\r
731 first.useUsermove = second.useUsermove = FALSE;
\r
732 first.sendICS = second.sendICS = FALSE;
\r
733 first.sendName = second.sendName = appData.icsActive;
\r
734 first.sdKludge = second.sdKludge = FALSE;
\r
735 first.stKludge = second.stKludge = FALSE;
\r
736 TidyProgramName(first.program, first.host, first.tidy);
\r
737 TidyProgramName(second.program, second.host, second.tidy);
\r
738 first.matchWins = second.matchWins = 0;
\r
739 strcpy(first.variants, appData.variant);
\r
740 strcpy(second.variants, appData.variant);
\r
741 first.analysisSupport = second.analysisSupport = 2; /* detect */
\r
742 first.analyzing = second.analyzing = FALSE;
\r
743 first.initDone = second.initDone = FALSE;
\r
745 /* New features added by Tord: */
\r
746 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
\r
747 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
\r
748 /* End of new features added by Tord. */
\r
750 /* [HGM] time odds: set factor for each machine */
\r
751 first.timeOdds = appData.firstTimeOdds;
\r
752 second.timeOdds = appData.secondTimeOdds;
\r
754 if(appData.timeOddsMode) {
\r
755 norm = first.timeOdds;
\r
756 if(norm > second.timeOdds) norm = second.timeOdds;
\r
758 first.timeOdds /= norm;
\r
759 second.timeOdds /= norm;
\r
762 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
\r
763 first.accumulateTC = appData.firstAccumulateTC;
\r
764 second.accumulateTC = appData.secondAccumulateTC;
\r
765 first.maxNrOfSessions = second.maxNrOfSessions = 1;
\r
768 first.debug = second.debug = FALSE;
\r
769 first.supportsNPS = second.supportsNPS = UNKNOWN;
\r
771 /* [HGM] options */
\r
772 first.optionSettings = appData.firstOptions;
\r
773 second.optionSettings = appData.secondOptions;
\r
775 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
\r
776 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
\r
777 first.isUCI = appData.firstIsUCI; /* [AS] */
\r
778 second.isUCI = appData.secondIsUCI; /* [AS] */
\r
779 first.hasOwnBookUCI = appData.firstHasOwnBookUCI; /* [AS] */
\r
780 second.hasOwnBookUCI = appData.secondHasOwnBookUCI; /* [AS] */
\r
782 if (appData.firstProtocolVersion > PROTOVER ||
\r
783 appData.firstProtocolVersion < 1) {
\r
785 sprintf(buf, _("protocol version %d not supported"),
\r
786 appData.firstProtocolVersion);
\r
787 DisplayFatalError(buf, 0, 2);
\r
789 first.protocolVersion = appData.firstProtocolVersion;
\r
792 if (appData.secondProtocolVersion > PROTOVER ||
\r
793 appData.secondProtocolVersion < 1) {
\r
795 sprintf(buf, _("protocol version %d not supported"),
\r
796 appData.secondProtocolVersion);
\r
797 DisplayFatalError(buf, 0, 2);
\r
799 second.protocolVersion = appData.secondProtocolVersion;
\r
802 if (appData.icsActive) {
\r
803 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
\r
804 } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
\r
805 appData.clockMode = FALSE;
\r
806 first.sendTime = second.sendTime = 0;
\r
810 /* Override some settings from environment variables, for backward
\r
811 compatibility. Unfortunately it's not feasible to have the env
\r
812 vars just set defaults, at least in xboard. Ugh.
\r
814 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
\r
819 if (appData.noChessProgram) {
\r
820 programVersion = (char*) malloc(5 + strlen(PRODUCT) + strlen(VERSION)
\r
821 + strlen(PATCHLEVEL));
\r
822 sprintf(programVersion, "%s %s.%s", PRODUCT, VERSION, PATCHLEVEL);
\r
827 while (*q != ' ' && *q != NULLCHAR) q++;
\r
829 while (p > first.program && *(p-1) != '/' && *(p-1) != '\\') p--; /* [HGM] bckslash added */
\r
830 programVersion = (char*) malloc(8 + strlen(PRODUCT) + strlen(VERSION)
\r
831 + strlen(PATCHLEVEL) + (q - p));
\r
832 sprintf(programVersion, "%s %s.%s + ", PRODUCT, VERSION, PATCHLEVEL);
\r
833 strncat(programVersion, p, q - p);
\r
835 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
\r
836 programVersion = (char*) malloc(8 + strlen(PRODUCT) + strlen(VERSION)
\r
837 + strlen(PATCHLEVEL) + strlen(first.tidy));
\r
838 sprintf(programVersion, "%s %s.%s + %s", PRODUCT, VERSION, PATCHLEVEL, first.tidy);
\r
842 if (!appData.icsActive) {
\r
844 /* Check for variants that are supported only in ICS mode,
\r
845 or not at all. Some that are accepted here nevertheless
\r
846 have bugs; see comments below.
\r
848 VariantClass variant = StringToVariant(appData.variant);
\r
850 case VariantBughouse: /* need four players and two boards */
\r
851 case VariantKriegspiel: /* need to hide pieces and move details */
\r
852 /* case VariantFischeRandom: (Fabien: moved below) */
\r
853 sprintf(buf, _("Variant %s supported only in ICS mode"), appData.variant);
\r
854 DisplayFatalError(buf, 0, 2);
\r
857 case VariantUnknown:
\r
858 case VariantLoadable:
\r
868 sprintf(buf, _("Unknown variant name %s"), appData.variant);
\r
869 DisplayFatalError(buf, 0, 2);
\r
872 case VariantXiangqi: /* [HGM] repetition rules not implemented */
\r
873 case VariantFairy: /* [HGM] TestLegality definitely off! */
\r
874 case VariantGothic: /* [HGM] should work */
\r
875 case VariantCapablanca: /* [HGM] should work */
\r
876 case VariantCourier: /* [HGM] initial forced moves not implemented */
\r
877 case VariantShogi: /* [HGM] drops not tested for legality */
\r
878 case VariantKnightmate: /* [HGM] should work */
\r
879 case VariantCylinder: /* [HGM] untested */
\r
880 case VariantFalcon: /* [HGM] untested */
\r
881 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
\r
882 offboard interposition not understood */
\r
883 case VariantNormal: /* definitely works! */
\r
884 case VariantWildCastle: /* pieces not automatically shuffled */
\r
885 case VariantNoCastle: /* pieces not automatically shuffled */
\r
886 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
\r
887 case VariantLosers: /* should work except for win condition,
\r
888 and doesn't know captures are mandatory */
\r
889 case VariantSuicide: /* should work except for win condition,
\r
890 and doesn't know captures are mandatory */
\r
891 case VariantGiveaway: /* should work except for win condition,
\r
892 and doesn't know captures are mandatory */
\r
893 case VariantTwoKings: /* should work */
\r
894 case VariantAtomic: /* should work except for win condition */
\r
895 case Variant3Check: /* should work except for win condition */
\r
896 case VariantShatranj: /* should work except for all win conditions */
\r
897 case VariantBerolina: /* might work if TestLegality is off */
\r
898 case VariantCapaRandom: /* should work */
\r
899 case VariantJanus: /* should work */
\r
900 case VariantSuper: /* experimental */
\r
901 case VariantGreat: /* experimental, requires legality testing to be off */
\r
906 InitEngineUCI( installDir, &first ); // [HGM] moved here from winboard.c, to make available in xboard
\r
907 InitEngineUCI( installDir, &second );
\r
910 int NextIntegerFromString( char ** str, long * value )
\r
915 while( *s == ' ' || *s == '\t' ) {
\r
921 if( *s >= '0' && *s <= '9' ) {
\r
922 while( *s >= '0' && *s <= '9' ) {
\r
923 *value = *value * 10 + (*s - '0');
\r
935 int NextTimeControlFromString( char ** str, long * value )
\r
938 int result = NextIntegerFromString( str, &temp );
\r
940 if( result == 0 ) {
\r
941 *value = temp * 60; /* Minutes */
\r
942 if( **str == ':' ) {
\r
944 result = NextIntegerFromString( str, &temp );
\r
945 *value += temp; /* Seconds */
\r
952 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc)
\r
953 { /* [HGM] routine added to read '+moves/time' for secondary time control */
\r
954 int result = -1; long temp, temp2;
\r
956 if(**str != '+') return -1; // old params remain in force!
\r
958 if( NextTimeControlFromString( str, &temp ) ) return -1;
\r
961 /* time only: incremental or sudden-death time control */
\r
962 if(**str == '+') { /* increment follows; read it */
\r
964 if(result = NextIntegerFromString( str, &temp2)) return -1;
\r
965 *inc = temp2 * 1000;
\r
967 *moves = 0; *tc = temp * 1000;
\r
969 } else if(temp % 60 != 0) return -1; /* moves was given as min:sec */
\r
971 (*str)++; /* classical time control */
\r
972 result = NextTimeControlFromString( str, &temp2);
\r
975 *tc = temp2 * 1000;
\r
981 int GetTimeQuota(int movenr)
\r
982 { /* [HGM] get time to add from the multi-session time-control string */
\r
983 int moves=1; /* kludge to force reading of first session */
\r
984 long time, increment;
\r
985 char *s = fullTimeControlString;
\r
987 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", fullTimeControlString);
\r
989 if(moves) NextSessionFromString(&s, &moves, &time, &increment);
\r
990 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
\r
991 if(movenr == -1) return time; /* last move before new session */
\r
992 if(!moves) return increment; /* current session is incremental */
\r
993 if(movenr >= 0) movenr -= moves; /* we already finished this session */
\r
994 } while(movenr >= -1); /* try again for next session */
\r
996 return 0; // no new time quota on this move
\r
1000 ParseTimeControl(tc, ti, mps)
\r
1006 int matched, min, sec;
\r
1008 matched = sscanf(tc, "%d:%d", &min, &sec);
\r
1009 if (matched == 1) {
\r
1010 timeControl = min * 60 * 1000;
\r
1011 } else if (matched == 2) {
\r
1012 timeControl = (min * 60 + sec) * 1000;
\r
1019 char buf[MSG_SIZ];
\r
1021 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
\r
1024 sprintf(buf, "+%d/%s+%d", mps, tc, ti);
\r
1025 else sprintf(buf, "+%s+%d", tc, ti);
\r
1028 sprintf(buf, "+%d/%s", mps, tc);
\r
1029 else sprintf(buf, "+%s", tc);
\r
1031 fullTimeControlString = StrSave(buf);
\r
1033 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
\r
1037 if( *tc == '/' ) {
\r
1038 /* Parse second time control */
\r
1041 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
\r
1049 timeControl_2 = tc2 * 1000;
\r
1052 timeControl_2 = 0;
\r
1059 timeControl = tc1 * 1000;
\r
1063 timeIncrement = ti * 1000; /* convert to ms */
\r
1064 movesPerSession = 0;
\r
1066 timeIncrement = 0;
\r
1067 movesPerSession = mps;
\r
1075 if (appData.debugMode) {
\r
1076 fprintf(debugFP, "%s\n", programVersion);
\r
1079 if (appData.matchGames > 0) {
\r
1080 appData.matchMode = TRUE;
\r
1081 } else if (appData.matchMode) {
\r
1082 appData.matchGames = 1;
\r
1084 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
\r
1085 appData.matchGames = appData.sameColorGames;
\r
1086 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
\r
1087 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
\r
1088 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
\r
1090 Reset(TRUE, FALSE);
\r
1091 if (appData.noChessProgram || first.protocolVersion == 1) {
\r
1094 /* kludge: allow timeout for initial "feature" commands */
\r
1096 DisplayMessage("", _("Starting chess program"));
\r
1097 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
\r
1102 InitBackEnd3 P((void))
\r
1104 GameMode initialMode;
\r
1105 char buf[MSG_SIZ];
\r
1108 InitChessProgram(&first, startedFromSetupPosition);
\r
1111 if (appData.icsActive) {
\r
1113 /* [DM] Make a console window if needed [HGM] merged ifs */
\r
1116 err = establish();
\r
1118 if (*appData.icsCommPort != NULLCHAR) {
\r
1119 sprintf(buf, _("Could not open comm port %s"),
\r
1120 appData.icsCommPort);
\r
1122 sprintf(buf, _("Could not connect to host %s, port %s"),
\r
1123 appData.icsHost, appData.icsPort);
\r
1125 DisplayFatalError(buf, err, 1);
\r
1130 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
\r
1132 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
\r
1133 } else if (appData.noChessProgram) {
\r
1139 if (*appData.cmailGameName != NULLCHAR) {
\r
1141 OpenLoopback(&cmailPR);
\r
1143 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
\r
1147 DisplayMessage("", "");
\r
1148 if (StrCaseCmp(appData.initialMode, "") == 0) {
\r
1149 initialMode = BeginningOfGame;
\r
1150 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
\r
1151 initialMode = TwoMachinesPlay;
\r
1152 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
\r
1153 initialMode = AnalyzeFile;
\r
1154 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
\r
1155 initialMode = AnalyzeMode;
\r
1156 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
\r
1157 initialMode = MachinePlaysWhite;
\r
1158 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
\r
1159 initialMode = MachinePlaysBlack;
\r
1160 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
\r
1161 initialMode = EditGame;
\r
1162 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
\r
1163 initialMode = EditPosition;
\r
1164 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
\r
1165 initialMode = Training;
\r
1167 sprintf(buf, _("Unknown initialMode %s"), appData.initialMode);
\r
1168 DisplayFatalError(buf, 0, 2);
\r
1172 if (appData.matchMode) {
\r
1173 /* Set up machine vs. machine match */
\r
1174 if (appData.noChessProgram) {
\r
1175 DisplayFatalError(_("Can't have a match with no chess programs"),
\r
1181 if (*appData.loadGameFile != NULLCHAR) {
\r
1182 int index = appData.loadGameIndex; // [HGM] autoinc
\r
1183 if(index<0) lastIndex = index = 1;
\r
1184 if (!LoadGameFromFile(appData.loadGameFile,
\r
1186 appData.loadGameFile, FALSE)) {
\r
1187 DisplayFatalError(_("Bad game file"), 0, 1);
\r
1190 } else if (*appData.loadPositionFile != NULLCHAR) {
\r
1191 int index = appData.loadPositionIndex; // [HGM] autoinc
\r
1192 if(index<0) lastIndex = index = 1;
\r
1193 if (!LoadPositionFromFile(appData.loadPositionFile,
\r
1195 appData.loadPositionFile)) {
\r
1196 DisplayFatalError(_("Bad position file"), 0, 1);
\r
1200 TwoMachinesEvent();
\r
1201 } else if (*appData.cmailGameName != NULLCHAR) {
\r
1202 /* Set up cmail mode */
\r
1203 ReloadCmailMsgEvent(TRUE);
\r
1205 /* Set up other modes */
\r
1206 if (initialMode == AnalyzeFile) {
\r
1207 if (*appData.loadGameFile == NULLCHAR) {
\r
1208 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
\r
1212 if (*appData.loadGameFile != NULLCHAR) {
\r
1213 (void) LoadGameFromFile(appData.loadGameFile,
\r
1214 appData.loadGameIndex,
\r
1215 appData.loadGameFile, TRUE);
\r
1216 } else if (*appData.loadPositionFile != NULLCHAR) {
\r
1217 (void) LoadPositionFromFile(appData.loadPositionFile,
\r
1218 appData.loadPositionIndex,
\r
1219 appData.loadPositionFile);
\r
1220 /* [HGM] try to make self-starting even after FEN load */
\r
1221 /* to allow automatic setup of fairy variants with wtm */
\r
1222 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
\r
1223 gameMode = BeginningOfGame;
\r
1224 setboardSpoiledMachineBlack = 1;
\r
1226 /* [HGM] loadPos: make that every new game uses the setup */
\r
1227 /* from file as long as we do not switch variant */
\r
1228 if(!blackPlaysFirst) { int i;
\r
1229 startedFromPositionFile = TRUE;
\r
1230 CopyBoard(filePosition, boards[0]);
\r
1231 for(i=0; i<BOARD_SIZE; i++) fileRights[i] = castlingRights[0][i];
\r
1234 if (initialMode == AnalyzeMode) {
\r
1235 if (appData.noChessProgram) {
\r
1236 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
\r
1239 if (appData.icsActive) {
\r
1240 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
\r
1243 AnalyzeModeEvent();
\r
1244 } else if (initialMode == AnalyzeFile) {
\r
1245 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
\r
1246 ShowThinkingEvent();
\r
1247 AnalyzeFileEvent();
\r
1248 AnalysisPeriodicEvent(1);
\r
1249 } else if (initialMode == MachinePlaysWhite) {
\r
1250 if (appData.noChessProgram) {
\r
1251 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
\r
1255 if (appData.icsActive) {
\r
1256 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
\r
1260 MachineWhiteEvent();
\r
1261 } else if (initialMode == MachinePlaysBlack) {
\r
1262 if (appData.noChessProgram) {
\r
1263 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
\r
1267 if (appData.icsActive) {
\r
1268 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
\r
1272 MachineBlackEvent();
\r
1273 } else if (initialMode == TwoMachinesPlay) {
\r
1274 if (appData.noChessProgram) {
\r
1275 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
\r
1279 if (appData.icsActive) {
\r
1280 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
\r
1284 TwoMachinesEvent();
\r
1285 } else if (initialMode == EditGame) {
\r
1287 } else if (initialMode == EditPosition) {
\r
1288 EditPositionEvent();
\r
1289 } else if (initialMode == Training) {
\r
1290 if (*appData.loadGameFile == NULLCHAR) {
\r
1291 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
\r
1300 * Establish will establish a contact to a remote host.port.
\r
1301 * Sets icsPR to a ProcRef for a process (or pseudo-process)
\r
1302 * used to talk to the host.
\r
1303 * Returns 0 if okay, error code if not.
\r
1308 char buf[MSG_SIZ];
\r
1310 if (*appData.icsCommPort != NULLCHAR) {
\r
1311 /* Talk to the host through a serial comm port */
\r
1312 return OpenCommPort(appData.icsCommPort, &icsPR);
\r
1314 } else if (*appData.gateway != NULLCHAR) {
\r
1315 if (*appData.remoteShell == NULLCHAR) {
\r
1316 /* Use the rcmd protocol to run telnet program on a gateway host */
\r
1317 sprintf(buf, "%s %s %s",
\r
1318 appData.telnetProgram, appData.icsHost, appData.icsPort);
\r
1319 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
\r
1322 /* Use the rsh program to run telnet program on a gateway host */
\r
1323 if (*appData.remoteUser == NULLCHAR) {
\r
1324 sprintf(buf, "%s %s %s %s %s", appData.remoteShell,
\r
1325 appData.gateway, appData.telnetProgram,
\r
1326 appData.icsHost, appData.icsPort);
\r
1328 sprintf(buf, "%s %s -l %s %s %s %s",
\r
1329 appData.remoteShell, appData.gateway,
\r
1330 appData.remoteUser, appData.telnetProgram,
\r
1331 appData.icsHost, appData.icsPort);
\r
1333 return StartChildProcess(buf, "", &icsPR);
\r
1336 } else if (appData.useTelnet) {
\r
1337 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
\r
1340 /* TCP socket interface differs somewhat between
\r
1341 Unix and NT; handle details in the front end.
\r
1343 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
\r
1348 show_bytes(fp, buf, count)
\r
1354 if (*buf < 040 || *(unsigned char *) buf > 0177) {
\r
1355 fprintf(fp, "\\%03o", *buf & 0xff);
\r
1364 /* Returns an errno value */
\r
1366 OutputMaybeTelnet(pr, message, count, outError)
\r
1372 char buf[8192], *p, *q, *buflim;
\r
1373 int left, newcount, outcount;
\r
1375 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
\r
1376 *appData.gateway != NULLCHAR) {
\r
1377 if (appData.debugMode) {
\r
1378 fprintf(debugFP, ">ICS: ");
\r
1379 show_bytes(debugFP, message, count);
\r
1380 fprintf(debugFP, "\n");
\r
1382 return OutputToProcess(pr, message, count, outError);
\r
1385 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
\r
1391 if (q >= buflim) {
\r
1392 if (appData.debugMode) {
\r
1393 fprintf(debugFP, ">ICS: ");
\r
1394 show_bytes(debugFP, buf, newcount);
\r
1395 fprintf(debugFP, "\n");
\r
1397 outcount = OutputToProcess(pr, buf, newcount, outError);
\r
1398 if (outcount < newcount) return -1; /* to be sure */
\r
1405 } else if (((unsigned char) *p) == TN_IAC) {
\r
1406 *q++ = (char) TN_IAC;
\r
1413 if (appData.debugMode) {
\r
1414 fprintf(debugFP, ">ICS: ");
\r
1415 show_bytes(debugFP, buf, newcount);
\r
1416 fprintf(debugFP, "\n");
\r
1418 outcount = OutputToProcess(pr, buf, newcount, outError);
\r
1419 if (outcount < newcount) return -1; /* to be sure */
\r
1424 read_from_player(isr, closure, message, count, error)
\r
1425 InputSourceRef isr;
\r
1431 int outError, outCount;
\r
1432 static int gotEof = 0;
\r
1434 /* Pass data read from player on to ICS */
\r
1437 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
\r
1438 if (outCount < count) {
\r
1439 DisplayFatalError(_("Error writing to ICS"), outError, 1);
\r
1441 } else if (count < 0) {
\r
1442 RemoveInputSource(isr);
\r
1443 DisplayFatalError(_("Error reading from keyboard"), error, 1);
\r
1444 } else if (gotEof++ > 0) {
\r
1445 RemoveInputSource(isr);
\r
1446 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
\r
1454 int count, outCount, outError;
\r
1456 if (icsPR == NULL) return;
\r
1458 count = strlen(s);
\r
1459 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
\r
1460 if (outCount < count) {
\r
1461 DisplayFatalError(_("Error writing to ICS"), outError, 1);
\r
1465 /* This is used for sending logon scripts to the ICS. Sending
\r
1466 without a delay causes problems when using timestamp on ICC
\r
1467 (at least on my machine). */
\r
1469 SendToICSDelayed(s,msdelay)
\r
1473 int count, outCount, outError;
\r
1475 if (icsPR == NULL) return;
\r
1477 count = strlen(s);
\r
1478 if (appData.debugMode) {
\r
1479 fprintf(debugFP, ">ICS: ");
\r
1480 show_bytes(debugFP, s, count);
\r
1481 fprintf(debugFP, "\n");
\r
1483 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
\r
1485 if (outCount < count) {
\r
1486 DisplayFatalError(_("Error writing to ICS"), outError, 1);
\r
1491 /* Remove all highlighting escape sequences in s
\r
1492 Also deletes any suffix starting with '('
\r
1495 StripHighlightAndTitle(s)
\r
1498 static char retbuf[MSG_SIZ];
\r
1501 while (*s != NULLCHAR) {
\r
1502 while (*s == '\033') {
\r
1503 while (*s != NULLCHAR && !isalpha(*s)) s++;
\r
1504 if (*s != NULLCHAR) s++;
\r
1506 while (*s != NULLCHAR && *s != '\033') {
\r
1507 if (*s == '(' || *s == '[') {
\r
1518 /* Remove all highlighting escape sequences in s */
\r
1523 static char retbuf[MSG_SIZ];
\r
1526 while (*s != NULLCHAR) {
\r
1527 while (*s == '\033') {
\r
1528 while (*s != NULLCHAR && !isalpha(*s)) s++;
\r
1529 if (*s != NULLCHAR) s++;
\r
1531 while (*s != NULLCHAR && *s != '\033') {
\r
1539 char *variantNames[] = VARIANT_NAMES;
\r
1544 return variantNames[v];
\r
1548 /* Identify a variant from the strings the chess servers use or the
\r
1549 PGN Variant tag names we use. */
\r
1551 StringToVariant(e)
\r
1556 VariantClass v = VariantNormal;
\r
1557 int i, found = FALSE;
\r
1558 char buf[MSG_SIZ];
\r
1562 /* [HGM] skip over optional board-size prefixes */
\r
1563 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
\r
1564 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
\r
1565 while( *e++ != '_');
\r
1568 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
\r
1569 if (StrCaseStr(e, variantNames[i])) {
\r
1570 v = (VariantClass) i;
\r
1577 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
\r
1578 || StrCaseStr(e, "wild/fr")
\r
1579 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
\r
1580 v = VariantFischeRandom;
\r
1581 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
\r
1582 (i = 1, p = StrCaseStr(e, "w"))) {
\r
1584 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
\r
1585 if (isdigit(*p)) {
\r
1591 case 0: /* FICS only, actually */
\r
1593 /* Castling legal even if K starts on d-file */
\r
1594 v = VariantWildCastle;
\r
1599 /* Castling illegal even if K & R happen to start in
\r
1600 normal positions. */
\r
1601 v = VariantNoCastle;
\r
1614 /* Castling legal iff K & R start in normal positions */
\r
1615 v = VariantNormal;
\r
1620 /* Special wilds for position setup; unclear what to do here */
\r
1621 v = VariantLoadable;
\r
1624 /* Bizarre ICC game */
\r
1625 v = VariantTwoKings;
\r
1628 v = VariantKriegspiel;
\r
1631 v = VariantLosers;
\r
1634 v = VariantFischeRandom;
\r
1637 v = VariantCrazyhouse;
\r
1640 v = VariantBughouse;
\r
1643 v = Variant3Check;
\r
1646 /* Not quite the same as FICS suicide! */
\r
1647 v = VariantGiveaway;
\r
1650 v = VariantAtomic;
\r
1653 v = VariantShatranj;
\r
1656 /* Temporary names for future ICC types. The name *will* change in
\r
1657 the next xboard/WinBoard release after ICC defines it. */
\r
1686 v = VariantXiangqi;
\r
1689 v = VariantCourier;
\r
1692 v = VariantGothic;
\r
1695 v = VariantCapablanca;
\r
1698 v = VariantKnightmate;
\r
1704 v = VariantCylinder;
\r
1707 v = VariantFalcon;
\r
1710 v = VariantCapaRandom;
\r
1713 v = VariantBerolina;
\r
1725 /* Found "wild" or "w" in the string but no number;
\r
1726 must assume it's normal chess. */
\r
1727 v = VariantNormal;
\r
1730 sprintf(buf, _("Unknown wild type %d"), wnum);
\r
1731 DisplayError(buf, 0);
\r
1732 v = VariantUnknown;
\r
1737 if (appData.debugMode) {
\r
1738 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
\r
1739 e, wnum, VariantName(v));
\r
1744 static int leftover_start = 0, leftover_len = 0;
\r
1745 char star_match[STAR_MATCH_N][MSG_SIZ];
\r
1747 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
\r
1748 advance *index beyond it, and set leftover_start to the new value of
\r
1749 *index; else return FALSE. If pattern contains the character '*', it
\r
1750 matches any sequence of characters not containing '\r', '\n', or the
\r
1751 character following the '*' (if any), and the matched sequence(s) are
\r
1752 copied into star_match.
\r
1755 looking_at(buf, index, pattern)
\r
1760 char *bufp = &buf[*index], *patternp = pattern;
\r
1761 int star_count = 0;
\r
1762 char *matchp = star_match[0];
\r
1765 if (*patternp == NULLCHAR) {
\r
1766 *index = leftover_start = bufp - buf;
\r
1767 *matchp = NULLCHAR;
\r
1770 if (*bufp == NULLCHAR) return FALSE;
\r
1771 if (*patternp == '*') {
\r
1772 if (*bufp == *(patternp + 1)) {
\r
1773 *matchp = NULLCHAR;
\r
1774 matchp = star_match[++star_count];
\r
1778 } else if (*bufp == '\n' || *bufp == '\r') {
\r
1780 if (*patternp == NULLCHAR)
\r
1785 *matchp++ = *bufp++;
\r
1789 if (*patternp != *bufp) return FALSE;
\r
1796 SendToPlayer(data, length)
\r
1800 int error, outCount;
\r
1801 outCount = OutputToProcess(NoProc, data, length, &error);
\r
1802 if (outCount < length) {
\r
1803 DisplayFatalError(_("Error writing to display"), error, 1);
\r
1808 PackHolding(packed, holding)
\r
1812 char *p = holding;
\r
1814 int runlength = 0;
\r
1820 switch (runlength) {
\r
1831 sprintf(q, "%d", runlength);
\r
1843 /* Telnet protocol requests from the front end */
\r
1845 TelnetRequest(ddww, option)
\r
1846 unsigned char ddww, option;
\r
1848 unsigned char msg[3];
\r
1849 int outCount, outError;
\r
1851 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
\r
1853 if (appData.debugMode) {
\r
1854 char buf1[8], buf2[8], *ddwwStr, *optionStr;
\r
1870 sprintf(buf1, "%d", ddww);
\r
1875 optionStr = "ECHO";
\r
1879 sprintf(buf2, "%d", option);
\r
1882 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
\r
1887 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
\r
1888 if (outCount < 3) {
\r
1889 DisplayFatalError(_("Error writing to ICS"), outError, 1);
\r
1896 if (!appData.icsActive) return;
\r
1897 TelnetRequest(TN_DO, TN_ECHO);
\r
1903 if (!appData.icsActive) return;
\r
1904 TelnetRequest(TN_DONT, TN_ECHO);
\r
1908 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
\r
1910 /* put the holdings sent to us by the server on the board holdings area */
\r
1911 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
\r
1913 ChessSquare piece;
\r
1915 if(gameInfo.holdingsWidth < 2) return;
\r
1917 if( (int)lowestPiece >= BlackPawn ) {
\r
1918 holdingsColumn = 0;
\r
1920 holdingsStartRow = BOARD_HEIGHT-1;
\r
1923 holdingsColumn = BOARD_WIDTH-1;
\r
1924 countsColumn = BOARD_WIDTH-2;
\r
1925 holdingsStartRow = 0;
\r
1929 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
\r
1930 board[i][holdingsColumn] = EmptySquare;
\r
1931 board[i][countsColumn] = (ChessSquare) 0;
\r
1933 while( (p=*holdings++) != NULLCHAR ) {
\r
1934 piece = CharToPiece( ToUpper(p) );
\r
1935 if(piece == EmptySquare) continue;
\r
1936 /*j = (int) piece - (int) WhitePawn;*/
\r
1937 j = PieceToNumber(piece);
\r
1938 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
\r
1939 if(j < 0) continue; /* should not happen */
\r
1940 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
\r
1941 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
\r
1942 board[holdingsStartRow+j*direction][countsColumn]++;
\r
1949 VariantSwitch(Board board, VariantClass newVariant)
\r
1951 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
\r
1952 int oldCurrentMove = currentMove, oldForwardMostMove = forwardMostMove, oldBackwardMostMove = backwardMostMove;
\r
1953 // Board tempBoard; int saveCastling[BOARD_SIZE], saveEP;
\r
1955 startedFromPositionFile = FALSE;
\r
1956 if(gameInfo.variant == newVariant) return;
\r
1958 /* [HGM] This routine is called each time an assignment is made to
\r
1959 * gameInfo.variant during a game, to make sure the board sizes
\r
1960 * are set to match the new variant. If that means adding or deleting
\r
1961 * holdings, we shift the playing board accordingly
\r
1962 * This kludge is needed because in ICS observe mode, we get boards
\r
1963 * of an ongoing game without knowing the variant, and learn about the
\r
1964 * latter only later. This can be because of the move list we requested,
\r
1965 * in which case the game history is refilled from the beginning anyway,
\r
1966 * but also when receiving holdings of a crazyhouse game. In the latter
\r
1967 * case we want to add those holdings to the already received position.
\r
1971 if (appData.debugMode) {
\r
1972 fprintf(debugFP, "Switch board from %s to %s\n",
\r
1973 VariantName(gameInfo.variant), VariantName(newVariant));
\r
1974 setbuf(debugFP, NULL);
\r
1976 shuffleOpenings = 0; /* [HGM] shuffle */
\r
1977 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
\r
1978 switch(newVariant) {
\r
1979 case VariantShogi:
\r
1980 newWidth = 9; newHeight = 9;
\r
1981 gameInfo.holdingsSize = 7;
\r
1982 case VariantBughouse:
\r
1983 case VariantCrazyhouse:
\r
1984 newHoldingsWidth = 2; break;
\r
1986 newHoldingsWidth = gameInfo.holdingsSize = 0;
\r
1989 if(newWidth != gameInfo.boardWidth ||
\r
1990 newHeight != gameInfo.boardHeight ||
\r
1991 newHoldingsWidth != gameInfo.holdingsWidth ) {
\r
1993 /* shift position to new playing area, if needed */
\r
1994 if(newHoldingsWidth > gameInfo.holdingsWidth) {
\r
1995 for(i=0; i<BOARD_HEIGHT; i++)
\r
1996 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
\r
1997 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
\r
1999 for(i=0; i<newHeight; i++) {
\r
2000 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
\r
2001 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
\r
2003 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
\r
2004 for(i=0; i<BOARD_HEIGHT; i++)
\r
2005 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
\r
2006 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
\r
2010 gameInfo.boardWidth = newWidth;
\r
2011 gameInfo.boardHeight = newHeight;
\r
2012 gameInfo.holdingsWidth = newHoldingsWidth;
\r
2013 gameInfo.variant = newVariant;
\r
2014 InitDrawingSizes(-2, 0);
\r
2016 /* [HGM] The following should definitely be solved in a better way */
\r
2018 CopyBoard(board, tempBoard); /* save position in case it is board[0] */
\r
2019 for(i=0; i<BOARD_SIZE; i++) saveCastling[i] = castlingRights[0][i];
\r
2020 saveEP = epStatus[0];
\r
2022 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
\r
2024 epStatus[0] = saveEP;
\r
2025 for(i=0; i<BOARD_SIZE; i++) castlingRights[0][i] = saveCastling[i];
\r
2026 CopyBoard(tempBoard, board); /* restore position received from ICS */
\r
2028 } else { gameInfo.variant = newVariant; InitPosition(FALSE); }
\r
2030 forwardMostMove = oldForwardMostMove;
\r
2031 backwardMostMove = oldBackwardMostMove;
\r
2032 currentMove = oldCurrentMove; /* InitPos reset these, but we need still to redraw the position */
\r
2035 static int loggedOn = FALSE;
\r
2037 /*-- Game start info cache: --*/
\r
2039 char gs_kind[MSG_SIZ];
\r
2040 static char player1Name[128] = "";
\r
2041 static char player2Name[128] = "";
\r
2042 static int player1Rating = -1;
\r
2043 static int player2Rating = -1;
\r
2044 /*----------------------------*/
\r
2046 ColorClass curColor = ColorNormal;
\r
2047 int suppressKibitz = 0;
\r
2050 read_from_ics(isr, closure, data, count, error)
\r
2051 InputSourceRef isr;
\r
2057 #define BUF_SIZE 8192
\r
2058 #define STARTED_NONE 0
\r
2059 #define STARTED_MOVES 1
\r
2060 #define STARTED_BOARD 2
\r
2061 #define STARTED_OBSERVE 3
\r
2062 #define STARTED_HOLDINGS 4
\r
2063 #define STARTED_CHATTER 5
\r
2064 #define STARTED_COMMENT 6
\r
2065 #define STARTED_MOVES_NOHIDE 7
\r
2067 static int started = STARTED_NONE;
\r
2068 static char parse[20000];
\r
2069 static int parse_pos = 0;
\r
2070 static char buf[BUF_SIZE + 1];
\r
2071 static int firstTime = TRUE, intfSet = FALSE;
\r
2072 static ColorClass prevColor = ColorNormal;
\r
2073 static int savingComment = FALSE;
\r
2079 int backup; /* [DM] For zippy color lines */
\r
2082 if (appData.debugMode) {
\r
2084 fprintf(debugFP, "<ICS: ");
\r
2085 show_bytes(debugFP, data, count);
\r
2086 fprintf(debugFP, "\n");
\r
2090 if (appData.debugMode) { int f = forwardMostMove;
\r
2091 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
\r
2092 castlingRights[f][0],castlingRights[f][1],castlingRights[f][2],castlingRights[f][3],castlingRights[f][4],castlingRights[f][5]);
\r
2095 /* If last read ended with a partial line that we couldn't parse,
\r
2096 prepend it to the new read and try again. */
\r
2097 if (leftover_len > 0) {
\r
2098 for (i=0; i<leftover_len; i++)
\r
2099 buf[i] = buf[leftover_start + i];
\r
2102 /* Copy in new characters, removing nulls and \r's */
\r
2103 buf_len = leftover_len;
\r
2104 for (i = 0; i < count; i++) {
\r
2105 if (data[i] != NULLCHAR && data[i] != '\r')
\r
2106 buf[buf_len++] = data[i];
\r
2107 if(buf_len >= 5 && buf[buf_len-5]=='\n' && buf[buf_len-4]=='\\' &&
\r
2108 buf[buf_len-3]==' ' && buf[buf_len-2]==' ' && buf[buf_len-1]==' ')
\r
2109 buf_len -= 5; // [HGM] ICS: join continuation line of Lasker 2.2.3 server with previous
\r
2112 buf[buf_len] = NULLCHAR;
\r
2113 next_out = leftover_len;
\r
2114 leftover_start = 0;
\r
2117 while (i < buf_len) {
\r
2118 /* Deal with part of the TELNET option negotiation
\r
2119 protocol. We refuse to do anything beyond the
\r
2120 defaults, except that we allow the WILL ECHO option,
\r
2121 which ICS uses to turn off password echoing when we are
\r
2122 directly connected to it. We reject this option
\r
2123 if localLineEditing mode is on (always on in xboard)
\r
2124 and we are talking to port 23, which might be a real
\r
2125 telnet server that will try to keep WILL ECHO on permanently.
\r
2127 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
\r
2128 static int remoteEchoOption = FALSE; /* telnet ECHO option */
\r
2129 unsigned char option;
\r
2131 switch ((unsigned char) buf[++i]) {
\r
2133 if (appData.debugMode)
\r
2134 fprintf(debugFP, "\n<WILL ");
\r
2135 switch (option = (unsigned char) buf[++i]) {
\r
2137 if (appData.debugMode)
\r
2138 fprintf(debugFP, "ECHO ");
\r
2139 /* Reply only if this is a change, according
\r
2140 to the protocol rules. */
\r
2141 if (remoteEchoOption) break;
\r
2142 if (appData.localLineEditing &&
\r
2143 atoi(appData.icsPort) == TN_PORT) {
\r
2144 TelnetRequest(TN_DONT, TN_ECHO);
\r
2147 TelnetRequest(TN_DO, TN_ECHO);
\r
2148 remoteEchoOption = TRUE;
\r
2152 if (appData.debugMode)
\r
2153 fprintf(debugFP, "%d ", option);
\r
2154 /* Whatever this is, we don't want it. */
\r
2155 TelnetRequest(TN_DONT, option);
\r
2160 if (appData.debugMode)
\r
2161 fprintf(debugFP, "\n<WONT ");
\r
2162 switch (option = (unsigned char) buf[++i]) {
\r
2164 if (appData.debugMode)
\r
2165 fprintf(debugFP, "ECHO ");
\r
2166 /* Reply only if this is a change, according
\r
2167 to the protocol rules. */
\r
2168 if (!remoteEchoOption) break;
\r
2170 TelnetRequest(TN_DONT, TN_ECHO);
\r
2171 remoteEchoOption = FALSE;
\r
2174 if (appData.debugMode)
\r
2175 fprintf(debugFP, "%d ", (unsigned char) option);
\r
2176 /* Whatever this is, it must already be turned
\r
2177 off, because we never agree to turn on
\r
2178 anything non-default, so according to the
\r
2179 protocol rules, we don't reply. */
\r
2184 if (appData.debugMode)
\r
2185 fprintf(debugFP, "\n<DO ");
\r
2186 switch (option = (unsigned char) buf[++i]) {
\r
2188 /* Whatever this is, we refuse to do it. */
\r
2189 if (appData.debugMode)
\r
2190 fprintf(debugFP, "%d ", option);
\r
2191 TelnetRequest(TN_WONT, option);
\r
2196 if (appData.debugMode)
\r
2197 fprintf(debugFP, "\n<DONT ");
\r
2198 switch (option = (unsigned char) buf[++i]) {
\r
2200 if (appData.debugMode)
\r
2201 fprintf(debugFP, "%d ", option);
\r
2202 /* Whatever this is, we are already not doing
\r
2203 it, because we never agree to do anything
\r
2204 non-default, so according to the protocol
\r
2205 rules, we don't reply. */
\r
2210 if (appData.debugMode)
\r
2211 fprintf(debugFP, "\n<IAC ");
\r
2212 /* Doubled IAC; pass it through */
\r
2216 if (appData.debugMode)
\r
2217 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
\r
2218 /* Drop all other telnet commands on the floor */
\r
2221 if (oldi > next_out)
\r
2222 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2223 if (++i > next_out)
\r
2228 /* OK, this at least will *usually* work */
\r
2229 if (!loggedOn && looking_at(buf, &i, "ics%")) {
\r
2233 if (loggedOn && !intfSet) {
\r
2234 if (ics_type == ICS_ICC) {
\r
2236 "/set-quietly interface %s\n/set-quietly style 12\n",
\r
2239 } else if (ics_type == ICS_CHESSNET) {
\r
2240 sprintf(str, "/style 12\n");
\r
2242 strcpy(str, "alias $ @\n$set interface ");
\r
2243 strcat(str, programVersion);
\r
2244 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
\r
2246 strcat(str, "$iset nohighlight 1\n");
\r
2248 strcat(str, "$iset lock 1\n$style 12\n");
\r
2254 if (started == STARTED_COMMENT) {
\r
2255 /* Accumulate characters in comment */
\r
2256 parse[parse_pos++] = buf[i];
\r
2257 if (buf[i] == '\n') {
\r
2258 parse[parse_pos] = NULLCHAR;
\r
2259 if(!suppressKibitz) // [HGM] kibitz
\r
2260 AppendComment(forwardMostMove, StripHighlight(parse));
\r
2261 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
\r
2262 int nrDigit = 0, nrAlph = 0, i;
\r
2263 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
\r
2264 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
\r
2265 parse[parse_pos] = NULLCHAR;
\r
2266 // try to be smart: if it does not look like search info, it should go to
\r
2267 // ICS interaction window after all, not to engine-output window.
\r
2268 for(i=0; i<parse_pos; i++) { // count letters and digits
\r
2269 nrDigit += (parse[i] >= '0' && parse[i] <= '9');
\r
2270 nrAlph += (parse[i] >= 'a' && parse[i] <= 'z');
\r
2271 nrAlph += (parse[i] >= 'A' && parse[i] <= 'Z');
\r
2273 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
\r
2274 OutputKibitz(suppressKibitz, parse);
\r
2276 char tmp[MSG_SIZ];
\r
2277 sprintf(tmp, _("your opponent kibitzes: %s"), parse);
\r
2278 SendToPlayer(tmp, strlen(tmp));
\r
2281 started = STARTED_NONE;
\r
2283 /* Don't match patterns against characters in chatter */
\r
2288 if (started == STARTED_CHATTER) {
\r
2289 if (buf[i] != '\n') {
\r
2290 /* Don't match patterns against characters in chatter */
\r
2294 started = STARTED_NONE;
\r
2297 /* Kludge to deal with rcmd protocol */
\r
2298 if (firstTime && looking_at(buf, &i, "\001*")) {
\r
2299 DisplayFatalError(&buf[1], 0, 1);
\r
2302 firstTime = FALSE;
\r
2305 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
\r
2306 ics_type = ICS_ICC;
\r
2308 if (appData.debugMode)
\r
2309 fprintf(debugFP, "ics_type %d\n", ics_type);
\r
2312 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
\r
2313 ics_type = ICS_FICS;
\r
2315 if (appData.debugMode)
\r
2316 fprintf(debugFP, "ics_type %d\n", ics_type);
\r
2319 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
\r
2320 ics_type = ICS_CHESSNET;
\r
2322 if (appData.debugMode)
\r
2323 fprintf(debugFP, "ics_type %d\n", ics_type);
\r
2328 (looking_at(buf, &i, "\"*\" is *a registered name") ||
\r
2329 looking_at(buf, &i, "Logging you in as \"*\"") ||
\r
2330 looking_at(buf, &i, "will be \"*\""))) {
\r
2331 strcpy(ics_handle, star_match[0]);
\r
2335 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
\r
2336 char buf[MSG_SIZ];
\r
2337 sprintf(buf, "%s@%s", ics_handle, appData.icsHost);
\r
2338 DisplayIcsInteractionTitle(buf);
\r
2339 have_set_title = TRUE;
\r
2342 /* skip finger notes */
\r
2343 if (started == STARTED_NONE &&
\r
2344 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
\r
2345 (buf[i] == '1' && buf[i+1] == '0')) &&
\r
2346 buf[i+2] == ':' && buf[i+3] == ' ') {
\r
2347 started = STARTED_CHATTER;
\r
2352 /* skip formula vars */
\r
2353 if (started == STARTED_NONE &&
\r
2354 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
\r
2355 started = STARTED_CHATTER;
\r
2361 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
\r
2362 if (appData.autoKibitz && started == STARTED_NONE &&
\r
2363 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
\r
2364 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
\r
2365 if(looking_at(buf, &i, "* kibitzes: ") &&
\r
2366 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
\r
2367 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
\r
2368 suppressKibitz = TRUE;
\r
2369 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
\r
2370 && (gameMode == IcsPlayingWhite)) ||
\r
2371 (StrStr(star_match[0], gameInfo.black) == star_match[0]
\r
2372 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
\r
2373 started = STARTED_CHATTER; // own kibitz we simply discard
\r
2375 started = STARTED_COMMENT; // make sure it will be collected in parse[]
\r
2376 parse_pos = 0; parse[0] = NULLCHAR;
\r
2377 savingComment = TRUE;
\r
2378 suppressKibitz = gameMode != IcsObserving ? 2 :
\r
2379 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
\r
2383 if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
\r
2384 started = STARTED_CHATTER;
\r
2385 suppressKibitz = TRUE;
\r
2387 } // [HGM] kibitz: end of patch
\r
2389 if (appData.zippyTalk || appData.zippyPlay) {
\r
2390 /* [DM] Backup address for color zippy lines */
\r
2394 if (loggedOn == TRUE)
\r
2395 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
\r
2396 (appData.zippyPlay && ZippyMatch(buf, &backup)));
\r
2398 if (ZippyControl(buf, &i) ||
\r
2399 ZippyConverse(buf, &i) ||
\r
2400 (appData.zippyPlay && ZippyMatch(buf, &i))) {
\r
2402 if (!appData.colorize) continue;
\r
2406 } // [DM] 'else { ' deleted
\r
2407 if (/* Don't color "message" or "messages" output */
\r
2408 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
\r
2409 looking_at(buf, &i, "*. * at *:*: ") ||
\r
2410 looking_at(buf, &i, "--* (*:*): ") ||
\r
2411 /* Regular tells and says */
\r
2412 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
\r
2413 looking_at(buf, &i, "* (your partner) tells you: ") ||
\r
2414 looking_at(buf, &i, "* says: ") ||
\r
2415 /* Message notifications (same color as tells) */
\r
2416 looking_at(buf, &i, "* has left a message ") ||
\r
2417 looking_at(buf, &i, "* just sent you a message:\n") ||
\r
2418 /* Whispers and kibitzes */
\r
2419 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
\r
2420 looking_at(buf, &i, "* kibitzes: ") ||
\r
2421 /* Channel tells */
\r
2422 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
\r
2424 if (tkind == 1 && strchr(star_match[0], ':')) {
\r
2425 /* Avoid "tells you:" spoofs in channels */
\r
2428 if (star_match[0][0] == NULLCHAR ||
\r
2429 strchr(star_match[0], ' ') ||
\r
2430 (tkind == 3 && strchr(star_match[1], ' '))) {
\r
2431 /* Reject bogus matches */
\r
2434 if (appData.colorize) {
\r
2435 if (oldi > next_out) {
\r
2436 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2441 Colorize(ColorTell, FALSE);
\r
2442 curColor = ColorTell;
\r
2445 Colorize(ColorKibitz, FALSE);
\r
2446 curColor = ColorKibitz;
\r
2449 p = strrchr(star_match[1], '(');
\r
2451 p = star_match[1];
\r
2455 if (atoi(p) == 1) {
\r
2456 Colorize(ColorChannel1, FALSE);
\r
2457 curColor = ColorChannel1;
\r
2459 Colorize(ColorChannel, FALSE);
\r
2460 curColor = ColorChannel;
\r
2464 curColor = ColorNormal;
\r
2468 if (started == STARTED_NONE && appData.autoComment &&
\r
2469 (gameMode == IcsObserving ||
\r
2470 gameMode == IcsPlayingWhite ||
\r
2471 gameMode == IcsPlayingBlack)) {
\r
2472 parse_pos = i - oldi;
\r
2473 memcpy(parse, &buf[oldi], parse_pos);
\r
2474 parse[parse_pos] = NULLCHAR;
\r
2475 started = STARTED_COMMENT;
\r
2476 savingComment = TRUE;
\r
2478 started = STARTED_CHATTER;
\r
2479 savingComment = FALSE;
\r
2486 if (looking_at(buf, &i, "* s-shouts: ") ||
\r
2487 looking_at(buf, &i, "* c-shouts: ")) {
\r
2488 if (appData.colorize) {
\r
2489 if (oldi > next_out) {
\r
2490 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2493 Colorize(ColorSShout, FALSE);
\r
2494 curColor = ColorSShout;
\r
2497 started = STARTED_CHATTER;
\r
2501 if (looking_at(buf, &i, "--->")) {
\r
2506 if (looking_at(buf, &i, "* shouts: ") ||
\r
2507 looking_at(buf, &i, "--> ")) {
\r
2508 if (appData.colorize) {
\r
2509 if (oldi > next_out) {
\r
2510 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2513 Colorize(ColorShout, FALSE);
\r
2514 curColor = ColorShout;
\r
2517 started = STARTED_CHATTER;
\r
2521 if (looking_at( buf, &i, "Challenge:")) {
\r
2522 if (appData.colorize) {
\r
2523 if (oldi > next_out) {
\r
2524 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2527 Colorize(ColorChallenge, FALSE);
\r
2528 curColor = ColorChallenge;
\r
2534 if (looking_at(buf, &i, "* offers you") ||
\r
2535 looking_at(buf, &i, "* offers to be") ||
\r
2536 looking_at(buf, &i, "* would like to") ||
\r
2537 looking_at(buf, &i, "* requests to") ||
\r
2538 looking_at(buf, &i, "Your opponent offers") ||
\r
2539 looking_at(buf, &i, "Your opponent requests")) {
\r
2541 if (appData.colorize) {
\r
2542 if (oldi > next_out) {
\r
2543 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2546 Colorize(ColorRequest, FALSE);
\r
2547 curColor = ColorRequest;
\r
2552 if (looking_at(buf, &i, "* (*) seeking")) {
\r
2553 if (appData.colorize) {
\r
2554 if (oldi > next_out) {
\r
2555 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2558 Colorize(ColorSeek, FALSE);
\r
2559 curColor = ColorSeek;
\r
2564 if (looking_at(buf, &i, "\\ ")) {
\r
2565 if (prevColor != ColorNormal) {
\r
2566 if (oldi > next_out) {
\r
2567 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2570 Colorize(prevColor, TRUE);
\r
2571 curColor = prevColor;
\r
2573 if (savingComment) {
\r
2574 parse_pos = i - oldi;
\r
2575 memcpy(parse, &buf[oldi], parse_pos);
\r
2576 parse[parse_pos] = NULLCHAR;
\r
2577 started = STARTED_COMMENT;
\r
2579 started = STARTED_CHATTER;
\r
2584 if (looking_at(buf, &i, "Black Strength :") ||
\r
2585 looking_at(buf, &i, "<<< style 10 board >>>") ||
\r
2586 looking_at(buf, &i, "<10>") ||
\r
2587 looking_at(buf, &i, "#@#")) {
\r
2588 /* Wrong board style */
\r
2590 SendToICS(ics_prefix);
\r
2591 SendToICS("set style 12\n");
\r
2592 SendToICS(ics_prefix);
\r
2593 SendToICS("refresh\n");
\r
2597 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
\r
2599 have_sent_ICS_logon = 1;
\r
2603 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
\r
2604 (looking_at(buf, &i, "\n<12> ") ||
\r
2605 looking_at(buf, &i, "<12> "))) {
\r
2607 if (oldi > next_out) {
\r
2608 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2611 started = STARTED_BOARD;
\r
2616 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
\r
2617 looking_at(buf, &i, "<b1> ")) {
\r
2618 if (oldi > next_out) {
\r
2619 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2622 started = STARTED_HOLDINGS;
\r
2627 if (looking_at(buf, &i, "* *vs. * *--- *")) {
\r
2629 /* Header for a move list -- first line */
\r
2631 switch (ics_getting_history) {
\r
2633 switch (gameMode) {
\r
2635 case BeginningOfGame:
\r
2636 /* User typed "moves" or "oldmoves" while we
\r
2637 were idle. Pretend we asked for these
\r
2638 moves and soak them up so user can step
\r
2639 through them and/or save them.
\r
2641 Reset(FALSE, TRUE);
\r
2642 gameMode = IcsObserving;
\r
2645 ics_getting_history = H_GOT_UNREQ_HEADER;
\r
2647 case EditGame: /*?*/
\r
2648 case EditPosition: /*?*/
\r
2649 /* Should above feature work in these modes too? */
\r
2650 /* For now it doesn't */
\r
2651 ics_getting_history = H_GOT_UNWANTED_HEADER;
\r
2654 ics_getting_history = H_GOT_UNWANTED_HEADER;
\r
2659 /* Is this the right one? */
\r
2660 if (gameInfo.white && gameInfo.black &&
\r
2661 strcmp(gameInfo.white, star_match[0]) == 0 &&
\r
2662 strcmp(gameInfo.black, star_match[2]) == 0) {
\r
2664 ics_getting_history = H_GOT_REQ_HEADER;
\r
2667 case H_GOT_REQ_HEADER:
\r
2668 case H_GOT_UNREQ_HEADER:
\r
2669 case H_GOT_UNWANTED_HEADER:
\r
2670 case H_GETTING_MOVES:
\r
2671 /* Should not happen */
\r
2672 DisplayError(_("Error gathering move list: two headers"), 0);
\r
2673 ics_getting_history = H_FALSE;
\r
2677 /* Save player ratings into gameInfo if needed */
\r
2678 if ((ics_getting_history == H_GOT_REQ_HEADER ||
\r
2679 ics_getting_history == H_GOT_UNREQ_HEADER) &&
\r
2680 (gameInfo.whiteRating == -1 ||
\r
2681 gameInfo.blackRating == -1)) {
\r
2683 gameInfo.whiteRating = string_to_rating(star_match[1]);
\r
2684 gameInfo.blackRating = string_to_rating(star_match[3]);
\r
2685 if (appData.debugMode)
\r
2686 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
\r
2687 gameInfo.whiteRating, gameInfo.blackRating);
\r
2692 if (looking_at(buf, &i,
\r
2693 "* * match, initial time: * minute*, increment: * second")) {
\r
2694 /* Header for a move list -- second line */
\r
2695 /* Initial board will follow if this is a wild game */
\r
2696 if (gameInfo.event != NULL) free(gameInfo.event);
\r
2697 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
\r
2698 gameInfo.event = StrSave(str);
\r
2699 /* [HGM] we switched variant. Translate boards if needed. */
\r
2700 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
\r
2704 if (looking_at(buf, &i, "Move ")) {
\r
2705 /* Beginning of a move list */
\r
2706 switch (ics_getting_history) {
\r
2708 /* Normally should not happen */
\r
2709 /* Maybe user hit reset while we were parsing */
\r
2712 /* Happens if we are ignoring a move list that is not
\r
2713 * the one we just requested. Common if the user
\r
2714 * tries to observe two games without turning off
\r
2717 case H_GETTING_MOVES:
\r
2718 /* Should not happen */
\r
2719 DisplayError(_("Error gathering move list: nested"), 0);
\r
2720 ics_getting_history = H_FALSE;
\r
2722 case H_GOT_REQ_HEADER:
\r
2723 ics_getting_history = H_GETTING_MOVES;
\r
2724 started = STARTED_MOVES;
\r
2726 if (oldi > next_out) {
\r
2727 SendToPlayer(&buf[next_out], oldi - next_out);
\r
2730 case H_GOT_UNREQ_HEADER:
\r
2731 ics_getting_history = H_GETTING_MOVES;
\r
2732 started = STARTED_MOVES_NOHIDE;
\r
2735 case H_GOT_UNWANTED_HEADER:
\r
2736 ics_getting_history = H_FALSE;
\r
2742 if (looking_at(buf, &i, "% ") ||
\r
2743 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
\r
2744 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
\r
2745 savingComment = FALSE;
\r
2746 switch (started) {
\r
2747 case STARTED_MOVES:
\r
2748 case STARTED_MOVES_NOHIDE:
\r
2749 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
\r
2750 parse[parse_pos + i - oldi] = NULLCHAR;
\r
2751 ParseGameHistory(parse);
\r
2753 if (appData.zippyPlay && first.initDone) {
\r
2754 FeedMovesToProgram(&first, forwardMostMove);
\r
2755 if (gameMode == IcsPlayingWhite) {
\r
2756 if (WhiteOnMove(forwardMostMove)) {
\r
2757 if (first.sendTime) {
\r
2758 if (first.useColors) {
\r
2759 SendToProgram("black\n", &first);
\r
2761 SendTimeRemaining(&first, TRUE);
\r
2764 if (first.useColors) {
\r
2765 SendToProgram("white\ngo\n", &first);
\r
2767 SendToProgram("go\n", &first);
\r
2770 if (first.useColors) {
\r
2771 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
\r
2773 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
\r
2775 first.maybeThinking = TRUE;
\r
2777 if (first.usePlayother) {
\r
2778 if (first.sendTime) {
\r
2779 SendTimeRemaining(&first, TRUE);
\r
2781 SendToProgram("playother\n", &first);
\r
2782 firstMove = FALSE;
\r
2787 } else if (gameMode == IcsPlayingBlack) {
\r
2788 if (!WhiteOnMove(forwardMostMove)) {
\r
2789 if (first.sendTime) {
\r
2790 if (first.useColors) {
\r
2791 SendToProgram("white\n", &first);
\r
2793 SendTimeRemaining(&first, FALSE);
\r
2796 if (first.useColors) {
\r
2797 SendToProgram("black\ngo\n", &first);
\r
2799 SendToProgram("go\n", &first);
\r
2802 if (first.useColors) {
\r
2803 SendToProgram("black\n", &first);
\r
2805 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
\r
2807 first.maybeThinking = TRUE;
\r
2809 if (first.usePlayother) {
\r
2810 if (first.sendTime) {
\r
2811 SendTimeRemaining(&first, FALSE);
\r
2813 SendToProgram("playother\n", &first);
\r
2814 firstMove = FALSE;
\r
2822 if (gameMode == IcsObserving && ics_gamenum == -1) {
\r
2823 /* Moves came from oldmoves or moves command
\r
2824 while we weren't doing anything else.
\r
2826 currentMove = forwardMostMove;
\r
2827 ClearHighlights();/*!!could figure this out*/
\r
2828 flipView = appData.flipView;
\r
2829 DrawPosition(FALSE, boards[currentMove]);
\r
2830 DisplayBothClocks();
\r
2831 sprintf(str, "%s vs. %s",
\r
2832 gameInfo.white, gameInfo.black);
\r
2833 DisplayTitle(str);
\r
2834 gameMode = IcsIdle;
\r
2836 /* Moves were history of an active game */
\r
2837 if (gameInfo.resultDetails != NULL) {
\r
2838 free(gameInfo.resultDetails);
\r
2839 gameInfo.resultDetails = NULL;
\r
2842 HistorySet(parseList, backwardMostMove,
\r
2843 forwardMostMove, currentMove-1);
\r
2844 DisplayMove(currentMove - 1);
\r
2845 if (started == STARTED_MOVES) next_out = i;
\r
2846 started = STARTED_NONE;
\r
2847 ics_getting_history = H_FALSE;
\r
2850 case STARTED_OBSERVE:
\r
2851 started = STARTED_NONE;
\r
2852 SendToICS(ics_prefix);
\r
2853 SendToICS("refresh\n");
\r
2859 if(bookHit) { // [HGM] book: simulate book reply
\r
2860 static char bookMove[MSG_SIZ]; // a bit generous?
\r
2862 programStats.nodes = programStats.depth = programStats.time =
\r
2863 programStats.score = programStats.got_only_move = 0;
\r
2864 sprintf(programStats.movelist, "%s (xbook)", bookHit);
\r
2866 strcpy(bookMove, "move ");
\r
2867 strcat(bookMove, bookHit);
\r
2868 HandleMachineMove(bookMove, &first);
\r
2873 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
\r
2874 started == STARTED_HOLDINGS ||
\r
2875 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
\r
2876 /* Accumulate characters in move list or board */
\r
2877 parse[parse_pos++] = buf[i];
\r
2880 /* Start of game messages. Mostly we detect start of game
\r
2881 when the first board image arrives. On some versions
\r
2882 of the ICS, though, we need to do a "refresh" after starting
\r
2883 to observe in order to get the current board right away. */
\r
2884 if (looking_at(buf, &i, "Adding game * to observation list")) {
\r
2885 started = STARTED_OBSERVE;
\r
2889 /* Handle auto-observe */
\r
2890 if (appData.autoObserve &&
\r
2891 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
\r
2892 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
\r
2894 /* Choose the player that was highlighted, if any. */
\r
2895 if (star_match[0][0] == '\033' ||
\r
2896 star_match[1][0] != '\033') {
\r
2897 player = star_match[0];
\r
2899 player = star_match[2];
\r
2901 sprintf(str, "%sobserve %s\n",
\r
2902 ics_prefix, StripHighlightAndTitle(player));
\r
2905 /* Save ratings from notify string */
\r
2906 strcpy(player1Name, star_match[0]);
\r
2907 player1Rating = string_to_rating(star_match[1]);
\r
2908 strcpy(player2Name, star_match[2]);
\r
2909 player2Rating = string_to_rating(star_match[3]);
\r
2911 if (appData.debugMode)
\r
2913 "Ratings from 'Game notification:' %s %d, %s %d\n",
\r
2914 player1Name, player1Rating,
\r
2915 player2Name, player2Rating);
\r
2920 /* Deal with automatic examine mode after a game,
\r
2921 and with IcsObserving -> IcsExamining transition */
\r
2922 if (looking_at(buf, &i, "Entering examine mode for game *") ||
\r
2923 looking_at(buf, &i, "has made you an examiner of game *")) {
\r
2925 int gamenum = atoi(star_match[0]);
\r
2926 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
\r
2927 gamenum == ics_gamenum) {
\r
2928 /* We were already playing or observing this game;
\r
2929 no need to refetch history */
\r
2930 gameMode = IcsExamining;
\r
2932 pauseExamForwardMostMove = forwardMostMove;
\r
2933 } else if (currentMove < forwardMostMove) {
\r
2934 ForwardInner(forwardMostMove);
\r
2937 /* I don't think this case really can happen */
\r
2938 SendToICS(ics_prefix);
\r
2939 SendToICS("refresh\n");
\r
2944 /* Error messages */
\r
2945 if (ics_user_moved) {
\r
2946 if (looking_at(buf, &i, "Illegal move") ||
\r
2947 looking_at(buf, &i, "Not a legal move") ||
\r
2948 looking_at(buf, &i, "Your king is in check") ||
\r
2949 looking_at(buf, &i, "It isn't your turn") ||
\r
2950 looking_at(buf, &i, "It is not your move")) {
\r
2951 /* Illegal move */
\r
2952 ics_user_moved = 0;
\r
2953 if (forwardMostMove > backwardMostMove) {
\r
2954 currentMove = --forwardMostMove;
\r
2955 DisplayMove(currentMove - 1); /* before DMError */
\r
2956 DisplayMoveError(_("Illegal move (rejected by ICS)"));
\r
2957 DrawPosition(FALSE, boards[currentMove]);
\r
2959 DisplayBothClocks();
\r
2965 if (looking_at(buf, &i, "still have time") ||
\r
2966 looking_at(buf, &i, "not out of time") ||
\r
2967 looking_at(buf, &i, "either player is out of time") ||
\r
2968 looking_at(buf, &i, "has timeseal; checking")) {
\r
2969 /* We must have called his flag a little too soon */
\r
2970 whiteFlag = blackFlag = FALSE;
\r
2974 if (looking_at(buf, &i, "added * seconds to") ||
\r
2975 looking_at(buf, &i, "seconds were added to")) {
\r
2976 /* Update the clocks */
\r
2977 SendToICS(ics_prefix);
\r
2978 SendToICS("refresh\n");
\r
2982 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
\r
2983 ics_clock_paused = TRUE;
\r
2988 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
\r
2989 ics_clock_paused = FALSE;
\r
2994 /* Grab player ratings from the Creating: message.
\r
2995 Note we have to check for the special case when
\r
2996 the ICS inserts things like [white] or [black]. */
\r
2997 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
\r
2998 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
\r
3000 0 player 1 name (not necessarily white)
\r
3002 2 empty, white, or black (IGNORED)
\r
3003 3 player 2 name (not necessarily black)
\r
3006 The names/ratings are sorted out when the game
\r
3007 actually starts (below).
\r
3009 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
\r
3010 player1Rating = string_to_rating(star_match[1]);
\r
3011 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
\r
3012 player2Rating = string_to_rating(star_match[4]);
\r
3014 if (appData.debugMode)
\r
3016 "Ratings from 'Creating:' %s %d, %s %d\n",
\r
3017 player1Name, player1Rating,
\r
3018 player2Name, player2Rating);
\r
3023 /* Improved generic start/end-of-game messages */
\r
3024 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
\r
3025 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
\r
3026 /* If tkind == 0: */
\r
3027 /* star_match[0] is the game number */
\r
3028 /* [1] is the white player's name */
\r
3029 /* [2] is the black player's name */
\r
3030 /* For end-of-game: */
\r
3031 /* [3] is the reason for the game end */
\r
3032 /* [4] is a PGN end game-token, preceded by " " */
\r
3033 /* For start-of-game: */
\r
3034 /* [3] begins with "Creating" or "Continuing" */
\r
3035 /* [4] is " *" or empty (don't care). */
\r
3036 int gamenum = atoi(star_match[0]);
\r
3037 char *whitename, *blackname, *why, *endtoken;
\r
3038 ChessMove endtype = (ChessMove) 0;
\r
3041 whitename = star_match[1];
\r
3042 blackname = star_match[2];
\r
3043 why = star_match[3];
\r
3044 endtoken = star_match[4];
\r
3046 whitename = star_match[1];
\r
3047 blackname = star_match[3];
\r
3048 why = star_match[5];
\r
3049 endtoken = star_match[6];
\r
3052 /* Game start messages */
\r
3053 if (strncmp(why, "Creating ", 9) == 0 ||
\r
3054 strncmp(why, "Continuing ", 11) == 0) {
\r
3055 gs_gamenum = gamenum;
\r
3056 strcpy(gs_kind, strchr(why, ' ') + 1);
\r
3058 if (appData.zippyPlay) {
\r
3059 ZippyGameStart(whitename, blackname);
\r
3065 /* Game end messages */
\r
3066 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
\r
3067 ics_gamenum != gamenum) {
\r
3070 while (endtoken[0] == ' ') endtoken++;
\r
3071 switch (endtoken[0]) {
\r
3074 endtype = GameUnfinished;
\r
3077 endtype = BlackWins;
\r
3080 if (endtoken[1] == '/')
\r
3081 endtype = GameIsDrawn;
\r
3083 endtype = WhiteWins;
\r
3086 GameEnds(endtype, why, GE_ICS);
\r
3088 if (appData.zippyPlay && first.initDone) {
\r
3089 ZippyGameEnd(endtype, why);
\r
3090 if (first.pr == NULL) {
\r
3091 /* Start the next process early so that we'll
\r
3092 be ready for the next challenge */
\r
3093 StartChessProgram(&first);
\r
3095 /* Send "new" early, in case this command takes
\r
3096 a long time to finish, so that we'll be ready
\r
3097 for the next challenge. */
\r
3098 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
\r
3099 Reset(TRUE, TRUE);
\r
3105 if (looking_at(buf, &i, "Removing game * from observation") ||
\r
3106 looking_at(buf, &i, "no longer observing game *") ||
\r
3107 looking_at(buf, &i, "Game * (*) has no examiners")) {
\r
3108 if (gameMode == IcsObserving &&
\r
3109 atoi(star_match[0]) == ics_gamenum)
\r
3111 /* icsEngineAnalyze */
\r
3112 if (appData.icsEngineAnalyze) {
\r
3113 ExitAnalyzeMode();
\r
3117 gameMode = IcsIdle;
\r
3119 ics_user_moved = FALSE;
\r
3124 if (looking_at(buf, &i, "no longer examining game *")) {
\r
3125 if (gameMode == IcsExamining &&
\r
3126 atoi(star_match[0]) == ics_gamenum)
\r
3128 gameMode = IcsIdle;
\r
3130 ics_user_moved = FALSE;
\r
3135 /* Advance leftover_start past any newlines we find,
\r
3136 so only partial lines can get reparsed */
\r
3137 if (looking_at(buf, &i, "\n")) {
\r
3138 prevColor = curColor;
\r
3139 if (curColor != ColorNormal) {
\r
3140 if (oldi > next_out) {
\r
3141 SendToPlayer(&buf[next_out], oldi - next_out);
\r
3144 Colorize(ColorNormal, FALSE);
\r
3145 curColor = ColorNormal;
\r
3147 if (started == STARTED_BOARD) {
\r
3148 started = STARTED_NONE;
\r
3149 parse[parse_pos] = NULLCHAR;
\r
3150 ParseBoard12(parse);
\r
3151 ics_user_moved = 0;
\r
3153 /* Send premove here */
\r
3154 if (appData.premove) {
\r
3155 char str[MSG_SIZ];
\r
3156 if (currentMove == 0 &&
\r
3157 gameMode == IcsPlayingWhite &&
\r
3158 appData.premoveWhite) {
\r
3159 sprintf(str, "%s%s\n", ics_prefix,
\r
3160 appData.premoveWhiteText);
\r
3161 if (appData.debugMode)
\r
3162 fprintf(debugFP, "Sending premove:\n");
\r
3164 } else if (currentMove == 1 &&
\r
3165 gameMode == IcsPlayingBlack &&
\r
3166 appData.premoveBlack) {
\r
3167 sprintf(str, "%s%s\n", ics_prefix,
\r
3168 appData.premoveBlackText);
\r
3169 if (appData.debugMode)
\r
3170 fprintf(debugFP, "Sending premove:\n");
\r
3172 } else if (gotPremove) {
\r
3174 ClearPremoveHighlights();
\r
3175 if (appData.debugMode)
\r
3176 fprintf(debugFP, "Sending premove:\n");
\r
3177 UserMoveEvent(premoveFromX, premoveFromY,
\r
3178 premoveToX, premoveToY,
\r
3179 premovePromoChar);
\r
3183 /* Usually suppress following prompt */
\r
3184 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
\r
3185 if (looking_at(buf, &i, "*% ")) {
\r
3186 savingComment = FALSE;
\r
3190 } else if (started == STARTED_HOLDINGS) {
\r
3192 char new_piece[MSG_SIZ];
\r
3193 started = STARTED_NONE;
\r
3194 parse[parse_pos] = NULLCHAR;
\r
3195 if (appData.debugMode)
\r
3196 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
\r
3197 parse, currentMove);
\r
3198 if (sscanf(parse, " game %d", &gamenum) == 1 &&
\r
3199 gamenum == ics_gamenum) {
\r
3200 if (gameInfo.variant == VariantNormal) {
\r
3201 /* [HGM] We seem to switch variant during a game!
\r
3202 * Presumably no holdings were displayed, so we have
\r
3203 * to move the position two files to the right to
\r
3204 * create room for them!
\r
3206 VariantSwitch(boards[currentMove], VariantCrazyhouse); /* temp guess */
\r
3207 /* Get a move list just to see the header, which
\r
3208 will tell us whether this is really bug or zh */
\r
3209 if (ics_getting_history == H_FALSE) {
\r
3210 ics_getting_history = H_REQUESTED;
\r
3211 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
\r
3215 new_piece[0] = NULLCHAR;
\r
3216 sscanf(parse, "game %d white [%s black [%s <- %s",
\r
3217 &gamenum, white_holding, black_holding,
\r
3219 white_holding[strlen(white_holding)-1] = NULLCHAR;
\r
3220 black_holding[strlen(black_holding)-1] = NULLCHAR;
\r
3221 /* [HGM] copy holdings to board holdings area */
\r
3222 CopyHoldings(boards[currentMove], white_holding, WhitePawn);
\r
3223 CopyHoldings(boards[currentMove], black_holding, BlackPawn);
\r
3225 if (appData.zippyPlay && first.initDone) {
\r
3226 ZippyHoldings(white_holding, black_holding,
\r
3230 if (tinyLayout || smallLayout) {
\r
3231 char wh[16], bh[16];
\r
3232 PackHolding(wh, white_holding);
\r
3233 PackHolding(bh, black_holding);
\r
3234 sprintf(str, "[%s-%s] %s-%s", wh, bh,
\r
3235 gameInfo.white, gameInfo.black);
\r
3237 sprintf(str, "%s [%s] vs. %s [%s]",
\r
3238 gameInfo.white, white_holding,
\r
3239 gameInfo.black, black_holding);
\r
3242 DrawPosition(FALSE, boards[currentMove]);
\r
3243 DisplayTitle(str);
\r
3245 /* Suppress following prompt */
\r
3246 if (looking_at(buf, &i, "*% ")) {
\r
3247 savingComment = FALSE;
\r
3254 i++; /* skip unparsed character and loop back */
\r
3257 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
\r
3258 started != STARTED_HOLDINGS && i > next_out) {
\r
3259 SendToPlayer(&buf[next_out], i - next_out);
\r
3262 suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
\r
3264 leftover_len = buf_len - leftover_start;
\r
3265 /* if buffer ends with something we couldn't parse,
\r
3266 reparse it after appending the next read */
\r
3268 } else if (count == 0) {
\r
3269 RemoveInputSource(isr);
\r
3270 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
\r
3272 DisplayFatalError(_("Error reading from ICS"), error, 1);
\r
3277 /* Board style 12 looks like this:
\r
3279 <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
3281 * The "<12> " is stripped before it gets to this routine. The two
\r
3282 * trailing 0's (flip state and clock ticking) are later addition, and
\r
3283 * some chess servers may not have them, or may have only the first.
\r
3284 * Additional trailing fields may be added in the future.
\r
3287 #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
3289 #define RELATION_OBSERVING_PLAYED 0
\r
3290 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
\r
3291 #define RELATION_PLAYING_MYMOVE 1
\r
3292 #define RELATION_PLAYING_NOTMYMOVE -1
\r
3293 #define RELATION_EXAMINING 2
\r
3294 #define RELATION_ISOLATED_BOARD -3
\r
3295 #define RELATION_STARTING_POSITION -4 /* FICS only */
\r
3298 ParseBoard12(string)
\r
3301 GameMode newGameMode;
\r
3302 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
\r
3303 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
\r
3304 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
\r
3305 char to_play, board_chars[200];
\r
3306 char move_str[500], str[500], elapsed_time[500];
\r
3307 char black[32], white[32];
\r
3309 int prevMove = currentMove;
\r
3311 ChessMove moveType;
\r
3312 int fromX, fromY, toX, toY;
\r
3314 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
\r
3315 char *bookHit = NULL; // [HGM] book
\r
3317 fromX = fromY = toX = toY = -1;
\r
3321 if (appData.debugMode)
\r
3322 fprintf(debugFP, _("Parsing board: %s\n"), string);
\r
3324 move_str[0] = NULLCHAR;
\r
3325 elapsed_time[0] = NULLCHAR;
\r
3326 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
\r
3328 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
\r
3329 if(string[i] == ' ') { ranks++; files = 0; }
\r
3333 for(j = 0; j <i; j++) board_chars[j] = string[j];
\r
3334 board_chars[i] = '\0';
\r
3337 n = sscanf(string, PATTERN, &to_play, &double_push,
\r
3338 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
\r
3339 &gamenum, white, black, &relation, &basetime, &increment,
\r
3340 &white_stren, &black_stren, &white_time, &black_time,
\r
3341 &moveNum, str, elapsed_time, move_str, &ics_flip,
\r
3345 sprintf(str, _("Failed to parse board string:\n\"%s\""), string);
\r
3346 DisplayError(str, 0);
\r
3350 /* Convert the move number to internal form */
\r
3351 moveNum = (moveNum - 1) * 2;
\r
3352 if (to_play == 'B') moveNum++;
\r
3353 if (moveNum >= MAX_MOVES) {
\r
3354 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
\r
3359 switch (relation) {
\r
3360 case RELATION_OBSERVING_PLAYED:
\r
3361 case RELATION_OBSERVING_STATIC:
\r
3362 if (gamenum == -1) {
\r
3363 /* Old ICC buglet */
\r
3364 relation = RELATION_OBSERVING_STATIC;
\r
3366 newGameMode = IcsObserving;
\r
3368 case RELATION_PLAYING_MYMOVE:
\r
3369 case RELATION_PLAYING_NOTMYMOVE:
\r
3371 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
\r
3372 IcsPlayingWhite : IcsPlayingBlack;
\r
3374 case RELATION_EXAMINING:
\r
3375 newGameMode = IcsExamining;
\r
3377 case RELATION_ISOLATED_BOARD:
\r
3379 /* Just display this board. If user was doing something else,
\r
3380 we will forget about it until the next board comes. */
\r
3381 newGameMode = IcsIdle;
\r
3383 case RELATION_STARTING_POSITION:
\r
3384 newGameMode = gameMode;
\r
3388 /* Modify behavior for initial board display on move listing
\r
3391 switch (ics_getting_history) {
\r
3395 case H_GOT_REQ_HEADER:
\r
3396 case H_GOT_UNREQ_HEADER:
\r
3397 /* This is the initial position of the current game */
\r
3398 gamenum = ics_gamenum;
\r
3399 moveNum = 0; /* old ICS bug workaround */
\r
3400 if (to_play == 'B') {
\r
3401 startedFromSetupPosition = TRUE;
\r
3402 blackPlaysFirst = TRUE;
\r
3404 if (forwardMostMove == 0) forwardMostMove = 1;
\r
3405 if (backwardMostMove == 0) backwardMostMove = 1;
\r
3406 if (currentMove == 0) currentMove = 1;
\r
3408 newGameMode = gameMode;
\r
3409 relation = RELATION_STARTING_POSITION; /* ICC needs this */
\r
3411 case H_GOT_UNWANTED_HEADER:
\r
3412 /* This is an initial board that we don't want */
\r
3414 case H_GETTING_MOVES:
\r
3415 /* Should not happen */
\r
3416 DisplayError(_("Error gathering move list: extra board"), 0);
\r
3417 ics_getting_history = H_FALSE;
\r
3421 /* Take action if this is the first board of a new game, or of a
\r
3422 different game than is currently being displayed. */
\r
3423 if (gamenum != ics_gamenum || newGameMode != gameMode ||
\r
3424 relation == RELATION_ISOLATED_BOARD) {
\r
3426 /* Forget the old game and get the history (if any) of the new one */
\r
3427 if (gameMode != BeginningOfGame) {
\r
3428 Reset(FALSE, TRUE);
\r
3431 if (appData.autoRaiseBoard) BoardToTop();
\r
3433 if (gamenum == -1) {
\r
3434 newGameMode = IcsIdle;
\r
3435 } else if (moveNum > 0 && newGameMode != IcsIdle &&
\r
3436 appData.getMoveList) {
\r
3437 /* Need to get game history */
\r
3438 ics_getting_history = H_REQUESTED;
\r
3439 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
\r
3443 /* Initially flip the board to have black on the bottom if playing
\r
3444 black or if the ICS flip flag is set, but let the user change
\r
3445 it with the Flip View button. */
\r
3446 flipView = appData.autoFlipView ?
\r
3447 (newGameMode == IcsPlayingBlack) || ics_flip :
\r
3450 /* Done with values from previous mode; copy in new ones */
\r
3451 gameMode = newGameMode;
\r
3453 ics_gamenum = gamenum;
\r
3454 if (gamenum == gs_gamenum) {
\r
3455 int klen = strlen(gs_kind);
\r
3456 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
\r
3457 sprintf(str, "ICS %s", gs_kind);
\r
3458 gameInfo.event = StrSave(str);
\r
3460 gameInfo.event = StrSave("ICS game");
\r
3462 gameInfo.site = StrSave(appData.icsHost);
\r
3463 gameInfo.date = PGNDate();
\r
3464 gameInfo.round = StrSave("-");
\r
3465 gameInfo.white = StrSave(white);
\r
3466 gameInfo.black = StrSave(black);
\r
3467 timeControl = basetime * 60 * 1000;
\r
3468 timeControl_2 = 0;
\r
3469 timeIncrement = increment * 1000;
\r
3470 movesPerSession = 0;
\r
3471 gameInfo.timeControl = TimeControlTagValue();
\r
3472 VariantSwitch(board, StringToVariant(gameInfo.event) );
\r
3473 if (appData.debugMode) {
\r
3474 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
\r
3475 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
\r
3476 setbuf(debugFP, NULL);
\r