2 * backend.c -- Common back end for X and Windows NT versions of
4 * Copyright 1991 by Digital Equipment Corporation, Maynard,
7 * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8 * 2007, 2008, 2009, 2010, 2011 Free Software Foundation, Inc.
10 * Enhancements Copyright 2005 Alessandro Scotti
12 * The following terms apply to Digital Equipment Corporation's copyright
14 * ------------------------------------------------------------------------
17 * Permission to use, copy, modify, and distribute this software and its
18 * documentation for any purpose and without fee is hereby granted,
19 * provided that the above copyright notice appear in all copies and that
20 * both that copyright notice and this permission notice appear in
21 * supporting documentation, and that the name of Digital not be
22 * used in advertising or publicity pertaining to distribution of the
23 * software without specific, written prior permission.
25 * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27 * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
32 * ------------------------------------------------------------------------
34 * The following terms apply to the enhanced version of XBoard
35 * distributed by the Free Software Foundation:
36 * ------------------------------------------------------------------------
38 * GNU XBoard is free software: you can redistribute it and/or modify
39 * it under the terms of the GNU General Public License as published by
40 * the Free Software Foundation, either version 3 of the License, or (at
41 * your option) any later version.
43 * GNU XBoard is distributed in the hope that it will be useful, but
44 * WITHOUT ANY WARRANTY; without even the implied warranty of
45 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46 * General Public License for more details.
48 * You should have received a copy of the GNU General Public License
49 * along with this program. If not, see http://www.gnu.org/licenses/. *
51 *------------------------------------------------------------------------
52 ** See the file ChangeLog for a revision history. */
54 /* [AS] Also useful here for debugging */
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
60 int flock(int f, int code);
65 #define DoSleep( n ) if( (n) >= 0) sleep(n)
75 #include <sys/types.h>
84 #else /* not STDC_HEADERS */
87 # else /* not HAVE_STRING_H */
89 # endif /* not HAVE_STRING_H */
90 #endif /* not STDC_HEADERS */
93 # include <sys/fcntl.h>
94 #else /* not HAVE_SYS_FCNTL_H */
97 # endif /* HAVE_FCNTL_H */
98 #endif /* not HAVE_SYS_FCNTL_H */
100 #if TIME_WITH_SYS_TIME
101 # include <sys/time.h>
105 # include <sys/time.h>
111 #if defined(_amigados) && !defined(__GNUC__)
116 extern int gettimeofday(struct timeval *, struct timezone *);
124 #include "frontend.h"
131 #include "backendz.h"
135 # define _(s) gettext (s)
136 # define N_(s) gettext_noop (s)
137 # define T_(s) gettext(s)
150 /* A point in time */
152 long sec; /* Assuming this is >= 32 bits */
153 int ms; /* Assuming this is >= 16 bits */
156 int establish P((void));
157 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
158 char *buf, int count, int error));
159 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
160 char *buf, int count, int error));
161 void ics_printf P((char *format, ...));
162 void SendToICS P((char *s));
163 void SendToICSDelayed P((char *s, long msdelay));
164 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
165 void HandleMachineMove P((char *message, ChessProgramState *cps));
166 int AutoPlayOneMove P((void));
167 int LoadGameOneMove P((ChessMove readAhead));
168 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
169 int LoadPositionFromFile P((char *filename, int n, char *title));
170 int SavePositionToFile P((char *filename));
171 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
173 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
174 void ShowMove P((int fromX, int fromY, int toX, int toY));
175 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
176 /*char*/int promoChar));
177 void BackwardInner P((int target));
178 void ForwardInner P((int target));
179 int Adjudicate P((ChessProgramState *cps));
180 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
181 void EditPositionDone P((Boolean fakeRights));
182 void PrintOpponents P((FILE *fp));
183 void PrintPosition P((FILE *fp, int move));
184 void StartChessProgram P((ChessProgramState *cps));
185 void SendToProgram P((char *message, ChessProgramState *cps));
186 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
187 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
188 char *buf, int count, int error));
189 void SendTimeControl P((ChessProgramState *cps,
190 int mps, long tc, int inc, int sd, int st));
191 char *TimeControlTagValue P((void));
192 void Attention P((ChessProgramState *cps));
193 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
194 void ResurrectChessProgram P((void));
195 void DisplayComment P((int moveNumber, char *text));
196 void DisplayMove P((int moveNumber));
198 void ParseGameHistory P((char *game));
199 void ParseBoard12 P((char *string));
200 void KeepAlive P((void));
201 void StartClocks P((void));
202 void SwitchClocks P((int nr));
203 void StopClocks P((void));
204 void ResetClocks P((void));
205 char *PGNDate P((void));
206 void SetGameInfo P((void));
207 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
208 int RegisterMove P((void));
209 void MakeRegisteredMove P((void));
210 void TruncateGame P((void));
211 int looking_at P((char *, int *, char *));
212 void CopyPlayerNameIntoFileName P((char **, char *));
213 char *SavePart P((char *));
214 int SaveGameOldStyle P((FILE *));
215 int SaveGamePGN P((FILE *));
216 void GetTimeMark P((TimeMark *));
217 long SubtractTimeMarks P((TimeMark *, TimeMark *));
218 int CheckFlags P((void));
219 long NextTickLength P((long));
220 void CheckTimeControl P((void));
221 void show_bytes P((FILE *, char *, int));
222 int string_to_rating P((char *str));
223 void ParseFeatures P((char* args, ChessProgramState *cps));
224 void InitBackEnd3 P((void));
225 void FeatureDone P((ChessProgramState* cps, int val));
226 void InitChessProgram P((ChessProgramState *cps, int setup));
227 void OutputKibitz(int window, char *text);
228 int PerpetualChase(int first, int last);
229 int EngineOutputIsUp();
230 void InitDrawingSizes(int x, int y);
233 extern void ConsoleCreate();
236 ChessProgramState *WhitePlayer();
237 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
238 int VerifyDisplayMode P(());
240 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
241 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
242 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
243 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
244 void ics_update_width P((int new_width));
245 extern char installDir[MSG_SIZ];
246 VariantClass startVariant; /* [HGM] nicks: initial variant */
248 extern int tinyLayout, smallLayout;
249 ChessProgramStats programStats;
250 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
252 static int exiting = 0; /* [HGM] moved to top */
253 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
254 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
255 Board partnerBoard; /* [HGM] bughouse: for peeking at partner game */
256 int partnerHighlight[2];
257 Boolean partnerBoardValid = 0;
258 char partnerStatus[MSG_SIZ];
260 Boolean originalFlip;
261 Boolean twoBoards = 0;
262 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
263 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
264 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
265 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
266 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
267 int opponentKibitzes;
268 int lastSavedGame; /* [HGM] save: ID of game */
269 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
270 extern int chatCount;
272 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
273 ChessSquare pieceSweep = EmptySquare;
274 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
275 int promoDefaultAltered;
277 /* States for ics_getting_history */
279 #define H_REQUESTED 1
280 #define H_GOT_REQ_HEADER 2
281 #define H_GOT_UNREQ_HEADER 3
282 #define H_GETTING_MOVES 4
283 #define H_GOT_UNWANTED_HEADER 5
285 /* whosays values for GameEnds */
294 /* Maximum number of games in a cmail message */
295 #define CMAIL_MAX_GAMES 20
297 /* Different types of move when calling RegisterMove */
299 #define CMAIL_RESIGN 1
301 #define CMAIL_ACCEPT 3
303 /* Different types of result to remember for each game */
304 #define CMAIL_NOT_RESULT 0
305 #define CMAIL_OLD_RESULT 1
306 #define CMAIL_NEW_RESULT 2
308 /* Telnet protocol constants */
319 safeStrCpy( char *dst, const char *src, size_t count )
322 assert( dst != NULL );
323 assert( src != NULL );
326 for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
327 if( i == count && dst[count-1] != NULLCHAR)
329 dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
330 if(appData.debugMode)
331 fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
337 /* Some compiler can't cast u64 to double
338 * This function do the job for us:
340 * We use the highest bit for cast, this only
341 * works if the highest bit is not
342 * in use (This should not happen)
344 * We used this for all compiler
347 u64ToDouble(u64 value)
350 u64 tmp = value & u64Const(0x7fffffffffffffff);
351 r = (double)(s64)tmp;
352 if (value & u64Const(0x8000000000000000))
353 r += 9.2233720368547758080e18; /* 2^63 */
357 /* Fake up flags for now, as we aren't keeping track of castling
358 availability yet. [HGM] Change of logic: the flag now only
359 indicates the type of castlings allowed by the rule of the game.
360 The actual rights themselves are maintained in the array
361 castlingRights, as part of the game history, and are not probed
367 int flags = F_ALL_CASTLE_OK;
368 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
369 switch (gameInfo.variant) {
371 flags &= ~F_ALL_CASTLE_OK;
372 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
373 flags |= F_IGNORE_CHECK;
375 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
378 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
380 case VariantKriegspiel:
381 flags |= F_KRIEGSPIEL_CAPTURE;
383 case VariantCapaRandom:
384 case VariantFischeRandom:
385 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
386 case VariantNoCastle:
387 case VariantShatranj:
390 flags &= ~F_ALL_CASTLE_OK;
398 FILE *gameFileFP, *debugFP;
401 [AS] Note: sometimes, the sscanf() function is used to parse the input
402 into a fixed-size buffer. Because of this, we must be prepared to
403 receive strings as long as the size of the input buffer, which is currently
404 set to 4K for Windows and 8K for the rest.
405 So, we must either allocate sufficiently large buffers here, or
406 reduce the size of the input buffer in the input reading part.
409 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
410 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
411 char thinkOutput1[MSG_SIZ*10];
413 ChessProgramState first, second;
415 /* premove variables */
418 int premoveFromX = 0;
419 int premoveFromY = 0;
420 int premovePromoChar = 0;
422 Boolean alarmSounded;
423 /* end premove variables */
425 char *ics_prefix = "$";
426 int ics_type = ICS_GENERIC;
428 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
429 int pauseExamForwardMostMove = 0;
430 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
431 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
432 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
433 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
434 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
435 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
436 int whiteFlag = FALSE, blackFlag = FALSE;
437 int userOfferedDraw = FALSE;
438 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
439 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
440 int cmailMoveType[CMAIL_MAX_GAMES];
441 long ics_clock_paused = 0;
442 ProcRef icsPR = NoProc, cmailPR = NoProc;
443 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
444 GameMode gameMode = BeginningOfGame;
445 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
446 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
447 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
448 int hiddenThinkOutputState = 0; /* [AS] */
449 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
450 int adjudicateLossPlies = 6;
451 char white_holding[64], black_holding[64];
452 TimeMark lastNodeCountTime;
453 long lastNodeCount=0;
454 int shiftKey; // [HGM] set by mouse handler
456 int have_sent_ICS_logon = 0;
458 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
459 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
460 long timeControl_2; /* [AS] Allow separate time controls */
461 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
462 long timeRemaining[2][MAX_MOVES];
464 TimeMark programStartTime;
465 char ics_handle[MSG_SIZ];
466 int have_set_title = 0;
468 /* animateTraining preserves the state of appData.animate
469 * when Training mode is activated. This allows the
470 * response to be animated when appData.animate == TRUE and
471 * appData.animateDragging == TRUE.
473 Boolean animateTraining;
479 Board boards[MAX_MOVES];
480 /* [HGM] Following 7 needed for accurate legality tests: */
481 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
482 signed char initialRights[BOARD_FILES];
483 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
484 int initialRulePlies, FENrulePlies;
485 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
488 int mute; // mute all sounds
490 // [HGM] vari: next 12 to save and restore variations
491 #define MAX_VARIATIONS 10
492 int framePtr = MAX_MOVES-1; // points to free stack entry
494 int savedFirst[MAX_VARIATIONS];
495 int savedLast[MAX_VARIATIONS];
496 int savedFramePtr[MAX_VARIATIONS];
497 char *savedDetails[MAX_VARIATIONS];
498 ChessMove savedResult[MAX_VARIATIONS];
500 void PushTail P((int firstMove, int lastMove));
501 Boolean PopTail P((Boolean annotate));
502 void CleanupTail P((void));
504 ChessSquare FIDEArray[2][BOARD_FILES] = {
505 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
506 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
507 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
508 BlackKing, BlackBishop, BlackKnight, BlackRook }
511 ChessSquare twoKingsArray[2][BOARD_FILES] = {
512 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
513 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
514 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
515 BlackKing, BlackKing, BlackKnight, BlackRook }
518 ChessSquare KnightmateArray[2][BOARD_FILES] = {
519 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
520 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
521 { BlackRook, BlackMan, BlackBishop, BlackQueen,
522 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
525 ChessSquare SpartanArray[2][BOARD_FILES] = {
526 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
527 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
528 { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
529 BlackDragon, BlackKing, BlackAngel, BlackAlfil }
532 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
533 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
534 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
535 { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
536 BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
539 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
540 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
541 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
542 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
543 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
546 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
547 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
548 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
549 { BlackRook, BlackKnight, BlackMan, BlackFerz,
550 BlackKing, BlackMan, BlackKnight, BlackRook }
554 #if (BOARD_FILES>=10)
555 ChessSquare ShogiArray[2][BOARD_FILES] = {
556 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
557 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
558 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
559 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
562 ChessSquare XiangqiArray[2][BOARD_FILES] = {
563 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
564 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
565 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
566 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
569 ChessSquare CapablancaArray[2][BOARD_FILES] = {
570 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
571 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
572 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
573 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
576 ChessSquare GreatArray[2][BOARD_FILES] = {
577 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
578 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
579 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
580 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
583 ChessSquare JanusArray[2][BOARD_FILES] = {
584 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
585 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
586 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
587 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
591 ChessSquare GothicArray[2][BOARD_FILES] = {
592 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
593 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
594 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
595 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
598 #define GothicArray CapablancaArray
602 ChessSquare FalconArray[2][BOARD_FILES] = {
603 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
604 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
605 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
606 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
609 #define FalconArray CapablancaArray
612 #else // !(BOARD_FILES>=10)
613 #define XiangqiPosition FIDEArray
614 #define CapablancaArray FIDEArray
615 #define GothicArray FIDEArray
616 #define GreatArray FIDEArray
617 #endif // !(BOARD_FILES>=10)
619 #if (BOARD_FILES>=12)
620 ChessSquare CourierArray[2][BOARD_FILES] = {
621 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
622 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
623 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
624 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
626 #else // !(BOARD_FILES>=12)
627 #define CourierArray CapablancaArray
628 #endif // !(BOARD_FILES>=12)
631 Board initialPosition;
634 /* Convert str to a rating. Checks for special cases of "----",
636 "++++", etc. Also strips ()'s */
638 string_to_rating(str)
641 while(*str && !isdigit(*str)) ++str;
643 return 0; /* One of the special "no rating" cases */
651 /* Init programStats */
652 programStats.movelist[0] = 0;
653 programStats.depth = 0;
654 programStats.nr_moves = 0;
655 programStats.moves_left = 0;
656 programStats.nodes = 0;
657 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
658 programStats.score = 0;
659 programStats.got_only_move = 0;
660 programStats.got_fail = 0;
661 programStats.line_is_book = 0;
666 { // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
667 if (appData.firstPlaysBlack) {
668 first.twoMachinesColor = "black\n";
669 second.twoMachinesColor = "white\n";
671 first.twoMachinesColor = "white\n";
672 second.twoMachinesColor = "black\n";
675 first.other = &second;
676 second.other = &first;
679 if(appData.timeOddsMode) {
680 norm = appData.timeOdds[0];
681 if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
683 first.timeOdds = appData.timeOdds[0]/norm;
684 second.timeOdds = appData.timeOdds[1]/norm;
687 if(programVersion) free(programVersion);
688 if (appData.noChessProgram) {
689 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
690 sprintf(programVersion, "%s", PACKAGE_STRING);
692 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
693 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
694 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
699 UnloadEngine(ChessProgramState *cps)
701 /* Kill off first chess program */
702 if (cps->isr != NULL)
703 RemoveInputSource(cps->isr);
706 if (cps->pr != NoProc) {
708 DoSleep( appData.delayBeforeQuit );
709 SendToProgram("quit\n", cps);
710 DoSleep( appData.delayAfterQuit );
711 DestroyChildProcess(cps->pr, cps->useSigterm);
717 ClearOptions(ChessProgramState *cps)
720 cps->nrOptions = cps->comboCnt = 0;
721 for(i=0; i<MAX_OPTIONS; i++) {
722 cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
723 cps->option[i].textValue = 0;
727 char *engineNames[] = {
732 InitEngine(ChessProgramState *cps, int n)
733 { // [HGM] all engine initialiation put in a function that does one engine
737 cps->which = engineNames[n];
738 cps->maybeThinking = FALSE;
742 cps->sendDrawOffers = 1;
744 cps->program = appData.chessProgram[n];
745 cps->host = appData.host[n];
746 cps->dir = appData.directory[n];
747 cps->initString = appData.engInitString[n];
748 cps->computerString = appData.computerString[n];
749 cps->useSigint = TRUE;
750 cps->useSigterm = TRUE;
751 cps->reuse = appData.reuse[n];
752 cps->nps = appData.NPS[n]; // [HGM] nps: copy nodes per second
753 cps->useSetboard = FALSE;
755 cps->usePing = FALSE;
758 cps->usePlayother = FALSE;
759 cps->useColors = TRUE;
760 cps->useUsermove = FALSE;
761 cps->sendICS = FALSE;
762 cps->sendName = appData.icsActive;
763 cps->sdKludge = FALSE;
764 cps->stKludge = FALSE;
765 TidyProgramName(cps->program, cps->host, cps->tidy);
767 safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
768 cps->analysisSupport = 2; /* detect */
769 cps->analyzing = FALSE;
770 cps->initDone = FALSE;
772 /* New features added by Tord: */
773 cps->useFEN960 = FALSE;
774 cps->useOOCastle = TRUE;
775 /* End of new features added by Tord. */
776 cps->fenOverride = appData.fenOverride[n];
778 /* [HGM] time odds: set factor for each machine */
779 cps->timeOdds = appData.timeOdds[n];
781 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
782 cps->accumulateTC = appData.accumulateTC[n];
783 cps->maxNrOfSessions = 1;
787 cps->supportsNPS = UNKNOWN;
790 cps->optionSettings = appData.engOptions[n];
792 cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
793 cps->isUCI = appData.isUCI[n]; /* [AS] */
794 cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
796 if (appData.protocolVersion[n] > PROTOVER
797 || appData.protocolVersion[n] < 1)
802 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
803 appData.protocolVersion[n]);
804 if( (len > MSG_SIZ) && appData.debugMode )
805 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
807 DisplayFatalError(buf, 0, 2);
811 cps->protocolVersion = appData.protocolVersion[n];
814 InitEngineUCI( installDir, cps ); // [HGM] moved here from winboard.c, to make available in xboard
817 ChessProgramState *savCps;
823 if(WaitForEngine(savCps, LoadEngine)) return;
824 CommonEngineInit(); // recalculate time odds
825 if(gameInfo.variant != StringToVariant(appData.variant)) {
826 // we changed variant when loading the engine; this forces us to reset
827 Reset(TRUE, savCps != &first);
828 EditGameEvent(); // for consistency with other path, as Reset changes mode
830 InitChessProgram(savCps, FALSE);
831 SendToProgram("force\n", savCps);
832 DisplayMessage("", "");
833 if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
834 for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
840 ReplaceEngine(ChessProgramState *cps, int n)
844 appData.noChessProgram = False;
845 appData.clockMode = True;
847 if(n) return; // only startup first engine immediately; second can wait
848 savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
855 int matched, min, sec;
857 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
858 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
860 GetTimeMark(&programStartTime);
861 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
864 programStats.ok_to_send = 1;
865 programStats.seen_stat = 0;
868 * Initialize game list
874 * Internet chess server status
876 if (appData.icsActive) {
877 appData.matchMode = FALSE;
878 appData.matchGames = 0;
880 appData.noChessProgram = !appData.zippyPlay;
882 appData.zippyPlay = FALSE;
883 appData.zippyTalk = FALSE;
884 appData.noChessProgram = TRUE;
886 if (*appData.icsHelper != NULLCHAR) {
887 appData.useTelnet = TRUE;
888 appData.telnetProgram = appData.icsHelper;
891 appData.zippyTalk = appData.zippyPlay = FALSE;
894 /* [AS] Initialize pv info list [HGM] and game state */
898 for( i=0; i<=framePtr; i++ ) {
899 pvInfoList[i].depth = -1;
900 boards[i][EP_STATUS] = EP_NONE;
901 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
906 * Parse timeControl resource
908 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
909 appData.movesPerSession)) {
911 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
912 DisplayFatalError(buf, 0, 2);
916 * Parse searchTime resource
918 if (*appData.searchTime != NULLCHAR) {
919 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
921 searchTime = min * 60;
922 } else if (matched == 2) {
923 searchTime = min * 60 + sec;
926 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
927 DisplayFatalError(buf, 0, 2);
931 /* [AS] Adjudication threshold */
932 adjudicateLossThreshold = appData.adjudicateLossThreshold;
934 InitEngine(&first, 0);
935 InitEngine(&second, 1);
938 if (appData.icsActive) {
939 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
940 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
941 appData.clockMode = FALSE;
942 first.sendTime = second.sendTime = 0;
946 /* Override some settings from environment variables, for backward
947 compatibility. Unfortunately it's not feasible to have the env
948 vars just set defaults, at least in xboard. Ugh.
950 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
955 if (!appData.icsActive) {
959 /* Check for variants that are supported only in ICS mode,
960 or not at all. Some that are accepted here nevertheless
961 have bugs; see comments below.
963 VariantClass variant = StringToVariant(appData.variant);
965 case VariantBughouse: /* need four players and two boards */
966 case VariantKriegspiel: /* need to hide pieces and move details */
967 /* case VariantFischeRandom: (Fabien: moved below) */
968 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
969 if( (len > MSG_SIZ) && appData.debugMode )
970 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
972 DisplayFatalError(buf, 0, 2);
976 case VariantLoadable:
986 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
987 if( (len > MSG_SIZ) && appData.debugMode )
988 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
990 DisplayFatalError(buf, 0, 2);
993 case VariantXiangqi: /* [HGM] repetition rules not implemented */
994 case VariantFairy: /* [HGM] TestLegality definitely off! */
995 case VariantGothic: /* [HGM] should work */
996 case VariantCapablanca: /* [HGM] should work */
997 case VariantCourier: /* [HGM] initial forced moves not implemented */
998 case VariantShogi: /* [HGM] could still mate with pawn drop */
999 case VariantKnightmate: /* [HGM] should work */
1000 case VariantCylinder: /* [HGM] untested */
1001 case VariantFalcon: /* [HGM] untested */
1002 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1003 offboard interposition not understood */
1004 case VariantNormal: /* definitely works! */
1005 case VariantWildCastle: /* pieces not automatically shuffled */
1006 case VariantNoCastle: /* pieces not automatically shuffled */
1007 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1008 case VariantLosers: /* should work except for win condition,
1009 and doesn't know captures are mandatory */
1010 case VariantSuicide: /* should work except for win condition,
1011 and doesn't know captures are mandatory */
1012 case VariantGiveaway: /* should work except for win condition,
1013 and doesn't know captures are mandatory */
1014 case VariantTwoKings: /* should work */
1015 case VariantAtomic: /* should work except for win condition */
1016 case Variant3Check: /* should work except for win condition */
1017 case VariantShatranj: /* should work except for all win conditions */
1018 case VariantMakruk: /* should work except for daw countdown */
1019 case VariantBerolina: /* might work if TestLegality is off */
1020 case VariantCapaRandom: /* should work */
1021 case VariantJanus: /* should work */
1022 case VariantSuper: /* experimental */
1023 case VariantGreat: /* experimental, requires legality testing to be off */
1024 case VariantSChess: /* S-Chess, should work */
1025 case VariantSpartan: /* should work */
1032 int NextIntegerFromString( char ** str, long * value )
1037 while( *s == ' ' || *s == '\t' ) {
1043 if( *s >= '0' && *s <= '9' ) {
1044 while( *s >= '0' && *s <= '9' ) {
1045 *value = *value * 10 + (*s - '0');
1057 int NextTimeControlFromString( char ** str, long * value )
1060 int result = NextIntegerFromString( str, &temp );
1063 *value = temp * 60; /* Minutes */
1064 if( **str == ':' ) {
1066 result = NextIntegerFromString( str, &temp );
1067 *value += temp; /* Seconds */
1074 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1075 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1076 int result = -1, type = 0; long temp, temp2;
1078 if(**str != ':') return -1; // old params remain in force!
1080 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1081 if( NextIntegerFromString( str, &temp ) ) return -1;
1082 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1085 /* time only: incremental or sudden-death time control */
1086 if(**str == '+') { /* increment follows; read it */
1088 if(**str == '!') type = *(*str)++; // Bronstein TC
1089 if(result = NextIntegerFromString( str, &temp2)) return -1;
1090 *inc = temp2 * 1000;
1091 if(**str == '.') { // read fraction of increment
1092 char *start = ++(*str);
1093 if(result = NextIntegerFromString( str, &temp2)) return -1;
1095 while(start++ < *str) temp2 /= 10;
1099 *moves = 0; *tc = temp * 1000; *incType = type;
1103 (*str)++; /* classical time control */
1104 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1115 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1116 { /* [HGM] get time to add from the multi-session time-control string */
1117 int incType, moves=1; /* kludge to force reading of first session */
1118 long time, increment;
1121 if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1122 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1124 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1125 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1126 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1127 if(movenr == -1) return time; /* last move before new session */
1128 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1129 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1130 if(!moves) return increment; /* current session is incremental */
1131 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1132 } while(movenr >= -1); /* try again for next session */
1134 return 0; // no new time quota on this move
1138 ParseTimeControl(tc, ti, mps)
1145 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1148 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1149 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1150 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1154 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1156 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1159 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1161 snprintf(buf, MSG_SIZ, ":%s", mytc);
1163 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1165 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1170 /* Parse second time control */
1173 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1181 timeControl_2 = tc2 * 1000;
1191 timeControl = tc1 * 1000;
1194 timeIncrement = ti * 1000; /* convert to ms */
1195 movesPerSession = 0;
1198 movesPerSession = mps;
1206 if (appData.debugMode) {
1207 fprintf(debugFP, "%s\n", programVersion);
1210 set_cont_sequence(appData.wrapContSeq);
1211 if (appData.matchGames > 0) {
1212 appData.matchMode = TRUE;
1213 } else if (appData.matchMode) {
1214 appData.matchGames = 1;
1216 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1217 appData.matchGames = appData.sameColorGames;
1218 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1219 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1220 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1223 if (appData.noChessProgram || first.protocolVersion == 1) {
1226 /* kludge: allow timeout for initial "feature" commands */
1228 DisplayMessage("", _("Starting chess program"));
1229 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1234 MatchEvent(int mode)
1235 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1236 /* Set up machine vs. machine match */
1237 if (appData.noChessProgram) {
1238 DisplayFatalError(_("Can't have a match with no chess programs"),
1244 if (*appData.loadGameFile != NULLCHAR) {
1245 int index = appData.loadGameIndex; // [HGM] autoinc
1246 if(index<0) lastIndex = index = 1;
1247 if (!LoadGameFromFile(appData.loadGameFile,
1249 appData.loadGameFile, FALSE)) {
1250 DisplayFatalError(_("Bad game file"), 0, 1);
1253 } else if (*appData.loadPositionFile != NULLCHAR) {
1254 int index = appData.loadPositionIndex; // [HGM] autoinc
1255 if(index<0) lastIndex = index = 1;
1256 if (!LoadPositionFromFile(appData.loadPositionFile,
1258 appData.loadPositionFile)) {
1259 DisplayFatalError(_("Bad position file"), 0, 1);
1263 first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
\r
1268 InitBackEnd3 P((void))
1270 GameMode initialMode;
1274 InitChessProgram(&first, startedFromSetupPosition);
1276 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1277 free(programVersion);
1278 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1279 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1282 if (appData.icsActive) {
1284 /* [DM] Make a console window if needed [HGM] merged ifs */
1290 if (*appData.icsCommPort != NULLCHAR)
1291 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1292 appData.icsCommPort);
1294 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1295 appData.icsHost, appData.icsPort);
1297 if( (len > MSG_SIZ) && appData.debugMode )
1298 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1300 DisplayFatalError(buf, err, 1);
1305 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1307 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1308 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1309 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1310 } else if (appData.noChessProgram) {
1316 if (*appData.cmailGameName != NULLCHAR) {
1318 OpenLoopback(&cmailPR);
1320 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1324 DisplayMessage("", "");
1325 if (StrCaseCmp(appData.initialMode, "") == 0) {
1326 initialMode = BeginningOfGame;
1327 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1328 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1329 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1330 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1333 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1334 initialMode = TwoMachinesPlay;
1335 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1336 initialMode = AnalyzeFile;
1337 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1338 initialMode = AnalyzeMode;
1339 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1340 initialMode = MachinePlaysWhite;
1341 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1342 initialMode = MachinePlaysBlack;
1343 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1344 initialMode = EditGame;
1345 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1346 initialMode = EditPosition;
1347 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1348 initialMode = Training;
1350 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1351 if( (len > MSG_SIZ) && appData.debugMode )
1352 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1354 DisplayFatalError(buf, 0, 2);
1358 if (appData.matchMode) {
1360 } else if (*appData.cmailGameName != NULLCHAR) {
1361 /* Set up cmail mode */
1362 ReloadCmailMsgEvent(TRUE);
1364 /* Set up other modes */
1365 if (initialMode == AnalyzeFile) {
1366 if (*appData.loadGameFile == NULLCHAR) {
1367 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1371 if (*appData.loadGameFile != NULLCHAR) {
1372 (void) LoadGameFromFile(appData.loadGameFile,
1373 appData.loadGameIndex,
1374 appData.loadGameFile, TRUE);
1375 } else if (*appData.loadPositionFile != NULLCHAR) {
1376 (void) LoadPositionFromFile(appData.loadPositionFile,
1377 appData.loadPositionIndex,
1378 appData.loadPositionFile);
1379 /* [HGM] try to make self-starting even after FEN load */
1380 /* to allow automatic setup of fairy variants with wtm */
1381 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1382 gameMode = BeginningOfGame;
1383 setboardSpoiledMachineBlack = 1;
1385 /* [HGM] loadPos: make that every new game uses the setup */
1386 /* from file as long as we do not switch variant */
1387 if(!blackPlaysFirst) {
1388 startedFromPositionFile = TRUE;
1389 CopyBoard(filePosition, boards[0]);
1392 if (initialMode == AnalyzeMode) {
1393 if (appData.noChessProgram) {
1394 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1397 if (appData.icsActive) {
1398 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1402 } else if (initialMode == AnalyzeFile) {
1403 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1404 ShowThinkingEvent();
1406 AnalysisPeriodicEvent(1);
1407 } else if (initialMode == MachinePlaysWhite) {
1408 if (appData.noChessProgram) {
1409 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1413 if (appData.icsActive) {
1414 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1418 MachineWhiteEvent();
1419 } else if (initialMode == MachinePlaysBlack) {
1420 if (appData.noChessProgram) {
1421 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1425 if (appData.icsActive) {
1426 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1430 MachineBlackEvent();
1431 } else if (initialMode == TwoMachinesPlay) {
1432 if (appData.noChessProgram) {
1433 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1437 if (appData.icsActive) {
1438 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1443 } else if (initialMode == EditGame) {
1445 } else if (initialMode == EditPosition) {
1446 EditPositionEvent();
1447 } else if (initialMode == Training) {
1448 if (*appData.loadGameFile == NULLCHAR) {
1449 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1458 * Establish will establish a contact to a remote host.port.
1459 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1460 * used to talk to the host.
1461 * Returns 0 if okay, error code if not.
1468 if (*appData.icsCommPort != NULLCHAR) {
1469 /* Talk to the host through a serial comm port */
1470 return OpenCommPort(appData.icsCommPort, &icsPR);
1472 } else if (*appData.gateway != NULLCHAR) {
1473 if (*appData.remoteShell == NULLCHAR) {
1474 /* Use the rcmd protocol to run telnet program on a gateway host */
1475 snprintf(buf, sizeof(buf), "%s %s %s",
1476 appData.telnetProgram, appData.icsHost, appData.icsPort);
1477 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1480 /* Use the rsh program to run telnet program on a gateway host */
1481 if (*appData.remoteUser == NULLCHAR) {
1482 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1483 appData.gateway, appData.telnetProgram,
1484 appData.icsHost, appData.icsPort);
1486 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1487 appData.remoteShell, appData.gateway,
1488 appData.remoteUser, appData.telnetProgram,
1489 appData.icsHost, appData.icsPort);
1491 return StartChildProcess(buf, "", &icsPR);
1494 } else if (appData.useTelnet) {
1495 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1498 /* TCP socket interface differs somewhat between
1499 Unix and NT; handle details in the front end.
1501 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1505 void EscapeExpand(char *p, char *q)
1506 { // [HGM] initstring: routine to shape up string arguments
1507 while(*p++ = *q++) if(p[-1] == '\\')
1509 case 'n': p[-1] = '\n'; break;
1510 case 'r': p[-1] = '\r'; break;
1511 case 't': p[-1] = '\t'; break;
1512 case '\\': p[-1] = '\\'; break;
1513 case 0: *p = 0; return;
1514 default: p[-1] = q[-1]; break;
1519 show_bytes(fp, buf, count)
1525 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1526 fprintf(fp, "\\%03o", *buf & 0xff);
1535 /* Returns an errno value */
1537 OutputMaybeTelnet(pr, message, count, outError)
1543 char buf[8192], *p, *q, *buflim;
1544 int left, newcount, outcount;
1546 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1547 *appData.gateway != NULLCHAR) {
1548 if (appData.debugMode) {
1549 fprintf(debugFP, ">ICS: ");
1550 show_bytes(debugFP, message, count);
1551 fprintf(debugFP, "\n");
1553 return OutputToProcess(pr, message, count, outError);
1556 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1563 if (appData.debugMode) {
1564 fprintf(debugFP, ">ICS: ");
1565 show_bytes(debugFP, buf, newcount);
1566 fprintf(debugFP, "\n");
1568 outcount = OutputToProcess(pr, buf, newcount, outError);
1569 if (outcount < newcount) return -1; /* to be sure */
1576 } else if (((unsigned char) *p) == TN_IAC) {
1577 *q++ = (char) TN_IAC;
1584 if (appData.debugMode) {
1585 fprintf(debugFP, ">ICS: ");
1586 show_bytes(debugFP, buf, newcount);
1587 fprintf(debugFP, "\n");
1589 outcount = OutputToProcess(pr, buf, newcount, outError);
1590 if (outcount < newcount) return -1; /* to be sure */
1595 read_from_player(isr, closure, message, count, error)
1602 int outError, outCount;
1603 static int gotEof = 0;
1605 /* Pass data read from player on to ICS */
1608 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1609 if (outCount < count) {
1610 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1612 } else if (count < 0) {
1613 RemoveInputSource(isr);
1614 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1615 } else if (gotEof++ > 0) {
1616 RemoveInputSource(isr);
1617 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1623 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1624 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1625 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1626 SendToICS("date\n");
1627 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1630 /* added routine for printf style output to ics */
1631 void ics_printf(char *format, ...)
1633 char buffer[MSG_SIZ];
1636 va_start(args, format);
1637 vsnprintf(buffer, sizeof(buffer), format, args);
1638 buffer[sizeof(buffer)-1] = '\0';
1647 int count, outCount, outError;
1649 if (icsPR == NULL) return;
1652 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1653 if (outCount < count) {
1654 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1658 /* This is used for sending logon scripts to the ICS. Sending
1659 without a delay causes problems when using timestamp on ICC
1660 (at least on my machine). */
1662 SendToICSDelayed(s,msdelay)
1666 int count, outCount, outError;
1668 if (icsPR == NULL) return;
1671 if (appData.debugMode) {
1672 fprintf(debugFP, ">ICS: ");
1673 show_bytes(debugFP, s, count);
1674 fprintf(debugFP, "\n");
1676 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1678 if (outCount < count) {
1679 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1684 /* Remove all highlighting escape sequences in s
1685 Also deletes any suffix starting with '('
1688 StripHighlightAndTitle(s)
1691 static char retbuf[MSG_SIZ];
1694 while (*s != NULLCHAR) {
1695 while (*s == '\033') {
1696 while (*s != NULLCHAR && !isalpha(*s)) s++;
1697 if (*s != NULLCHAR) s++;
1699 while (*s != NULLCHAR && *s != '\033') {
1700 if (*s == '(' || *s == '[') {
1711 /* Remove all highlighting escape sequences in s */
1716 static char retbuf[MSG_SIZ];
1719 while (*s != NULLCHAR) {
1720 while (*s == '\033') {
1721 while (*s != NULLCHAR && !isalpha(*s)) s++;
1722 if (*s != NULLCHAR) s++;
1724 while (*s != NULLCHAR && *s != '\033') {
1732 char *variantNames[] = VARIANT_NAMES;
1737 return variantNames[v];
1741 /* Identify a variant from the strings the chess servers use or the
1742 PGN Variant tag names we use. */
1749 VariantClass v = VariantNormal;
1750 int i, found = FALSE;
1756 /* [HGM] skip over optional board-size prefixes */
1757 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1758 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1759 while( *e++ != '_');
1762 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1766 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1767 if (StrCaseStr(e, variantNames[i])) {
1768 v = (VariantClass) i;
1775 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1776 || StrCaseStr(e, "wild/fr")
1777 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1778 v = VariantFischeRandom;
1779 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1780 (i = 1, p = StrCaseStr(e, "w"))) {
1782 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1789 case 0: /* FICS only, actually */
1791 /* Castling legal even if K starts on d-file */
1792 v = VariantWildCastle;
1797 /* Castling illegal even if K & R happen to start in
1798 normal positions. */
1799 v = VariantNoCastle;
1812 /* Castling legal iff K & R start in normal positions */
1818 /* Special wilds for position setup; unclear what to do here */
1819 v = VariantLoadable;
1822 /* Bizarre ICC game */
1823 v = VariantTwoKings;
1826 v = VariantKriegspiel;
1832 v = VariantFischeRandom;
1835 v = VariantCrazyhouse;
1838 v = VariantBughouse;
1844 /* Not quite the same as FICS suicide! */
1845 v = VariantGiveaway;
1851 v = VariantShatranj;
1854 /* Temporary names for future ICC types. The name *will* change in
1855 the next xboard/WinBoard release after ICC defines it. */
1893 v = VariantCapablanca;
1896 v = VariantKnightmate;
1902 v = VariantCylinder;
1908 v = VariantCapaRandom;
1911 v = VariantBerolina;
1923 /* Found "wild" or "w" in the string but no number;
1924 must assume it's normal chess. */
1928 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
1929 if( (len > MSG_SIZ) && appData.debugMode )
1930 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
1932 DisplayError(buf, 0);
1938 if (appData.debugMode) {
1939 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1940 e, wnum, VariantName(v));
1945 static int leftover_start = 0, leftover_len = 0;
1946 char star_match[STAR_MATCH_N][MSG_SIZ];
1948 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1949 advance *index beyond it, and set leftover_start to the new value of
1950 *index; else return FALSE. If pattern contains the character '*', it
1951 matches any sequence of characters not containing '\r', '\n', or the
1952 character following the '*' (if any), and the matched sequence(s) are
1953 copied into star_match.
1956 looking_at(buf, index, pattern)
1961 char *bufp = &buf[*index], *patternp = pattern;
1963 char *matchp = star_match[0];
1966 if (*patternp == NULLCHAR) {
1967 *index = leftover_start = bufp - buf;
1971 if (*bufp == NULLCHAR) return FALSE;
1972 if (*patternp == '*') {
1973 if (*bufp == *(patternp + 1)) {
1975 matchp = star_match[++star_count];
1979 } else if (*bufp == '\n' || *bufp == '\r') {
1981 if (*patternp == NULLCHAR)
1986 *matchp++ = *bufp++;
1990 if (*patternp != *bufp) return FALSE;
1997 SendToPlayer(data, length)
2001 int error, outCount;
2002 outCount = OutputToProcess(NoProc, data, length, &error);
2003 if (outCount < length) {
2004 DisplayFatalError(_("Error writing to display"), error, 1);
2009 PackHolding(packed, holding)
2021 switch (runlength) {
2032 sprintf(q, "%d", runlength);
2044 /* Telnet protocol requests from the front end */
2046 TelnetRequest(ddww, option)
2047 unsigned char ddww, option;
2049 unsigned char msg[3];
2050 int outCount, outError;
2052 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2054 if (appData.debugMode) {
2055 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2071 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2080 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2083 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2088 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2090 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2097 if (!appData.icsActive) return;
2098 TelnetRequest(TN_DO, TN_ECHO);
2104 if (!appData.icsActive) return;
2105 TelnetRequest(TN_DONT, TN_ECHO);
2109 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2111 /* put the holdings sent to us by the server on the board holdings area */
2112 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2116 if(gameInfo.holdingsWidth < 2) return;
2117 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2118 return; // prevent overwriting by pre-board holdings
2120 if( (int)lowestPiece >= BlackPawn ) {
2123 holdingsStartRow = BOARD_HEIGHT-1;
2126 holdingsColumn = BOARD_WIDTH-1;
2127 countsColumn = BOARD_WIDTH-2;
2128 holdingsStartRow = 0;
2132 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2133 board[i][holdingsColumn] = EmptySquare;
2134 board[i][countsColumn] = (ChessSquare) 0;
2136 while( (p=*holdings++) != NULLCHAR ) {
2137 piece = CharToPiece( ToUpper(p) );
2138 if(piece == EmptySquare) continue;
2139 /*j = (int) piece - (int) WhitePawn;*/
2140 j = PieceToNumber(piece);
2141 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2142 if(j < 0) continue; /* should not happen */
2143 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2144 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2145 board[holdingsStartRow+j*direction][countsColumn]++;
2151 VariantSwitch(Board board, VariantClass newVariant)
2153 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2154 static Board oldBoard;
2156 startedFromPositionFile = FALSE;
2157 if(gameInfo.variant == newVariant) return;
2159 /* [HGM] This routine is called each time an assignment is made to
2160 * gameInfo.variant during a game, to make sure the board sizes
2161 * are set to match the new variant. If that means adding or deleting
2162 * holdings, we shift the playing board accordingly
2163 * This kludge is needed because in ICS observe mode, we get boards
2164 * of an ongoing game without knowing the variant, and learn about the
2165 * latter only later. This can be because of the move list we requested,
2166 * in which case the game history is refilled from the beginning anyway,
2167 * but also when receiving holdings of a crazyhouse game. In the latter
2168 * case we want to add those holdings to the already received position.
2172 if (appData.debugMode) {
2173 fprintf(debugFP, "Switch board from %s to %s\n",
2174 VariantName(gameInfo.variant), VariantName(newVariant));
2175 setbuf(debugFP, NULL);
2177 shuffleOpenings = 0; /* [HGM] shuffle */
2178 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2182 newWidth = 9; newHeight = 9;
2183 gameInfo.holdingsSize = 7;
2184 case VariantBughouse:
2185 case VariantCrazyhouse:
2186 newHoldingsWidth = 2; break;
2190 newHoldingsWidth = 2;
2191 gameInfo.holdingsSize = 8;
2194 case VariantCapablanca:
2195 case VariantCapaRandom:
2198 newHoldingsWidth = gameInfo.holdingsSize = 0;
2201 if(newWidth != gameInfo.boardWidth ||
2202 newHeight != gameInfo.boardHeight ||
2203 newHoldingsWidth != gameInfo.holdingsWidth ) {
2205 /* shift position to new playing area, if needed */
2206 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2207 for(i=0; i<BOARD_HEIGHT; i++)
2208 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2209 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2211 for(i=0; i<newHeight; i++) {
2212 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2213 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2215 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2216 for(i=0; i<BOARD_HEIGHT; i++)
2217 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2218 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2221 gameInfo.boardWidth = newWidth;
2222 gameInfo.boardHeight = newHeight;
2223 gameInfo.holdingsWidth = newHoldingsWidth;
2224 gameInfo.variant = newVariant;
2225 InitDrawingSizes(-2, 0);
2226 } else gameInfo.variant = newVariant;
2227 CopyBoard(oldBoard, board); // remember correctly formatted board
2228 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2229 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2232 static int loggedOn = FALSE;
2234 /*-- Game start info cache: --*/
2236 char gs_kind[MSG_SIZ];
2237 static char player1Name[128] = "";
2238 static char player2Name[128] = "";
2239 static char cont_seq[] = "\n\\ ";
2240 static int player1Rating = -1;
2241 static int player2Rating = -1;
2242 /*----------------------------*/
2244 ColorClass curColor = ColorNormal;
2245 int suppressKibitz = 0;
2248 Boolean soughtPending = FALSE;
2249 Boolean seekGraphUp;
2250 #define MAX_SEEK_ADS 200
2252 char *seekAdList[MAX_SEEK_ADS];
2253 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2254 float tcList[MAX_SEEK_ADS];
2255 char colorList[MAX_SEEK_ADS];
2256 int nrOfSeekAds = 0;
2257 int minRating = 1010, maxRating = 2800;
2258 int hMargin = 10, vMargin = 20, h, w;
2259 extern int squareSize, lineGap;
2264 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2265 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2266 if(r < minRating+100 && r >=0 ) r = minRating+100;
2267 if(r > maxRating) r = maxRating;
2268 if(tc < 1.) tc = 1.;
2269 if(tc > 95.) tc = 95.;
2270 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2271 y = ((double)r - minRating)/(maxRating - minRating)
2272 * (h-vMargin-squareSize/8-1) + vMargin;
2273 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2274 if(strstr(seekAdList[i], " u ")) color = 1;
2275 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2276 !strstr(seekAdList[i], "bullet") &&
2277 !strstr(seekAdList[i], "blitz") &&
2278 !strstr(seekAdList[i], "standard") ) color = 2;
2279 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2280 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2284 AddAd(char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2286 char buf[MSG_SIZ], *ext = "";
2287 VariantClass v = StringToVariant(type);
2288 if(strstr(type, "wild")) {
2289 ext = type + 4; // append wild number
2290 if(v == VariantFischeRandom) type = "chess960"; else
2291 if(v == VariantLoadable) type = "setup"; else
2292 type = VariantName(v);
2294 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2295 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2296 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2297 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2298 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2299 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2300 seekNrList[nrOfSeekAds] = nr;
2301 zList[nrOfSeekAds] = 0;
2302 seekAdList[nrOfSeekAds++] = StrSave(buf);
2303 if(plot) PlotSeekAd(nrOfSeekAds-1);
2310 int x = xList[i], y = yList[i], d=squareSize/4, k;
2311 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2312 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2313 // now replot every dot that overlapped
2314 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2315 int xx = xList[k], yy = yList[k];
2316 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2317 DrawSeekDot(xx, yy, colorList[k]);
2322 RemoveSeekAd(int nr)
2325 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2327 if(seekAdList[i]) free(seekAdList[i]);
2328 seekAdList[i] = seekAdList[--nrOfSeekAds];
2329 seekNrList[i] = seekNrList[nrOfSeekAds];
2330 ratingList[i] = ratingList[nrOfSeekAds];
2331 colorList[i] = colorList[nrOfSeekAds];
2332 tcList[i] = tcList[nrOfSeekAds];
2333 xList[i] = xList[nrOfSeekAds];
2334 yList[i] = yList[nrOfSeekAds];
2335 zList[i] = zList[nrOfSeekAds];
2336 seekAdList[nrOfSeekAds] = NULL;
2342 MatchSoughtLine(char *line)
2344 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2345 int nr, base, inc, u=0; char dummy;
2347 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2348 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2350 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2351 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2352 // match: compact and save the line
2353 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2363 if(!seekGraphUp) return FALSE;
2364 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2365 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2367 DrawSeekBackground(0, 0, w, h);
2368 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2369 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2370 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2371 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2373 DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2376 snprintf(buf, MSG_SIZ, "%d", i);
2377 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2380 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2381 for(i=1; i<100; i+=(i<10?1:5)) {
2382 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2383 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2384 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2386 snprintf(buf, MSG_SIZ, "%d", i);
2387 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2390 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2394 int SeekGraphClick(ClickType click, int x, int y, int moving)
2396 static int lastDown = 0, displayed = 0, lastSecond;
2397 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2398 if(click == Release || moving) return FALSE;
2400 soughtPending = TRUE;
2401 SendToICS(ics_prefix);
2402 SendToICS("sought\n"); // should this be "sought all"?
2403 } else { // issue challenge based on clicked ad
2404 int dist = 10000; int i, closest = 0, second = 0;
2405 for(i=0; i<nrOfSeekAds; i++) {
2406 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2407 if(d < dist) { dist = d; closest = i; }
2408 second += (d - zList[i] < 120); // count in-range ads
2409 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2413 second = (second > 1);
2414 if(displayed != closest || second != lastSecond) {
2415 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2416 lastSecond = second; displayed = closest;
2418 if(click == Press) {
2419 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2422 } // on press 'hit', only show info
2423 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2424 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2425 SendToICS(ics_prefix);
2427 return TRUE; // let incoming board of started game pop down the graph
2428 } else if(click == Release) { // release 'miss' is ignored
2429 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2430 if(moving == 2) { // right up-click
2431 nrOfSeekAds = 0; // refresh graph
2432 soughtPending = TRUE;
2433 SendToICS(ics_prefix);
2434 SendToICS("sought\n"); // should this be "sought all"?
2437 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2438 // press miss or release hit 'pop down' seek graph
2439 seekGraphUp = FALSE;
2440 DrawPosition(TRUE, NULL);
2446 read_from_ics(isr, closure, data, count, error)
2453 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2454 #define STARTED_NONE 0
2455 #define STARTED_MOVES 1
2456 #define STARTED_BOARD 2
2457 #define STARTED_OBSERVE 3
2458 #define STARTED_HOLDINGS 4
2459 #define STARTED_CHATTER 5
2460 #define STARTED_COMMENT 6
2461 #define STARTED_MOVES_NOHIDE 7
2463 static int started = STARTED_NONE;
2464 static char parse[20000];
2465 static int parse_pos = 0;
2466 static char buf[BUF_SIZE + 1];
2467 static int firstTime = TRUE, intfSet = FALSE;
2468 static ColorClass prevColor = ColorNormal;
2469 static int savingComment = FALSE;
2470 static int cmatch = 0; // continuation sequence match
2477 int backup; /* [DM] For zippy color lines */
2479 char talker[MSG_SIZ]; // [HGM] chat
2482 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2484 if (appData.debugMode) {
2486 fprintf(debugFP, "<ICS: ");
2487 show_bytes(debugFP, data, count);
2488 fprintf(debugFP, "\n");
2492 if (appData.debugMode) { int f = forwardMostMove;
2493 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2494 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2495 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2498 /* If last read ended with a partial line that we couldn't parse,
2499 prepend it to the new read and try again. */
2500 if (leftover_len > 0) {
2501 for (i=0; i<leftover_len; i++)
2502 buf[i] = buf[leftover_start + i];
2505 /* copy new characters into the buffer */
2506 bp = buf + leftover_len;
2507 buf_len=leftover_len;
2508 for (i=0; i<count; i++)
2511 if (data[i] == '\r')
2514 // join lines split by ICS?
2515 if (!appData.noJoin)
2518 Joining just consists of finding matches against the
2519 continuation sequence, and discarding that sequence
2520 if found instead of copying it. So, until a match
2521 fails, there's nothing to do since it might be the
2522 complete sequence, and thus, something we don't want
2525 if (data[i] == cont_seq[cmatch])
2528 if (cmatch == strlen(cont_seq))
2530 cmatch = 0; // complete match. just reset the counter
2533 it's possible for the ICS to not include the space
2534 at the end of the last word, making our [correct]
2535 join operation fuse two separate words. the server
2536 does this when the space occurs at the width setting.
2538 if (!buf_len || buf[buf_len-1] != ' ')
2549 match failed, so we have to copy what matched before
2550 falling through and copying this character. In reality,
2551 this will only ever be just the newline character, but
2552 it doesn't hurt to be precise.
2554 strncpy(bp, cont_seq, cmatch);
2566 buf[buf_len] = NULLCHAR;
2567 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2572 while (i < buf_len) {
2573 /* Deal with part of the TELNET option negotiation
2574 protocol. We refuse to do anything beyond the
2575 defaults, except that we allow the WILL ECHO option,
2576 which ICS uses to turn off password echoing when we are
2577 directly connected to it. We reject this option
2578 if localLineEditing mode is on (always on in xboard)
2579 and we are talking to port 23, which might be a real
2580 telnet server that will try to keep WILL ECHO on permanently.
2582 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2583 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2584 unsigned char option;
2586 switch ((unsigned char) buf[++i]) {
2588 if (appData.debugMode)
2589 fprintf(debugFP, "\n<WILL ");
2590 switch (option = (unsigned char) buf[++i]) {
2592 if (appData.debugMode)
2593 fprintf(debugFP, "ECHO ");
2594 /* Reply only if this is a change, according
2595 to the protocol rules. */
2596 if (remoteEchoOption) break;
2597 if (appData.localLineEditing &&
2598 atoi(appData.icsPort) == TN_PORT) {
2599 TelnetRequest(TN_DONT, TN_ECHO);
2602 TelnetRequest(TN_DO, TN_ECHO);
2603 remoteEchoOption = TRUE;
2607 if (appData.debugMode)
2608 fprintf(debugFP, "%d ", option);
2609 /* Whatever this is, we don't want it. */
2610 TelnetRequest(TN_DONT, option);
2615 if (appData.debugMode)
2616 fprintf(debugFP, "\n<WONT ");
2617 switch (option = (unsigned char) buf[++i]) {
2619 if (appData.debugMode)
2620 fprintf(debugFP, "ECHO ");
2621 /* Reply only if this is a change, according
2622 to the protocol rules. */
2623 if (!remoteEchoOption) break;
2625 TelnetRequest(TN_DONT, TN_ECHO);
2626 remoteEchoOption = FALSE;
2629 if (appData.debugMode)
2630 fprintf(debugFP, "%d ", (unsigned char) option);
2631 /* Whatever this is, it must already be turned
2632 off, because we never agree to turn on
2633 anything non-default, so according to the
2634 protocol rules, we don't reply. */
2639 if (appData.debugMode)
2640 fprintf(debugFP, "\n<DO ");
2641 switch (option = (unsigned char) buf[++i]) {
2643 /* Whatever this is, we refuse to do it. */
2644 if (appData.debugMode)
2645 fprintf(debugFP, "%d ", option);
2646 TelnetRequest(TN_WONT, option);
2651 if (appData.debugMode)
2652 fprintf(debugFP, "\n<DONT ");
2653 switch (option = (unsigned char) buf[++i]) {
2655 if (appData.debugMode)
2656 fprintf(debugFP, "%d ", option);
2657 /* Whatever this is, we are already not doing
2658 it, because we never agree to do anything
2659 non-default, so according to the protocol
2660 rules, we don't reply. */
2665 if (appData.debugMode)
2666 fprintf(debugFP, "\n<IAC ");
2667 /* Doubled IAC; pass it through */
2671 if (appData.debugMode)
2672 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2673 /* Drop all other telnet commands on the floor */
2676 if (oldi > next_out)
2677 SendToPlayer(&buf[next_out], oldi - next_out);
2683 /* OK, this at least will *usually* work */
2684 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2688 if (loggedOn && !intfSet) {
2689 if (ics_type == ICS_ICC) {
2690 snprintf(str, MSG_SIZ,
2691 "/set-quietly interface %s\n/set-quietly style 12\n",
2693 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2694 strcat(str, "/set-2 51 1\n/set seek 1\n");
2695 } else if (ics_type == ICS_CHESSNET) {
2696 snprintf(str, MSG_SIZ, "/style 12\n");
2698 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2699 strcat(str, programVersion);
2700 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2701 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2702 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2704 strcat(str, "$iset nohighlight 1\n");
2706 strcat(str, "$iset lock 1\n$style 12\n");
2709 NotifyFrontendLogin();
2713 if (started == STARTED_COMMENT) {
2714 /* Accumulate characters in comment */
2715 parse[parse_pos++] = buf[i];
2716 if (buf[i] == '\n') {
2717 parse[parse_pos] = NULLCHAR;
2718 if(chattingPartner>=0) {
2720 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2721 OutputChatMessage(chattingPartner, mess);
2722 chattingPartner = -1;
2723 next_out = i+1; // [HGM] suppress printing in ICS window
2725 if(!suppressKibitz) // [HGM] kibitz
2726 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2727 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2728 int nrDigit = 0, nrAlph = 0, j;
2729 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2730 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2731 parse[parse_pos] = NULLCHAR;
2732 // try to be smart: if it does not look like search info, it should go to
2733 // ICS interaction window after all, not to engine-output window.
2734 for(j=0; j<parse_pos; j++) { // count letters and digits
2735 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2736 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2737 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2739 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2740 int depth=0; float score;
2741 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2742 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2743 pvInfoList[forwardMostMove-1].depth = depth;
2744 pvInfoList[forwardMostMove-1].score = 100*score;
2746 OutputKibitz(suppressKibitz, parse);
2749 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2750 SendToPlayer(tmp, strlen(tmp));
2752 next_out = i+1; // [HGM] suppress printing in ICS window
2754 started = STARTED_NONE;
2756 /* Don't match patterns against characters in comment */
2761 if (started == STARTED_CHATTER) {
2762 if (buf[i] != '\n') {
2763 /* Don't match patterns against characters in chatter */
2767 started = STARTED_NONE;
2768 if(suppressKibitz) next_out = i+1;
2771 /* Kludge to deal with rcmd protocol */
2772 if (firstTime && looking_at(buf, &i, "\001*")) {
2773 DisplayFatalError(&buf[1], 0, 1);
2779 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2782 if (appData.debugMode)
2783 fprintf(debugFP, "ics_type %d\n", ics_type);
2786 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2787 ics_type = ICS_FICS;
2789 if (appData.debugMode)
2790 fprintf(debugFP, "ics_type %d\n", ics_type);
2793 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2794 ics_type = ICS_CHESSNET;
2796 if (appData.debugMode)
2797 fprintf(debugFP, "ics_type %d\n", ics_type);
2802 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2803 looking_at(buf, &i, "Logging you in as \"*\"") ||
2804 looking_at(buf, &i, "will be \"*\""))) {
2805 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2809 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2811 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2812 DisplayIcsInteractionTitle(buf);
2813 have_set_title = TRUE;
2816 /* skip finger notes */
2817 if (started == STARTED_NONE &&
2818 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2819 (buf[i] == '1' && buf[i+1] == '0')) &&
2820 buf[i+2] == ':' && buf[i+3] == ' ') {
2821 started = STARTED_CHATTER;
2827 // [HGM] seekgraph: recognize sought lines and end-of-sought message
2828 if(appData.seekGraph) {
2829 if(soughtPending && MatchSoughtLine(buf+i)) {
2830 i = strstr(buf+i, "rated") - buf;
2831 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2832 next_out = leftover_start = i;
2833 started = STARTED_CHATTER;
2834 suppressKibitz = TRUE;
2837 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2838 && looking_at(buf, &i, "* ads displayed")) {
2839 soughtPending = FALSE;
2844 if(appData.autoRefresh) {
2845 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2846 int s = (ics_type == ICS_ICC); // ICC format differs
2848 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
2849 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2850 looking_at(buf, &i, "*% "); // eat prompt
2851 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2852 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2853 next_out = i; // suppress
2856 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2857 char *p = star_match[0];
2859 if(seekGraphUp) RemoveSeekAd(atoi(p));
2860 while(*p && *p++ != ' '); // next
2862 looking_at(buf, &i, "*% "); // eat prompt
2863 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2870 /* skip formula vars */
2871 if (started == STARTED_NONE &&
2872 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2873 started = STARTED_CHATTER;
2878 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2879 if (appData.autoKibitz && started == STARTED_NONE &&
2880 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2881 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2882 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
2883 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2884 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2885 suppressKibitz = TRUE;
2886 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2888 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2889 && (gameMode == IcsPlayingWhite)) ||
2890 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2891 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2892 started = STARTED_CHATTER; // own kibitz we simply discard
2894 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2895 parse_pos = 0; parse[0] = NULLCHAR;
2896 savingComment = TRUE;
2897 suppressKibitz = gameMode != IcsObserving ? 2 :
2898 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2902 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2903 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2904 && atoi(star_match[0])) {
2905 // suppress the acknowledgements of our own autoKibitz
2907 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2908 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2909 SendToPlayer(star_match[0], strlen(star_match[0]));
2910 if(looking_at(buf, &i, "*% ")) // eat prompt
2911 suppressKibitz = FALSE;
2915 } // [HGM] kibitz: end of patch
2917 // [HGM] chat: intercept tells by users for which we have an open chat window
2919 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2920 looking_at(buf, &i, "* whispers:") ||
2921 looking_at(buf, &i, "* kibitzes:") ||
2922 looking_at(buf, &i, "* shouts:") ||
2923 looking_at(buf, &i, "* c-shouts:") ||
2924 looking_at(buf, &i, "--> * ") ||
2925 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2926 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2927 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2928 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2930 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2931 chattingPartner = -1;
2933 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2934 for(p=0; p<MAX_CHAT; p++) {
2935 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
2936 talker[0] = '['; strcat(talker, "] ");
2937 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2938 chattingPartner = p; break;
2941 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
2942 for(p=0; p<MAX_CHAT; p++) {
2943 if(!strcmp("kibitzes", chatPartner[p])) {
2944 talker[0] = '['; strcat(talker, "] ");
2945 chattingPartner = p; break;
2948 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2949 for(p=0; p<MAX_CHAT; p++) {
2950 if(!strcmp("whispers", chatPartner[p])) {
2951 talker[0] = '['; strcat(talker, "] ");
2952 chattingPartner = p; break;
2955 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
2956 if(buf[i-8] == '-' && buf[i-3] == 't')
2957 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
2958 if(!strcmp("c-shouts", chatPartner[p])) {
2959 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
2960 chattingPartner = p; break;
2963 if(chattingPartner < 0)
2964 for(p=0; p<MAX_CHAT; p++) {
2965 if(!strcmp("shouts", chatPartner[p])) {
2966 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2967 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2968 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2969 chattingPartner = p; break;
2973 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2974 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2975 talker[0] = 0; Colorize(ColorTell, FALSE);
2976 chattingPartner = p; break;
2978 if(chattingPartner<0) i = oldi; else {
2979 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2980 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2981 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2982 started = STARTED_COMMENT;
2983 parse_pos = 0; parse[0] = NULLCHAR;
2984 savingComment = 3 + chattingPartner; // counts as TRUE
2985 suppressKibitz = TRUE;
2988 } // [HGM] chat: end of patch
2991 if (appData.zippyTalk || appData.zippyPlay) {
2992 /* [DM] Backup address for color zippy lines */
2994 if (loggedOn == TRUE)
2995 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2996 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2998 } // [DM] 'else { ' deleted
3000 /* Regular tells and says */
3001 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3002 looking_at(buf, &i, "* (your partner) tells you: ") ||
3003 looking_at(buf, &i, "* says: ") ||
3004 /* Don't color "message" or "messages" output */
3005 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3006 looking_at(buf, &i, "*. * at *:*: ") ||
3007 looking_at(buf, &i, "--* (*:*): ") ||
3008 /* Message notifications (same color as tells) */
3009 looking_at(buf, &i, "* has left a message ") ||
3010 looking_at(buf, &i, "* just sent you a message:\n") ||
3011 /* Whispers and kibitzes */
3012 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3013 looking_at(buf, &i, "* kibitzes: ") ||
3015 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3017 if (tkind == 1 && strchr(star_match[0], ':')) {
3018 /* Avoid "tells you:" spoofs in channels */
3021 if (star_match[0][0] == NULLCHAR ||
3022 strchr(star_match[0], ' ') ||
3023 (tkind == 3 && strchr(star_match[1], ' '))) {
3024 /* Reject bogus matches */
3027 if (appData.colorize) {
3028 if (oldi > next_out) {
3029 SendToPlayer(&buf[next_out], oldi - next_out);
3034 Colorize(ColorTell, FALSE);
3035 curColor = ColorTell;
3038 Colorize(ColorKibitz, FALSE);
3039 curColor = ColorKibitz;
3042 p = strrchr(star_match[1], '(');
3049 Colorize(ColorChannel1, FALSE);
3050 curColor = ColorChannel1;
3052 Colorize(ColorChannel, FALSE);
3053 curColor = ColorChannel;
3057 curColor = ColorNormal;
3061 if (started == STARTED_NONE && appData.autoComment &&
3062 (gameMode == IcsObserving ||
3063 gameMode == IcsPlayingWhite ||
3064 gameMode == IcsPlayingBlack)) {
3065 parse_pos = i - oldi;
3066 memcpy(parse, &buf[oldi], parse_pos);
3067 parse[parse_pos] = NULLCHAR;
3068 started = STARTED_COMMENT;
3069 savingComment = TRUE;
3071 started = STARTED_CHATTER;
3072 savingComment = FALSE;
3079 if (looking_at(buf, &i, "* s-shouts: ") ||
3080 looking_at(buf, &i, "* c-shouts: ")) {
3081 if (appData.colorize) {
3082 if (oldi > next_out) {
3083 SendToPlayer(&buf[next_out], oldi - next_out);
3086 Colorize(ColorSShout, FALSE);
3087 curColor = ColorSShout;
3090 started = STARTED_CHATTER;
3094 if (looking_at(buf, &i, "--->")) {
3099 if (looking_at(buf, &i, "* shouts: ") ||
3100 looking_at(buf, &i, "--> ")) {
3101 if (appData.colorize) {
3102 if (oldi > next_out) {
3103 SendToPlayer(&buf[next_out], oldi - next_out);
3106 Colorize(ColorShout, FALSE);
3107 curColor = ColorShout;
3110 started = STARTED_CHATTER;
3114 if (looking_at( buf, &i, "Challenge:")) {
3115 if (appData.colorize) {
3116 if (oldi > next_out) {
3117 SendToPlayer(&buf[next_out], oldi - next_out);
3120 Colorize(ColorChallenge, FALSE);
3121 curColor = ColorChallenge;
3127 if (looking_at(buf, &i, "* offers you") ||
3128 looking_at(buf, &i, "* offers to be") ||
3129 looking_at(buf, &i, "* would like to") ||
3130 looking_at(buf, &i, "* requests to") ||
3131 looking_at(buf, &i, "Your opponent offers") ||
3132 looking_at(buf, &i, "Your opponent requests")) {
3134 if (appData.colorize) {
3135 if (oldi > next_out) {
3136 SendToPlayer(&buf[next_out], oldi - next_out);
3139 Colorize(ColorRequest, FALSE);
3140 curColor = ColorRequest;
3145 if (looking_at(buf, &i, "* (*) seeking")) {
3146 if (appData.colorize) {
3147 if (oldi > next_out) {
3148 SendToPlayer(&buf[next_out], oldi - next_out);
3151 Colorize(ColorSeek, FALSE);
3152 curColor = ColorSeek;
3157 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3159 if (looking_at(buf, &i, "\\ ")) {
3160 if (prevColor != ColorNormal) {
3161 if (oldi > next_out) {
3162 SendToPlayer(&buf[next_out], oldi - next_out);
3165 Colorize(prevColor, TRUE);
3166 curColor = prevColor;
3168 if (savingComment) {
3169 parse_pos = i - oldi;
3170 memcpy(parse, &buf[oldi], parse_pos);
3171 parse[parse_pos] = NULLCHAR;
3172 started = STARTED_COMMENT;
3173 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3174 chattingPartner = savingComment - 3; // kludge to remember the box
3176 started = STARTED_CHATTER;
3181 if (looking_at(buf, &i, "Black Strength :") ||
3182 looking_at(buf, &i, "<<< style 10 board >>>") ||
3183 looking_at(buf, &i, "<10>") ||
3184 looking_at(buf, &i, "#@#")) {
3185 /* Wrong board style */
3187 SendToICS(ics_prefix);
3188 SendToICS("set style 12\n");
3189 SendToICS(ics_prefix);
3190 SendToICS("refresh\n");
3194 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3196 have_sent_ICS_logon = 1;
3200 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3201 (looking_at(buf, &i, "\n<12> ") ||
3202 looking_at(buf, &i, "<12> "))) {
3204 if (oldi > next_out) {
3205 SendToPlayer(&buf[next_out], oldi - next_out);
3208 started = STARTED_BOARD;
3213 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3214 looking_at(buf, &i, "<b1> ")) {
3215 if (oldi > next_out) {
3216 SendToPlayer(&buf[next_out], oldi - next_out);
3219 started = STARTED_HOLDINGS;
3224 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3226 /* Header for a move list -- first line */
3228 switch (ics_getting_history) {
3232 case BeginningOfGame:
3233 /* User typed "moves" or "oldmoves" while we
3234 were idle. Pretend we asked for these
3235 moves and soak them up so user can step
3236 through them and/or save them.
3239 gameMode = IcsObserving;
3242 ics_getting_history = H_GOT_UNREQ_HEADER;
3244 case EditGame: /*?*/
3245 case EditPosition: /*?*/
3246 /* Should above feature work in these modes too? */
3247 /* For now it doesn't */
3248 ics_getting_history = H_GOT_UNWANTED_HEADER;
3251 ics_getting_history = H_GOT_UNWANTED_HEADER;
3256 /* Is this the right one? */
3257 if (gameInfo.white && gameInfo.black &&
3258 strcmp(gameInfo.white, star_match[0]) == 0 &&
3259 strcmp(gameInfo.black, star_match[2]) == 0) {
3261 ics_getting_history = H_GOT_REQ_HEADER;
3264 case H_GOT_REQ_HEADER:
3265 case H_GOT_UNREQ_HEADER:
3266 case H_GOT_UNWANTED_HEADER:
3267 case H_GETTING_MOVES:
3268 /* Should not happen */
3269 DisplayError(_("Error gathering move list: two headers"), 0);
3270 ics_getting_history = H_FALSE;
3274 /* Save player ratings into gameInfo if needed */
3275 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3276 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3277 (gameInfo.whiteRating == -1 ||
3278 gameInfo.blackRating == -1)) {
3280 gameInfo.whiteRating = string_to_rating(star_match[1]);
3281 gameInfo.blackRating = string_to_rating(star_match[3]);
3282 if (appData.debugMode)
3283 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3284 gameInfo.whiteRating, gameInfo.blackRating);
3289 if (looking_at(buf, &i,
3290 "* * match, initial time: * minute*, increment: * second")) {
3291 /* Header for a move list -- second line */
3292 /* Initial board will follow if this is a wild game */
3293 if (gameInfo.event != NULL) free(gameInfo.event);
3294 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3295 gameInfo.event = StrSave(str);
3296 /* [HGM] we switched variant. Translate boards if needed. */
3297 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3301 if (looking_at(buf, &i, "Move ")) {
3302 /* Beginning of a move list */
3303 switch (ics_getting_history) {
3305 /* Normally should not happen */
3306 /* Maybe user hit reset while we were parsing */
3309 /* Happens if we are ignoring a move list that is not
3310 * the one we just requested. Common if the user
3311 * tries to observe two games without turning off
3314 case H_GETTING_MOVES:
3315 /* Should not happen */
3316 DisplayError(_("Error gathering move list: nested"), 0);
3317 ics_getting_history = H_FALSE;
3319 case H_GOT_REQ_HEADER:
3320 ics_getting_history = H_GETTING_MOVES;
3321 started = STARTED_MOVES;
3323 if (oldi > next_out) {
3324 SendToPlayer(&buf[next_out], oldi - next_out);
3327 case H_GOT_UNREQ_HEADER:
3328 ics_getting_history = H_GETTING_MOVES;
3329 started = STARTED_MOVES_NOHIDE;
3332 case H_GOT_UNWANTED_HEADER:
3333 ics_getting_history = H_FALSE;
3339 if (looking_at(buf, &i, "% ") ||
3340 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3341 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3342 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3343 soughtPending = FALSE;
3347 if(suppressKibitz) next_out = i;
3348 savingComment = FALSE;
3352 case STARTED_MOVES_NOHIDE:
3353 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3354 parse[parse_pos + i - oldi] = NULLCHAR;
3355 ParseGameHistory(parse);
3357 if (appData.zippyPlay && first.initDone) {
3358 FeedMovesToProgram(&first, forwardMostMove);
3359 if (gameMode == IcsPlayingWhite) {
3360 if (WhiteOnMove(forwardMostMove)) {
3361 if (first.sendTime) {
3362 if (first.useColors) {
3363 SendToProgram("black\n", &first);
3365 SendTimeRemaining(&first, TRUE);
3367 if (first.useColors) {
3368 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3370 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3371 first.maybeThinking = TRUE;
3373 if (first.usePlayother) {
3374 if (first.sendTime) {
3375 SendTimeRemaining(&first, TRUE);
3377 SendToProgram("playother\n", &first);
3383 } else if (gameMode == IcsPlayingBlack) {
3384 if (!WhiteOnMove(forwardMostMove)) {
3385 if (first.sendTime) {
3386 if (first.useColors) {
3387 SendToProgram("white\n", &first);
3389 SendTimeRemaining(&first, FALSE);
3391 if (first.useColors) {
3392 SendToProgram("black\n", &first);
3394 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3395 first.maybeThinking = TRUE;
3397 if (first.usePlayother) {
3398 if (first.sendTime) {
3399 SendTimeRemaining(&first, FALSE);
3401 SendToProgram("playother\n", &first);
3410 if (gameMode == IcsObserving && ics_gamenum == -1) {
3411 /* Moves came from oldmoves or moves command
3412 while we weren't doing anything else.
3414 currentMove = forwardMostMove;
3415 ClearHighlights();/*!!could figure this out*/
3416 flipView = appData.flipView;
3417 DrawPosition(TRUE, boards[currentMove]);
3418 DisplayBothClocks();
3419 snprintf(str, MSG_SIZ, "%s vs. %s",
3420 gameInfo.white, gameInfo.black);
3424 /* Moves were history of an active game */
3425 if (gameInfo.resultDetails != NULL) {
3426 free(gameInfo.resultDetails);
3427 gameInfo.resultDetails = NULL;
3430 HistorySet(parseList, backwardMostMove,
3431 forwardMostMove, currentMove-1);
3432 DisplayMove(currentMove - 1);
3433 if (started == STARTED_MOVES) next_out = i;
3434 started = STARTED_NONE;
3435 ics_getting_history = H_FALSE;
3438 case STARTED_OBSERVE:
3439 started = STARTED_NONE;
3440 SendToICS(ics_prefix);
3441 SendToICS("refresh\n");
3447 if(bookHit) { // [HGM] book: simulate book reply
3448 static char bookMove[MSG_SIZ]; // a bit generous?
3450 programStats.nodes = programStats.depth = programStats.time =
3451 programStats.score = programStats.got_only_move = 0;
3452 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3454 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3455 strcat(bookMove, bookHit);
3456 HandleMachineMove(bookMove, &first);
3461 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3462 started == STARTED_HOLDINGS ||
3463 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3464 /* Accumulate characters in move list or board */
3465 parse[parse_pos++] = buf[i];
3468 /* Start of game messages. Mostly we detect start of game
3469 when the first board image arrives. On some versions
3470 of the ICS, though, we need to do a "refresh" after starting
3471 to observe in order to get the current board right away. */
3472 if (looking_at(buf, &i, "Adding game * to observation list")) {
3473 started = STARTED_OBSERVE;
3477 /* Handle auto-observe */
3478 if (appData.autoObserve &&
3479 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3480 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3482 /* Choose the player that was highlighted, if any. */
3483 if (star_match[0][0] == '\033' ||
3484 star_match[1][0] != '\033') {
3485 player = star_match[0];
3487 player = star_match[2];
3489 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3490 ics_prefix, StripHighlightAndTitle(player));
3493 /* Save ratings from notify string */
3494 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3495 player1Rating = string_to_rating(star_match[1]);
3496 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3497 player2Rating = string_to_rating(star_match[3]);
3499 if (appData.debugMode)
3501 "Ratings from 'Game notification:' %s %d, %s %d\n",
3502 player1Name, player1Rating,
3503 player2Name, player2Rating);
3508 /* Deal with automatic examine mode after a game,
3509 and with IcsObserving -> IcsExamining transition */
3510 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3511 looking_at(buf, &i, "has made you an examiner of game *")) {
3513 int gamenum = atoi(star_match[0]);
3514 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3515 gamenum == ics_gamenum) {
3516 /* We were already playing or observing this game;
3517 no need to refetch history */
3518 gameMode = IcsExamining;
3520 pauseExamForwardMostMove = forwardMostMove;
3521 } else if (currentMove < forwardMostMove) {
3522 ForwardInner(forwardMostMove);
3525 /* I don't think this case really can happen */
3526 SendToICS(ics_prefix);
3527 SendToICS("refresh\n");
3532 /* Error messages */
3533 // if (ics_user_moved) {
3534 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3535 if (looking_at(buf, &i, "Illegal move") ||
3536 looking_at(buf, &i, "Not a legal move") ||
3537 looking_at(buf, &i, "Your king is in check") ||
3538 looking_at(buf, &i, "It isn't your turn") ||
3539 looking_at(buf, &i, "It is not your move")) {
3541 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3542 currentMove = forwardMostMove-1;
3543 DisplayMove(currentMove - 1); /* before DMError */
3544 DrawPosition(FALSE, boards[currentMove]);
3545 SwitchClocks(forwardMostMove-1); // [HGM] race
3546 DisplayBothClocks();
3548 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3554 if (looking_at(buf, &i, "still have time") ||
3555 looking_at(buf, &i, "not out of time") ||
3556 looking_at(buf, &i, "either player is out of time") ||
3557 looking_at(buf, &i, "has timeseal; checking")) {
3558 /* We must have called his flag a little too soon */
3559 whiteFlag = blackFlag = FALSE;
3563 if (looking_at(buf, &i, "added * seconds to") ||
3564 looking_at(buf, &i, "seconds were added to")) {
3565 /* Update the clocks */
3566 SendToICS(ics_prefix);
3567 SendToICS("refresh\n");
3571 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3572 ics_clock_paused = TRUE;
3577 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3578 ics_clock_paused = FALSE;
3583 /* Grab player ratings from the Creating: message.
3584 Note we have to check for the special case when
3585 the ICS inserts things like [white] or [black]. */
3586 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3587 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3589 0 player 1 name (not necessarily white)
3591 2 empty, white, or black (IGNORED)
3592 3 player 2 name (not necessarily black)
3595 The names/ratings are sorted out when the game
3596 actually starts (below).
3598 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3599 player1Rating = string_to_rating(star_match[1]);
3600 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3601 player2Rating = string_to_rating(star_match[4]);
3603 if (appData.debugMode)
3605 "Ratings from 'Creating:' %s %d, %s %d\n",
3606 player1Name, player1Rating,
3607 player2Name, player2Rating);
3612 /* Improved generic start/end-of-game messages */
3613 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3614 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3615 /* If tkind == 0: */
3616 /* star_match[0] is the game number */
3617 /* [1] is the white player's name */
3618 /* [2] is the black player's name */
3619 /* For end-of-game: */
3620 /* [3] is the reason for the game end */
3621 /* [4] is a PGN end game-token, preceded by " " */
3622 /* For start-of-game: */
3623 /* [3] begins with "Creating" or "Continuing" */
3624 /* [4] is " *" or empty (don't care). */
3625 int gamenum = atoi(star_match[0]);
3626 char *whitename, *blackname, *why, *endtoken;
3627 ChessMove endtype = EndOfFile;
3630 whitename = star_match[1];
3631 blackname = star_match[2];
3632 why = star_match[3];
3633 endtoken = star_match[4];
3635 whitename = star_match[1];
3636 blackname = star_match[3];
3637 why = star_match[5];
3638 endtoken = star_match[6];
3641 /* Game start messages */
3642 if (strncmp(why, "Creating ", 9) == 0 ||
3643 strncmp(why, "Continuing ", 11) == 0) {
3644 gs_gamenum = gamenum;
3645 safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3646 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3648 if (appData.zippyPlay) {
3649 ZippyGameStart(whitename, blackname);
3652 partnerBoardValid = FALSE; // [HGM] bughouse
3656 /* Game end messages */
3657 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3658 ics_gamenum != gamenum) {
3661 while (endtoken[0] == ' ') endtoken++;
3662 switch (endtoken[0]) {
3665 endtype = GameUnfinished;
3668 endtype = BlackWins;
3671 if (endtoken[1] == '/')
3672 endtype = GameIsDrawn;
3674 endtype = WhiteWins;
3677 GameEnds(endtype, why, GE_ICS);
3679 if (appData.zippyPlay && first.initDone) {
3680 ZippyGameEnd(endtype, why);
3681 if (first.pr == NULL) {
3682 /* Start the next process early so that we'll
3683 be ready for the next challenge */
3684 StartChessProgram(&first);
3686 /* Send "new" early, in case this command takes
3687 a long time to finish, so that we'll be ready
3688 for the next challenge. */
3689 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3693 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3697 if (looking_at(buf, &i, "Removing game * from observation") ||
3698 looking_at(buf, &i, "no longer observing game *") ||
3699 looking_at(buf, &i, "Game * (*) has no examiners")) {
3700 if (gameMode == IcsObserving &&
3701 atoi(star_match[0]) == ics_gamenum)
3703 /* icsEngineAnalyze */
3704 if (appData.icsEngineAnalyze) {
3711 ics_user_moved = FALSE;
3716 if (looking_at(buf, &i, "no longer examining game *")) {
3717 if (gameMode == IcsExamining &&
3718 atoi(star_match[0]) == ics_gamenum)
3722 ics_user_moved = FALSE;
3727 /* Advance leftover_start past any newlines we find,
3728 so only partial lines can get reparsed */
3729 if (looking_at(buf, &i, "\n")) {
3730 prevColor = curColor;
3731 if (curColor != ColorNormal) {
3732 if (oldi > next_out) {
3733 SendToPlayer(&buf[next_out], oldi - next_out);
3736 Colorize(ColorNormal, FALSE);
3737 curColor = ColorNormal;
3739 if (started == STARTED_BOARD) {
3740 started = STARTED_NONE;
3741 parse[parse_pos] = NULLCHAR;
3742 ParseBoard12(parse);
3745 /* Send premove here */
3746 if (appData.premove) {
3748 if (currentMove == 0 &&
3749 gameMode == IcsPlayingWhite &&
3750 appData.premoveWhite) {
3751 snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3752 if (appData.debugMode)
3753 fprintf(debugFP, "Sending premove:\n");
3755 } else if (currentMove == 1 &&
3756 gameMode == IcsPlayingBlack &&
3757 appData.premoveBlack) {
3758 snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3759 if (appData.debugMode)
3760 fprintf(debugFP, "Sending premove:\n");
3762 } else if (gotPremove) {
3764 ClearPremoveHighlights();
3765 if (appData.debugMode)
3766 fprintf(debugFP, "Sending premove:\n");
3767 UserMoveEvent(premoveFromX, premoveFromY,
3768 premoveToX, premoveToY,
3773 /* Usually suppress following prompt */
3774 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3775 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3776 if (looking_at(buf, &i, "*% ")) {
3777 savingComment = FALSE;
3782 } else if (started == STARTED_HOLDINGS) {
3784 char new_piece[MSG_SIZ];
3785 started = STARTED_NONE;
3786 parse[parse_pos] = NULLCHAR;
3787 if (appData.debugMode)
3788 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3789 parse, currentMove);
3790 if (sscanf(parse, " game %d", &gamenum) == 1) {
3791 if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3792 if (gameInfo.variant == VariantNormal) {
3793 /* [HGM] We seem to switch variant during a game!
3794 * Presumably no holdings were displayed, so we have
3795 * to move the position two files to the right to
3796 * create room for them!
3798 VariantClass newVariant;
3799 switch(gameInfo.boardWidth) { // base guess on board width
3800 case 9: newVariant = VariantShogi; break;
3801 case 10: newVariant = VariantGreat; break;
3802 default: newVariant = VariantCrazyhouse; break;
3804 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3805 /* Get a move list just to see the header, which
3806 will tell us whether this is really bug or zh */
3807 if (ics_getting_history == H_FALSE) {
3808 ics_getting_history = H_REQUESTED;
3809 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
3813 new_piece[0] = NULLCHAR;
3814 sscanf(parse, "game %d white [%s black [%s <- %s",
3815 &gamenum, white_holding, black_holding,
3817 white_holding[strlen(white_holding)-1] = NULLCHAR;
3818 black_holding[strlen(black_holding)-1] = NULLCHAR;
3819 /* [HGM] copy holdings to board holdings area */
3820 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3821 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3822 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3824 if (appData.zippyPlay && first.initDone) {