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, 2012, 2013 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 int flock(int f, int code);
75 #include <sys/types.h>
84 #else /* not STDC_HEADERS */
87 # else /* not HAVE_STRING_H */
89 # endif /* not HAVE_STRING_H */
90 #endif /* not STDC_HEADERS */
93 # include <sys/fcntl.h>
94 #else /* not HAVE_SYS_FCNTL_H */
97 # endif /* HAVE_FCNTL_H */
98 #endif /* not HAVE_SYS_FCNTL_H */
100 #if TIME_WITH_SYS_TIME
101 # include <sys/time.h>
105 # include <sys/time.h>
111 #if defined(_amigados) && !defined(__GNUC__)
116 extern int gettimeofday(struct timeval *, struct timezone *);
124 #include "frontend.h"
131 #include "backendz.h"
132 #include "evalgraph.h"
136 # define _(s) gettext (s)
137 # define N_(s) gettext_noop (s)
138 # define T_(s) gettext(s)
151 int establish P((void));
152 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
153 char *buf, int count, int error));
154 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
155 char *buf, int count, int error));
156 void SendToICS P((char *s));
157 void SendToICSDelayed P((char *s, long msdelay));
158 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
159 void HandleMachineMove P((char *message, ChessProgramState *cps));
160 int AutoPlayOneMove P((void));
161 int LoadGameOneMove P((ChessMove readAhead));
162 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
163 int LoadPositionFromFile P((char *filename, int n, char *title));
164 int SavePositionToFile P((char *filename));
165 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
166 void ShowMove P((int fromX, int fromY, int toX, int toY));
167 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
168 /*char*/int promoChar));
169 void BackwardInner P((int target));
170 void ForwardInner P((int target));
171 int Adjudicate P((ChessProgramState *cps));
172 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
173 void EditPositionDone P((Boolean fakeRights));
174 void PrintOpponents P((FILE *fp));
175 void PrintPosition P((FILE *fp, int move));
176 void StartChessProgram P((ChessProgramState *cps));
177 void SendToProgram P((char *message, ChessProgramState *cps));
178 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
179 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
180 char *buf, int count, int error));
181 void SendTimeControl P((ChessProgramState *cps,
182 int mps, long tc, int inc, int sd, int st));
183 char *TimeControlTagValue P((void));
184 void Attention P((ChessProgramState *cps));
185 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
186 int ResurrectChessProgram P((void));
187 void DisplayComment P((int moveNumber, char *text));
188 void DisplayMove P((int moveNumber));
190 void ParseGameHistory P((char *game));
191 void ParseBoard12 P((char *string));
192 void KeepAlive P((void));
193 void StartClocks P((void));
194 void SwitchClocks P((int nr));
195 void StopClocks P((void));
196 void ResetClocks P((void));
197 char *PGNDate P((void));
198 void SetGameInfo P((void));
199 int RegisterMove P((void));
200 void MakeRegisteredMove P((void));
201 void TruncateGame P((void));
202 int looking_at P((char *, int *, char *));
203 void CopyPlayerNameIntoFileName P((char **, char *));
204 char *SavePart P((char *));
205 int SaveGameOldStyle P((FILE *));
206 int SaveGamePGN P((FILE *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
220 void NextMatchGame P((void));
221 int NextTourneyGame P((int nr, int *swap));
222 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
223 FILE *WriteTourneyFile P((char *results, FILE *f));
224 void DisplayTwoMachinesTitle P(());
225 static void ExcludeClick P((int index));
226 void ToggleSecond P((void));
227 void PauseEngine P((ChessProgramState *cps));
228 static int NonStandardBoardSize P((void));
231 extern void ConsoleCreate();
234 ChessProgramState *WhitePlayer();
235 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
236 int VerifyDisplayMode P(());
238 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
239 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
240 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
241 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
242 void ics_update_width P((int new_width));
243 extern char installDir[MSG_SIZ];
244 VariantClass startVariant; /* [HGM] nicks: initial variant */
247 extern int tinyLayout, smallLayout;
248 ChessProgramStats programStats;
249 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
251 static int exiting = 0; /* [HGM] moved to top */
252 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
253 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
254 Board partnerBoard; /* [HGM] bughouse: for peeking at partner game */
255 int partnerHighlight[2];
256 Boolean partnerBoardValid = 0;
257 char partnerStatus[MSG_SIZ];
259 Boolean originalFlip;
260 Boolean twoBoards = 0;
261 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
262 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
263 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
264 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
265 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
266 int opponentKibitzes;
267 int lastSavedGame; /* [HGM] save: ID of game */
268 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
269 extern int chatCount;
271 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
272 char legal[BOARD_RANKS][BOARD_FILES]; /* [HGM] legal target squares */
273 char lastMsg[MSG_SIZ];
274 ChessSquare pieceSweep = EmptySquare;
275 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
276 int promoDefaultAltered;
277 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
279 /* States for ics_getting_history */
281 #define H_REQUESTED 1
282 #define H_GOT_REQ_HEADER 2
283 #define H_GOT_UNREQ_HEADER 3
284 #define H_GETTING_MOVES 4
285 #define H_GOT_UNWANTED_HEADER 5
287 /* whosays values for GameEnds */
296 /* Maximum number of games in a cmail message */
297 #define CMAIL_MAX_GAMES 20
299 /* Different types of move when calling RegisterMove */
301 #define CMAIL_RESIGN 1
303 #define CMAIL_ACCEPT 3
305 /* Different types of result to remember for each game */
306 #define CMAIL_NOT_RESULT 0
307 #define CMAIL_OLD_RESULT 1
308 #define CMAIL_NEW_RESULT 2
310 /* Telnet protocol constants */
321 safeStrCpy (char *dst, const char *src, size_t count)
324 assert( dst != NULL );
325 assert( src != NULL );
328 for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
329 if( i == count && dst[count-1] != NULLCHAR)
331 dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
332 if(appData.debugMode)
333 fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
339 /* Some compiler can't cast u64 to double
340 * This function do the job for us:
342 * We use the highest bit for cast, this only
343 * works if the highest bit is not
344 * in use (This should not happen)
346 * We used this for all compiler
349 u64ToDouble (u64 value)
352 u64 tmp = value & u64Const(0x7fffffffffffffff);
353 r = (double)(s64)tmp;
354 if (value & u64Const(0x8000000000000000))
355 r += 9.2233720368547758080e18; /* 2^63 */
359 /* Fake up flags for now, as we aren't keeping track of castling
360 availability yet. [HGM] Change of logic: the flag now only
361 indicates the type of castlings allowed by the rule of the game.
362 The actual rights themselves are maintained in the array
363 castlingRights, as part of the game history, and are not probed
369 int flags = F_ALL_CASTLE_OK;
370 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
371 switch (gameInfo.variant) {
373 flags &= ~F_ALL_CASTLE_OK;
374 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
375 flags |= F_IGNORE_CHECK;
377 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
380 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
382 case VariantKriegspiel:
383 flags |= F_KRIEGSPIEL_CAPTURE;
385 case VariantCapaRandom:
386 case VariantFischeRandom:
387 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
388 case VariantNoCastle:
389 case VariantShatranj:
394 flags &= ~F_ALL_CASTLE_OK;
402 FILE *gameFileFP, *debugFP, *serverFP;
403 char *currentDebugFile; // [HGM] debug split: to remember name
406 [AS] Note: sometimes, the sscanf() function is used to parse the input
407 into a fixed-size buffer. Because of this, we must be prepared to
408 receive strings as long as the size of the input buffer, which is currently
409 set to 4K for Windows and 8K for the rest.
410 So, we must either allocate sufficiently large buffers here, or
411 reduce the size of the input buffer in the input reading part.
414 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
415 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
416 char thinkOutput1[MSG_SIZ*10];
418 ChessProgramState first, second, pairing;
420 /* premove variables */
423 int premoveFromX = 0;
424 int premoveFromY = 0;
425 int premovePromoChar = 0;
427 Boolean alarmSounded;
428 /* end premove variables */
430 char *ics_prefix = "$";
431 enum ICS_TYPE ics_type = ICS_GENERIC;
433 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
434 int pauseExamForwardMostMove = 0;
435 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
436 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
437 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
438 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
439 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
440 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
441 int whiteFlag = FALSE, blackFlag = FALSE;
442 int userOfferedDraw = FALSE;
443 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
444 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
445 int cmailMoveType[CMAIL_MAX_GAMES];
446 long ics_clock_paused = 0;
447 ProcRef icsPR = NoProc, cmailPR = NoProc;
448 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
449 GameMode gameMode = BeginningOfGame;
450 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
451 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
452 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
453 int hiddenThinkOutputState = 0; /* [AS] */
454 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
455 int adjudicateLossPlies = 6;
456 char white_holding[64], black_holding[64];
457 TimeMark lastNodeCountTime;
458 long lastNodeCount=0;
459 int shiftKey, controlKey; // [HGM] set by mouse handler
461 int have_sent_ICS_logon = 0;
463 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
464 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
465 Boolean adjustedClock;
466 long timeControl_2; /* [AS] Allow separate time controls */
467 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
468 long timeRemaining[2][MAX_MOVES];
469 int matchGame = 0, nextGame = 0, roundNr = 0;
470 Boolean waitingForGame = FALSE, startingEngine = FALSE;
471 TimeMark programStartTime, pauseStart;
472 char ics_handle[MSG_SIZ];
473 int have_set_title = 0;
475 /* animateTraining preserves the state of appData.animate
476 * when Training mode is activated. This allows the
477 * response to be animated when appData.animate == TRUE and
478 * appData.animateDragging == TRUE.
480 Boolean animateTraining;
486 Board boards[MAX_MOVES];
487 /* [HGM] Following 7 needed for accurate legality tests: */
488 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
489 signed char initialRights[BOARD_FILES];
490 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
491 int initialRulePlies, FENrulePlies;
492 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
494 Boolean shuffleOpenings;
495 int mute; // mute all sounds
497 // [HGM] vari: next 12 to save and restore variations
498 #define MAX_VARIATIONS 10
499 int framePtr = MAX_MOVES-1; // points to free stack entry
501 int savedFirst[MAX_VARIATIONS];
502 int savedLast[MAX_VARIATIONS];
503 int savedFramePtr[MAX_VARIATIONS];
504 char *savedDetails[MAX_VARIATIONS];
505 ChessMove savedResult[MAX_VARIATIONS];
507 void PushTail P((int firstMove, int lastMove));
508 Boolean PopTail P((Boolean annotate));
509 void PushInner P((int firstMove, int lastMove));
510 void PopInner P((Boolean annotate));
511 void CleanupTail P((void));
513 ChessSquare FIDEArray[2][BOARD_FILES] = {
514 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
515 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
516 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
517 BlackKing, BlackBishop, BlackKnight, BlackRook }
520 ChessSquare twoKingsArray[2][BOARD_FILES] = {
521 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
522 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
523 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
524 BlackKing, BlackKing, BlackKnight, BlackRook }
527 ChessSquare KnightmateArray[2][BOARD_FILES] = {
528 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
529 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
530 { BlackRook, BlackMan, BlackBishop, BlackQueen,
531 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
534 ChessSquare SpartanArray[2][BOARD_FILES] = {
535 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
536 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
537 { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
538 BlackDragon, BlackKing, BlackAngel, BlackAlfil }
541 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
542 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
543 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
544 { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
545 BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
548 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
549 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
550 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
551 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
552 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
555 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
556 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
557 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
558 { BlackRook, BlackKnight, BlackMan, BlackFerz,
559 BlackKing, BlackMan, BlackKnight, BlackRook }
562 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
563 { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
564 WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
565 { BlackRook, BlackKnight, BlackMan, BlackFerz,
566 BlackKing, BlackMan, BlackKnight, BlackRook }
570 #if (BOARD_FILES>=10)
571 ChessSquare ShogiArray[2][BOARD_FILES] = {
572 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
573 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
574 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
575 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
578 ChessSquare XiangqiArray[2][BOARD_FILES] = {
579 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
580 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
581 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
582 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
585 ChessSquare CapablancaArray[2][BOARD_FILES] = {
586 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
587 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
588 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
589 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
592 ChessSquare GreatArray[2][BOARD_FILES] = {
593 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
594 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
595 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
596 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
599 ChessSquare JanusArray[2][BOARD_FILES] = {
600 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
601 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
602 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
603 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
606 ChessSquare GrandArray[2][BOARD_FILES] = {
607 { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
608 WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
609 { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
610 BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
614 ChessSquare GothicArray[2][BOARD_FILES] = {
615 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
616 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
617 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
618 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
621 #define GothicArray CapablancaArray
625 ChessSquare FalconArray[2][BOARD_FILES] = {
626 { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
627 WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
628 { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
629 BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
632 #define FalconArray CapablancaArray
635 #else // !(BOARD_FILES>=10)
636 #define XiangqiPosition FIDEArray
637 #define CapablancaArray FIDEArray
638 #define GothicArray FIDEArray
639 #define GreatArray FIDEArray
640 #endif // !(BOARD_FILES>=10)
642 #if (BOARD_FILES>=12)
643 ChessSquare CourierArray[2][BOARD_FILES] = {
644 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
645 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
646 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
647 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
649 #else // !(BOARD_FILES>=12)
650 #define CourierArray CapablancaArray
651 #endif // !(BOARD_FILES>=12)
654 Board initialPosition;
657 /* Convert str to a rating. Checks for special cases of "----",
659 "++++", etc. Also strips ()'s */
661 string_to_rating (char *str)
663 while(*str && !isdigit(*str)) ++str;
665 return 0; /* One of the special "no rating" cases */
673 /* Init programStats */
674 programStats.movelist[0] = 0;
675 programStats.depth = 0;
676 programStats.nr_moves = 0;
677 programStats.moves_left = 0;
678 programStats.nodes = 0;
679 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
680 programStats.score = 0;
681 programStats.got_only_move = 0;
682 programStats.got_fail = 0;
683 programStats.line_is_book = 0;
688 { // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
689 if (appData.firstPlaysBlack) {
690 first.twoMachinesColor = "black\n";
691 second.twoMachinesColor = "white\n";
693 first.twoMachinesColor = "white\n";
694 second.twoMachinesColor = "black\n";
697 first.other = &second;
698 second.other = &first;
701 if(appData.timeOddsMode) {
702 norm = appData.timeOdds[0];
703 if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
705 first.timeOdds = appData.timeOdds[0]/norm;
706 second.timeOdds = appData.timeOdds[1]/norm;
709 if(programVersion) free(programVersion);
710 if (appData.noChessProgram) {
711 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
712 sprintf(programVersion, "%s", PACKAGE_STRING);
714 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
715 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
716 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
721 UnloadEngine (ChessProgramState *cps)
723 /* Kill off first chess program */
724 if (cps->isr != NULL)
725 RemoveInputSource(cps->isr);
728 if (cps->pr != NoProc) {
730 DoSleep( appData.delayBeforeQuit );
731 SendToProgram("quit\n", cps);
732 DoSleep( appData.delayAfterQuit );
733 DestroyChildProcess(cps->pr, cps->useSigterm);
736 if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
740 ClearOptions (ChessProgramState *cps)
743 cps->nrOptions = cps->comboCnt = 0;
744 for(i=0; i<MAX_OPTIONS; i++) {
745 cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
746 cps->option[i].textValue = 0;
750 char *engineNames[] = {
751 /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
752 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
754 /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
755 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
760 InitEngine (ChessProgramState *cps, int n)
761 { // [HGM] all engine initialiation put in a function that does one engine
765 cps->which = engineNames[n];
766 cps->maybeThinking = FALSE;
770 cps->sendDrawOffers = 1;
772 cps->program = appData.chessProgram[n];
773 cps->host = appData.host[n];
774 cps->dir = appData.directory[n];
775 cps->initString = appData.engInitString[n];
776 cps->computerString = appData.computerString[n];
777 cps->useSigint = TRUE;
778 cps->useSigterm = TRUE;
779 cps->reuse = appData.reuse[n];
780 cps->nps = appData.NPS[n]; // [HGM] nps: copy nodes per second
781 cps->useSetboard = FALSE;
783 cps->usePing = FALSE;
786 cps->usePlayother = FALSE;
787 cps->useColors = TRUE;
788 cps->useUsermove = FALSE;
789 cps->sendICS = FALSE;
790 cps->sendName = appData.icsActive;
791 cps->sdKludge = FALSE;
792 cps->stKludge = FALSE;
793 if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
794 TidyProgramName(cps->program, cps->host, cps->tidy);
796 ASSIGN(cps->variants, appData.variant);
797 cps->analysisSupport = 2; /* detect */
798 cps->analyzing = FALSE;
799 cps->initDone = FALSE;
802 /* New features added by Tord: */
803 cps->useFEN960 = FALSE;
804 cps->useOOCastle = TRUE;
805 /* End of new features added by Tord. */
806 cps->fenOverride = appData.fenOverride[n];
808 /* [HGM] time odds: set factor for each machine */
809 cps->timeOdds = appData.timeOdds[n];
811 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
812 cps->accumulateTC = appData.accumulateTC[n];
813 cps->maxNrOfSessions = 1;
818 cps->supportsNPS = UNKNOWN;
819 cps->memSize = FALSE;
820 cps->maxCores = FALSE;
821 ASSIGN(cps->egtFormats, "");
824 cps->optionSettings = appData.engOptions[n];
826 cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
827 cps->isUCI = appData.isUCI[n]; /* [AS] */
828 cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
831 if (appData.protocolVersion[n] > PROTOVER
832 || appData.protocolVersion[n] < 1)
837 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
838 appData.protocolVersion[n]);
839 if( (len >= MSG_SIZ) && appData.debugMode )
840 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
842 DisplayFatalError(buf, 0, 2);
846 cps->protocolVersion = appData.protocolVersion[n];
849 InitEngineUCI( installDir, cps ); // [HGM] moved here from winboard.c, to make available in xboard
850 ParseFeatures(appData.featureDefaults, cps);
853 ChessProgramState *savCps;
861 if(WaitForEngine(savCps, LoadEngine)) return;
862 CommonEngineInit(); // recalculate time odds
863 if(gameInfo.variant != StringToVariant(appData.variant)) {
864 // we changed variant when loading the engine; this forces us to reset
865 Reset(TRUE, savCps != &first);
866 oldMode = BeginningOfGame; // to prevent restoring old mode
868 InitChessProgram(savCps, FALSE);
869 if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
870 DisplayMessage("", "");
871 if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
872 for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
875 if(oldMode == AnalyzeMode) AnalyzeModeEvent();
879 ReplaceEngine (ChessProgramState *cps, int n)
881 oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
883 if(oldMode != BeginningOfGame) EditGameEvent();
886 appData.noChessProgram = FALSE;
887 appData.clockMode = TRUE;
890 if(n) return; // only startup first engine immediately; second can wait
891 savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
895 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
896 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
898 static char resetOptions[] =
899 "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
900 "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
901 "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
902 "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
905 FloatToFront(char **list, char *engineLine)
907 char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
909 if(appData.recentEngines <= 0) return;
910 TidyProgramName(engineLine, "localhost", tidy+1);
911 tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
912 strncpy(buf+1, *list, MSG_SIZ-50);
913 if(p = strstr(buf, tidy)) { // tidy name appears in list
914 q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
915 while(*p++ = *++q); // squeeze out
917 strcat(tidy, buf+1); // put list behind tidy name
918 p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
919 if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
920 ASSIGN(*list, tidy+1);
923 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
926 Load (ChessProgramState *cps, int i)
928 char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
929 if(engineLine && engineLine[0]) { // an engine was selected from the combo box
930 snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
931 SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
932 ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
933 FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
934 appData.firstProtocolVersion = PROTOVER;
935 ParseArgsFromString(buf);
937 ReplaceEngine(cps, i);
938 FloatToFront(&appData.recentEngineList, engineLine);
942 while(q = strchr(p, SLASH)) p = q+1;
943 if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
944 if(engineDir[0] != NULLCHAR) {
945 ASSIGN(appData.directory[i], engineDir); p = engineName;
946 } else if(p != engineName) { // derive directory from engine path, when not given
948 ASSIGN(appData.directory[i], engineName);
950 if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
951 } else { ASSIGN(appData.directory[i], "."); }
952 jar = (strstr(p, ".jar") == p + strlen(p) - 4);
954 if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
955 snprintf(command, MSG_SIZ, "%s %s", p, params);
958 if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
959 ASSIGN(appData.chessProgram[i], p);
960 appData.isUCI[i] = isUCI;
961 appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
962 appData.hasOwnBookUCI[i] = hasBook;
963 if(!nickName[0]) useNick = FALSE;
964 if(useNick) ASSIGN(appData.pgnName[i], nickName);
968 q = firstChessProgramNames;
969 if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
970 quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
971 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
972 quote, p, quote, appData.directory[i],
973 useNick ? " -fn \"" : "",
974 useNick ? nickName : "",
976 v1 ? " -firstProtocolVersion 1" : "",
977 hasBook ? "" : " -fNoOwnBookUCI",
978 isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
979 storeVariant ? " -variant " : "",
980 storeVariant ? VariantName(gameInfo.variant) : "");
981 if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
982 firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
983 if(insert != q) insert[-1] = NULLCHAR;
984 snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
986 FloatToFront(&appData.recentEngineList, buf);
988 ReplaceEngine(cps, i);
994 int matched, min, sec;
996 * Parse timeControl resource
998 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
999 appData.movesPerSession)) {
1001 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1002 DisplayFatalError(buf, 0, 2);
1006 * Parse searchTime resource
1008 if (*appData.searchTime != NULLCHAR) {
1009 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1011 searchTime = min * 60;
1012 } else if (matched == 2) {
1013 searchTime = min * 60 + sec;
1016 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1017 DisplayFatalError(buf, 0, 2);
1026 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1027 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1029 GetTimeMark(&programStartTime);
1030 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1031 appData.seedBase = random() + (random()<<15);
1032 pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1034 ClearProgramStats();
1035 programStats.ok_to_send = 1;
1036 programStats.seen_stat = 0;
1039 * Initialize game list
1045 * Internet chess server status
1047 if (appData.icsActive) {
1048 appData.matchMode = FALSE;
1049 appData.matchGames = 0;
1051 appData.noChessProgram = !appData.zippyPlay;
1053 appData.zippyPlay = FALSE;
1054 appData.zippyTalk = FALSE;
1055 appData.noChessProgram = TRUE;
1057 if (*appData.icsHelper != NULLCHAR) {
1058 appData.useTelnet = TRUE;
1059 appData.telnetProgram = appData.icsHelper;
1062 appData.zippyTalk = appData.zippyPlay = FALSE;
1065 /* [AS] Initialize pv info list [HGM] and game state */
1069 for( i=0; i<=framePtr; i++ ) {
1070 pvInfoList[i].depth = -1;
1071 boards[i][EP_STATUS] = EP_NONE;
1072 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1078 /* [AS] Adjudication threshold */
1079 adjudicateLossThreshold = appData.adjudicateLossThreshold;
1081 InitEngine(&first, 0);
1082 InitEngine(&second, 1);
1085 pairing.which = "pairing"; // pairing engine
1086 pairing.pr = NoProc;
1088 pairing.program = appData.pairingEngine;
1089 pairing.host = "localhost";
1092 if (appData.icsActive) {
1093 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
1094 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1095 appData.clockMode = FALSE;
1096 first.sendTime = second.sendTime = 0;
1100 /* Override some settings from environment variables, for backward
1101 compatibility. Unfortunately it's not feasible to have the env
1102 vars just set defaults, at least in xboard. Ugh.
1104 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1109 if (!appData.icsActive) {
1113 /* Check for variants that are supported only in ICS mode,
1114 or not at all. Some that are accepted here nevertheless
1115 have bugs; see comments below.
1117 VariantClass variant = StringToVariant(appData.variant);
1119 case VariantBughouse: /* need four players and two boards */
1120 case VariantKriegspiel: /* need to hide pieces and move details */
1121 /* case VariantFischeRandom: (Fabien: moved below) */
1122 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1123 if( (len >= MSG_SIZ) && appData.debugMode )
1124 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1126 DisplayFatalError(buf, 0, 2);
1129 case VariantUnknown:
1130 case VariantLoadable:
1140 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1141 if( (len >= MSG_SIZ) && appData.debugMode )
1142 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1144 DisplayFatalError(buf, 0, 2);
1147 case VariantXiangqi: /* [HGM] repetition rules not implemented */
1148 case VariantFairy: /* [HGM] TestLegality definitely off! */
1149 case VariantGothic: /* [HGM] should work */
1150 case VariantCapablanca: /* [HGM] should work */
1151 case VariantCourier: /* [HGM] initial forced moves not implemented */
1152 case VariantShogi: /* [HGM] could still mate with pawn drop */
1153 case VariantKnightmate: /* [HGM] should work */
1154 case VariantCylinder: /* [HGM] untested */
1155 case VariantFalcon: /* [HGM] untested */
1156 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1157 offboard interposition not understood */
1158 case VariantNormal: /* definitely works! */
1159 case VariantWildCastle: /* pieces not automatically shuffled */
1160 case VariantNoCastle: /* pieces not automatically shuffled */
1161 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1162 case VariantLosers: /* should work except for win condition,
1163 and doesn't know captures are mandatory */
1164 case VariantSuicide: /* should work except for win condition,
1165 and doesn't know captures are mandatory */
1166 case VariantGiveaway: /* should work except for win condition,
1167 and doesn't know captures are mandatory */
1168 case VariantTwoKings: /* should work */
1169 case VariantAtomic: /* should work except for win condition */
1170 case Variant3Check: /* should work except for win condition */
1171 case VariantShatranj: /* should work except for all win conditions */
1172 case VariantMakruk: /* should work except for draw countdown */
1173 case VariantASEAN : /* should work except for draw countdown */
1174 case VariantBerolina: /* might work if TestLegality is off */
1175 case VariantCapaRandom: /* should work */
1176 case VariantJanus: /* should work */
1177 case VariantSuper: /* experimental */
1178 case VariantGreat: /* experimental, requires legality testing to be off */
1179 case VariantSChess: /* S-Chess, should work */
1180 case VariantGrand: /* should work */
1181 case VariantSpartan: /* should work */
1189 NextIntegerFromString (char ** str, long * value)
1194 while( *s == ' ' || *s == '\t' ) {
1200 if( *s >= '0' && *s <= '9' ) {
1201 while( *s >= '0' && *s <= '9' ) {
1202 *value = *value * 10 + (*s - '0');
1215 NextTimeControlFromString (char ** str, long * value)
1218 int result = NextIntegerFromString( str, &temp );
1221 *value = temp * 60; /* Minutes */
1222 if( **str == ':' ) {
1224 result = NextIntegerFromString( str, &temp );
1225 *value += temp; /* Seconds */
1233 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1234 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1235 int result = -1, type = 0; long temp, temp2;
1237 if(**str != ':') return -1; // old params remain in force!
1239 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1240 if( NextIntegerFromString( str, &temp ) ) return -1;
1241 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1244 /* time only: incremental or sudden-death time control */
1245 if(**str == '+') { /* increment follows; read it */
1247 if(**str == '!') type = *(*str)++; // Bronstein TC
1248 if(result = NextIntegerFromString( str, &temp2)) return -1;
1249 *inc = temp2 * 1000;
1250 if(**str == '.') { // read fraction of increment
1251 char *start = ++(*str);
1252 if(result = NextIntegerFromString( str, &temp2)) return -1;
1254 while(start++ < *str) temp2 /= 10;
1258 *moves = 0; *tc = temp * 1000; *incType = type;
1262 (*str)++; /* classical time control */
1263 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1275 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1276 { /* [HGM] get time to add from the multi-session time-control string */
1277 int incType, moves=1; /* kludge to force reading of first session */
1278 long time, increment;
1281 if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1283 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1284 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1285 if(movenr == -1) return time; /* last move before new session */
1286 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1287 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1288 if(!moves) return increment; /* current session is incremental */
1289 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1290 } while(movenr >= -1); /* try again for next session */
1292 return 0; // no new time quota on this move
1296 ParseTimeControl (char *tc, float ti, int mps)
1300 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1303 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1304 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1305 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1309 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1311 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1314 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1316 snprintf(buf, MSG_SIZ, ":%s", mytc);
1318 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1320 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1325 /* Parse second time control */
1328 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1336 timeControl_2 = tc2 * 1000;
1346 timeControl = tc1 * 1000;
1349 timeIncrement = ti * 1000; /* convert to ms */
1350 movesPerSession = 0;
1353 movesPerSession = mps;
1361 if (appData.debugMode) {
1362 # ifdef __GIT_VERSION
1363 fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1365 fprintf(debugFP, "Version: %s\n", programVersion);
1368 ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1370 set_cont_sequence(appData.wrapContSeq);
1371 if (appData.matchGames > 0) {
1372 appData.matchMode = TRUE;
1373 } else if (appData.matchMode) {
1374 appData.matchGames = 1;
1376 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1377 appData.matchGames = appData.sameColorGames;
1378 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1379 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1380 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1383 if (appData.noChessProgram || first.protocolVersion == 1) {
1386 /* kludge: allow timeout for initial "feature" commands */
1388 DisplayMessage("", _("Starting chess program"));
1389 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1394 CalculateIndex (int index, int gameNr)
1395 { // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1397 if(index > 0) return index; // fixed nmber
1398 if(index == 0) return 1;
1399 res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1400 if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1405 LoadGameOrPosition (int gameNr)
1406 { // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1407 if (*appData.loadGameFile != NULLCHAR) {
1408 if (!LoadGameFromFile(appData.loadGameFile,
1409 CalculateIndex(appData.loadGameIndex, gameNr),
1410 appData.loadGameFile, FALSE)) {
1411 DisplayFatalError(_("Bad game file"), 0, 1);
1414 } else if (*appData.loadPositionFile != NULLCHAR) {
1415 if (!LoadPositionFromFile(appData.loadPositionFile,
1416 CalculateIndex(appData.loadPositionIndex, gameNr),
1417 appData.loadPositionFile)) {
1418 DisplayFatalError(_("Bad position file"), 0, 1);
1426 ReserveGame (int gameNr, char resChar)
1428 FILE *tf = fopen(appData.tourneyFile, "r+");
1429 char *p, *q, c, buf[MSG_SIZ];
1430 if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1431 safeStrCpy(buf, lastMsg, MSG_SIZ);
1432 DisplayMessage(_("Pick new game"), "");
1433 flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1434 ParseArgsFromFile(tf);
1435 p = q = appData.results;
1436 if(appData.debugMode) {
1437 char *r = appData.participants;
1438 fprintf(debugFP, "results = '%s'\n", p);
1439 while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1440 fprintf(debugFP, "\n");
1442 while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1444 q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1445 safeStrCpy(q, p, strlen(p) + 2);
1446 if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1447 if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1448 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1449 if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1452 fseek(tf, -(strlen(p)+4), SEEK_END);
1454 if(c != '"') // depending on DOS or Unix line endings we can be one off
1455 fseek(tf, -(strlen(p)+2), SEEK_END);
1456 else fseek(tf, -(strlen(p)+3), SEEK_END);
1457 fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1458 DisplayMessage(buf, "");
1459 free(p); appData.results = q;
1460 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1461 (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1462 int round = appData.defaultMatchGames * appData.tourneyType;
1463 if(gameNr < 0 || appData.tourneyType < 1 || // gauntlet engine can always stay loaded as first engine
1464 appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1465 UnloadEngine(&first); // next game belongs to other pairing;
1466 UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1468 if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1472 MatchEvent (int mode)
1473 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1475 if(matchMode) { // already in match mode: switch it off
1477 if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1480 // if(gameMode != BeginningOfGame) {
1481 // DisplayError(_("You can only start a match from the initial position."), 0);
1485 if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1486 /* Set up machine vs. machine match */
1488 NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1489 if(appData.tourneyFile[0]) {
1491 if(nextGame > appData.matchGames) {
1493 if(strchr(appData.results, '*') == NULL) {
1495 appData.tourneyCycles++;
1496 if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1498 NextTourneyGame(-1, &dummy);
1500 if(nextGame <= appData.matchGames) {
1501 DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1503 ScheduleDelayedEvent(NextMatchGame, 10000);
1508 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1509 DisplayError(buf, 0);
1510 appData.tourneyFile[0] = 0;
1514 if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically
1515 DisplayFatalError(_("Can't have a match with no chess programs"),
1520 matchGame = roundNr = 1;
1521 first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1525 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1528 InitBackEnd3 P((void))
1530 GameMode initialMode;
1534 InitChessProgram(&first, startedFromSetupPosition);
1536 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1537 free(programVersion);
1538 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1539 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1540 FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1543 if (appData.icsActive) {
1545 /* [DM] Make a console window if needed [HGM] merged ifs */
1551 if (*appData.icsCommPort != NULLCHAR)
1552 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1553 appData.icsCommPort);
1555 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1556 appData.icsHost, appData.icsPort);
1558 if( (len >= MSG_SIZ) && appData.debugMode )
1559 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1561 DisplayFatalError(buf, err, 1);
1566 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1568 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1569 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1570 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1571 } else if (appData.noChessProgram) {
1577 if (*appData.cmailGameName != NULLCHAR) {
1579 OpenLoopback(&cmailPR);
1581 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1585 DisplayMessage("", "");
1586 if (StrCaseCmp(appData.initialMode, "") == 0) {
1587 initialMode = BeginningOfGame;
1588 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1589 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1590 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1591 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1594 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1595 initialMode = TwoMachinesPlay;
1596 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1597 initialMode = AnalyzeFile;
1598 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1599 initialMode = AnalyzeMode;
1600 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1601 initialMode = MachinePlaysWhite;
1602 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1603 initialMode = MachinePlaysBlack;
1604 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1605 initialMode = EditGame;
1606 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1607 initialMode = EditPosition;
1608 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1609 initialMode = Training;
1611 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1612 if( (len >= MSG_SIZ) && appData.debugMode )
1613 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1615 DisplayFatalError(buf, 0, 2);
1619 if (appData.matchMode) {
1620 if(appData.tourneyFile[0]) { // start tourney from command line
1622 if(f = fopen(appData.tourneyFile, "r")) {
1623 ParseArgsFromFile(f); // make sure tourney parmeters re known
1625 appData.clockMode = TRUE;
1627 } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1630 } else if (*appData.cmailGameName != NULLCHAR) {
1631 /* Set up cmail mode */
1632 ReloadCmailMsgEvent(TRUE);
1634 /* Set up other modes */
1635 if (initialMode == AnalyzeFile) {
1636 if (*appData.loadGameFile == NULLCHAR) {
1637 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1641 if (*appData.loadGameFile != NULLCHAR) {
1642 (void) LoadGameFromFile(appData.loadGameFile,
1643 appData.loadGameIndex,
1644 appData.loadGameFile, TRUE);
1645 } else if (*appData.loadPositionFile != NULLCHAR) {
1646 (void) LoadPositionFromFile(appData.loadPositionFile,
1647 appData.loadPositionIndex,
1648 appData.loadPositionFile);
1649 /* [HGM] try to make self-starting even after FEN load */
1650 /* to allow automatic setup of fairy variants with wtm */
1651 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1652 gameMode = BeginningOfGame;
1653 setboardSpoiledMachineBlack = 1;
1655 /* [HGM] loadPos: make that every new game uses the setup */
1656 /* from file as long as we do not switch variant */
1657 if(!blackPlaysFirst) {
1658 startedFromPositionFile = TRUE;
1659 CopyBoard(filePosition, boards[0]);
1662 if (initialMode == AnalyzeMode) {
1663 if (appData.noChessProgram) {
1664 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1667 if (appData.icsActive) {
1668 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1672 } else if (initialMode == AnalyzeFile) {
1673 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1674 ShowThinkingEvent();
1676 AnalysisPeriodicEvent(1);
1677 } else if (initialMode == MachinePlaysWhite) {
1678 if (appData.noChessProgram) {
1679 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1683 if (appData.icsActive) {
1684 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1688 MachineWhiteEvent();
1689 } else if (initialMode == MachinePlaysBlack) {
1690 if (appData.noChessProgram) {
1691 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1695 if (appData.icsActive) {
1696 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1700 MachineBlackEvent();
1701 } else if (initialMode == TwoMachinesPlay) {
1702 if (appData.noChessProgram) {
1703 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1707 if (appData.icsActive) {
1708 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1713 } else if (initialMode == EditGame) {
1715 } else if (initialMode == EditPosition) {
1716 EditPositionEvent();
1717 } else if (initialMode == Training) {
1718 if (*appData.loadGameFile == NULLCHAR) {
1719 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1728 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1730 DisplayBook(current+1);
1732 MoveHistorySet( movelist, first, last, current, pvInfoList );
1734 EvalGraphSet( first, last, current, pvInfoList );
1736 MakeEngineOutputTitle();
1740 * Establish will establish a contact to a remote host.port.
1741 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1742 * used to talk to the host.
1743 * Returns 0 if okay, error code if not.
1750 if (*appData.icsCommPort != NULLCHAR) {
1751 /* Talk to the host through a serial comm port */
1752 return OpenCommPort(appData.icsCommPort, &icsPR);
1754 } else if (*appData.gateway != NULLCHAR) {
1755 if (*appData.remoteShell == NULLCHAR) {
1756 /* Use the rcmd protocol to run telnet program on a gateway host */
1757 snprintf(buf, sizeof(buf), "%s %s %s",
1758 appData.telnetProgram, appData.icsHost, appData.icsPort);
1759 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1762 /* Use the rsh program to run telnet program on a gateway host */
1763 if (*appData.remoteUser == NULLCHAR) {
1764 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1765 appData.gateway, appData.telnetProgram,
1766 appData.icsHost, appData.icsPort);
1768 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1769 appData.remoteShell, appData.gateway,
1770 appData.remoteUser, appData.telnetProgram,
1771 appData.icsHost, appData.icsPort);
1773 return StartChildProcess(buf, "", &icsPR);
1776 } else if (appData.useTelnet) {
1777 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1780 /* TCP socket interface differs somewhat between
1781 Unix and NT; handle details in the front end.
1783 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1788 EscapeExpand (char *p, char *q)
1789 { // [HGM] initstring: routine to shape up string arguments
1790 while(*p++ = *q++) if(p[-1] == '\\')
1792 case 'n': p[-1] = '\n'; break;
1793 case 'r': p[-1] = '\r'; break;
1794 case 't': p[-1] = '\t'; break;
1795 case '\\': p[-1] = '\\'; break;
1796 case 0: *p = 0; return;
1797 default: p[-1] = q[-1]; break;
1802 show_bytes (FILE *fp, char *buf, int count)
1805 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1806 fprintf(fp, "\\%03o", *buf & 0xff);
1815 /* Returns an errno value */
1817 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1819 char buf[8192], *p, *q, *buflim;
1820 int left, newcount, outcount;
1822 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1823 *appData.gateway != NULLCHAR) {
1824 if (appData.debugMode) {
1825 fprintf(debugFP, ">ICS: ");
1826 show_bytes(debugFP, message, count);
1827 fprintf(debugFP, "\n");
1829 return OutputToProcess(pr, message, count, outError);
1832 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1839 if (appData.debugMode) {
1840 fprintf(debugFP, ">ICS: ");
1841 show_bytes(debugFP, buf, newcount);
1842 fprintf(debugFP, "\n");
1844 outcount = OutputToProcess(pr, buf, newcount, outError);
1845 if (outcount < newcount) return -1; /* to be sure */
1852 } else if (((unsigned char) *p) == TN_IAC) {
1853 *q++ = (char) TN_IAC;
1860 if (appData.debugMode) {
1861 fprintf(debugFP, ">ICS: ");
1862 show_bytes(debugFP, buf, newcount);
1863 fprintf(debugFP, "\n");
1865 outcount = OutputToProcess(pr, buf, newcount, outError);
1866 if (outcount < newcount) return -1; /* to be sure */
1871 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1873 int outError, outCount;
1874 static int gotEof = 0;
1877 /* Pass data read from player on to ICS */
1880 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1881 if (outCount < count) {
1882 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1884 if(have_sent_ICS_logon == 2) {
1885 if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1886 fprintf(ini, "%s", message);
1887 have_sent_ICS_logon = 3;
1889 have_sent_ICS_logon = 1;
1890 } else if(have_sent_ICS_logon == 3) {
1891 fprintf(ini, "%s", message);
1893 have_sent_ICS_logon = 1;
1895 } else if (count < 0) {
1896 RemoveInputSource(isr);
1897 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1898 } else if (gotEof++ > 0) {
1899 RemoveInputSource(isr);
1900 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1906 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1907 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1908 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1909 SendToICS("date\n");
1910 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1913 /* added routine for printf style output to ics */
1915 ics_printf (char *format, ...)
1917 char buffer[MSG_SIZ];
1920 va_start(args, format);
1921 vsnprintf(buffer, sizeof(buffer), format, args);
1922 buffer[sizeof(buffer)-1] = '\0';
1930 int count, outCount, outError;
1932 if (icsPR == NoProc) return;
1935 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1936 if (outCount < count) {
1937 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1941 /* This is used for sending logon scripts to the ICS. Sending
1942 without a delay causes problems when using timestamp on ICC
1943 (at least on my machine). */
1945 SendToICSDelayed (char *s, long msdelay)
1947 int count, outCount, outError;
1949 if (icsPR == NoProc) return;
1952 if (appData.debugMode) {
1953 fprintf(debugFP, ">ICS: ");
1954 show_bytes(debugFP, s, count);
1955 fprintf(debugFP, "\n");
1957 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1959 if (outCount < count) {
1960 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1965 /* Remove all highlighting escape sequences in s
1966 Also deletes any suffix starting with '('
1969 StripHighlightAndTitle (char *s)
1971 static char retbuf[MSG_SIZ];
1974 while (*s != NULLCHAR) {
1975 while (*s == '\033') {
1976 while (*s != NULLCHAR && !isalpha(*s)) s++;
1977 if (*s != NULLCHAR) s++;
1979 while (*s != NULLCHAR && *s != '\033') {
1980 if (*s == '(' || *s == '[') {
1991 /* Remove all highlighting escape sequences in s */
1993 StripHighlight (char *s)
1995 static char retbuf[MSG_SIZ];
1998 while (*s != NULLCHAR) {
1999 while (*s == '\033') {
2000 while (*s != NULLCHAR && !isalpha(*s)) s++;
2001 if (*s != NULLCHAR) s++;
2003 while (*s != NULLCHAR && *s != '\033') {
2011 char engineVariant[MSG_SIZ];
2012 char *variantNames[] = VARIANT_NAMES;
2014 VariantName (VariantClass v)
2016 if(v == VariantUnknown || *engineVariant) return engineVariant;
2017 return variantNames[v];
2021 /* Identify a variant from the strings the chess servers use or the
2022 PGN Variant tag names we use. */
2024 StringToVariant (char *e)
2028 VariantClass v = VariantNormal;
2029 int i, found = FALSE;
2035 /* [HGM] skip over optional board-size prefixes */
2036 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
2037 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
2038 while( *e++ != '_');
2041 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2045 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2046 if (StrCaseStr(e, variantNames[i])) {
2047 v = (VariantClass) i;
2054 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2055 || StrCaseStr(e, "wild/fr")
2056 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2057 v = VariantFischeRandom;
2058 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2059 (i = 1, p = StrCaseStr(e, "w"))) {
2061 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2068 case 0: /* FICS only, actually */
2070 /* Castling legal even if K starts on d-file */
2071 v = VariantWildCastle;
2076 /* Castling illegal even if K & R happen to start in
2077 normal positions. */
2078 v = VariantNoCastle;
2091 /* Castling legal iff K & R start in normal positions */
2097 /* Special wilds for position setup; unclear what to do here */
2098 v = VariantLoadable;
2101 /* Bizarre ICC game */
2102 v = VariantTwoKings;
2105 v = VariantKriegspiel;
2111 v = VariantFischeRandom;
2114 v = VariantCrazyhouse;
2117 v = VariantBughouse;
2123 /* Not quite the same as FICS suicide! */
2124 v = VariantGiveaway;
2130 v = VariantShatranj;
2133 /* Temporary names for future ICC types. The name *will* change in
2134 the next xboard/WinBoard release after ICC defines it. */
2172 v = VariantCapablanca;
2175 v = VariantKnightmate;
2181 v = VariantCylinder;
2187 v = VariantCapaRandom;
2190 v = VariantBerolina;
2202 /* Found "wild" or "w" in the string but no number;
2203 must assume it's normal chess. */
2207 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2208 if( (len >= MSG_SIZ) && appData.debugMode )
2209 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2211 DisplayError(buf, 0);
2217 if (appData.debugMode) {
2218 fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2219 e, wnum, VariantName(v));
2224 static int leftover_start = 0, leftover_len = 0;
2225 char star_match[STAR_MATCH_N][MSG_SIZ];
2227 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2228 advance *index beyond it, and set leftover_start to the new value of
2229 *index; else return FALSE. If pattern contains the character '*', it
2230 matches any sequence of characters not containing '\r', '\n', or the
2231 character following the '*' (if any), and the matched sequence(s) are
2232 copied into star_match.
2235 looking_at ( char *buf, int *index, char *pattern)
2237 char *bufp = &buf[*index], *patternp = pattern;
2239 char *matchp = star_match[0];
2242 if (*patternp == NULLCHAR) {
2243 *index = leftover_start = bufp - buf;
2247 if (*bufp == NULLCHAR) return FALSE;
2248 if (*patternp == '*') {
2249 if (*bufp == *(patternp + 1)) {
2251 matchp = star_match[++star_count];
2255 } else if (*bufp == '\n' || *bufp == '\r') {
2257 if (*patternp == NULLCHAR)
2262 *matchp++ = *bufp++;
2266 if (*patternp != *bufp) return FALSE;
2273 SendToPlayer (char *data, int length)
2275 int error, outCount;
2276 outCount = OutputToProcess(NoProc, data, length, &error);
2277 if (outCount < length) {
2278 DisplayFatalError(_("Error writing to display"), error, 1);
2283 PackHolding (char packed[], char *holding)
2293 switch (runlength) {
2304 sprintf(q, "%d", runlength);
2316 /* Telnet protocol requests from the front end */
2318 TelnetRequest (unsigned char ddww, unsigned char option)
2320 unsigned char msg[3];
2321 int outCount, outError;
2323 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2325 if (appData.debugMode) {
2326 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2342 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2351 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2354 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2359 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2361 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2368 if (!appData.icsActive) return;
2369 TelnetRequest(TN_DO, TN_ECHO);
2375 if (!appData.icsActive) return;
2376 TelnetRequest(TN_DONT, TN_ECHO);
2380 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2382 /* put the holdings sent to us by the server on the board holdings area */
2383 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2387 if(gameInfo.holdingsWidth < 2) return;
2388 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2389 return; // prevent overwriting by pre-board holdings
2391 if( (int)lowestPiece >= BlackPawn ) {
2394 holdingsStartRow = BOARD_HEIGHT-1;
2397 holdingsColumn = BOARD_WIDTH-1;
2398 countsColumn = BOARD_WIDTH-2;
2399 holdingsStartRow = 0;
2403 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2404 board[i][holdingsColumn] = EmptySquare;
2405 board[i][countsColumn] = (ChessSquare) 0;
2407 while( (p=*holdings++) != NULLCHAR ) {
2408 piece = CharToPiece( ToUpper(p) );
2409 if(piece == EmptySquare) continue;
2410 /*j = (int) piece - (int) WhitePawn;*/
2411 j = PieceToNumber(piece);
2412 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2413 if(j < 0) continue; /* should not happen */
2414 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2415 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2416 board[holdingsStartRow+j*direction][countsColumn]++;
2422 VariantSwitch (Board board, VariantClass newVariant)
2424 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2425 static Board oldBoard;
2427 startedFromPositionFile = FALSE;
2428 if(gameInfo.variant == newVariant) return;
2430 /* [HGM] This routine is called each time an assignment is made to
2431 * gameInfo.variant during a game, to make sure the board sizes
2432 * are set to match the new variant. If that means adding or deleting
2433 * holdings, we shift the playing board accordingly
2434 * This kludge is needed because in ICS observe mode, we get boards
2435 * of an ongoing game without knowing the variant, and learn about the
2436 * latter only later. This can be because of the move list we requested,
2437 * in which case the game history is refilled from the beginning anyway,
2438 * but also when receiving holdings of a crazyhouse game. In the latter
2439 * case we want to add those holdings to the already received position.
2443 if (appData.debugMode) {
2444 fprintf(debugFP, "Switch board from %s to %s\n",
2445 VariantName(gameInfo.variant), VariantName(newVariant));
2446 setbuf(debugFP, NULL);
2448 shuffleOpenings = 0; /* [HGM] shuffle */
2449 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2453 newWidth = 9; newHeight = 9;
2454 gameInfo.holdingsSize = 7;
2455 case VariantBughouse:
2456 case VariantCrazyhouse:
2457 newHoldingsWidth = 2; break;
2461 newHoldingsWidth = 2;
2462 gameInfo.holdingsSize = 8;
2465 case VariantCapablanca:
2466 case VariantCapaRandom:
2469 newHoldingsWidth = gameInfo.holdingsSize = 0;
2472 if(newWidth != gameInfo.boardWidth ||
2473 newHeight != gameInfo.boardHeight ||
2474 newHoldingsWidth != gameInfo.holdingsWidth ) {
2476 /* shift position to new playing area, if needed */
2477 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2478 for(i=0; i<BOARD_HEIGHT; i++)
2479 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2480 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2482 for(i=0; i<newHeight; i++) {
2483 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2484 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2486 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2487 for(i=0; i<BOARD_HEIGHT; i++)
2488 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2489 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2492 board[HOLDINGS_SET] = 0;
2493 gameInfo.boardWidth = newWidth;
2494 gameInfo.boardHeight = newHeight;
2495 gameInfo.holdingsWidth = newHoldingsWidth;
2496 gameInfo.variant = newVariant;
2497 InitDrawingSizes(-2, 0);
2498 } else gameInfo.variant = newVariant;
2499 CopyBoard(oldBoard, board); // remember correctly formatted board
2500 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2501 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2504 static int loggedOn = FALSE;
2506 /*-- Game start info cache: --*/
2508 char gs_kind[MSG_SIZ];
2509 static char player1Name[128] = "";
2510 static char player2Name[128] = "";
2511 static char cont_seq[] = "\n\\ ";
2512 static int player1Rating = -1;
2513 static int player2Rating = -1;
2514 /*----------------------------*/
2516 ColorClass curColor = ColorNormal;
2517 int suppressKibitz = 0;
2520 Boolean soughtPending = FALSE;
2521 Boolean seekGraphUp;
2522 #define MAX_SEEK_ADS 200
2524 char *seekAdList[MAX_SEEK_ADS];
2525 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2526 float tcList[MAX_SEEK_ADS];
2527 char colorList[MAX_SEEK_ADS];
2528 int nrOfSeekAds = 0;
2529 int minRating = 1010, maxRating = 2800;
2530 int hMargin = 10, vMargin = 20, h, w;
2531 extern int squareSize, lineGap;
2536 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2537 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2538 if(r < minRating+100 && r >=0 ) r = minRating+100;
2539 if(r > maxRating) r = maxRating;
2540 if(tc < 1.f) tc = 1.f;
2541 if(tc > 95.f) tc = 95.f;
2542 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2543 y = ((double)r - minRating)/(maxRating - minRating)
2544 * (h-vMargin-squareSize/8-1) + vMargin;
2545 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2546 if(strstr(seekAdList[i], " u ")) color = 1;
2547 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2548 !strstr(seekAdList[i], "bullet") &&
2549 !strstr(seekAdList[i], "blitz") &&
2550 !strstr(seekAdList[i], "standard") ) color = 2;
2551 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2552 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2556 PlotSingleSeekAd (int i)
2562 AddAd (char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2564 char buf[MSG_SIZ], *ext = "";
2565 VariantClass v = StringToVariant(type);
2566 if(strstr(type, "wild")) {
2567 ext = type + 4; // append wild number
2568 if(v == VariantFischeRandom) type = "chess960"; else
2569 if(v == VariantLoadable) type = "setup"; else
2570 type = VariantName(v);
2572 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2573 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2574 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2575 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2576 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2577 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2578 seekNrList[nrOfSeekAds] = nr;
2579 zList[nrOfSeekAds] = 0;
2580 seekAdList[nrOfSeekAds++] = StrSave(buf);
2581 if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2586 EraseSeekDot (int i)
2588 int x = xList[i], y = yList[i], d=squareSize/4, k;
2589 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2590 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2591 // now replot every dot that overlapped
2592 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2593 int xx = xList[k], yy = yList[k];
2594 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2595 DrawSeekDot(xx, yy, colorList[k]);
2600 RemoveSeekAd (int nr)
2603 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2605 if(seekAdList[i]) free(seekAdList[i]);
2606 seekAdList[i] = seekAdList[--nrOfSeekAds];
2607 seekNrList[i] = seekNrList[nrOfSeekAds];
2608 ratingList[i] = ratingList[nrOfSeekAds];
2609 colorList[i] = colorList[nrOfSeekAds];
2610 tcList[i] = tcList[nrOfSeekAds];
2611 xList[i] = xList[nrOfSeekAds];
2612 yList[i] = yList[nrOfSeekAds];
2613 zList[i] = zList[nrOfSeekAds];
2614 seekAdList[nrOfSeekAds] = NULL;
2620 MatchSoughtLine (char *line)
2622 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2623 int nr, base, inc, u=0; char dummy;
2625 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2626 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2628 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2629 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2630 // match: compact and save the line
2631 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2641 if(!seekGraphUp) return FALSE;
2642 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2643 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2645 DrawSeekBackground(0, 0, w, h);
2646 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2647 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2648 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2649 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2651 DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2654 snprintf(buf, MSG_SIZ, "%d", i);
2655 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2658 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2659 for(i=1; i<100; i+=(i<10?1:5)) {
2660 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2661 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2662 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2664 snprintf(buf, MSG_SIZ, "%d", i);
2665 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2668 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2673 SeekGraphClick (ClickType click, int x, int y, int moving)
2675 static int lastDown = 0, displayed = 0, lastSecond;
2676 if(y < 0) return FALSE;
2677 if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2678 (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2679 if(!seekGraphUp) return FALSE;
2680 seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2681 DrawPosition(TRUE, NULL);
2684 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2685 if(click == Release || moving) return FALSE;
2687 soughtPending = TRUE;
2688 SendToICS(ics_prefix);
2689 SendToICS("sought\n"); // should this be "sought all"?
2690 } else { // issue challenge based on clicked ad
2691 int dist = 10000; int i, closest = 0, second = 0;
2692 for(i=0; i<nrOfSeekAds; i++) {
2693 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2694 if(d < dist) { dist = d; closest = i; }
2695 second += (d - zList[i] < 120); // count in-range ads
2696 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2700 second = (second > 1);
2701 if(displayed != closest || second != lastSecond) {
2702 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2703 lastSecond = second; displayed = closest;
2705 if(click == Press) {
2706 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2709 } // on press 'hit', only show info
2710 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2711 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2712 SendToICS(ics_prefix);
2714 return TRUE; // let incoming board of started game pop down the graph
2715 } else if(click == Release) { // release 'miss' is ignored
2716 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2717 if(moving == 2) { // right up-click
2718 nrOfSeekAds = 0; // refresh graph
2719 soughtPending = TRUE;
2720 SendToICS(ics_prefix);
2721 SendToICS("sought\n"); // should this be "sought all"?
2724 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2725 // press miss or release hit 'pop down' seek graph
2726 seekGraphUp = FALSE;
2727 DrawPosition(TRUE, NULL);
2733 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2735 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2736 #define STARTED_NONE 0
2737 #define STARTED_MOVES 1
2738 #define STARTED_BOARD 2
2739 #define STARTED_OBSERVE 3
2740 #define STARTED_HOLDINGS 4
2741 #define STARTED_CHATTER 5
2742 #define STARTED_COMMENT 6
2743 #define STARTED_MOVES_NOHIDE 7
2745 static int started = STARTED_NONE;
2746 static char parse[20000];
2747 static int parse_pos = 0;
2748 static char buf[BUF_SIZE + 1];
2749 static int firstTime = TRUE, intfSet = FALSE;
2750 static ColorClass prevColor = ColorNormal;
2751 static int savingComment = FALSE;
2752 static int cmatch = 0; // continuation sequence match
2759 int backup; /* [DM] For zippy color lines */
2761 char talker[MSG_SIZ]; // [HGM] chat
2764 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2766 if (appData.debugMode) {
2768 fprintf(debugFP, "<ICS: ");
2769 show_bytes(debugFP, data, count);
2770 fprintf(debugFP, "\n");
2774 if (appData.debugMode) { int f = forwardMostMove;
2775 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2776 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2777 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2780 /* If last read ended with a partial line that we couldn't parse,
2781 prepend it to the new read and try again. */
2782 if (leftover_len > 0) {
2783 for (i=0; i<leftover_len; i++)
2784 buf[i] = buf[leftover_start + i];
2787 /* copy new characters into the buffer */
2788 bp = buf + leftover_len;
2789 buf_len=leftover_len;
2790 for (i=0; i<count; i++)
2793 if (data[i] == '\r')
2796 // join lines split by ICS?
2797 if (!appData.noJoin)
2800 Joining just consists of finding matches against the
2801 continuation sequence, and discarding that sequence
2802 if found instead of copying it. So, until a match
2803 fails, there's nothing to do since it might be the
2804 complete sequence, and thus, something we don't want
2807 if (data[i] == cont_seq[cmatch])
2810 if (cmatch == strlen(cont_seq))
2812 cmatch = 0; // complete match. just reset the counter
2815 it's possible for the ICS to not include the space
2816 at the end of the last word, making our [correct]
2817 join operation fuse two separate words. the server
2818 does this when the space occurs at the width setting.
2820 if (!buf_len || buf[buf_len-1] != ' ')
2831 match failed, so we have to copy what matched before
2832 falling through and copying this character. In reality,
2833 this will only ever be just the newline character, but
2834 it doesn't hurt to be precise.
2836 strncpy(bp, cont_seq, cmatch);
2848 buf[buf_len] = NULLCHAR;
2849 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2854 while (i < buf_len) {
2855 /* Deal with part of the TELNET option negotiation
2856 protocol. We refuse to do anything beyond the
2857 defaults, except that we allow the WILL ECHO option,
2858 which ICS uses to turn off password echoing when we are
2859 directly connected to it. We reject this option
2860 if localLineEditing mode is on (always on in xboard)
2861 and we are talking to port 23, which might be a real
2862 telnet server that will try to keep WILL ECHO on permanently.
2864 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2865 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2866 unsigned char option;
2868 switch ((unsigned char) buf[++i]) {
2870 if (appData.debugMode)
2871 fprintf(debugFP, "\n<WILL ");
2872 switch (option = (unsigned char) buf[++i]) {
2874 if (appData.debugMode)
2875 fprintf(debugFP, "ECHO ");
2876 /* Reply only if this is a change, according
2877 to the protocol rules. */
2878 if (remoteEchoOption) break;
2879 if (appData.localLineEditing &&
2880 atoi(appData.icsPort) == TN_PORT) {
2881 TelnetRequest(TN_DONT, TN_ECHO);
2884 TelnetRequest(TN_DO, TN_ECHO);
2885 remoteEchoOption = TRUE;
2889 if (appData.debugMode)
2890 fprintf(debugFP, "%d ", option);
2891 /* Whatever this is, we don't want it. */
2892 TelnetRequest(TN_DONT, option);
2897 if (appData.debugMode)
2898 fprintf(debugFP, "\n<WONT ");
2899 switch (option = (unsigned char) buf[++i]) {
2901 if (appData.debugMode)
2902 fprintf(debugFP, "ECHO ");
2903 /* Reply only if this is a change, according
2904 to the protocol rules. */
2905 if (!remoteEchoOption) break;
2907 TelnetRequest(TN_DONT, TN_ECHO);
2908 remoteEchoOption = FALSE;
2911 if (appData.debugMode)
2912 fprintf(debugFP, "%d ", (unsigned char) option);
2913 /* Whatever this is, it must already be turned
2914 off, because we never agree to turn on
2915 anything non-default, so according to the
2916 protocol rules, we don't reply. */
2921 if (appData.debugMode)
2922 fprintf(debugFP, "\n<DO ");
2923 switch (option = (unsigned char) buf[++i]) {
2925 /* Whatever this is, we refuse to do it. */
2926 if (appData.debugMode)
2927 fprintf(debugFP, "%d ", option);
2928 TelnetRequest(TN_WONT, option);
2933 if (appData.debugMode)
2934 fprintf(debugFP, "\n<DONT ");
2935 switch (option = (unsigned char) buf[++i]) {
2937 if (appData.debugMode)
2938 fprintf(debugFP, "%d ", option);
2939 /* Whatever this is, we are already not doing
2940 it, because we never agree to do anything
2941 non-default, so according to the protocol
2942 rules, we don't reply. */
2947 if (appData.debugMode)
2948 fprintf(debugFP, "\n<IAC ");
2949 /* Doubled IAC; pass it through */
2953 if (appData.debugMode)
2954 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2955 /* Drop all other telnet commands on the floor */
2958 if (oldi > next_out)
2959 SendToPlayer(&buf[next_out], oldi - next_out);
2965 /* OK, this at least will *usually* work */
2966 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2970 if (loggedOn && !intfSet) {
2971 if (ics_type == ICS_ICC) {
2972 snprintf(str, MSG_SIZ,
2973 "/set-quietly interface %s\n/set-quietly style 12\n",
2975 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2976 strcat(str, "/set-2 51 1\n/set seek 1\n");
2977 } else if (ics_type == ICS_CHESSNET) {
2978 snprintf(str, MSG_SIZ, "/style 12\n");
2980 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2981 strcat(str, programVersion);
2982 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2983 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2984 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2986 strcat(str, "$iset nohighlight 1\n");
2988 strcat(str, "$iset lock 1\n$style 12\n");
2991 NotifyFrontendLogin();
2995 if (started == STARTED_COMMENT) {
2996 /* Accumulate characters in comment */
2997 parse[parse_pos++] = buf[i];
2998 if (buf[i] == '\n') {
2999 parse[parse_pos] = NULLCHAR;
3000 if(chattingPartner>=0) {
3002 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3003 OutputChatMessage(chattingPartner, mess);
3004 chattingPartner = -1;
3005 next_out = i+1; // [HGM] suppress printing in ICS window
3007 if(!suppressKibitz) // [HGM] kibitz
3008 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3009 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3010 int nrDigit = 0, nrAlph = 0, j;
3011 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3012 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3013 parse[parse_pos] = NULLCHAR;
3014 // try to be smart: if it does not look like search info, it should go to
3015 // ICS interaction window after all, not to engine-output window.
3016 for(j=0; j<parse_pos; j++) { // count letters and digits
3017 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3018 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
3019 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
3021 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3022 int depth=0; float score;
3023 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3024 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3025 pvInfoList[forwardMostMove-1].depth = depth;
3026 pvInfoList[forwardMostMove-1].score = 100*score;
3028 OutputKibitz(suppressKibitz, parse);
3031 if(gameMode == IcsObserving) // restore original ICS messages
3032 /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3033 snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3035 /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3036 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3037 SendToPlayer(tmp, strlen(tmp));
3039 next_out = i+1; // [HGM] suppress printing in ICS window
3041 started = STARTED_NONE;
3043 /* Don't match patterns against characters in comment */
3048 if (started == STARTED_CHATTER) {
3049 if (buf[i] != '\n') {
3050 /* Don't match patterns against characters in chatter */
3054 started = STARTED_NONE;
3055 if(suppressKibitz) next_out = i+1;
3058 /* Kludge to deal with rcmd protocol */
3059 if (firstTime && looking_at(buf, &i, "\001*")) {
3060 DisplayFatalError(&buf[1], 0, 1);
3066 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3069 if (appData.debugMode)
3070 fprintf(debugFP, "ics_type %d\n", ics_type);
3073 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3074 ics_type = ICS_FICS;
3076 if (appData.debugMode)
3077 fprintf(debugFP, "ics_type %d\n", ics_type);
3080 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3081 ics_type = ICS_CHESSNET;
3083 if (appData.debugMode)
3084 fprintf(debugFP, "ics_type %d\n", ics_type);
3089 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3090 looking_at(buf, &i, "Logging you in as \"*\"") ||
3091 looking_at(buf, &i, "will be \"*\""))) {
3092 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3096 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3098 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3099 DisplayIcsInteractionTitle(buf);
3100 have_set_title = TRUE;
3103 /* skip finger notes */
3104 if (started == STARTED_NONE &&
3105 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3106 (buf[i] == '1' && buf[i+1] == '0')) &&
3107 buf[i+2] == ':' && buf[i+3] == ' ') {
3108 started = STARTED_CHATTER;
3114 // [HGM] seekgraph: recognize sought lines and end-of-sought message
3115 if(appData.seekGraph) {
3116 if(soughtPending && MatchSoughtLine(buf+i)) {
3117 i = strstr(buf+i, "rated") - buf;
3118 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3119 next_out = leftover_start = i;
3120 started = STARTED_CHATTER;
3121 suppressKibitz = TRUE;
3124 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3125 && looking_at(buf, &i, "* ads displayed")) {
3126 soughtPending = FALSE;
3131 if(appData.autoRefresh) {
3132 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3133 int s = (ics_type == ICS_ICC); // ICC format differs
3135 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3136 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3137 looking_at(buf, &i, "*% "); // eat prompt
3138 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3139 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3140 next_out = i; // suppress
3143 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3144 char *p = star_match[0];
3146 if(seekGraphUp) RemoveSeekAd(atoi(p));
3147 while(*p && *p++ != ' '); // next
3149 looking_at(buf, &i, "*% "); // eat prompt
3150 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3157 /* skip formula vars */
3158 if (started == STARTED_NONE &&
3159 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3160 started = STARTED_CHATTER;
3165 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3166 if (appData.autoKibitz && started == STARTED_NONE &&
3167 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3168 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3169 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3170 looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3171 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3172 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3173 suppressKibitz = TRUE;
3174 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3176 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3177 && (gameMode == IcsPlayingWhite)) ||
3178 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3179 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3180 started = STARTED_CHATTER; // own kibitz we simply discard
3182 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3183 parse_pos = 0; parse[0] = NULLCHAR;
3184 savingComment = TRUE;
3185 suppressKibitz = gameMode != IcsObserving ? 2 :
3186 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3190 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3191 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3192 && atoi(star_match[0])) {
3193 // suppress the acknowledgements of our own autoKibitz
3195 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3196 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3197 SendToPlayer(star_match[0], strlen(star_match[0]));
3198 if(looking_at(buf, &i, "*% ")) // eat prompt
3199 suppressKibitz = FALSE;
3203 } // [HGM] kibitz: end of patch
3205 if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3207 // [HGM] chat: intercept tells by users for which we have an open chat window
3209 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3210 looking_at(buf, &i, "* whispers:") ||
3211 looking_at(buf, &i, "* kibitzes:") ||
3212 looking_at(buf, &i, "* shouts:") ||
3213 looking_at(buf, &i, "* c-shouts:") ||
3214 looking_at(buf, &i, "--> * ") ||
3215 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3216 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3217 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3218 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3220 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3221 chattingPartner = -1;
3223 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3224 for(p=0; p<MAX_CHAT; p++) {
3225 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3226 talker[0] = '['; strcat(talker, "] ");
3227 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3228 chattingPartner = p; break;
3231 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3232 for(p=0; p<MAX_CHAT; p++) {
3233 if(!strcmp("kibitzes", chatPartner[p])) {
3234 talker[0] = '['; strcat(talker, "] ");
3235 chattingPartner = p; break;
3238 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3239 for(p=0; p<MAX_CHAT; p++) {
3240 if(!strcmp("whispers", chatPartner[p])) {
3241 talker[0] = '['; strcat(talker, "] ");
3242 chattingPartner = p; break;
3245 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3246 if(buf[i-8] == '-' && buf[i-3] == 't')
3247 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3248 if(!strcmp("c-shouts", chatPartner[p])) {
3249 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3250 chattingPartner = p; break;
3253 if(chattingPartner < 0)
3254 for(p=0; p<MAX_CHAT; p++) {
3255 if(!strcmp("shouts", chatPartner[p])) {
3256 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3257 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3258 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3259 chattingPartner = p; break;
3263 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3264 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3265 talker[0] = 0; Colorize(ColorTell, FALSE);
3266 chattingPartner = p; break;
3268 if(chattingPartner<0) i = oldi; else {
3269 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3270 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3271 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3272 started = STARTED_COMMENT;
3273 parse_pos = 0; parse[0] = NULLCHAR;
3274 savingComment = 3 + chattingPartner; // counts as TRUE
3275 suppressKibitz = TRUE;
3278 } // [HGM] chat: end of patch
3281 if (appData.zippyTalk || appData.zippyPlay) {
3282 /* [DM] Backup address for color zippy lines */
3284 if (loggedOn == TRUE)
3285 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3286 (appData.zippyPlay && ZippyMatch(buf, &backup)));
3288 } // [DM] 'else { ' deleted
3290 /* Regular tells and says */
3291 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3292 looking_at(buf, &i, "* (your partner) tells you: ") ||
3293 looking_at(buf, &i, "* says: ") ||
3294 /* Don't color "message" or "messages" output */
3295 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3296 looking_at(buf, &i, "*. * at *:*: ") ||
3297 looking_at(buf, &i, "--* (*:*): ") ||
3298 /* Message notifications (same color as tells) */
3299 looking_at(buf, &i, "* has left a message ") ||
3300 looking_at(buf, &i, "* just sent you a message:\n") ||
3301 /* Whispers and kibitzes */
3302 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3303 looking_at(buf, &i, "* kibitzes: ") ||
3305 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3307 if (tkind == 1 && strchr(star_match[0], ':')) {
3308 /* Avoid "tells you:" spoofs in channels */
3311 if (star_match[0][0] == NULLCHAR ||
3312 strchr(star_match[0], ' ') ||
3313 (tkind == 3 && strchr(star_match[1], ' '))) {
3314 /* Reject bogus matches */
3317 if (appData.colorize) {
3318 if (oldi > next_out) {
3319 SendToPlayer(&buf[next_out], oldi - next_out);
3324 Colorize(ColorTell, FALSE);
3325 curColor = ColorTell;
3328 Colorize(ColorKibitz, FALSE);
3329 curColor = ColorKibitz;
3332 p = strrchr(star_match[1], '(');
3339 Colorize(ColorChannel1, FALSE);
3340 curColor = ColorChannel1;
3342 Colorize(ColorChannel, FALSE);
3343 curColor = ColorChannel;
3347 curColor = ColorNormal;
3351 if (started == STARTED_NONE && appData.autoComment &&
3352 (gameMode == IcsObserving ||
3353 gameMode == IcsPlayingWhite ||
3354 gameMode == IcsPlayingBlack)) {
3355 parse_pos = i - oldi;
3356 memcpy(parse, &buf[oldi], parse_pos);
3357 parse[parse_pos] = NULLCHAR;
3358 started = STARTED_COMMENT;
3359 savingComment = TRUE;
3361 started = STARTED_CHATTER;
3362 savingComment = FALSE;
3369 if (looking_at(buf, &i, "* s-shouts: ") ||
3370 looking_at(buf, &i, "* c-shouts: ")) {
3371 if (appData.colorize) {
3372 if (oldi > next_out) {
3373 SendToPlayer(&buf[next_out], oldi - next_out);
3376 Colorize(ColorSShout, FALSE);
3377 curColor = ColorSShout;
3380 started = STARTED_CHATTER;
3384 if (looking_at(buf, &i, "--->")) {
3389 if (looking_at(buf, &i, "* shouts: ") ||
3390 looking_at(buf, &i, "--> ")) {
3391 if (appData.colorize) {
3392 if (oldi > next_out) {
3393 SendToPlayer(&buf[next_out], oldi - next_out);
3396 Colorize(ColorShout, FALSE);
3397 curColor = ColorShout;
3400 started = STARTED_CHATTER;
3404 if (looking_at( buf, &i, "Challenge:")) {
3405 if (appData.colorize) {
3406 if (oldi > next_out) {
3407 SendToPlayer(&buf[next_out], oldi - next_out);
3410 Colorize(ColorChallenge, FALSE);
3411 curColor = ColorChallenge;
3417 if (looking_at(buf, &i, "* offers you") ||
3418 looking_at(buf, &i, "* offers to be") ||
3419 looking_at(buf, &i, "* would like to") ||
3420 looking_at(buf, &i, "* requests to") ||
3421 looking_at(buf, &i, "Your opponent offers") ||
3422 looking_at(buf, &i, "Your opponent requests")) {
3424 if (appData.colorize) {
3425 if (oldi > next_out) {
3426 SendToPlayer(&buf[next_out], oldi - next_out);
3429 Colorize(ColorRequest, FALSE);
3430 curColor = ColorRequest;
3435 if (looking_at(buf, &i, "* (*) seeking")) {
3436 if (appData.colorize) {
3437 if (oldi > next_out) {
3438 SendToPlayer(&buf[next_out], oldi - next_out);
3441 Colorize(ColorSeek, FALSE);
3442 curColor = ColorSeek;
3447 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3449 if (looking_at(buf, &i, "\\ ")) {
3450 if (prevColor != ColorNormal) {
3451 if (oldi > next_out) {
3452 SendToPlayer(&buf[next_out], oldi - next_out);
3455 Colorize(prevColor, TRUE);
3456 curColor = prevColor;
3458 if (savingComment) {
3459 parse_pos = i - oldi;
3460 memcpy(parse, &buf[oldi], parse_pos);
3461 parse[parse_pos] = NULLCHAR;
3462 started = STARTED_COMMENT;
3463 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3464 chattingPartner = savingComment - 3; // kludge to remember the box
3466 started = STARTED_CHATTER;
3471 if (looking_at(buf, &i, "Black Strength :") ||
3472 looking_at(buf, &i, "<<< style 10 board >>>") ||
3473 looking_at(buf, &i, "<10>") ||
3474 looking_at(buf, &i, "#@#")) {
3475 /* Wrong board style */
3477 SendToICS(ics_prefix);
3478 SendToICS("set style 12\n");
3479 SendToICS(ics_prefix);
3480 SendToICS("refresh\n");
3484 if (looking_at(buf, &i, "login:")) {
3485 if (!have_sent_ICS_logon) {
3487 have_sent_ICS_logon = 1;
3488 else // no init script was found
3489 have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3490 } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3491 have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3496 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3497 (looking_at(buf, &i, "\n<12> ") ||
3498 looking_at(buf, &i, "<12> "))) {
3500 if (oldi > next_out) {
3501 SendToPlayer(&buf[next_out], oldi - next_out);
3504 started = STARTED_BOARD;
3509 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3510 looking_at(buf, &i, "<b1> ")) {
3511 if (oldi > next_out) {
3512 SendToPlayer(&buf[next_out], oldi - next_out);
3515 started = STARTED_HOLDINGS;
3520 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3522 /* Header for a move list -- first line */
3524 switch (ics_getting_history) {
3528 case BeginningOfGame:
3529 /* User typed "moves" or "oldmoves" while we
3530 were idle. Pretend we asked for these
3531 moves and soak them up so user can step
3532 through them and/or save them.
3535 gameMode = IcsObserving;
3538 ics_getting_history = H_GOT_UNREQ_HEADER;
3540 case EditGame: /*?*/
3541 case EditPosition: /*?*/
3542 /* Should above feature work in these modes too? */
3543 /* For now it doesn't */
3544 ics_getting_history = H_GOT_UNWANTED_HEADER;
3547 ics_getting_history = H_GOT_UNWANTED_HEADER;
3552 /* Is this the right one? */
3553 if (gameInfo.white && gameInfo.black &&
3554 strcmp(gameInfo.white, star_match[0]) == 0 &&
3555 strcmp(gameInfo.black, star_match[2]) == 0) {
3557 ics_getting_history = H_GOT_REQ_HEADER;
3560 case H_GOT_REQ_HEADER:
3561 case H_GOT_UNREQ_HEADER:
3562 case H_GOT_UNWANTED_HEADER:
3563 case H_GETTING_MOVES:
3564 /* Should not happen */
3565 DisplayError(_("Error gathering move list: two headers"), 0);
3566 ics_getting_history = H_FALSE;
3570 /* Save player ratings into gameInfo if needed */
3571 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3572 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3573 (gameInfo.whiteRating == -1 ||
3574 gameInfo.blackRating == -1)) {
3576 gameInfo.whiteRating = string_to_rating(star_match[1]);
3577 gameInfo.blackRating = string_to_rating(star_match[3]);
3578 if (appData.debugMode)
3579 fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3580 gameInfo.whiteRating, gameInfo.blackRating);
3585 if (looking_at(buf, &i,
3586 "* * match, initial time: * minute*, increment: * second")) {
3587 /* Header for a move list -- second line */
3588 /* Initial board will follow if this is a wild game */
3589 if (gameInfo.event != NULL) free(gameInfo.event);
3590 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3591 gameInfo.event = StrSave(str);
3592 /* [HGM] we switched variant. Translate boards if needed. */
3593 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3597 if (looking_at(buf, &i, "Move ")) {
3598 /* Beginning of a move list */
3599 switch (ics_getting_history) {
3601 /* Normally should not happen */
3602 /* Maybe user hit reset while we were parsing */
3605 /* Happens if we are ignoring a move list that is not
3606 * the one we just requested. Common if the user
3607 * tries to observe two games without turning off
3610 case H_GETTING_MOVES:
3611 /* Should not happen */
3612 DisplayError(_("Error gathering move list: nested"), 0);
3613 ics_getting_history = H_FALSE;
3615 case H_GOT_REQ_HEADER:
3616 ics_getting_history = H_GETTING_MOVES;
3617 started = STARTED_MOVES;
3619 if (oldi > next_out) {
3620 SendToPlayer(&buf[next_out], oldi - next_out);
3623 case H_GOT_UNREQ_HEADER:
3624 ics_getting_history = H_GETTING_MOVES;
3625 started = STARTED_MOVES_NOHIDE;
3628 case H_GOT_UNWANTED_HEADER:
3629 ics_getting_history = H_FALSE;
3635 if (looking_at(buf, &i, "% ") ||
3636 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3637 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3638 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3639 soughtPending = FALSE;
3643 if(suppressKibitz) next_out = i;
3644 savingComment = FALSE;
3648 case STARTED_MOVES_NOHIDE:
3649 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3650 parse[parse_pos + i - oldi] = NULLCHAR;
3651 ParseGameHistory(parse);
3653 if (appData.zippyPlay && first.initDone) {
3654 FeedMovesToProgram(&first, forwardMostMove);
3655 if (gameMode == IcsPlayingWhite) {
3656 if (WhiteOnMove(forwardMostMove)) {
3657 if (first.sendTime) {
3658 if (first.useColors) {
3659 SendToProgram("black\n", &first);
3661 SendTimeRemaining(&first, TRUE);
3663 if (first.useColors) {
3664 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3666 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3667 first.maybeThinking = TRUE;
3669 if (first.usePlayother) {
3670 if (first.sendTime) {
3671 SendTimeRemaining(&first, TRUE);
3673 SendToProgram("playother\n", &first);
3679 } else if (gameMode == IcsPlayingBlack) {
3680 if (!WhiteOnMove(forwardMostMove)) {
3681 if (first.sendTime) {
3682 if (first.useColors) {
3683 SendToProgram("white\n", &first);
3685 SendTimeRemaining(&first, FALSE);
3687 if (first.useColors) {
3688 SendToProgram("black\n", &first);
3690 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3691 first.maybeThinking = TRUE;
3693 if (first.usePlayother) {
3694 if (first.sendTime) {
3695 SendTimeRemaining(&first, FALSE);
3697 SendToProgram("playother\n", &first);
3706 if (gameMode == IcsObserving && ics_gamenum == -1) {
3707 /* Moves came from oldmoves or moves command
3708 while we weren't doing anything else.
3710 currentMove = forwardMostMove;
3711 ClearHighlights();/*!!could figure this out*/
3712 flipView = appData.flipView;
3713 DrawPosition(TRUE, boards[currentMove]);
3714 DisplayBothClocks();
3715 snprintf(str, MSG_SIZ, "%s %s %s",
3716 gameInfo.white, _("vs."), gameInfo.black);
3720 /* Moves were history of an active game */
3721 if (gameInfo.resultDetails != NULL) {
3722 free(gameInfo.resultDetails);
3723 gameInfo.resultDetails = NULL;
3726 HistorySet(parseList, backwardMostMove,
3727 forwardMostMove, currentMove-1);
3728 DisplayMove(currentMove - 1);
3729 if (started == STARTED_MOVES) next_out = i;
3730 started = STARTED_NONE;
3731 ics_getting_history = H_FALSE;
3734 case STARTED_OBSERVE:
3735 started = STARTED_NONE;
3736 SendToICS(ics_prefix);
3737 SendToICS("refresh\n");
3743 if(bookHit) { // [HGM] book: simulate book reply
3744 static char bookMove[MSG_SIZ]; // a bit generous?
3746 programStats.nodes = programStats.depth = programStats.time =
3747 programStats.score = programStats.got_only_move = 0;
3748 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3750 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3751 strcat(bookMove, bookHit);
3752 HandleMachineMove(bookMove, &first);
3757 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3758 started == STARTED_HOLDINGS ||
3759 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3760 /* Accumulate characters in move list or board */
3761 parse[parse_pos++] = buf[i];
3764 /* Start of game messages. Mostly we detect start of game
3765 when the first board image arrives. On some versions
3766 of the ICS, though, we need to do a "refresh" after starting
3767 to observe in order to get the current board right away. */
3768 if (looking_at(buf, &i, "Adding game * to observation list")) {
3769 started = STARTED_OBSERVE;
3773 /* Handle auto-observe */
3774 if (appData.autoObserve &&
3775 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3776 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3778 /* Choose the player that was highlighted, if any. */
3779 if (star_match[0][0] == '\033' ||
3780 star_match[1][0] != '\033') {
3781 player = star_match[0];
3783 player = star_match[2];
3785 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3786 ics_prefix, StripHighlightAndTitle(player));
3789 /* Save ratings from notify string */
3790 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3791 player1Rating = string_to_rating(star_match[1]);
3792 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3793 player2Rating = string_to_rating(star_match[3]);
3795 if (appData.debugMode)
3797 "Ratings from 'Game notification:' %s %d, %s %d\n",
3798 player1Name, player1Rating,
3799 player2Name, player2Rating);
3804 /* Deal with automatic examine mode after a game,
3805 and with IcsObserving -> IcsExamining transition */
3806 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3807 looking_at(buf, &i, "has made you an examiner of game *")) {
3809 int gamenum = atoi(star_match[0]);
3810 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3811 gamenum == ics_gamenum) {
3812 /* We were already playing or observing this game;
3813 no need to refetch history */
3814 gameMode = IcsExamining;
3816 pauseExamForwardMostMove = forwardMostMove;
3817 } else if (currentMove < forwardMostMove) {
3818 ForwardInner(forwardMostMove);
3821 /* I don't think this case really can happen */
3822 SendToICS(ics_prefix);
3823 SendToICS("refresh\n");
3828 /* Error messages */
3829 // if (ics_user_moved) {
3830 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3831 if (looking_at(buf, &i, "Illegal move") ||
3832 looking_at(buf, &i, "Not a legal move") ||
3833 looking_at(buf, &i, "Your king is in check") ||
3834 looking_at(buf, &i, "It isn't your turn") ||
3835 looking_at(buf, &i, "It is not your move")) {
3837 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3838 currentMove = forwardMostMove-1;
3839 DisplayMove(currentMove - 1); /* before DMError */
3840 DrawPosition(FALSE, boards[currentMove]);
3841 SwitchClocks(forwardMostMove-1); // [HGM] race
3842 DisplayBothClocks();
3844 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3850 if (looking_at(buf, &i, "still have time") ||
3851 looking_at(buf, &i, "not out of time") ||
3852 looking_at(buf, &i, "either player is out of time") ||
3853 looking_at(buf, &i, "has timeseal; checking")) {
3854 /* We must have called his flag a little too soon */
3855 whiteFlag = blackFlag = FALSE;
3859 if (looking_at(buf, &i, "added * seconds to") ||
3860 looking_at(buf, &i, "seconds were added to")) {
3861 /* Update the clocks */
3862 SendToICS(ics_prefix);
3863 SendToICS("refresh\n");
3867 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3868 ics_clock_paused = TRUE;