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) );
62 #define DoSleep( n ) if( (n) >= 0) sleep(n)
72 #include <sys/types.h>
81 #else /* not STDC_HEADERS */
84 # else /* not HAVE_STRING_H */
86 # endif /* not HAVE_STRING_H */
87 #endif /* not STDC_HEADERS */
90 # include <sys/fcntl.h>
91 #else /* not HAVE_SYS_FCNTL_H */
94 # endif /* HAVE_FCNTL_H */
95 #endif /* not HAVE_SYS_FCNTL_H */
97 #if TIME_WITH_SYS_TIME
98 # include <sys/time.h>
102 # include <sys/time.h>
108 #if defined(_amigados) && !defined(__GNUC__)
113 extern int gettimeofday(struct timeval *, struct timezone *);
121 #include "frontend.h"
128 #include "backendz.h"
132 # define _(s) gettext (s)
133 # define N_(s) gettext_noop (s)
134 # define T_(s) gettext(s)
147 /* A point in time */
149 long sec; /* Assuming this is >= 32 bits */
150 int ms; /* Assuming this is >= 16 bits */
153 int establish P((void));
154 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
155 char *buf, int count, int error));
156 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
157 char *buf, int count, int error));
158 void ics_printf P((char *format, ...));
159 void SendToICS P((char *s));
160 void SendToICSDelayed P((char *s, long msdelay));
161 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
162 void HandleMachineMove P((char *message, ChessProgramState *cps));
163 int AutoPlayOneMove P((void));
164 int LoadGameOneMove P((ChessMove readAhead));
165 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
166 int LoadPositionFromFile P((char *filename, int n, char *title));
167 int SavePositionToFile P((char *filename));
168 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
170 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
171 void ShowMove P((int fromX, int fromY, int toX, int toY));
172 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
173 /*char*/int promoChar));
174 void BackwardInner P((int target));
175 void ForwardInner P((int target));
176 int Adjudicate P((ChessProgramState *cps));
177 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
178 void EditPositionDone P((Boolean fakeRights));
179 void PrintOpponents P((FILE *fp));
180 void PrintPosition P((FILE *fp, int move));
181 void StartChessProgram P((ChessProgramState *cps));
182 void SendToProgram P((char *message, ChessProgramState *cps));
183 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
184 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
185 char *buf, int count, int error));
186 void SendTimeControl P((ChessProgramState *cps,
187 int mps, long tc, int inc, int sd, int st));
188 char *TimeControlTagValue P((void));
189 void Attention P((ChessProgramState *cps));
190 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
191 void ResurrectChessProgram P((void));
192 void DisplayComment P((int moveNumber, char *text));
193 void DisplayMove P((int moveNumber));
195 void ParseGameHistory P((char *game));
196 void ParseBoard12 P((char *string));
197 void KeepAlive P((void));
198 void StartClocks P((void));
199 void SwitchClocks P((int nr));
200 void StopClocks P((void));
201 void ResetClocks P((void));
202 char *PGNDate P((void));
203 void SetGameInfo P((void));
204 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
205 int RegisterMove P((void));
206 void MakeRegisteredMove P((void));
207 void TruncateGame P((void));
208 int looking_at P((char *, int *, char *));
209 void CopyPlayerNameIntoFileName P((char **, char *));
210 char *SavePart P((char *));
211 int SaveGameOldStyle P((FILE *));
212 int SaveGamePGN P((FILE *));
213 void GetTimeMark P((TimeMark *));
214 long SubtractTimeMarks P((TimeMark *, TimeMark *));
215 int CheckFlags P((void));
216 long NextTickLength P((long));
217 void CheckTimeControl P((void));
218 void show_bytes P((FILE *, char *, int));
219 int string_to_rating P((char *str));
220 void ParseFeatures P((char* args, ChessProgramState *cps));
221 void InitBackEnd3 P((void));
222 void FeatureDone P((ChessProgramState* cps, int val));
223 void InitChessProgram P((ChessProgramState *cps, int setup));
224 void OutputKibitz(int window, char *text);
225 int PerpetualChase(int first, int last);
226 int EngineOutputIsUp();
227 void InitDrawingSizes(int x, int y);
230 extern void ConsoleCreate();
233 ChessProgramState *WhitePlayer();
234 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
235 int VerifyDisplayMode P(());
237 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
238 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
239 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
240 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
241 void ics_update_width P((int new_width));
242 extern char installDir[MSG_SIZ];
243 VariantClass startVariant; /* [HGM] nicks: initial variant */
245 extern int tinyLayout, smallLayout;
246 ChessProgramStats programStats;
247 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
249 static int exiting = 0; /* [HGM] moved to top */
250 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
251 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
252 Board partnerBoard; /* [HGM] bughouse: for peeking at partner game */
253 int partnerHighlight[2];
254 Boolean partnerBoardValid = 0;
255 char partnerStatus[MSG_SIZ];
257 Boolean originalFlip;
258 Boolean twoBoards = 0;
259 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
260 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
261 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
262 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
263 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
264 int opponentKibitzes;
265 int lastSavedGame; /* [HGM] save: ID of game */
266 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
267 extern int chatCount;
269 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
270 ChessSquare pieceSweep = EmptySquare;
271 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
272 int promoDefaultAltered;
274 /* States for ics_getting_history */
276 #define H_REQUESTED 1
277 #define H_GOT_REQ_HEADER 2
278 #define H_GOT_UNREQ_HEADER 3
279 #define H_GETTING_MOVES 4
280 #define H_GOT_UNWANTED_HEADER 5
282 /* whosays values for GameEnds */
291 /* Maximum number of games in a cmail message */
292 #define CMAIL_MAX_GAMES 20
294 /* Different types of move when calling RegisterMove */
296 #define CMAIL_RESIGN 1
298 #define CMAIL_ACCEPT 3
300 /* Different types of result to remember for each game */
301 #define CMAIL_NOT_RESULT 0
302 #define CMAIL_OLD_RESULT 1
303 #define CMAIL_NEW_RESULT 2
305 /* Telnet protocol constants */
316 safeStrCpy( char *dst, const char *src, size_t count )
319 assert( dst != NULL );
320 assert( src != NULL );
323 for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
324 if( i == count && dst[count-1] != NULLCHAR)
326 dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
327 if(appData.debugMode)
328 fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
334 /* Some compiler can't cast u64 to double
335 * This function do the job for us:
337 * We use the highest bit for cast, this only
338 * works if the highest bit is not
339 * in use (This should not happen)
341 * We used this for all compiler
344 u64ToDouble(u64 value)
347 u64 tmp = value & u64Const(0x7fffffffffffffff);
348 r = (double)(s64)tmp;
349 if (value & u64Const(0x8000000000000000))
350 r += 9.2233720368547758080e18; /* 2^63 */
354 /* Fake up flags for now, as we aren't keeping track of castling
355 availability yet. [HGM] Change of logic: the flag now only
356 indicates the type of castlings allowed by the rule of the game.
357 The actual rights themselves are maintained in the array
358 castlingRights, as part of the game history, and are not probed
364 int flags = F_ALL_CASTLE_OK;
365 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
366 switch (gameInfo.variant) {
368 flags &= ~F_ALL_CASTLE_OK;
369 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
370 flags |= F_IGNORE_CHECK;
372 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
375 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
377 case VariantKriegspiel:
378 flags |= F_KRIEGSPIEL_CAPTURE;
380 case VariantCapaRandom:
381 case VariantFischeRandom:
382 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
383 case VariantNoCastle:
384 case VariantShatranj:
387 flags &= ~F_ALL_CASTLE_OK;
395 FILE *gameFileFP, *debugFP;
398 [AS] Note: sometimes, the sscanf() function is used to parse the input
399 into a fixed-size buffer. Because of this, we must be prepared to
400 receive strings as long as the size of the input buffer, which is currently
401 set to 4K for Windows and 8K for the rest.
402 So, we must either allocate sufficiently large buffers here, or
403 reduce the size of the input buffer in the input reading part.
406 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
407 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
408 char thinkOutput1[MSG_SIZ*10];
410 ChessProgramState first, second;
412 /* premove variables */
415 int premoveFromX = 0;
416 int premoveFromY = 0;
417 int premovePromoChar = 0;
419 Boolean alarmSounded;
420 /* end premove variables */
422 char *ics_prefix = "$";
423 int ics_type = ICS_GENERIC;
425 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
426 int pauseExamForwardMostMove = 0;
427 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
428 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
429 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
430 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
431 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
432 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
433 int whiteFlag = FALSE, blackFlag = FALSE;
434 int userOfferedDraw = FALSE;
435 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
436 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
437 int cmailMoveType[CMAIL_MAX_GAMES];
438 long ics_clock_paused = 0;
439 ProcRef icsPR = NoProc, cmailPR = NoProc;
440 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
441 GameMode gameMode = BeginningOfGame;
442 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
443 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
444 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
445 int hiddenThinkOutputState = 0; /* [AS] */
446 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
447 int adjudicateLossPlies = 6;
448 char white_holding[64], black_holding[64];
449 TimeMark lastNodeCountTime;
450 long lastNodeCount=0;
451 int shiftKey; // [HGM] set by mouse handler
453 int have_sent_ICS_logon = 0;
455 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
456 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
457 long timeControl_2; /* [AS] Allow separate time controls */
458 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
459 long timeRemaining[2][MAX_MOVES];
461 TimeMark programStartTime;
462 char ics_handle[MSG_SIZ];
463 int have_set_title = 0;
465 /* animateTraining preserves the state of appData.animate
466 * when Training mode is activated. This allows the
467 * response to be animated when appData.animate == TRUE and
468 * appData.animateDragging == TRUE.
470 Boolean animateTraining;
476 Board boards[MAX_MOVES];
477 /* [HGM] Following 7 needed for accurate legality tests: */
478 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
479 signed char initialRights[BOARD_FILES];
480 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
481 int initialRulePlies, FENrulePlies;
482 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
485 int mute; // mute all sounds
487 // [HGM] vari: next 12 to save and restore variations
488 #define MAX_VARIATIONS 10
489 int framePtr = MAX_MOVES-1; // points to free stack entry
491 int savedFirst[MAX_VARIATIONS];
492 int savedLast[MAX_VARIATIONS];
493 int savedFramePtr[MAX_VARIATIONS];
494 char *savedDetails[MAX_VARIATIONS];
495 ChessMove savedResult[MAX_VARIATIONS];
497 void PushTail P((int firstMove, int lastMove));
498 Boolean PopTail P((Boolean annotate));
499 void CleanupTail P((void));
501 ChessSquare FIDEArray[2][BOARD_FILES] = {
502 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
503 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
504 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
505 BlackKing, BlackBishop, BlackKnight, BlackRook }
508 ChessSquare twoKingsArray[2][BOARD_FILES] = {
509 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
510 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
511 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
512 BlackKing, BlackKing, BlackKnight, BlackRook }
515 ChessSquare KnightmateArray[2][BOARD_FILES] = {
516 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
517 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
518 { BlackRook, BlackMan, BlackBishop, BlackQueen,
519 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
522 ChessSquare SpartanArray[2][BOARD_FILES] = {
523 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
524 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
525 { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
526 BlackDragon, BlackKing, BlackAngel, BlackAlfil }
529 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
530 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
531 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
532 { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
533 BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
536 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
537 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
538 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
539 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
540 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
543 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
544 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
545 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
546 { BlackRook, BlackKnight, BlackMan, BlackFerz,
547 BlackKing, BlackMan, BlackKnight, BlackRook }
551 #if (BOARD_FILES>=10)
552 ChessSquare ShogiArray[2][BOARD_FILES] = {
553 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
554 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
555 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
556 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
559 ChessSquare XiangqiArray[2][BOARD_FILES] = {
560 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
561 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
562 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
563 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
566 ChessSquare CapablancaArray[2][BOARD_FILES] = {
567 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
568 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
569 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
570 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
573 ChessSquare GreatArray[2][BOARD_FILES] = {
574 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
575 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
576 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
577 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
580 ChessSquare JanusArray[2][BOARD_FILES] = {
581 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
582 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
583 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
584 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
588 ChessSquare GothicArray[2][BOARD_FILES] = {
589 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
590 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
591 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
592 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
595 #define GothicArray CapablancaArray
599 ChessSquare FalconArray[2][BOARD_FILES] = {
600 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
601 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
602 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
603 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
606 #define FalconArray CapablancaArray
609 #else // !(BOARD_FILES>=10)
610 #define XiangqiPosition FIDEArray
611 #define CapablancaArray FIDEArray
612 #define GothicArray FIDEArray
613 #define GreatArray FIDEArray
614 #endif // !(BOARD_FILES>=10)
616 #if (BOARD_FILES>=12)
617 ChessSquare CourierArray[2][BOARD_FILES] = {
618 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
619 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
620 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
621 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
623 #else // !(BOARD_FILES>=12)
624 #define CourierArray CapablancaArray
625 #endif // !(BOARD_FILES>=12)
628 Board initialPosition;
631 /* Convert str to a rating. Checks for special cases of "----",
633 "++++", etc. Also strips ()'s */
635 string_to_rating(str)
638 while(*str && !isdigit(*str)) ++str;
640 return 0; /* One of the special "no rating" cases */
648 /* Init programStats */
649 programStats.movelist[0] = 0;
650 programStats.depth = 0;
651 programStats.nr_moves = 0;
652 programStats.moves_left = 0;
653 programStats.nodes = 0;
654 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
655 programStats.score = 0;
656 programStats.got_only_move = 0;
657 programStats.got_fail = 0;
658 programStats.line_is_book = 0;
663 { // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
664 if (appData.firstPlaysBlack) {
665 first.twoMachinesColor = "black\n";
666 second.twoMachinesColor = "white\n";
668 first.twoMachinesColor = "white\n";
669 second.twoMachinesColor = "black\n";
672 first.other = &second;
673 second.other = &first;
676 if(appData.timeOddsMode) {
677 norm = appData.timeOdds[0];
678 if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
680 first.timeOdds = appData.timeOdds[0]/norm;
681 second.timeOdds = appData.timeOdds[1]/norm;
684 if(programVersion) free(programVersion);
685 if (appData.noChessProgram) {
686 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
687 sprintf(programVersion, "%s", PACKAGE_STRING);
689 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
690 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
691 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
696 UnloadEngine(ChessProgramState *cps)
698 /* Kill off first chess program */
699 if (cps->isr != NULL)
700 RemoveInputSource(cps->isr);
703 if (cps->pr != NoProc) {
705 DoSleep( appData.delayBeforeQuit );
706 SendToProgram("quit\n", cps);
707 DoSleep( appData.delayAfterQuit );
708 DestroyChildProcess(cps->pr, cps->useSigterm);
714 ClearOptions(ChessProgramState *cps)
717 cps->nrOptions = cps->comboCnt = 0;
718 for(i=0; i<MAX_OPTIONS; i++) {
719 cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
720 cps->option[i].textValue = 0;
724 char *engineNames[] = {
729 InitEngine(ChessProgramState *cps, int n)
730 { // [HGM] all engine initialiation put in a function that does one engine
734 cps->which = engineNames[n];
735 cps->maybeThinking = FALSE;
739 cps->sendDrawOffers = 1;
741 cps->program = appData.chessProgram[n];
742 cps->host = appData.host[n];
743 cps->dir = appData.directory[n];
744 cps->initString = appData.engInitString[n];
745 cps->computerString = appData.computerString[n];
746 cps->useSigint = TRUE;
747 cps->useSigterm = TRUE;
748 cps->reuse = appData.reuse[n];
749 cps->nps = appData.NPS[n]; // [HGM] nps: copy nodes per second
750 cps->useSetboard = FALSE;
752 cps->usePing = FALSE;
755 cps->usePlayother = FALSE;
756 cps->useColors = TRUE;
757 cps->useUsermove = FALSE;
758 cps->sendICS = FALSE;
759 cps->sendName = appData.icsActive;
760 cps->sdKludge = FALSE;
761 cps->stKludge = FALSE;
762 TidyProgramName(cps->program, cps->host, cps->tidy);
764 safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
765 cps->analysisSupport = 2; /* detect */
766 cps->analyzing = FALSE;
767 cps->initDone = FALSE;
769 /* New features added by Tord: */
770 cps->useFEN960 = FALSE;
771 cps->useOOCastle = TRUE;
772 /* End of new features added by Tord. */
773 cps->fenOverride = appData.fenOverride[n];
775 /* [HGM] time odds: set factor for each machine */
776 cps->timeOdds = appData.timeOdds[n];
778 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
779 cps->accumulateTC = appData.accumulateTC[n];
780 cps->maxNrOfSessions = 1;
784 cps->supportsNPS = UNKNOWN;
787 cps->optionSettings = appData.engOptions[n];
789 cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
790 cps->isUCI = appData.isUCI[n]; /* [AS] */
791 cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
793 if (appData.protocolVersion[n] > PROTOVER
794 || appData.protocolVersion[n] < 1)
799 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
800 appData.protocolVersion[n]);
801 if( (len > MSG_SIZ) && appData.debugMode )
802 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
804 DisplayFatalError(buf, 0, 2);
808 cps->protocolVersion = appData.protocolVersion[n];
811 InitEngineUCI( installDir, cps ); // [HGM] moved here from winboard.c, to make available in xboard
814 ChessProgramState *savCps;
820 if(WaitForEngine(savCps, LoadEngine)) return;
821 CommonEngineInit(); // recalculate time odds
822 if(gameInfo.variant != StringToVariant(appData.variant)) {
823 // we changed variant when loading the engine; this forces us to reset
824 Reset(TRUE, savCps != &first);
825 EditGameEvent(); // for consistency with other path, as Reset changes mode
827 InitChessProgram(savCps, FALSE);
828 SendToProgram("force\n", savCps);
829 DisplayMessage("", "");
830 if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
831 for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
837 ReplaceEngine(ChessProgramState *cps, int n)
841 appData.noChessProgram = False;
842 appData.clockMode = True;
844 savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
851 int matched, min, sec;
853 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
854 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
856 GetTimeMark(&programStartTime);
857 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
860 programStats.ok_to_send = 1;
861 programStats.seen_stat = 0;
864 * Initialize game list
870 * Internet chess server status
872 if (appData.icsActive) {
873 appData.matchMode = FALSE;
874 appData.matchGames = 0;
876 appData.noChessProgram = !appData.zippyPlay;
878 appData.zippyPlay = FALSE;
879 appData.zippyTalk = FALSE;
880 appData.noChessProgram = TRUE;
882 if (*appData.icsHelper != NULLCHAR) {
883 appData.useTelnet = TRUE;
884 appData.telnetProgram = appData.icsHelper;
887 appData.zippyTalk = appData.zippyPlay = FALSE;
890 /* [AS] Initialize pv info list [HGM] and game state */
894 for( i=0; i<=framePtr; i++ ) {
895 pvInfoList[i].depth = -1;
896 boards[i][EP_STATUS] = EP_NONE;
897 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
902 * Parse timeControl resource
904 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
905 appData.movesPerSession)) {
907 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
908 DisplayFatalError(buf, 0, 2);
912 * Parse searchTime resource
914 if (*appData.searchTime != NULLCHAR) {
915 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
917 searchTime = min * 60;
918 } else if (matched == 2) {
919 searchTime = min * 60 + sec;
922 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
923 DisplayFatalError(buf, 0, 2);
927 /* [AS] Adjudication threshold */
928 adjudicateLossThreshold = appData.adjudicateLossThreshold;
930 InitEngine(&first, 0);
931 InitEngine(&second, 1);
934 if (appData.icsActive) {
935 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
936 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
937 appData.clockMode = FALSE;
938 first.sendTime = second.sendTime = 0;
942 /* Override some settings from environment variables, for backward
943 compatibility. Unfortunately it's not feasible to have the env
944 vars just set defaults, at least in xboard. Ugh.
946 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
951 if (!appData.icsActive) {
955 /* Check for variants that are supported only in ICS mode,
956 or not at all. Some that are accepted here nevertheless
957 have bugs; see comments below.
959 VariantClass variant = StringToVariant(appData.variant);
961 case VariantBughouse: /* need four players and two boards */
962 case VariantKriegspiel: /* need to hide pieces and move details */
963 /* case VariantFischeRandom: (Fabien: moved below) */
964 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
965 if( (len > MSG_SIZ) && appData.debugMode )
966 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
968 DisplayFatalError(buf, 0, 2);
972 case VariantLoadable:
982 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
983 if( (len > MSG_SIZ) && appData.debugMode )
984 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
986 DisplayFatalError(buf, 0, 2);
989 case VariantXiangqi: /* [HGM] repetition rules not implemented */
990 case VariantFairy: /* [HGM] TestLegality definitely off! */
991 case VariantGothic: /* [HGM] should work */
992 case VariantCapablanca: /* [HGM] should work */
993 case VariantCourier: /* [HGM] initial forced moves not implemented */
994 case VariantShogi: /* [HGM] could still mate with pawn drop */
995 case VariantKnightmate: /* [HGM] should work */
996 case VariantCylinder: /* [HGM] untested */
997 case VariantFalcon: /* [HGM] untested */
998 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
999 offboard interposition not understood */
1000 case VariantNormal: /* definitely works! */
1001 case VariantWildCastle: /* pieces not automatically shuffled */
1002 case VariantNoCastle: /* pieces not automatically shuffled */
1003 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1004 case VariantLosers: /* should work except for win condition,
1005 and doesn't know captures are mandatory */
1006 case VariantSuicide: /* should work except for win condition,
1007 and doesn't know captures are mandatory */
1008 case VariantGiveaway: /* should work except for win condition,
1009 and doesn't know captures are mandatory */
1010 case VariantTwoKings: /* should work */
1011 case VariantAtomic: /* should work except for win condition */
1012 case Variant3Check: /* should work except for win condition */
1013 case VariantShatranj: /* should work except for all win conditions */
1014 case VariantMakruk: /* should work except for daw countdown */
1015 case VariantBerolina: /* might work if TestLegality is off */
1016 case VariantCapaRandom: /* should work */
1017 case VariantJanus: /* should work */
1018 case VariantSuper: /* experimental */
1019 case VariantGreat: /* experimental, requires legality testing to be off */
1020 case VariantSChess: /* S-Chess, should work */
1021 case VariantSpartan: /* should work */
1028 int NextIntegerFromString( char ** str, long * value )
1033 while( *s == ' ' || *s == '\t' ) {
1039 if( *s >= '0' && *s <= '9' ) {
1040 while( *s >= '0' && *s <= '9' ) {
1041 *value = *value * 10 + (*s - '0');
1053 int NextTimeControlFromString( char ** str, long * value )
1056 int result = NextIntegerFromString( str, &temp );
1059 *value = temp * 60; /* Minutes */
1060 if( **str == ':' ) {
1062 result = NextIntegerFromString( str, &temp );
1063 *value += temp; /* Seconds */
1070 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1071 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1072 int result = -1, type = 0; long temp, temp2;
1074 if(**str != ':') return -1; // old params remain in force!
1076 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1077 if( NextIntegerFromString( str, &temp ) ) return -1;
1078 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1081 /* time only: incremental or sudden-death time control */
1082 if(**str == '+') { /* increment follows; read it */
1084 if(**str == '!') type = *(*str)++; // Bronstein TC
1085 if(result = NextIntegerFromString( str, &temp2)) return -1;
1086 *inc = temp2 * 1000;
1087 if(**str == '.') { // read fraction of increment
1088 char *start = ++(*str);
1089 if(result = NextIntegerFromString( str, &temp2)) return -1;
1091 while(start++ < *str) temp2 /= 10;
1095 *moves = 0; *tc = temp * 1000; *incType = type;
1099 (*str)++; /* classical time control */
1100 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1111 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1112 { /* [HGM] get time to add from the multi-session time-control string */
1113 int incType, moves=1; /* kludge to force reading of first session */
1114 long time, increment;
1117 if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1118 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1120 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1121 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1122 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1123 if(movenr == -1) return time; /* last move before new session */
1124 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1125 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1126 if(!moves) return increment; /* current session is incremental */
1127 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1128 } while(movenr >= -1); /* try again for next session */
1130 return 0; // no new time quota on this move
1134 ParseTimeControl(tc, ti, mps)
1141 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1144 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1145 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1146 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1150 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1152 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1155 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1157 snprintf(buf, MSG_SIZ, ":%s", mytc);
1159 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1161 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1166 /* Parse second time control */
1169 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1177 timeControl_2 = tc2 * 1000;
1187 timeControl = tc1 * 1000;
1190 timeIncrement = ti * 1000; /* convert to ms */
1191 movesPerSession = 0;
1194 movesPerSession = mps;
1202 if (appData.debugMode) {
1203 fprintf(debugFP, "%s\n", programVersion);
1206 set_cont_sequence(appData.wrapContSeq);
1207 if (appData.matchGames > 0) {
1208 appData.matchMode = TRUE;
1209 } else if (appData.matchMode) {
1210 appData.matchGames = 1;
1212 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1213 appData.matchGames = appData.sameColorGames;
1214 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1215 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1216 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1219 if (appData.noChessProgram || first.protocolVersion == 1) {
1222 /* kludge: allow timeout for initial "feature" commands */
1224 DisplayMessage("", _("Starting chess program"));
1225 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1230 MatchEvent(int mode)
1231 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1232 /* Set up machine vs. machine match */
1233 if (appData.noChessProgram) {
1234 DisplayFatalError(_("Can't have a match with no chess programs"),
1240 if (*appData.loadGameFile != NULLCHAR) {
1241 int index = appData.loadGameIndex; // [HGM] autoinc
1242 if(index<0) lastIndex = index = 1;
1243 if (!LoadGameFromFile(appData.loadGameFile,
1245 appData.loadGameFile, FALSE)) {
1246 DisplayFatalError(_("Bad game file"), 0, 1);
1249 } else if (*appData.loadPositionFile != NULLCHAR) {
1250 int index = appData.loadPositionIndex; // [HGM] autoinc
1251 if(index<0) lastIndex = index = 1;
1252 if (!LoadPositionFromFile(appData.loadPositionFile,
1254 appData.loadPositionFile)) {
1255 DisplayFatalError(_("Bad position file"), 0, 1);
1259 first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
\r
1264 InitBackEnd3 P((void))
1266 GameMode initialMode;
1270 InitChessProgram(&first, startedFromSetupPosition);
1272 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1273 free(programVersion);
1274 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1275 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1278 if (appData.icsActive) {
1280 /* [DM] Make a console window if needed [HGM] merged ifs */
1286 if (*appData.icsCommPort != NULLCHAR)
1287 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1288 appData.icsCommPort);
1290 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1291 appData.icsHost, appData.icsPort);
1293 if( (len > MSG_SIZ) && appData.debugMode )
1294 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1296 DisplayFatalError(buf, err, 1);
1301 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1303 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1304 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1305 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1306 } else if (appData.noChessProgram) {
1312 if (*appData.cmailGameName != NULLCHAR) {
1314 OpenLoopback(&cmailPR);
1316 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1320 DisplayMessage("", "");
1321 if (StrCaseCmp(appData.initialMode, "") == 0) {
1322 initialMode = BeginningOfGame;
1323 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1324 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1325 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1326 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1329 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1330 initialMode = TwoMachinesPlay;
1331 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1332 initialMode = AnalyzeFile;
1333 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1334 initialMode = AnalyzeMode;
1335 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1336 initialMode = MachinePlaysWhite;
1337 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1338 initialMode = MachinePlaysBlack;
1339 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1340 initialMode = EditGame;
1341 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1342 initialMode = EditPosition;
1343 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1344 initialMode = Training;
1346 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1347 if( (len > MSG_SIZ) && appData.debugMode )
1348 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1350 DisplayFatalError(buf, 0, 2);
1354 if (appData.matchMode) {
1356 } else if (*appData.cmailGameName != NULLCHAR) {
1357 /* Set up cmail mode */
1358 ReloadCmailMsgEvent(TRUE);
1360 /* Set up other modes */
1361 if (initialMode == AnalyzeFile) {
1362 if (*appData.loadGameFile == NULLCHAR) {
1363 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1367 if (*appData.loadGameFile != NULLCHAR) {
1368 (void) LoadGameFromFile(appData.loadGameFile,
1369 appData.loadGameIndex,
1370 appData.loadGameFile, TRUE);
1371 } else if (*appData.loadPositionFile != NULLCHAR) {
1372 (void) LoadPositionFromFile(appData.loadPositionFile,
1373 appData.loadPositionIndex,
1374 appData.loadPositionFile);
1375 /* [HGM] try to make self-starting even after FEN load */
1376 /* to allow automatic setup of fairy variants with wtm */
1377 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1378 gameMode = BeginningOfGame;
1379 setboardSpoiledMachineBlack = 1;
1381 /* [HGM] loadPos: make that every new game uses the setup */
1382 /* from file as long as we do not switch variant */
1383 if(!blackPlaysFirst) {
1384 startedFromPositionFile = TRUE;
1385 CopyBoard(filePosition, boards[0]);
1388 if (initialMode == AnalyzeMode) {
1389 if (appData.noChessProgram) {
1390 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1393 if (appData.icsActive) {
1394 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1398 } else if (initialMode == AnalyzeFile) {
1399 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1400 ShowThinkingEvent();
1402 AnalysisPeriodicEvent(1);
1403 } else if (initialMode == MachinePlaysWhite) {
1404 if (appData.noChessProgram) {
1405 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1409 if (appData.icsActive) {
1410 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1414 MachineWhiteEvent();
1415 } else if (initialMode == MachinePlaysBlack) {
1416 if (appData.noChessProgram) {
1417 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1421 if (appData.icsActive) {
1422 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1426 MachineBlackEvent();
1427 } else if (initialMode == TwoMachinesPlay) {
1428 if (appData.noChessProgram) {
1429 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1433 if (appData.icsActive) {
1434 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1439 } else if (initialMode == EditGame) {
1441 } else if (initialMode == EditPosition) {
1442 EditPositionEvent();
1443 } else if (initialMode == Training) {
1444 if (*appData.loadGameFile == NULLCHAR) {
1445 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1454 * Establish will establish a contact to a remote host.port.
1455 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1456 * used to talk to the host.
1457 * Returns 0 if okay, error code if not.
1464 if (*appData.icsCommPort != NULLCHAR) {
1465 /* Talk to the host through a serial comm port */
1466 return OpenCommPort(appData.icsCommPort, &icsPR);
1468 } else if (*appData.gateway != NULLCHAR) {
1469 if (*appData.remoteShell == NULLCHAR) {
1470 /* Use the rcmd protocol to run telnet program on a gateway host */
1471 snprintf(buf, sizeof(buf), "%s %s %s",
1472 appData.telnetProgram, appData.icsHost, appData.icsPort);
1473 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1476 /* Use the rsh program to run telnet program on a gateway host */
1477 if (*appData.remoteUser == NULLCHAR) {
1478 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1479 appData.gateway, appData.telnetProgram,
1480 appData.icsHost, appData.icsPort);
1482 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1483 appData.remoteShell, appData.gateway,
1484 appData.remoteUser, appData.telnetProgram,
1485 appData.icsHost, appData.icsPort);
1487 return StartChildProcess(buf, "", &icsPR);
1490 } else if (appData.useTelnet) {
1491 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1494 /* TCP socket interface differs somewhat between
1495 Unix and NT; handle details in the front end.
1497 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1501 void EscapeExpand(char *p, char *q)
1502 { // [HGM] initstring: routine to shape up string arguments
1503 while(*p++ = *q++) if(p[-1] == '\\')
1505 case 'n': p[-1] = '\n'; break;
1506 case 'r': p[-1] = '\r'; break;
1507 case 't': p[-1] = '\t'; break;
1508 case '\\': p[-1] = '\\'; break;
1509 case 0: *p = 0; return;
1510 default: p[-1] = q[-1]; break;
1515 show_bytes(fp, buf, count)
1521 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1522 fprintf(fp, "\\%03o", *buf & 0xff);
1531 /* Returns an errno value */
1533 OutputMaybeTelnet(pr, message, count, outError)
1539 char buf[8192], *p, *q, *buflim;
1540 int left, newcount, outcount;
1542 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1543 *appData.gateway != NULLCHAR) {
1544 if (appData.debugMode) {
1545 fprintf(debugFP, ">ICS: ");
1546 show_bytes(debugFP, message, count);
1547 fprintf(debugFP, "\n");
1549 return OutputToProcess(pr, message, count, outError);
1552 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1559 if (appData.debugMode) {
1560 fprintf(debugFP, ">ICS: ");
1561 show_bytes(debugFP, buf, newcount);
1562 fprintf(debugFP, "\n");
1564 outcount = OutputToProcess(pr, buf, newcount, outError);
1565 if (outcount < newcount) return -1; /* to be sure */
1572 } else if (((unsigned char) *p) == TN_IAC) {
1573 *q++ = (char) TN_IAC;
1580 if (appData.debugMode) {
1581 fprintf(debugFP, ">ICS: ");
1582 show_bytes(debugFP, buf, newcount);
1583 fprintf(debugFP, "\n");
1585 outcount = OutputToProcess(pr, buf, newcount, outError);
1586 if (outcount < newcount) return -1; /* to be sure */
1591 read_from_player(isr, closure, message, count, error)
1598 int outError, outCount;
1599 static int gotEof = 0;
1601 /* Pass data read from player on to ICS */
1604 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1605 if (outCount < count) {
1606 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1608 } else if (count < 0) {
1609 RemoveInputSource(isr);
1610 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1611 } else if (gotEof++ > 0) {
1612 RemoveInputSource(isr);
1613 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1619 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1620 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1621 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1622 SendToICS("date\n");
1623 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1626 /* added routine for printf style output to ics */
1627 void ics_printf(char *format, ...)
1629 char buffer[MSG_SIZ];
1632 va_start(args, format);
1633 vsnprintf(buffer, sizeof(buffer), format, args);
1634 buffer[sizeof(buffer)-1] = '\0';
1643 int count, outCount, outError;
1645 if (icsPR == NULL) return;
1648 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1649 if (outCount < count) {
1650 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1654 /* This is used for sending logon scripts to the ICS. Sending
1655 without a delay causes problems when using timestamp on ICC
1656 (at least on my machine). */
1658 SendToICSDelayed(s,msdelay)
1662 int count, outCount, outError;
1664 if (icsPR == NULL) return;
1667 if (appData.debugMode) {
1668 fprintf(debugFP, ">ICS: ");
1669 show_bytes(debugFP, s, count);
1670 fprintf(debugFP, "\n");
1672 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1674 if (outCount < count) {
1675 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1680 /* Remove all highlighting escape sequences in s
1681 Also deletes any suffix starting with '('
1684 StripHighlightAndTitle(s)
1687 static char retbuf[MSG_SIZ];
1690 while (*s != NULLCHAR) {
1691 while (*s == '\033') {
1692 while (*s != NULLCHAR && !isalpha(*s)) s++;
1693 if (*s != NULLCHAR) s++;
1695 while (*s != NULLCHAR && *s != '\033') {
1696 if (*s == '(' || *s == '[') {
1707 /* Remove all highlighting escape sequences in s */
1712 static char retbuf[MSG_SIZ];
1715 while (*s != NULLCHAR) {
1716 while (*s == '\033') {
1717 while (*s != NULLCHAR && !isalpha(*s)) s++;
1718 if (*s != NULLCHAR) s++;
1720 while (*s != NULLCHAR && *s != '\033') {
1728 char *variantNames[] = VARIANT_NAMES;
1733 return variantNames[v];
1737 /* Identify a variant from the strings the chess servers use or the
1738 PGN Variant tag names we use. */
1745 VariantClass v = VariantNormal;
1746 int i, found = FALSE;
1752 /* [HGM] skip over optional board-size prefixes */
1753 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1754 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1755 while( *e++ != '_');
1758 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1762 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1763 if (StrCaseStr(e, variantNames[i])) {
1764 v = (VariantClass) i;
1771 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1772 || StrCaseStr(e, "wild/fr")
1773 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1774 v = VariantFischeRandom;
1775 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1776 (i = 1, p = StrCaseStr(e, "w"))) {
1778 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1785 case 0: /* FICS only, actually */
1787 /* Castling legal even if K starts on d-file */
1788 v = VariantWildCastle;
1793 /* Castling illegal even if K & R happen to start in
1794 normal positions. */
1795 v = VariantNoCastle;
1808 /* Castling legal iff K & R start in normal positions */
1814 /* Special wilds for position setup; unclear what to do here */
1815 v = VariantLoadable;
1818 /* Bizarre ICC game */
1819 v = VariantTwoKings;
1822 v = VariantKriegspiel;
1828 v = VariantFischeRandom;
1831 v = VariantCrazyhouse;
1834 v = VariantBughouse;
1840 /* Not quite the same as FICS suicide! */
1841 v = VariantGiveaway;
1847 v = VariantShatranj;
1850 /* Temporary names for future ICC types. The name *will* change in
1851 the next xboard/WinBoard release after ICC defines it. */
1889 v = VariantCapablanca;
1892 v = VariantKnightmate;
1898 v = VariantCylinder;
1904 v = VariantCapaRandom;
1907 v = VariantBerolina;
1919 /* Found "wild" or "w" in the string but no number;
1920 must assume it's normal chess. */
1924 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
1925 if( (len > MSG_SIZ) && appData.debugMode )
1926 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
1928 DisplayError(buf, 0);
1934 if (appData.debugMode) {
1935 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
1936 e, wnum, VariantName(v));
1941 static int leftover_start = 0, leftover_len = 0;
1942 char star_match[STAR_MATCH_N][MSG_SIZ];
1944 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1945 advance *index beyond it, and set leftover_start to the new value of
1946 *index; else return FALSE. If pattern contains the character '*', it
1947 matches any sequence of characters not containing '\r', '\n', or the
1948 character following the '*' (if any), and the matched sequence(s) are
1949 copied into star_match.
1952 looking_at(buf, index, pattern)
1957 char *bufp = &buf[*index], *patternp = pattern;
1959 char *matchp = star_match[0];
1962 if (*patternp == NULLCHAR) {
1963 *index = leftover_start = bufp - buf;
1967 if (*bufp == NULLCHAR) return FALSE;
1968 if (*patternp == '*') {
1969 if (*bufp == *(patternp + 1)) {
1971 matchp = star_match[++star_count];
1975 } else if (*bufp == '\n' || *bufp == '\r') {
1977 if (*patternp == NULLCHAR)
1982 *matchp++ = *bufp++;
1986 if (*patternp != *bufp) return FALSE;
1993 SendToPlayer(data, length)
1997 int error, outCount;
1998 outCount = OutputToProcess(NoProc, data, length, &error);
1999 if (outCount < length) {
2000 DisplayFatalError(_("Error writing to display"), error, 1);
2005 PackHolding(packed, holding)
2017 switch (runlength) {
2028 sprintf(q, "%d", runlength);
2040 /* Telnet protocol requests from the front end */
2042 TelnetRequest(ddww, option)
2043 unsigned char ddww, option;
2045 unsigned char msg[3];
2046 int outCount, outError;
2048 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2050 if (appData.debugMode) {
2051 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2067 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2076 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2079 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2084 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2086 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2093 if (!appData.icsActive) return;
2094 TelnetRequest(TN_DO, TN_ECHO);
2100 if (!appData.icsActive) return;
2101 TelnetRequest(TN_DONT, TN_ECHO);
2105 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2107 /* put the holdings sent to us by the server on the board holdings area */
2108 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2112 if(gameInfo.holdingsWidth < 2) return;
2113 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2114 return; // prevent overwriting by pre-board holdings
2116 if( (int)lowestPiece >= BlackPawn ) {
2119 holdingsStartRow = BOARD_HEIGHT-1;
2122 holdingsColumn = BOARD_WIDTH-1;
2123 countsColumn = BOARD_WIDTH-2;
2124 holdingsStartRow = 0;
2128 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2129 board[i][holdingsColumn] = EmptySquare;
2130 board[i][countsColumn] = (ChessSquare) 0;
2132 while( (p=*holdings++) != NULLCHAR ) {
2133 piece = CharToPiece( ToUpper(p) );
2134 if(piece == EmptySquare) continue;
2135 /*j = (int) piece - (int) WhitePawn;*/
2136 j = PieceToNumber(piece);
2137 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2138 if(j < 0) continue; /* should not happen */
2139 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2140 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2141 board[holdingsStartRow+j*direction][countsColumn]++;
2147 VariantSwitch(Board board, VariantClass newVariant)
2149 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2150 static Board oldBoard;
2152 startedFromPositionFile = FALSE;
2153 if(gameInfo.variant == newVariant) return;
2155 /* [HGM] This routine is called each time an assignment is made to
2156 * gameInfo.variant during a game, to make sure the board sizes
2157 * are set to match the new variant. If that means adding or deleting
2158 * holdings, we shift the playing board accordingly
2159 * This kludge is needed because in ICS observe mode, we get boards
2160 * of an ongoing game without knowing the variant, and learn about the
2161 * latter only later. This can be because of the move list we requested,
2162 * in which case the game history is refilled from the beginning anyway,
2163 * but also when receiving holdings of a crazyhouse game. In the latter
2164 * case we want to add those holdings to the already received position.
2168 if (appData.debugMode) {
2169 fprintf(debugFP, "Switch board from %s to %s\n",
2170 VariantName(gameInfo.variant), VariantName(newVariant));
2171 setbuf(debugFP, NULL);
2173 shuffleOpenings = 0; /* [HGM] shuffle */
2174 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2178 newWidth = 9; newHeight = 9;
2179 gameInfo.holdingsSize = 7;
2180 case VariantBughouse:
2181 case VariantCrazyhouse:
2182 newHoldingsWidth = 2; break;
2186 newHoldingsWidth = 2;
2187 gameInfo.holdingsSize = 8;
2190 case VariantCapablanca:
2191 case VariantCapaRandom:
2194 newHoldingsWidth = gameInfo.holdingsSize = 0;
2197 if(newWidth != gameInfo.boardWidth ||
2198 newHeight != gameInfo.boardHeight ||
2199 newHoldingsWidth != gameInfo.holdingsWidth ) {
2201 /* shift position to new playing area, if needed */
2202 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2203 for(i=0; i<BOARD_HEIGHT; i++)
2204 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2205 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2207 for(i=0; i<newHeight; i++) {
2208 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2209 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2211 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2212 for(i=0; i<BOARD_HEIGHT; i++)
2213 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2214 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2217 gameInfo.boardWidth = newWidth;
2218 gameInfo.boardHeight = newHeight;
2219 gameInfo.holdingsWidth = newHoldingsWidth;
2220 gameInfo.variant = newVariant;
2221 InitDrawingSizes(-2, 0);
2222 } else gameInfo.variant = newVariant;
2223 CopyBoard(oldBoard, board); // remember correctly formatted board
2224 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2225 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2228 static int loggedOn = FALSE;
2230 /*-- Game start info cache: --*/
2232 char gs_kind[MSG_SIZ];
2233 static char player1Name[128] = "";
2234 static char player2Name[128] = "";
2235 static char cont_seq[] = "\n\\ ";
2236 static int player1Rating = -1;
2237 static int player2Rating = -1;
2238 /*----------------------------*/
2240 ColorClass curColor = ColorNormal;
2241 int suppressKibitz = 0;
2244 Boolean soughtPending = FALSE;
2245 Boolean seekGraphUp;
2246 #define MAX_SEEK_ADS 200
2248 char *seekAdList[MAX_SEEK_ADS];
2249 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2250 float tcList[MAX_SEEK_ADS];
2251 char colorList[MAX_SEEK_ADS];
2252 int nrOfSeekAds = 0;
2253 int minRating = 1010, maxRating = 2800;
2254 int hMargin = 10, vMargin = 20, h, w;
2255 extern int squareSize, lineGap;
2260 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2261 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2262 if(r < minRating+100 && r >=0 ) r = minRating+100;
2263 if(r > maxRating) r = maxRating;
2264 if(tc < 1.) tc = 1.;
2265 if(tc > 95.) tc = 95.;
2266 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2267 y = ((double)r - minRating)/(maxRating - minRating)
2268 * (h-vMargin-squareSize/8-1) + vMargin;
2269 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2270 if(strstr(seekAdList[i], " u ")) color = 1;
2271 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2272 !strstr(seekAdList[i], "bullet") &&
2273 !strstr(seekAdList[i], "blitz") &&
2274 !strstr(seekAdList[i], "standard") ) color = 2;
2275 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2276 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2280 AddAd(char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2282 char buf[MSG_SIZ], *ext = "";
2283 VariantClass v = StringToVariant(type);
2284 if(strstr(type, "wild")) {
2285 ext = type + 4; // append wild number
2286 if(v == VariantFischeRandom) type = "chess960"; else
2287 if(v == VariantLoadable) type = "setup"; else
2288 type = VariantName(v);
2290 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2291 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2292 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2293 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2294 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2295 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2296 seekNrList[nrOfSeekAds] = nr;
2297 zList[nrOfSeekAds] = 0;
2298 seekAdList[nrOfSeekAds++] = StrSave(buf);
2299 if(plot) PlotSeekAd(nrOfSeekAds-1);
2306 int x = xList[i], y = yList[i], d=squareSize/4, k;
2307 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2308 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2309 // now replot every dot that overlapped
2310 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2311 int xx = xList[k], yy = yList[k];
2312 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2313 DrawSeekDot(xx, yy, colorList[k]);
2318 RemoveSeekAd(int nr)
2321 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2323 if(seekAdList[i]) free(seekAdList[i]);
2324 seekAdList[i] = seekAdList[--nrOfSeekAds];
2325 seekNrList[i] = seekNrList[nrOfSeekAds];
2326 ratingList[i] = ratingList[nrOfSeekAds];
2327 colorList[i] = colorList[nrOfSeekAds];
2328 tcList[i] = tcList[nrOfSeekAds];
2329 xList[i] = xList[nrOfSeekAds];
2330 yList[i] = yList[nrOfSeekAds];
2331 zList[i] = zList[nrOfSeekAds];
2332 seekAdList[nrOfSeekAds] = NULL;
2338 MatchSoughtLine(char *line)
2340 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2341 int nr, base, inc, u=0; char dummy;
2343 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2344 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2346 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2347 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2348 // match: compact and save the line
2349 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2359 if(!seekGraphUp) return FALSE;
2360 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2361 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2363 DrawSeekBackground(0, 0, w, h);
2364 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2365 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2366 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2367 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2369 DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2372 snprintf(buf, MSG_SIZ, "%d", i);
2373 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2376 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2377 for(i=1; i<100; i+=(i<10?1:5)) {
2378 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2379 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2380 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2382 snprintf(buf, MSG_SIZ, "%d", i);
2383 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2386 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2390 int SeekGraphClick(ClickType click, int x, int y, int moving)
2392 static int lastDown = 0, displayed = 0, lastSecond;
2393 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2394 if(click == Release || moving) return FALSE;
2396 soughtPending = TRUE;
2397 SendToICS(ics_prefix);
2398 SendToICS("sought\n"); // should this be "sought all"?
2399 } else { // issue challenge based on clicked ad
2400 int dist = 10000; int i, closest = 0, second = 0;
2401 for(i=0; i<nrOfSeekAds; i++) {
2402 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2403 if(d < dist) { dist = d; closest = i; }
2404 second += (d - zList[i] < 120); // count in-range ads
2405 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2409 second = (second > 1);
2410 if(displayed != closest || second != lastSecond) {
2411 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2412 lastSecond = second; displayed = closest;
2414 if(click == Press) {
2415 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2418 } // on press 'hit', only show info
2419 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2420 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2421 SendToICS(ics_prefix);
2423 return TRUE; // let incoming board of started game pop down the graph
2424 } else if(click == Release) { // release 'miss' is ignored
2425 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2426 if(moving == 2) { // right up-click
2427 nrOfSeekAds = 0; // refresh graph
2428 soughtPending = TRUE;
2429 SendToICS(ics_prefix);
2430 SendToICS("sought\n"); // should this be "sought all"?
2433 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2434 // press miss or release hit 'pop down' seek graph
2435 seekGraphUp = FALSE;
2436 DrawPosition(TRUE, NULL);
2442 read_from_ics(isr, closure, data, count, error)
2449 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2450 #define STARTED_NONE 0
2451 #define STARTED_MOVES 1
2452 #define STARTED_BOARD 2
2453 #define STARTED_OBSERVE 3
2454 #define STARTED_HOLDINGS 4
2455 #define STARTED_CHATTER 5
2456 #define STARTED_COMMENT 6
2457 #define STARTED_MOVES_NOHIDE 7
2459 static int started = STARTED_NONE;
2460 static char parse[20000];
2461 static int parse_pos = 0;
2462 static char buf[BUF_SIZE + 1];
2463 static int firstTime = TRUE, intfSet = FALSE;
2464 static ColorClass prevColor = ColorNormal;
2465 static int savingComment = FALSE;
2466 static int cmatch = 0; // continuation sequence match
2473 int backup; /* [DM] For zippy color lines */
2475 char talker[MSG_SIZ]; // [HGM] chat
2478 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2480 if (appData.debugMode) {
2482 fprintf(debugFP, "<ICS: ");
2483 show_bytes(debugFP, data, count);
2484 fprintf(debugFP, "\n");
2488 if (appData.debugMode) { int f = forwardMostMove;
2489 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2490 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2491 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2494 /* If last read ended with a partial line that we couldn't parse,
2495 prepend it to the new read and try again. */
2496 if (leftover_len > 0) {
2497 for (i=0; i<leftover_len; i++)
2498 buf[i] = buf[leftover_start + i];
2501 /* copy new characters into the buffer */
2502 bp = buf + leftover_len;
2503 buf_len=leftover_len;
2504 for (i=0; i<count; i++)
2507 if (data[i] == '\r')
2510 // join lines split by ICS?
2511 if (!appData.noJoin)
2514 Joining just consists of finding matches against the
2515 continuation sequence, and discarding that sequence
2516 if found instead of copying it. So, until a match
2517 fails, there's nothing to do since it might be the
2518 complete sequence, and thus, something we don't want
2521 if (data[i] == cont_seq[cmatch])
2524 if (cmatch == strlen(cont_seq))
2526 cmatch = 0; // complete match. just reset the counter
2529 it's possible for the ICS to not include the space
2530 at the end of the last word, making our [correct]
2531 join operation fuse two separate words. the server
2532 does this when the space occurs at the width setting.
2534 if (!buf_len || buf[buf_len-1] != ' ')
2545 match failed, so we have to copy what matched before
2546 falling through and copying this character. In reality,
2547 this will only ever be just the newline character, but
2548 it doesn't hurt to be precise.
2550 strncpy(bp, cont_seq, cmatch);
2562 buf[buf_len] = NULLCHAR;
2563 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2568 while (i < buf_len) {
2569 /* Deal with part of the TELNET option negotiation
2570 protocol. We refuse to do anything beyond the
2571 defaults, except that we allow the WILL ECHO option,
2572 which ICS uses to turn off password echoing when we are
2573 directly connected to it. We reject this option
2574 if localLineEditing mode is on (always on in xboard)
2575 and we are talking to port 23, which might be a real
2576 telnet server that will try to keep WILL ECHO on permanently.
2578 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2579 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2580 unsigned char option;
2582 switch ((unsigned char) buf[++i]) {
2584 if (appData.debugMode)
2585 fprintf(debugFP, "\n<WILL ");
2586 switch (option = (unsigned char) buf[++i]) {
2588 if (appData.debugMode)
2589 fprintf(debugFP, "ECHO ");
2590 /* Reply only if this is a change, according
2591 to the protocol rules. */
2592 if (remoteEchoOption) break;
2593 if (appData.localLineEditing &&
2594 atoi(appData.icsPort) == TN_PORT) {
2595 TelnetRequest(TN_DONT, TN_ECHO);
2598 TelnetRequest(TN_DO, TN_ECHO);
2599 remoteEchoOption = TRUE;
2603 if (appData.debugMode)
2604 fprintf(debugFP, "%d ", option);
2605 /* Whatever this is, we don't want it. */
2606 TelnetRequest(TN_DONT, option);
2611 if (appData.debugMode)
2612 fprintf(debugFP, "\n<WONT ");
2613 switch (option = (unsigned char) buf[++i]) {
2615 if (appData.debugMode)
2616 fprintf(debugFP, "ECHO ");
2617 /* Reply only if this is a change, according
2618 to the protocol rules. */
2619 if (!remoteEchoOption) break;
2621 TelnetRequest(TN_DONT, TN_ECHO);
2622 remoteEchoOption = FALSE;
2625 if (appData.debugMode)
2626 fprintf(debugFP, "%d ", (unsigned char) option);
2627 /* Whatever this is, it must already be turned
2628 off, because we never agree to turn on
2629 anything non-default, so according to the
2630 protocol rules, we don't reply. */
2635 if (appData.debugMode)
2636 fprintf(debugFP, "\n<DO ");
2637 switch (option = (unsigned char) buf[++i]) {
2639 /* Whatever this is, we refuse to do it. */
2640 if (appData.debugMode)
2641 fprintf(debugFP, "%d ", option);
2642 TelnetRequest(TN_WONT, option);
2647 if (appData.debugMode)
2648 fprintf(debugFP, "\n<DONT ");
2649 switch (option = (unsigned char) buf[++i]) {
2651 if (appData.debugMode)
2652 fprintf(debugFP, "%d ", option);
2653 /* Whatever this is, we are already not doing
2654 it, because we never agree to do anything
2655 non-default, so according to the protocol
2656 rules, we don't reply. */
2661 if (appData.debugMode)
2662 fprintf(debugFP, "\n<IAC ");
2663 /* Doubled IAC; pass it through */
2667 if (appData.debugMode)
2668 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2669 /* Drop all other telnet commands on the floor */
2672 if (oldi > next_out)
2673 SendToPlayer(&buf[next_out], oldi - next_out);
2679 /* OK, this at least will *usually* work */
2680 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2684 if (loggedOn && !intfSet) {
2685 if (ics_type == ICS_ICC) {
2686 snprintf(str, MSG_SIZ,
2687 "/set-quietly interface %s\n/set-quietly style 12\n",
2689 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2690 strcat(str, "/set-2 51 1\n/set seek 1\n");
2691 } else if (ics_type == ICS_CHESSNET) {
2692 snprintf(str, MSG_SIZ, "/style 12\n");
2694 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2695 strcat(str, programVersion);
2696 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2697 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2698 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2700 strcat(str, "$iset nohighlight 1\n");
2702 strcat(str, "$iset lock 1\n$style 12\n");
2705 NotifyFrontendLogin();
2709 if (started == STARTED_COMMENT) {
2710 /* Accumulate characters in comment */
2711 parse[parse_pos++] = buf[i];
2712 if (buf[i] == '\n') {
2713 parse[parse_pos] = NULLCHAR;
2714 if(chattingPartner>=0) {
2716 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2717 OutputChatMessage(chattingPartner, mess);
2718 chattingPartner = -1;
2719 next_out = i+1; // [HGM] suppress printing in ICS window
2721 if(!suppressKibitz) // [HGM] kibitz
2722 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2723 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2724 int nrDigit = 0, nrAlph = 0, j;
2725 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2726 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2727 parse[parse_pos] = NULLCHAR;
2728 // try to be smart: if it does not look like search info, it should go to
2729 // ICS interaction window after all, not to engine-output window.
2730 for(j=0; j<parse_pos; j++) { // count letters and digits
2731 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2732 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2733 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2735 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2736 int depth=0; float score;
2737 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2738 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2739 pvInfoList[forwardMostMove-1].depth = depth;
2740 pvInfoList[forwardMostMove-1].score = 100*score;
2742 OutputKibitz(suppressKibitz, parse);
2745 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2746 SendToPlayer(tmp, strlen(tmp));
2748 next_out = i+1; // [HGM] suppress printing in ICS window
2750 started = STARTED_NONE;
2752 /* Don't match patterns against characters in comment */
2757 if (started == STARTED_CHATTER) {
2758 if (buf[i] != '\n') {
2759 /* Don't match patterns against characters in chatter */
2763 started = STARTED_NONE;
2764 if(suppressKibitz) next_out = i+1;
2767 /* Kludge to deal with rcmd protocol */
2768 if (firstTime && looking_at(buf, &i, "\001*")) {
2769 DisplayFatalError(&buf[1], 0, 1);
2775 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2778 if (appData.debugMode)
2779 fprintf(debugFP, "ics_type %d\n", ics_type);
2782 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2783 ics_type = ICS_FICS;
2785 if (appData.debugMode)
2786 fprintf(debugFP, "ics_type %d\n", ics_type);
2789 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2790 ics_type = ICS_CHESSNET;
2792 if (appData.debugMode)
2793 fprintf(debugFP, "ics_type %d\n", ics_type);
2798 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2799 looking_at(buf, &i, "Logging you in as \"*\"") ||
2800 looking_at(buf, &i, "will be \"*\""))) {
2801 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2805 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2807 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2808 DisplayIcsInteractionTitle(buf);
2809 have_set_title = TRUE;
2812 /* skip finger notes */
2813 if (started == STARTED_NONE &&
2814 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2815 (buf[i] == '1' && buf[i+1] == '0')) &&
2816 buf[i+2] == ':' && buf[i+3] == ' ') {
2817 started = STARTED_CHATTER;
2823 // [HGM] seekgraph: recognize sought lines and end-of-sought message
2824 if(appData.seekGraph) {
2825 if(soughtPending && MatchSoughtLine(buf+i)) {
2826 i = strstr(buf+i, "rated") - buf;
2827 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2828 next_out = leftover_start = i;
2829 started = STARTED_CHATTER;
2830 suppressKibitz = TRUE;
2833 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2834 && looking_at(buf, &i, "* ads displayed")) {
2835 soughtPending = FALSE;
2840 if(appData.autoRefresh) {
2841 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2842 int s = (ics_type == ICS_ICC); // ICC format differs
2844 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
2845 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2846 looking_at(buf, &i, "*% "); // eat prompt
2847 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2848 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2849 next_out = i; // suppress
2852 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
2853 char *p = star_match[0];
2855 if(seekGraphUp) RemoveSeekAd(atoi(p));
2856 while(*p && *p++ != ' '); // next
2858 looking_at(buf, &i, "*% "); // eat prompt
2859 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2866 /* skip formula vars */
2867 if (started == STARTED_NONE &&
2868 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
2869 started = STARTED_CHATTER;
2874 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
2875 if (appData.autoKibitz && started == STARTED_NONE &&
2876 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
2877 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
2878 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
2879 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
2880 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
2881 suppressKibitz = TRUE;
2882 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2884 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
2885 && (gameMode == IcsPlayingWhite)) ||
2886 (StrStr(star_match[0], gameInfo.black) == star_match[0]
2887 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
2888 started = STARTED_CHATTER; // own kibitz we simply discard
2890 started = STARTED_COMMENT; // make sure it will be collected in parse[]
2891 parse_pos = 0; parse[0] = NULLCHAR;
2892 savingComment = TRUE;
2893 suppressKibitz = gameMode != IcsObserving ? 2 :
2894 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
2898 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
2899 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
2900 && atoi(star_match[0])) {
2901 // suppress the acknowledgements of our own autoKibitz
2903 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2904 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
2905 SendToPlayer(star_match[0], strlen(star_match[0]));
2906 if(looking_at(buf, &i, "*% ")) // eat prompt
2907 suppressKibitz = FALSE;
2911 } // [HGM] kibitz: end of patch
2913 // [HGM] chat: intercept tells by users for which we have an open chat window
2915 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
2916 looking_at(buf, &i, "* whispers:") ||
2917 looking_at(buf, &i, "* kibitzes:") ||
2918 looking_at(buf, &i, "* shouts:") ||
2919 looking_at(buf, &i, "* c-shouts:") ||
2920 looking_at(buf, &i, "--> * ") ||
2921 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
2922 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
2923 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
2924 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
2926 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
2927 chattingPartner = -1;
2929 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
2930 for(p=0; p<MAX_CHAT; p++) {
2931 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
2932 talker[0] = '['; strcat(talker, "] ");
2933 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
2934 chattingPartner = p; break;
2937 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
2938 for(p=0; p<MAX_CHAT; p++) {
2939 if(!strcmp("kibitzes", chatPartner[p])) {
2940 talker[0] = '['; strcat(talker, "] ");
2941 chattingPartner = p; break;
2944 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
2945 for(p=0; p<MAX_CHAT; p++) {
2946 if(!strcmp("whispers", chatPartner[p])) {
2947 talker[0] = '['; strcat(talker, "] ");
2948 chattingPartner = p; break;
2951 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
2952 if(buf[i-8] == '-' && buf[i-3] == 't')
2953 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
2954 if(!strcmp("c-shouts", chatPartner[p])) {
2955 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
2956 chattingPartner = p; break;
2959 if(chattingPartner < 0)
2960 for(p=0; p<MAX_CHAT; p++) {
2961 if(!strcmp("shouts", chatPartner[p])) {
2962 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
2963 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
2964 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
2965 chattingPartner = p; break;
2969 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
2970 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
2971 talker[0] = 0; Colorize(ColorTell, FALSE);
2972 chattingPartner = p; break;
2974 if(chattingPartner<0) i = oldi; else {
2975 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
2976 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
2977 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2978 started = STARTED_COMMENT;
2979 parse_pos = 0; parse[0] = NULLCHAR;
2980 savingComment = 3 + chattingPartner; // counts as TRUE
2981 suppressKibitz = TRUE;
2984 } // [HGM] chat: end of patch
2987 if (appData.zippyTalk || appData.zippyPlay) {
2988 /* [DM] Backup address for color zippy lines */
2990 if (loggedOn == TRUE)
2991 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
2992 (appData.zippyPlay && ZippyMatch(buf, &backup)));
2994 } // [DM] 'else { ' deleted
2996 /* Regular tells and says */
2997 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
2998 looking_at(buf, &i, "* (your partner) tells you: ") ||
2999 looking_at(buf, &i, "* says: ") ||
3000 /* Don't color "message" or "messages" output */
3001 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3002 looking_at(buf, &i, "*. * at *:*: ") ||
3003 looking_at(buf, &i, "--* (*:*): ") ||
3004 /* Message notifications (same color as tells) */
3005 looking_at(buf, &i, "* has left a message ") ||
3006 looking_at(buf, &i, "* just sent you a message:\n") ||
3007 /* Whispers and kibitzes */
3008 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3009 looking_at(buf, &i, "* kibitzes: ") ||
3011 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3013 if (tkind == 1 && strchr(star_match[0], ':')) {
3014 /* Avoid "tells you:" spoofs in channels */
3017 if (star_match[0][0] == NULLCHAR ||
3018 strchr(star_match[0], ' ') ||
3019 (tkind == 3 && strchr(star_match[1], ' '))) {
3020 /* Reject bogus matches */
3023 if (appData.colorize) {
3024 if (oldi > next_out) {
3025 SendToPlayer(&buf[next_out], oldi - next_out);
3030 Colorize(ColorTell, FALSE);
3031 curColor = ColorTell;
3034 Colorize(ColorKibitz, FALSE);
3035 curColor = ColorKibitz;
3038 p = strrchr(star_match[1], '(');
3045 Colorize(ColorChannel1, FALSE);
3046 curColor = ColorChannel1;
3048 Colorize(ColorChannel, FALSE);
3049 curColor = ColorChannel;
3053 curColor = ColorNormal;
3057 if (started == STARTED_NONE && appData.autoComment &&
3058 (gameMode == IcsObserving ||
3059 gameMode == IcsPlayingWhite ||
3060 gameMode == IcsPlayingBlack)) {
3061 parse_pos = i - oldi;
3062 memcpy(parse, &buf[oldi], parse_pos);
3063 parse[parse_pos] = NULLCHAR;
3064 started = STARTED_COMMENT;
3065 savingComment = TRUE;
3067 started = STARTED_CHATTER;
3068 savingComment = FALSE;
3075 if (looking_at(buf, &i, "* s-shouts: ") ||
3076 looking_at(buf, &i, "* c-shouts: ")) {
3077 if (appData.colorize) {
3078 if (oldi > next_out) {
3079 SendToPlayer(&buf[next_out], oldi - next_out);
3082 Colorize(ColorSShout, FALSE);
3083 curColor = ColorSShout;
3086 started = STARTED_CHATTER;
3090 if (looking_at(buf, &i, "--->")) {
3095 if (looking_at(buf, &i, "* shouts: ") ||
3096 looking_at(buf, &i, "--> ")) {
3097 if (appData.colorize) {
3098 if (oldi > next_out) {
3099 SendToPlayer(&buf[next_out], oldi - next_out);
3102 Colorize(ColorShout, FALSE);
3103 curColor = ColorShout;
3106 started = STARTED_CHATTER;
3110 if (looking_at( buf, &i, "Challenge:")) {
3111 if (appData.colorize) {
3112 if (oldi > next_out) {
3113 SendToPlayer(&buf[next_out], oldi - next_out);
3116 Colorize(ColorChallenge, FALSE);
3117 curColor = ColorChallenge;
3123 if (looking_at(buf, &i, "* offers you") ||
3124 looking_at(buf, &i, "* offers to be") ||
3125 looking_at(buf, &i, "* would like to") ||
3126 looking_at(buf, &i, "* requests to") ||
3127 looking_at(buf, &i, "Your opponent offers") ||
3128 looking_at(buf, &i, "Your opponent requests")) {
3130 if (appData.colorize) {
3131 if (oldi > next_out) {
3132 SendToPlayer(&buf[next_out], oldi - next_out);
3135 Colorize(ColorRequest, FALSE);
3136 curColor = ColorRequest;
3141 if (looking_at(buf, &i, "* (*) seeking")) {
3142 if (appData.colorize) {
3143 if (oldi > next_out) {
3144 SendToPlayer(&buf[next_out], oldi - next_out);
3147 Colorize(ColorSeek, FALSE);
3148 curColor = ColorSeek;
3153 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3155 if (looking_at(buf, &i, "\\ ")) {
3156 if (prevColor != ColorNormal) {
3157 if (oldi > next_out) {
3158 SendToPlayer(&buf[next_out], oldi - next_out);
3161 Colorize(prevColor, TRUE);
3162 curColor = prevColor;
3164 if (savingComment) {
3165 parse_pos = i - oldi;
3166 memcpy(parse, &buf[oldi], parse_pos);
3167 parse[parse_pos] = NULLCHAR;
3168 started = STARTED_COMMENT;
3169 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3170 chattingPartner = savingComment - 3; // kludge to remember the box
3172 started = STARTED_CHATTER;
3177 if (looking_at(buf, &i, "Black Strength :") ||
3178 looking_at(buf, &i, "<<< style 10 board >>>") ||
3179 looking_at(buf, &i, "<10>") ||
3180 looking_at(buf, &i, "#@#")) {
3181 /* Wrong board style */
3183 SendToICS(ics_prefix);
3184 SendToICS("set style 12\n");
3185 SendToICS(ics_prefix);
3186 SendToICS("refresh\n");
3190 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3192 have_sent_ICS_logon = 1;
3196 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3197 (looking_at(buf, &i, "\n<12> ") ||
3198 looking_at(buf, &i, "<12> "))) {
3200 if (oldi > next_out) {
3201 SendToPlayer(&buf[next_out], oldi - next_out);
3204 started = STARTED_BOARD;
3209 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3210 looking_at(buf, &i, "<b1> ")) {
3211 if (oldi > next_out) {
3212 SendToPlayer(&buf[next_out], oldi - next_out);
3215 started = STARTED_HOLDINGS;
3220 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3222 /* Header for a move list -- first line */
3224 switch (ics_getting_history) {
3228 case BeginningOfGame:
3229 /* User typed "moves" or "oldmoves" while we
3230 were idle. Pretend we asked for these
3231 moves and soak them up so user can step
3232 through them and/or save them.
3235 gameMode = IcsObserving;
3238 ics_getting_history = H_GOT_UNREQ_HEADER;
3240 case EditGame: /*?*/
3241 case EditPosition: /*?*/
3242 /* Should above feature work in these modes too? */
3243 /* For now it doesn't */
3244 ics_getting_history = H_GOT_UNWANTED_HEADER;
3247 ics_getting_history = H_GOT_UNWANTED_HEADER;
3252 /* Is this the right one? */
3253 if (gameInfo.white && gameInfo.black &&
3254 strcmp(gameInfo.white, star_match[0]) == 0 &&
3255 strcmp(gameInfo.black, star_match[2]) == 0) {
3257 ics_getting_history = H_GOT_REQ_HEADER;
3260 case H_GOT_REQ_HEADER:
3261 case H_GOT_UNREQ_HEADER:
3262 case H_GOT_UNWANTED_HEADER:
3263 case H_GETTING_MOVES:
3264 /* Should not happen */
3265 DisplayError(_("Error gathering move list: two headers"), 0);
3266 ics_getting_history = H_FALSE;
3270 /* Save player ratings into gameInfo if needed */
3271 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3272 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3273 (gameInfo.whiteRating == -1 ||
3274 gameInfo.blackRating == -1)) {
3276 gameInfo.whiteRating = string_to_rating(star_match[1]);
3277 gameInfo.blackRating = string_to_rating(star_match[3]);
3278 if (appData.debugMode)
3279 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3280 gameInfo.whiteRating, gameInfo.blackRating);
3285 if (looking_at(buf, &i,
3286 "* * match, initial time: * minute*, increment: * second")) {
3287 /* Header for a move list -- second line */
3288 /* Initial board will follow if this is a wild game */
3289 if (gameInfo.event != NULL) free(gameInfo.event);
3290 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3291 gameInfo.event = StrSave(str);
3292 /* [HGM] we switched variant. Translate boards if needed. */
3293 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3297 if (looking_at(buf, &i, "Move ")) {
3298 /* Beginning of a move list */
3299 switch (ics_getting_history) {
3301 /* Normally should not happen */
3302 /* Maybe user hit reset while we were parsing */
3305 /* Happens if we are ignoring a move list that is not
3306 * the one we just requested. Common if the user
3307 * tries to observe two games without turning off
3310 case H_GETTING_MOVES:
3311 /* Should not happen */
3312 DisplayError(_("Error gathering move list: nested"), 0);
3313 ics_getting_history = H_FALSE;
3315 case H_GOT_REQ_HEADER:
3316 ics_getting_history = H_GETTING_MOVES;
3317 started = STARTED_MOVES;
3319 if (oldi > next_out) {
3320 SendToPlayer(&buf[next_out], oldi - next_out);
3323 case H_GOT_UNREQ_HEADER:
3324 ics_getting_history = H_GETTING_MOVES;
3325 started = STARTED_MOVES_NOHIDE;
3328 case H_GOT_UNWANTED_HEADER:
3329 ics_getting_history = H_FALSE;
3335 if (looking_at(buf, &i, "% ") ||
3336 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3337 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3338 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3339 soughtPending = FALSE;
3343 if(suppressKibitz) next_out = i;
3344 savingComment = FALSE;
3348 case STARTED_MOVES_NOHIDE:
3349 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3350 parse[parse_pos + i - oldi] = NULLCHAR;
3351 ParseGameHistory(parse);
3353 if (appData.zippyPlay && first.initDone) {
3354 FeedMovesToProgram(&first, forwardMostMove);
3355 if (gameMode == IcsPlayingWhite) {
3356 if (WhiteOnMove(forwardMostMove)) {
3357 if (first.sendTime) {
3358 if (first.useColors) {
3359 SendToProgram("black\n", &first);
3361 SendTimeRemaining(&first, TRUE);
3363 if (first.useColors) {
3364 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3366 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3367 first.maybeThinking = TRUE;
3369 if (first.usePlayother) {
3370 if (first.sendTime) {
3371 SendTimeRemaining(&first, TRUE);
3373 SendToProgram("playother\n", &first);
3379 } else if (gameMode == IcsPlayingBlack) {
3380 if (!WhiteOnMove(forwardMostMove)) {
3381 if (first.sendTime) {
3382 if (first.useColors) {
3383 SendToProgram("white\n", &first);
3385 SendTimeRemaining(&first, FALSE);
3387 if (first.useColors) {
3388 SendToProgram("black\n", &first);
3390 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3391 first.maybeThinking = TRUE;
3393 if (first.usePlayother) {
3394 if (first.sendTime) {
3395 SendTimeRemaining(&first, FALSE);
3397 SendToProgram("playother\n", &first);
3406 if (gameMode == IcsObserving && ics_gamenum == -1) {
3407 /* Moves came from oldmoves or moves command
3408 while we weren't doing anything else.
3410 currentMove = forwardMostMove;
3411 ClearHighlights();/*!!could figure this out*/
3412 flipView = appData.flipView;
3413 DrawPosition(TRUE, boards[currentMove]);
3414 DisplayBothClocks();
3415 snprintf(str, MSG_SIZ, "%s vs. %s",
3416 gameInfo.white, gameInfo.black);
3420 /* Moves were history of an active game */
3421 if (gameInfo.resultDetails != NULL) {
3422 free(gameInfo.resultDetails);
3423 gameInfo.resultDetails = NULL;
3426 HistorySet(parseList, backwardMostMove,
3427 forwardMostMove, currentMove-1);
3428 DisplayMove(currentMove - 1);
3429 if (started == STARTED_MOVES) next_out = i;
3430 started = STARTED_NONE;
3431 ics_getting_history = H_FALSE;
3434 case STARTED_OBSERVE:
3435 started = STARTED_NONE;
3436 SendToICS(ics_prefix);
3437 SendToICS("refresh\n");
3443 if(bookHit) { // [HGM] book: simulate book reply
3444 static char bookMove[MSG_SIZ]; // a bit generous?
3446 programStats.nodes = programStats.depth = programStats.time =
3447 programStats.score = programStats.got_only_move = 0;
3448 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3450 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3451 strcat(bookMove, bookHit);
3452 HandleMachineMove(bookMove, &first);
3457 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3458 started == STARTED_HOLDINGS ||
3459 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3460 /* Accumulate characters in move list or board */
3461 parse[parse_pos++] = buf[i];
3464 /* Start of game messages. Mostly we detect start of game
3465 when the first board image arrives. On some versions
3466 of the ICS, though, we need to do a "refresh" after starting
3467 to observe in order to get the current board right away. */
3468 if (looking_at(buf, &i, "Adding game * to observation list")) {
3469 started = STARTED_OBSERVE;
3473 /* Handle auto-observe */
3474 if (appData.autoObserve &&
3475 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3476 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3478 /* Choose the player that was highlighted, if any. */
3479 if (star_match[0][0] == '\033' ||
3480 star_match[1][0] != '\033') {
3481 player = star_match[0];
3483 player = star_match[2];
3485 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3486 ics_prefix, StripHighlightAndTitle(player));
3489 /* Save ratings from notify string */
3490 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3491 player1Rating = string_to_rating(star_match[1]);
3492 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3493 player2Rating = string_to_rating(star_match[3]);
3495 if (appData.debugMode)
3497 "Ratings from 'Game notification:' %s %d, %s %d\n",
3498 player1Name, player1Rating,
3499 player2Name, player2Rating);
3504 /* Deal with automatic examine mode after a game,
3505 and with IcsObserving -> IcsExamining transition */
3506 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3507 looking_at(buf, &i, "has made you an examiner of game *")) {
3509 int gamenum = atoi(star_match[0]);
3510 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3511 gamenum == ics_gamenum) {
3512 /* We were already playing or observing this game;
3513 no need to refetch history */
3514 gameMode = IcsExamining;
3516 pauseExamForwardMostMove = forwardMostMove;
3517 } else if (currentMove < forwardMostMove) {
3518 ForwardInner(forwardMostMove);
3521 /* I don't think this case really can happen */
3522 SendToICS(ics_prefix);
3523 SendToICS("refresh\n");
3528 /* Error messages */
3529 // if (ics_user_moved) {
3530 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3531 if (looking_at(buf, &i, "Illegal move") ||
3532 looking_at(buf, &i, "Not a legal move") ||
3533 looking_at(buf, &i, "Your king is in check") ||
3534 looking_at(buf, &i, "It isn't your turn") ||
3535 looking_at(buf, &i, "It is not your move")) {
3537 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3538 currentMove = forwardMostMove-1;
3539 DisplayMove(currentMove - 1); /* before DMError */
3540 DrawPosition(FALSE, boards[currentMove]);
3541 SwitchClocks(forwardMostMove-1); // [HGM] race
3542 DisplayBothClocks();
3544 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3550 if (looking_at(buf, &i, "still have time") ||
3551 looking_at(buf, &i, "not out of time") ||
3552 looking_at(buf, &i, "either player is out of time") ||
3553 looking_at(buf, &i, "has timeseal; checking")) {
3554 /* We must have called his flag a little too soon */
3555 whiteFlag = blackFlag = FALSE;
3559 if (looking_at(buf, &i, "added * seconds to") ||
3560 looking_at(buf, &i, "seconds were added to")) {
3561 /* Update the clocks */
3562 SendToICS(ics_prefix);
3563 SendToICS("refresh\n");
3567 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3568 ics_clock_paused = TRUE;
3573 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3574 ics_clock_paused = FALSE;
3579 /* Grab player ratings from the Creating: message.
3580 Note we have to check for the special case when
3581 the ICS inserts things like [white] or [black]. */
3582 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3583 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3585 0 player 1 name (not necessarily white)
3587 2 empty, white, or black (IGNORED)
3588 3 player 2 name (not necessarily black)
3591 The names/ratings are sorted out when the game
3592 actually starts (below).
3594 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3595 player1Rating = string_to_rating(star_match[1]);
3596 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3597 player2Rating = string_to_rating(star_match[4]);
3599 if (appData.debugMode)
3601 "Ratings from 'Creating:' %s %d, %s %d\n",
3602 player1Name, player1Rating,
3603 player2Name, player2Rating);
3608 /* Improved generic start/end-of-game messages */
3609 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3610 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3611 /* If tkind == 0: */
3612 /* star_match[0] is the game number */
3613 /* [1] is the white player's name */
3614 /* [2] is the black player's name */
3615 /* For end-of-game: */
3616 /* [3] is the reason for the game end */
3617 /* [4] is a PGN end game-token, preceded by " " */
3618 /* For start-of-game: */
3619 /* [3] begins with "Creating" or "Continuing" */
3620 /* [4] is " *" or empty (don't care). */
3621 int gamenum = atoi(star_match[0]);
3622 char *whitename, *blackname, *why, *endtoken;
3623 ChessMove endtype = EndOfFile;
3626 whitename = star_match[1];
3627 blackname = star_match[2];
3628 why = star_match[3];
3629 endtoken = star_match[4];
3631 whitename = star_match[1];
3632 blackname = star_match[3];
3633 why = star_match[5];
3634 endtoken = star_match[6];
3637 /* Game start messages */
3638 if (strncmp(why, "Creating ", 9) == 0 ||
3639 strncmp(why, "Continuing ", 11) == 0) {
3640 gs_gamenum = gamenum;
3641 safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3642 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3644 if (appData.zippyPlay) {
3645 ZippyGameStart(whitename, blackname);
3648 partnerBoardValid = FALSE; // [HGM] bughouse
3652 /* Game end messages */
3653 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3654 ics_gamenum != gamenum) {
3657 while (endtoken[0] == ' ') endtoken++;
3658 switch (endtoken[0]) {
3661 endtype = GameUnfinished;
3664 endtype = BlackWins;
3667 if (endtoken[1] == '/')
3668 endtype = GameIsDrawn;
3670 endtype = WhiteWins;
3673 GameEnds(endtype, why, GE_ICS);
3675 if (appData.zippyPlay && first.initDone) {
3676 ZippyGameEnd(endtype, why);
3677 if (first.pr == NULL) {
3678 /* Start the next process early so that we'll
3679 be ready for the next challenge */
3680 StartChessProgram(&first);
3682 /* Send "new" early, in case this command takes
3683 a long time to finish, so that we'll be ready
3684 for the next challenge. */
3685 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3689 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3693 if (looking_at(buf, &i, "Removing game * from observation") ||
3694 looking_at(buf, &i, "no longer observing game *") ||
3695 looking_at(buf, &i, "Game * (*) has no examiners")) {
3696 if (gameMode == IcsObserving &&
3697 atoi(star_match[0]) == ics_gamenum)
3699 /* icsEngineAnalyze */
3700 if (appData.icsEngineAnalyze) {
3707 ics_user_moved = FALSE;
3712 if (looking_at(buf, &i, "no longer examining game *")) {
3713 if (gameMode == IcsExamining &&
3714 atoi(star_match[0]) == ics_gamenum)
3718 ics_user_moved = FALSE;
3723 /* Advance leftover_start past any newlines we find,
3724 so only partial lines can get reparsed */
3725 if (looking_at(buf, &i, "\n")) {
3726 prevColor = curColor;
3727 if (curColor != ColorNormal) {
3728 if (oldi > next_out) {
3729 SendToPlayer(&buf[next_out], oldi - next_out);
3732 Colorize(ColorNormal, FALSE);
3733 curColor = ColorNormal;
3735 if (started == STARTED_BOARD) {
3736 started = STARTED_NONE;
3737 parse[parse_pos] = NULLCHAR;
3738 ParseBoard12(parse);
3741 /* Send premove here */
3742 if (appData.premove) {
3744 if (currentMove == 0 &&
3745 gameMode == IcsPlayingWhite &&
3746 appData.premoveWhite) {
3747 snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3748 if (appData.debugMode)
3749 fprintf(debugFP, "Sending premove:\n");
3751 } else if (currentMove == 1 &&
3752 gameMode == IcsPlayingBlack &&
3753 appData.premoveBlack) {
3754 snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3755 if (appData.debugMode)
3756 fprintf(debugFP, "Sending premove:\n");
3758 } else if (gotPremove) {
3760 ClearPremoveHighlights();
3761 if (appData.debugMode)
3762 fprintf(debugFP, "Sending premove:\n");
3763 UserMoveEvent(premoveFromX, premoveFromY,
3764 premoveToX, premoveToY,
3769 /* Usually suppress following prompt */
3770 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3771 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3772 if (looking_at(buf, &i, "*% ")) {
3773 savingComment = FALSE;
3778 } else if (started == STARTED_HOLDINGS) {
3780 char new_piece[MSG_SIZ];
3781 started = STARTED_NONE;
3782 parse[parse_pos] = NULLCHAR;
3783 if (appData.debugMode)
3784 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3785 parse, currentMove);
3786 if (sscanf(parse, " game %d", &gamenum) == 1) {
3787 if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3788 if (gameInfo.variant == VariantNormal) {
3789 /* [HGM] We seem to switch variant during a game!
3790 * Presumably no holdings were displayed, so we have
3791 * to move the position two files to the right to
3792 * create room for them!
3794 VariantClass newVariant;
3795 switch(gameInfo.boardWidth) { // base guess on board width
3796 case 9: newVariant = VariantShogi; break;
3797 case 10: newVariant = VariantGreat; break;
3798 default: newVariant = VariantCrazyhouse; break;
3800 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3801 /* Get a move list just to see the header, which
3802 will tell us whether this is really bug or zh */
3803 if (ics_getting_history == H_FALSE) {
3804 ics_getting_history = H_REQUESTED;
3805 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
3809 new_piece[0] = NULLCHAR;
3810 sscanf(parse, "game %d white [%s black [%s <- %s",
3811 &gamenum, white_holding, black_holding,
3813 white_holding[strlen(white_holding)-1] = NULLCHAR;
3814 black_holding[strlen(black_holding)-1] = NULLCHAR;
3815 /* [HGM] copy holdings to board holdings area */
3816 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3817 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3818 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3820 if (appData.zippyPlay && first.initDone) {
3821 ZippyHoldings(white_holding, black_holding,
3825 if (tinyLayout || smallLayout) {
3826 char wh[16], bh[16];
3827 PackHolding(wh, white_holding);
3828 PackHolding(bh, black_holding);
3829 snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
3830 gameInfo.white, gameInfo.black);
3832 snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
3833 gameInfo.white, white_holding,
3834 gameInfo.black, black_holding);
3836 if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...