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 ChessSquare ChuArray[6][BOARD_FILES] = {
650 { WhiteLance, WhiteUnicorn, WhiteMan, WhiteFerz, WhiteWazir, WhiteKing,
651 WhiteAlfil, WhiteWazir, WhiteFerz, WhiteMan, WhiteUnicorn, WhiteLance },
652 { BlackLance, BlackUnicorn, BlackMan, BlackFerz, BlackWazir, BlackAlfil,
653 BlackKing, BlackWazir, BlackFerz, BlackMan, BlackUnicorn, BlackLance },
654 { WhiteCannon, EmptySquare, WhiteBishop, EmptySquare, WhiteNightrider, WhiteMarshall,
655 WhiteAngel, WhiteNightrider, EmptySquare, WhiteBishop, EmptySquare, WhiteCannon },
656 { BlackCannon, EmptySquare, BlackBishop, EmptySquare, BlackNightrider, BlackAngel,
657 BlackMarshall, BlackNightrider, EmptySquare, BlackBishop, EmptySquare, BlackCannon },
658 { WhiteFalcon, WhiteSilver, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
659 WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSilver, WhiteFalcon },
660 { BlackFalcon, BlackSilver, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
661 BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSilver, BlackFalcon }
663 #else // !(BOARD_FILES>=12)
664 #define CourierArray CapablancaArray
665 #define ChuArray CapablancaArray
666 #endif // !(BOARD_FILES>=12)
669 Board initialPosition;
672 /* Convert str to a rating. Checks for special cases of "----",
674 "++++", etc. Also strips ()'s */
676 string_to_rating (char *str)
678 while(*str && !isdigit(*str)) ++str;
680 return 0; /* One of the special "no rating" cases */
688 /* Init programStats */
689 programStats.movelist[0] = 0;
690 programStats.depth = 0;
691 programStats.nr_moves = 0;
692 programStats.moves_left = 0;
693 programStats.nodes = 0;
694 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
695 programStats.score = 0;
696 programStats.got_only_move = 0;
697 programStats.got_fail = 0;
698 programStats.line_is_book = 0;
703 { // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
704 if (appData.firstPlaysBlack) {
705 first.twoMachinesColor = "black\n";
706 second.twoMachinesColor = "white\n";
708 first.twoMachinesColor = "white\n";
709 second.twoMachinesColor = "black\n";
712 first.other = &second;
713 second.other = &first;
716 if(appData.timeOddsMode) {
717 norm = appData.timeOdds[0];
718 if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
720 first.timeOdds = appData.timeOdds[0]/norm;
721 second.timeOdds = appData.timeOdds[1]/norm;
724 if(programVersion) free(programVersion);
725 if (appData.noChessProgram) {
726 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
727 sprintf(programVersion, "%s", PACKAGE_STRING);
729 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
730 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
731 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
736 UnloadEngine (ChessProgramState *cps)
738 /* Kill off first chess program */
739 if (cps->isr != NULL)
740 RemoveInputSource(cps->isr);
743 if (cps->pr != NoProc) {
745 DoSleep( appData.delayBeforeQuit );
746 SendToProgram("quit\n", cps);
747 DoSleep( appData.delayAfterQuit );
748 DestroyChildProcess(cps->pr, cps->useSigterm);
751 if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
755 ClearOptions (ChessProgramState *cps)
758 cps->nrOptions = cps->comboCnt = 0;
759 for(i=0; i<MAX_OPTIONS; i++) {
760 cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
761 cps->option[i].textValue = 0;
765 char *engineNames[] = {
766 /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
767 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
769 /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
770 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
775 InitEngine (ChessProgramState *cps, int n)
776 { // [HGM] all engine initialiation put in a function that does one engine
780 cps->which = engineNames[n];
781 cps->maybeThinking = FALSE;
785 cps->sendDrawOffers = 1;
787 cps->program = appData.chessProgram[n];
788 cps->host = appData.host[n];
789 cps->dir = appData.directory[n];
790 cps->initString = appData.engInitString[n];
791 cps->computerString = appData.computerString[n];
792 cps->useSigint = TRUE;
793 cps->useSigterm = TRUE;
794 cps->reuse = appData.reuse[n];
795 cps->nps = appData.NPS[n]; // [HGM] nps: copy nodes per second
796 cps->useSetboard = FALSE;
798 cps->usePing = FALSE;
801 cps->usePlayother = FALSE;
802 cps->useColors = TRUE;
803 cps->useUsermove = FALSE;
804 cps->sendICS = FALSE;
805 cps->sendName = appData.icsActive;
806 cps->sdKludge = FALSE;
807 cps->stKludge = FALSE;
808 if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
809 TidyProgramName(cps->program, cps->host, cps->tidy);
811 ASSIGN(cps->variants, appData.variant);
812 cps->analysisSupport = 2; /* detect */
813 cps->analyzing = FALSE;
814 cps->initDone = FALSE;
817 /* New features added by Tord: */
818 cps->useFEN960 = FALSE;
819 cps->useOOCastle = TRUE;
820 /* End of new features added by Tord. */
821 cps->fenOverride = appData.fenOverride[n];
823 /* [HGM] time odds: set factor for each machine */
824 cps->timeOdds = appData.timeOdds[n];
826 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
827 cps->accumulateTC = appData.accumulateTC[n];
828 cps->maxNrOfSessions = 1;
833 cps->supportsNPS = UNKNOWN;
834 cps->memSize = FALSE;
835 cps->maxCores = FALSE;
836 ASSIGN(cps->egtFormats, "");
839 cps->optionSettings = appData.engOptions[n];
841 cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
842 cps->isUCI = appData.isUCI[n]; /* [AS] */
843 cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
846 if (appData.protocolVersion[n] > PROTOVER
847 || appData.protocolVersion[n] < 1)
852 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
853 appData.protocolVersion[n]);
854 if( (len >= MSG_SIZ) && appData.debugMode )
855 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
857 DisplayFatalError(buf, 0, 2);
861 cps->protocolVersion = appData.protocolVersion[n];
864 InitEngineUCI( installDir, cps ); // [HGM] moved here from winboard.c, to make available in xboard
865 ParseFeatures(appData.featureDefaults, cps);
868 ChessProgramState *savCps;
876 if(WaitForEngine(savCps, LoadEngine)) return;
877 CommonEngineInit(); // recalculate time odds
878 if(gameInfo.variant != StringToVariant(appData.variant)) {
879 // we changed variant when loading the engine; this forces us to reset
880 Reset(TRUE, savCps != &first);
881 oldMode = BeginningOfGame; // to prevent restoring old mode
883 InitChessProgram(savCps, FALSE);
884 if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
885 DisplayMessage("", "");
886 if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
887 for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
890 if(oldMode == AnalyzeMode) AnalyzeModeEvent();
894 ReplaceEngine (ChessProgramState *cps, int n)
896 oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
898 if(oldMode != BeginningOfGame) EditGameEvent();
901 appData.noChessProgram = FALSE;
902 appData.clockMode = TRUE;
905 if(n) return; // only startup first engine immediately; second can wait
906 savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
910 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
911 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
913 static char resetOptions[] =
914 "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
915 "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
916 "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
917 "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
920 FloatToFront(char **list, char *engineLine)
922 char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
924 if(appData.recentEngines <= 0) return;
925 TidyProgramName(engineLine, "localhost", tidy+1);
926 tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
927 strncpy(buf+1, *list, MSG_SIZ-50);
928 if(p = strstr(buf, tidy)) { // tidy name appears in list
929 q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
930 while(*p++ = *++q); // squeeze out
932 strcat(tidy, buf+1); // put list behind tidy name
933 p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
934 if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
935 ASSIGN(*list, tidy+1);
938 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
941 Load (ChessProgramState *cps, int i)
943 char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
944 if(engineLine && engineLine[0]) { // an engine was selected from the combo box
945 snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
946 SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
947 ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
948 FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
949 appData.firstProtocolVersion = PROTOVER;
950 ParseArgsFromString(buf);
952 ReplaceEngine(cps, i);
953 FloatToFront(&appData.recentEngineList, engineLine);
957 while(q = strchr(p, SLASH)) p = q+1;
958 if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
959 if(engineDir[0] != NULLCHAR) {
960 ASSIGN(appData.directory[i], engineDir); p = engineName;
961 } else if(p != engineName) { // derive directory from engine path, when not given
963 ASSIGN(appData.directory[i], engineName);
965 if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
966 } else { ASSIGN(appData.directory[i], "."); }
967 jar = (strstr(p, ".jar") == p + strlen(p) - 4);
969 if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
970 snprintf(command, MSG_SIZ, "%s %s", p, params);
973 if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
974 ASSIGN(appData.chessProgram[i], p);
975 appData.isUCI[i] = isUCI;
976 appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
977 appData.hasOwnBookUCI[i] = hasBook;
978 if(!nickName[0]) useNick = FALSE;
979 if(useNick) ASSIGN(appData.pgnName[i], nickName);
983 q = firstChessProgramNames;
984 if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
985 quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
986 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
987 quote, p, quote, appData.directory[i],
988 useNick ? " -fn \"" : "",
989 useNick ? nickName : "",
991 v1 ? " -firstProtocolVersion 1" : "",
992 hasBook ? "" : " -fNoOwnBookUCI",
993 isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
994 storeVariant ? " -variant " : "",
995 storeVariant ? VariantName(gameInfo.variant) : "");
996 if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
997 firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
998 if(insert != q) insert[-1] = NULLCHAR;
999 snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
1001 FloatToFront(&appData.recentEngineList, buf);
1003 ReplaceEngine(cps, i);
1009 int matched, min, sec;
1011 * Parse timeControl resource
1013 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1014 appData.movesPerSession)) {
1016 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1017 DisplayFatalError(buf, 0, 2);
1021 * Parse searchTime resource
1023 if (*appData.searchTime != NULLCHAR) {
1024 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1026 searchTime = min * 60;
1027 } else if (matched == 2) {
1028 searchTime = min * 60 + sec;
1031 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1032 DisplayFatalError(buf, 0, 2);
1041 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1042 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1044 GetTimeMark(&programStartTime);
1045 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1046 appData.seedBase = random() + (random()<<15);
1047 pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1049 ClearProgramStats();
1050 programStats.ok_to_send = 1;
1051 programStats.seen_stat = 0;
1054 * Initialize game list
1060 * Internet chess server status
1062 if (appData.icsActive) {
1063 appData.matchMode = FALSE;
1064 appData.matchGames = 0;
1066 appData.noChessProgram = !appData.zippyPlay;
1068 appData.zippyPlay = FALSE;
1069 appData.zippyTalk = FALSE;
1070 appData.noChessProgram = TRUE;
1072 if (*appData.icsHelper != NULLCHAR) {
1073 appData.useTelnet = TRUE;
1074 appData.telnetProgram = appData.icsHelper;
1077 appData.zippyTalk = appData.zippyPlay = FALSE;
1080 /* [AS] Initialize pv info list [HGM] and game state */
1084 for( i=0; i<=framePtr; i++ ) {
1085 pvInfoList[i].depth = -1;
1086 boards[i][EP_STATUS] = EP_NONE;
1087 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1093 /* [AS] Adjudication threshold */
1094 adjudicateLossThreshold = appData.adjudicateLossThreshold;
1096 InitEngine(&first, 0);
1097 InitEngine(&second, 1);
1100 pairing.which = "pairing"; // pairing engine
1101 pairing.pr = NoProc;
1103 pairing.program = appData.pairingEngine;
1104 pairing.host = "localhost";
1107 if (appData.icsActive) {
1108 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
1109 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1110 appData.clockMode = FALSE;
1111 first.sendTime = second.sendTime = 0;
1115 /* Override some settings from environment variables, for backward
1116 compatibility. Unfortunately it's not feasible to have the env
1117 vars just set defaults, at least in xboard. Ugh.
1119 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1124 if (!appData.icsActive) {
1128 /* Check for variants that are supported only in ICS mode,
1129 or not at all. Some that are accepted here nevertheless
1130 have bugs; see comments below.
1132 VariantClass variant = StringToVariant(appData.variant);
1134 case VariantBughouse: /* need four players and two boards */
1135 case VariantKriegspiel: /* need to hide pieces and move details */
1136 /* case VariantFischeRandom: (Fabien: moved below) */
1137 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1138 if( (len >= MSG_SIZ) && appData.debugMode )
1139 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1141 DisplayFatalError(buf, 0, 2);
1144 case VariantUnknown:
1145 case VariantLoadable:
1155 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1156 if( (len >= MSG_SIZ) && appData.debugMode )
1157 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1159 DisplayFatalError(buf, 0, 2);
1162 case VariantXiangqi: /* [HGM] repetition rules not implemented */
1163 case VariantFairy: /* [HGM] TestLegality definitely off! */
1164 case VariantGothic: /* [HGM] should work */
1165 case VariantCapablanca: /* [HGM] should work */
1166 case VariantCourier: /* [HGM] initial forced moves not implemented */
1167 case VariantShogi: /* [HGM] could still mate with pawn drop */
1168 case VariantChu: /* [HGM] experimental */
1169 case VariantKnightmate: /* [HGM] should work */
1170 case VariantCylinder: /* [HGM] untested */
1171 case VariantFalcon: /* [HGM] untested */
1172 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1173 offboard interposition not understood */
1174 case VariantNormal: /* definitely works! */
1175 case VariantWildCastle: /* pieces not automatically shuffled */
1176 case VariantNoCastle: /* pieces not automatically shuffled */
1177 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1178 case VariantLosers: /* should work except for win condition,
1179 and doesn't know captures are mandatory */
1180 case VariantSuicide: /* should work except for win condition,
1181 and doesn't know captures are mandatory */
1182 case VariantGiveaway: /* should work except for win condition,
1183 and doesn't know captures are mandatory */
1184 case VariantTwoKings: /* should work */
1185 case VariantAtomic: /* should work except for win condition */
1186 case Variant3Check: /* should work except for win condition */
1187 case VariantShatranj: /* should work except for all win conditions */
1188 case VariantMakruk: /* should work except for draw countdown */
1189 case VariantASEAN : /* should work except for draw countdown */
1190 case VariantBerolina: /* might work if TestLegality is off */
1191 case VariantCapaRandom: /* should work */
1192 case VariantJanus: /* should work */
1193 case VariantSuper: /* experimental */
1194 case VariantGreat: /* experimental, requires legality testing to be off */
1195 case VariantSChess: /* S-Chess, should work */
1196 case VariantGrand: /* should work */
1197 case VariantSpartan: /* should work */
1205 NextIntegerFromString (char ** str, long * value)
1210 while( *s == ' ' || *s == '\t' ) {
1216 if( *s >= '0' && *s <= '9' ) {
1217 while( *s >= '0' && *s <= '9' ) {
1218 *value = *value * 10 + (*s - '0');
1231 NextTimeControlFromString (char ** str, long * value)
1234 int result = NextIntegerFromString( str, &temp );
1237 *value = temp * 60; /* Minutes */
1238 if( **str == ':' ) {
1240 result = NextIntegerFromString( str, &temp );
1241 *value += temp; /* Seconds */
1249 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1250 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1251 int result = -1, type = 0; long temp, temp2;
1253 if(**str != ':') return -1; // old params remain in force!
1255 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1256 if( NextIntegerFromString( str, &temp ) ) return -1;
1257 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1260 /* time only: incremental or sudden-death time control */
1261 if(**str == '+') { /* increment follows; read it */
1263 if(**str == '!') type = *(*str)++; // Bronstein TC
1264 if(result = NextIntegerFromString( str, &temp2)) return -1;
1265 *inc = temp2 * 1000;
1266 if(**str == '.') { // read fraction of increment
1267 char *start = ++(*str);
1268 if(result = NextIntegerFromString( str, &temp2)) return -1;
1270 while(start++ < *str) temp2 /= 10;
1274 *moves = 0; *tc = temp * 1000; *incType = type;
1278 (*str)++; /* classical time control */
1279 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1291 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1292 { /* [HGM] get time to add from the multi-session time-control string */
1293 int incType, moves=1; /* kludge to force reading of first session */
1294 long time, increment;
1297 if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1299 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1300 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1301 if(movenr == -1) return time; /* last move before new session */
1302 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1303 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1304 if(!moves) return increment; /* current session is incremental */
1305 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1306 } while(movenr >= -1); /* try again for next session */
1308 return 0; // no new time quota on this move
1312 ParseTimeControl (char *tc, float ti, int mps)
1316 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1319 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1320 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1321 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1325 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1327 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1330 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1332 snprintf(buf, MSG_SIZ, ":%s", mytc);
1334 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1336 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1341 /* Parse second time control */
1344 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1352 timeControl_2 = tc2 * 1000;
1362 timeControl = tc1 * 1000;
1365 timeIncrement = ti * 1000; /* convert to ms */
1366 movesPerSession = 0;
1369 movesPerSession = mps;
1377 if (appData.debugMode) {
1378 # ifdef __GIT_VERSION
1379 fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1381 fprintf(debugFP, "Version: %s\n", programVersion);
1384 ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1386 set_cont_sequence(appData.wrapContSeq);
1387 if (appData.matchGames > 0) {
1388 appData.matchMode = TRUE;
1389 } else if (appData.matchMode) {
1390 appData.matchGames = 1;
1392 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1393 appData.matchGames = appData.sameColorGames;
1394 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1395 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1396 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1399 if (appData.noChessProgram || first.protocolVersion == 1) {
1402 /* kludge: allow timeout for initial "feature" commands */
1404 DisplayMessage("", _("Starting chess program"));
1405 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1410 CalculateIndex (int index, int gameNr)
1411 { // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1413 if(index > 0) return index; // fixed nmber
1414 if(index == 0) return 1;
1415 res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1416 if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1421 LoadGameOrPosition (int gameNr)
1422 { // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1423 if (*appData.loadGameFile != NULLCHAR) {
1424 if (!LoadGameFromFile(appData.loadGameFile,
1425 CalculateIndex(appData.loadGameIndex, gameNr),
1426 appData.loadGameFile, FALSE)) {
1427 DisplayFatalError(_("Bad game file"), 0, 1);
1430 } else if (*appData.loadPositionFile != NULLCHAR) {
1431 if (!LoadPositionFromFile(appData.loadPositionFile,
1432 CalculateIndex(appData.loadPositionIndex, gameNr),
1433 appData.loadPositionFile)) {
1434 DisplayFatalError(_("Bad position file"), 0, 1);
1442 ReserveGame (int gameNr, char resChar)
1444 FILE *tf = fopen(appData.tourneyFile, "r+");
1445 char *p, *q, c, buf[MSG_SIZ];
1446 if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1447 safeStrCpy(buf, lastMsg, MSG_SIZ);
1448 DisplayMessage(_("Pick new game"), "");
1449 flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1450 ParseArgsFromFile(tf);
1451 p = q = appData.results;
1452 if(appData.debugMode) {
1453 char *r = appData.participants;
1454 fprintf(debugFP, "results = '%s'\n", p);
1455 while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1456 fprintf(debugFP, "\n");
1458 while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1460 q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1461 safeStrCpy(q, p, strlen(p) + 2);
1462 if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1463 if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1464 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1465 if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1468 fseek(tf, -(strlen(p)+4), SEEK_END);
1470 if(c != '"') // depending on DOS or Unix line endings we can be one off
1471 fseek(tf, -(strlen(p)+2), SEEK_END);
1472 else fseek(tf, -(strlen(p)+3), SEEK_END);
1473 fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1474 DisplayMessage(buf, "");
1475 free(p); appData.results = q;
1476 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1477 (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1478 int round = appData.defaultMatchGames * appData.tourneyType;
1479 if(gameNr < 0 || appData.tourneyType < 1 || // gauntlet engine can always stay loaded as first engine
1480 appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1481 UnloadEngine(&first); // next game belongs to other pairing;
1482 UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1484 if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1488 MatchEvent (int mode)
1489 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1491 if(matchMode) { // already in match mode: switch it off
1493 if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1496 // if(gameMode != BeginningOfGame) {
1497 // DisplayError(_("You can only start a match from the initial position."), 0);
1501 if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1502 /* Set up machine vs. machine match */
1504 NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1505 if(appData.tourneyFile[0]) {
1507 if(nextGame > appData.matchGames) {
1509 if(strchr(appData.results, '*') == NULL) {
1511 appData.tourneyCycles++;
1512 if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1514 NextTourneyGame(-1, &dummy);
1516 if(nextGame <= appData.matchGames) {
1517 DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1519 ScheduleDelayedEvent(NextMatchGame, 10000);
1524 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1525 DisplayError(buf, 0);
1526 appData.tourneyFile[0] = 0;
1530 if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically
1531 DisplayFatalError(_("Can't have a match with no chess programs"),
1536 matchGame = roundNr = 1;
1537 first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1541 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1544 InitBackEnd3 P((void))
1546 GameMode initialMode;
1550 InitChessProgram(&first, startedFromSetupPosition);
1552 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1553 free(programVersion);
1554 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1555 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1556 FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1559 if (appData.icsActive) {
1561 /* [DM] Make a console window if needed [HGM] merged ifs */
1567 if (*appData.icsCommPort != NULLCHAR)
1568 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1569 appData.icsCommPort);
1571 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1572 appData.icsHost, appData.icsPort);
1574 if( (len >= MSG_SIZ) && appData.debugMode )
1575 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1577 DisplayFatalError(buf, err, 1);
1582 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1584 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1585 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1586 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1587 } else if (appData.noChessProgram) {
1593 if (*appData.cmailGameName != NULLCHAR) {
1595 OpenLoopback(&cmailPR);
1597 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1601 DisplayMessage("", "");
1602 if (StrCaseCmp(appData.initialMode, "") == 0) {
1603 initialMode = BeginningOfGame;
1604 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1605 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1606 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1607 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1610 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1611 initialMode = TwoMachinesPlay;
1612 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1613 initialMode = AnalyzeFile;
1614 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1615 initialMode = AnalyzeMode;
1616 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1617 initialMode = MachinePlaysWhite;
1618 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1619 initialMode = MachinePlaysBlack;
1620 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1621 initialMode = EditGame;
1622 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1623 initialMode = EditPosition;
1624 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1625 initialMode = Training;
1627 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1628 if( (len >= MSG_SIZ) && appData.debugMode )
1629 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1631 DisplayFatalError(buf, 0, 2);
1635 if (appData.matchMode) {
1636 if(appData.tourneyFile[0]) { // start tourney from command line
1638 if(f = fopen(appData.tourneyFile, "r")) {
1639 ParseArgsFromFile(f); // make sure tourney parmeters re known
1641 appData.clockMode = TRUE;
1643 } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1646 } else if (*appData.cmailGameName != NULLCHAR) {
1647 /* Set up cmail mode */
1648 ReloadCmailMsgEvent(TRUE);
1650 /* Set up other modes */
1651 if (initialMode == AnalyzeFile) {
1652 if (*appData.loadGameFile == NULLCHAR) {
1653 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1657 if (*appData.loadGameFile != NULLCHAR) {
1658 (void) LoadGameFromFile(appData.loadGameFile,
1659 appData.loadGameIndex,
1660 appData.loadGameFile, TRUE);
1661 } else if (*appData.loadPositionFile != NULLCHAR) {
1662 (void) LoadPositionFromFile(appData.loadPositionFile,
1663 appData.loadPositionIndex,
1664 appData.loadPositionFile);
1665 /* [HGM] try to make self-starting even after FEN load */
1666 /* to allow automatic setup of fairy variants with wtm */
1667 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1668 gameMode = BeginningOfGame;
1669 setboardSpoiledMachineBlack = 1;
1671 /* [HGM] loadPos: make that every new game uses the setup */
1672 /* from file as long as we do not switch variant */
1673 if(!blackPlaysFirst) {
1674 startedFromPositionFile = TRUE;
1675 CopyBoard(filePosition, boards[0]);
1678 if (initialMode == AnalyzeMode) {
1679 if (appData.noChessProgram) {
1680 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1683 if (appData.icsActive) {
1684 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1688 } else if (initialMode == AnalyzeFile) {
1689 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1690 ShowThinkingEvent();
1692 AnalysisPeriodicEvent(1);
1693 } else if (initialMode == MachinePlaysWhite) {
1694 if (appData.noChessProgram) {
1695 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1699 if (appData.icsActive) {
1700 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1704 MachineWhiteEvent();
1705 } else if (initialMode == MachinePlaysBlack) {
1706 if (appData.noChessProgram) {
1707 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1711 if (appData.icsActive) {
1712 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1716 MachineBlackEvent();
1717 } else if (initialMode == TwoMachinesPlay) {
1718 if (appData.noChessProgram) {
1719 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1723 if (appData.icsActive) {
1724 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1729 } else if (initialMode == EditGame) {
1731 } else if (initialMode == EditPosition) {
1732 EditPositionEvent();
1733 } else if (initialMode == Training) {
1734 if (*appData.loadGameFile == NULLCHAR) {
1735 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1744 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1746 DisplayBook(current+1);
1748 MoveHistorySet( movelist, first, last, current, pvInfoList );
1750 EvalGraphSet( first, last, current, pvInfoList );
1752 MakeEngineOutputTitle();
1756 * Establish will establish a contact to a remote host.port.
1757 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1758 * used to talk to the host.
1759 * Returns 0 if okay, error code if not.
1766 if (*appData.icsCommPort != NULLCHAR) {
1767 /* Talk to the host through a serial comm port */
1768 return OpenCommPort(appData.icsCommPort, &icsPR);
1770 } else if (*appData.gateway != NULLCHAR) {
1771 if (*appData.remoteShell == NULLCHAR) {
1772 /* Use the rcmd protocol to run telnet program on a gateway host */
1773 snprintf(buf, sizeof(buf), "%s %s %s",
1774 appData.telnetProgram, appData.icsHost, appData.icsPort);
1775 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1778 /* Use the rsh program to run telnet program on a gateway host */
1779 if (*appData.remoteUser == NULLCHAR) {
1780 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1781 appData.gateway, appData.telnetProgram,
1782 appData.icsHost, appData.icsPort);
1784 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1785 appData.remoteShell, appData.gateway,
1786 appData.remoteUser, appData.telnetProgram,
1787 appData.icsHost, appData.icsPort);
1789 return StartChildProcess(buf, "", &icsPR);
1792 } else if (appData.useTelnet) {
1793 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1796 /* TCP socket interface differs somewhat between
1797 Unix and NT; handle details in the front end.
1799 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1804 EscapeExpand (char *p, char *q)
1805 { // [HGM] initstring: routine to shape up string arguments
1806 while(*p++ = *q++) if(p[-1] == '\\')
1808 case 'n': p[-1] = '\n'; break;
1809 case 'r': p[-1] = '\r'; break;
1810 case 't': p[-1] = '\t'; break;
1811 case '\\': p[-1] = '\\'; break;
1812 case 0: *p = 0; return;
1813 default: p[-1] = q[-1]; break;
1818 show_bytes (FILE *fp, char *buf, int count)
1821 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1822 fprintf(fp, "\\%03o", *buf & 0xff);
1831 /* Returns an errno value */
1833 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1835 char buf[8192], *p, *q, *buflim;
1836 int left, newcount, outcount;
1838 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1839 *appData.gateway != NULLCHAR) {
1840 if (appData.debugMode) {
1841 fprintf(debugFP, ">ICS: ");
1842 show_bytes(debugFP, message, count);
1843 fprintf(debugFP, "\n");
1845 return OutputToProcess(pr, message, count, outError);
1848 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1855 if (appData.debugMode) {
1856 fprintf(debugFP, ">ICS: ");
1857 show_bytes(debugFP, buf, newcount);
1858 fprintf(debugFP, "\n");
1860 outcount = OutputToProcess(pr, buf, newcount, outError);
1861 if (outcount < newcount) return -1; /* to be sure */
1868 } else if (((unsigned char) *p) == TN_IAC) {
1869 *q++ = (char) TN_IAC;
1876 if (appData.debugMode) {
1877 fprintf(debugFP, ">ICS: ");
1878 show_bytes(debugFP, buf, newcount);
1879 fprintf(debugFP, "\n");
1881 outcount = OutputToProcess(pr, buf, newcount, outError);
1882 if (outcount < newcount) return -1; /* to be sure */
1887 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1889 int outError, outCount;
1890 static int gotEof = 0;
1893 /* Pass data read from player on to ICS */
1896 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1897 if (outCount < count) {
1898 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1900 if(have_sent_ICS_logon == 2) {
1901 if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1902 fprintf(ini, "%s", message);
1903 have_sent_ICS_logon = 3;
1905 have_sent_ICS_logon = 1;
1906 } else if(have_sent_ICS_logon == 3) {
1907 fprintf(ini, "%s", message);
1909 have_sent_ICS_logon = 1;
1911 } else if (count < 0) {
1912 RemoveInputSource(isr);
1913 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1914 } else if (gotEof++ > 0) {
1915 RemoveInputSource(isr);
1916 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1922 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1923 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1924 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1925 SendToICS("date\n");
1926 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1929 /* added routine for printf style output to ics */
1931 ics_printf (char *format, ...)
1933 char buffer[MSG_SIZ];
1936 va_start(args, format);
1937 vsnprintf(buffer, sizeof(buffer), format, args);
1938 buffer[sizeof(buffer)-1] = '\0';
1946 int count, outCount, outError;
1948 if (icsPR == NoProc) return;
1951 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1952 if (outCount < count) {
1953 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1957 /* This is used for sending logon scripts to the ICS. Sending
1958 without a delay causes problems when using timestamp on ICC
1959 (at least on my machine). */
1961 SendToICSDelayed (char *s, long msdelay)
1963 int count, outCount, outError;
1965 if (icsPR == NoProc) return;
1968 if (appData.debugMode) {
1969 fprintf(debugFP, ">ICS: ");
1970 show_bytes(debugFP, s, count);
1971 fprintf(debugFP, "\n");
1973 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1975 if (outCount < count) {
1976 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1981 /* Remove all highlighting escape sequences in s
1982 Also deletes any suffix starting with '('
1985 StripHighlightAndTitle (char *s)
1987 static char retbuf[MSG_SIZ];
1990 while (*s != NULLCHAR) {
1991 while (*s == '\033') {
1992 while (*s != NULLCHAR && !isalpha(*s)) s++;
1993 if (*s != NULLCHAR) s++;
1995 while (*s != NULLCHAR && *s != '\033') {
1996 if (*s == '(' || *s == '[') {
2007 /* Remove all highlighting escape sequences in s */
2009 StripHighlight (char *s)
2011 static char retbuf[MSG_SIZ];
2014 while (*s != NULLCHAR) {
2015 while (*s == '\033') {
2016 while (*s != NULLCHAR && !isalpha(*s)) s++;
2017 if (*s != NULLCHAR) s++;
2019 while (*s != NULLCHAR && *s != '\033') {
2027 char engineVariant[MSG_SIZ];
2028 char *variantNames[] = VARIANT_NAMES;
2030 VariantName (VariantClass v)
2032 if(v == VariantUnknown || *engineVariant) return engineVariant;
2033 return variantNames[v];
2037 /* Identify a variant from the strings the chess servers use or the
2038 PGN Variant tag names we use. */
2040 StringToVariant (char *e)
2044 VariantClass v = VariantNormal;
2045 int i, found = FALSE;
2051 /* [HGM] skip over optional board-size prefixes */
2052 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
2053 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
2054 while( *e++ != '_');
2057 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2061 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2062 if (StrCaseStr(e, variantNames[i])) {
2063 v = (VariantClass) i;
2070 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2071 || StrCaseStr(e, "wild/fr")
2072 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2073 v = VariantFischeRandom;
2074 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2075 (i = 1, p = StrCaseStr(e, "w"))) {
2077 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2084 case 0: /* FICS only, actually */
2086 /* Castling legal even if K starts on d-file */
2087 v = VariantWildCastle;
2092 /* Castling illegal even if K & R happen to start in
2093 normal positions. */
2094 v = VariantNoCastle;
2107 /* Castling legal iff K & R start in normal positions */
2113 /* Special wilds for position setup; unclear what to do here */
2114 v = VariantLoadable;
2117 /* Bizarre ICC game */
2118 v = VariantTwoKings;
2121 v = VariantKriegspiel;
2127 v = VariantFischeRandom;
2130 v = VariantCrazyhouse;
2133 v = VariantBughouse;
2139 /* Not quite the same as FICS suicide! */
2140 v = VariantGiveaway;
2146 v = VariantShatranj;
2149 /* Temporary names for future ICC types. The name *will* change in
2150 the next xboard/WinBoard release after ICC defines it. */
2188 v = VariantCapablanca;
2191 v = VariantKnightmate;
2197 v = VariantCylinder;
2203 v = VariantCapaRandom;
2206 v = VariantBerolina;
2218 /* Found "wild" or "w" in the string but no number;
2219 must assume it's normal chess. */
2223 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2224 if( (len >= MSG_SIZ) && appData.debugMode )
2225 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2227 DisplayError(buf, 0);
2233 if (appData.debugMode) {
2234 fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2235 e, wnum, VariantName(v));
2240 static int leftover_start = 0, leftover_len = 0;
2241 char star_match[STAR_MATCH_N][MSG_SIZ];
2243 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2244 advance *index beyond it, and set leftover_start to the new value of
2245 *index; else return FALSE. If pattern contains the character '*', it
2246 matches any sequence of characters not containing '\r', '\n', or the
2247 character following the '*' (if any), and the matched sequence(s) are
2248 copied into star_match.
2251 looking_at ( char *buf, int *index, char *pattern)
2253 char *bufp = &buf[*index], *patternp = pattern;
2255 char *matchp = star_match[0];
2258 if (*patternp == NULLCHAR) {
2259 *index = leftover_start = bufp - buf;
2263 if (*bufp == NULLCHAR) return FALSE;
2264 if (*patternp == '*') {
2265 if (*bufp == *(patternp + 1)) {
2267 matchp = star_match[++star_count];
2271 } else if (*bufp == '\n' || *bufp == '\r') {
2273 if (*patternp == NULLCHAR)
2278 *matchp++ = *bufp++;
2282 if (*patternp != *bufp) return FALSE;
2289 SendToPlayer (char *data, int length)
2291 int error, outCount;
2292 outCount = OutputToProcess(NoProc, data, length, &error);
2293 if (outCount < length) {
2294 DisplayFatalError(_("Error writing to display"), error, 1);
2299 PackHolding (char packed[], char *holding)
2309 switch (runlength) {
2320 sprintf(q, "%d", runlength);
2332 /* Telnet protocol requests from the front end */
2334 TelnetRequest (unsigned char ddww, unsigned char option)
2336 unsigned char msg[3];
2337 int outCount, outError;
2339 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2341 if (appData.debugMode) {
2342 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2358 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2367 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2370 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2375 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2377 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2384 if (!appData.icsActive) return;
2385 TelnetRequest(TN_DO, TN_ECHO);
2391 if (!appData.icsActive) return;
2392 TelnetRequest(TN_DONT, TN_ECHO);
2396 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2398 /* put the holdings sent to us by the server on the board holdings area */
2399 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2403 if(gameInfo.holdingsWidth < 2) return;
2404 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2405 return; // prevent overwriting by pre-board holdings
2407 if( (int)lowestPiece >= BlackPawn ) {
2410 holdingsStartRow = BOARD_HEIGHT-1;
2413 holdingsColumn = BOARD_WIDTH-1;
2414 countsColumn = BOARD_WIDTH-2;
2415 holdingsStartRow = 0;
2419 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2420 board[i][holdingsColumn] = EmptySquare;
2421 board[i][countsColumn] = (ChessSquare) 0;
2423 while( (p=*holdings++) != NULLCHAR ) {
2424 piece = CharToPiece( ToUpper(p) );
2425 if(piece == EmptySquare) continue;
2426 /*j = (int) piece - (int) WhitePawn;*/
2427 j = PieceToNumber(piece);
2428 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2429 if(j < 0) continue; /* should not happen */
2430 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2431 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2432 board[holdingsStartRow+j*direction][countsColumn]++;
2438 VariantSwitch (Board board, VariantClass newVariant)
2440 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2441 static Board oldBoard;
2443 startedFromPositionFile = FALSE;
2444 if(gameInfo.variant == newVariant) return;
2446 /* [HGM] This routine is called each time an assignment is made to
2447 * gameInfo.variant during a game, to make sure the board sizes
2448 * are set to match the new variant. If that means adding or deleting
2449 * holdings, we shift the playing board accordingly
2450 * This kludge is needed because in ICS observe mode, we get boards
2451 * of an ongoing game without knowing the variant, and learn about the
2452 * latter only later. This can be because of the move list we requested,
2453 * in which case the game history is refilled from the beginning anyway,
2454 * but also when receiving holdings of a crazyhouse game. In the latter
2455 * case we want to add those holdings to the already received position.
2459 if (appData.debugMode) {
2460 fprintf(debugFP, "Switch board from %s to %s\n",
2461 VariantName(gameInfo.variant), VariantName(newVariant));
2462 setbuf(debugFP, NULL);
2464 shuffleOpenings = 0; /* [HGM] shuffle */
2465 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2469 newWidth = 9; newHeight = 9;
2470 gameInfo.holdingsSize = 7;
2471 case VariantBughouse:
2472 case VariantCrazyhouse:
2473 newHoldingsWidth = 2; break;
2477 newHoldingsWidth = 2;
2478 gameInfo.holdingsSize = 8;
2481 case VariantCapablanca:
2482 case VariantCapaRandom:
2485 newHoldingsWidth = gameInfo.holdingsSize = 0;
2488 if(newWidth != gameInfo.boardWidth ||
2489 newHeight != gameInfo.boardHeight ||
2490 newHoldingsWidth != gameInfo.holdingsWidth ) {
2492 /* shift position to new playing area, if needed */
2493 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2494 for(i=0; i<BOARD_HEIGHT; i++)
2495 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2496 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2498 for(i=0; i<newHeight; i++) {
2499 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2500 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2502 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2503 for(i=0; i<BOARD_HEIGHT; i++)
2504 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2505 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2508 board[HOLDINGS_SET] = 0;
2509 gameInfo.boardWidth = newWidth;
2510 gameInfo.boardHeight = newHeight;
2511 gameInfo.holdingsWidth = newHoldingsWidth;
2512 gameInfo.variant = newVariant;
2513 InitDrawingSizes(-2, 0);
2514 } else gameInfo.variant = newVariant;
2515 CopyBoard(oldBoard, board); // remember correctly formatted board
2516 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2517 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2520 static int loggedOn = FALSE;
2522 /*-- Game start info cache: --*/
2524 char gs_kind[MSG_SIZ];
2525 static char player1Name[128] = "";
2526 static char player2Name[128] = "";
2527 static char cont_seq[] = "\n\\ ";
2528 static int player1Rating = -1;
2529 static int player2Rating = -1;
2530 /*----------------------------*/
2532 ColorClass curColor = ColorNormal;
2533 int suppressKibitz = 0;
2536 Boolean soughtPending = FALSE;
2537 Boolean seekGraphUp;
2538 #define MAX_SEEK_ADS 200
2540 char *seekAdList[MAX_SEEK_ADS];
2541 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2542 float tcList[MAX_SEEK_ADS];
2543 char colorList[MAX_SEEK_ADS];
2544 int nrOfSeekAds = 0;
2545 int minRating = 1010, maxRating = 2800;
2546 int hMargin = 10, vMargin = 20, h, w;
2547 extern int squareSize, lineGap;
2552 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2553 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2554 if(r < minRating+100 && r >=0 ) r = minRating+100;
2555 if(r > maxRating) r = maxRating;
2556 if(tc < 1.f) tc = 1.f;
2557 if(tc > 95.f) tc = 95.f;
2558 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2559 y = ((double)r - minRating)/(maxRating - minRating)
2560 * (h-vMargin-squareSize/8-1) + vMargin;
2561 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2562 if(strstr(seekAdList[i], " u ")) color = 1;
2563 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2564 !strstr(seekAdList[i], "bullet") &&
2565 !strstr(seekAdList[i], "blitz") &&
2566 !strstr(seekAdList[i], "standard") ) color = 2;
2567 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2568 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2572 PlotSingleSeekAd (int i)
2578 AddAd (char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2580 char buf[MSG_SIZ], *ext = "";
2581 VariantClass v = StringToVariant(type);
2582 if(strstr(type, "wild")) {
2583 ext = type + 4; // append wild number
2584 if(v == VariantFischeRandom) type = "chess960"; else
2585 if(v == VariantLoadable) type = "setup"; else
2586 type = VariantName(v);
2588 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2589 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2590 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2591 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2592 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2593 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2594 seekNrList[nrOfSeekAds] = nr;
2595 zList[nrOfSeekAds] = 0;
2596 seekAdList[nrOfSeekAds++] = StrSave(buf);
2597 if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2602 EraseSeekDot (int i)
2604 int x = xList[i], y = yList[i], d=squareSize/4, k;
2605 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2606 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2607 // now replot every dot that overlapped
2608 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2609 int xx = xList[k], yy = yList[k];
2610 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2611 DrawSeekDot(xx, yy, colorList[k]);
2616 RemoveSeekAd (int nr)
2619 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2621 if(seekAdList[i]) free(seekAdList[i]);
2622 seekAdList[i] = seekAdList[--nrOfSeekAds];
2623 seekNrList[i] = seekNrList[nrOfSeekAds];
2624 ratingList[i] = ratingList[nrOfSeekAds];
2625 colorList[i] = colorList[nrOfSeekAds];
2626 tcList[i] = tcList[nrOfSeekAds];
2627 xList[i] = xList[nrOfSeekAds];
2628 yList[i] = yList[nrOfSeekAds];
2629 zList[i] = zList[nrOfSeekAds];
2630 seekAdList[nrOfSeekAds] = NULL;
2636 MatchSoughtLine (char *line)
2638 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2639 int nr, base, inc, u=0; char dummy;
2641 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2642 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2644 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2645 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2646 // match: compact and save the line
2647 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2657 if(!seekGraphUp) return FALSE;
2658 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2659 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2661 DrawSeekBackground(0, 0, w, h);
2662 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2663 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2664 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2665 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2667 DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2670 snprintf(buf, MSG_SIZ, "%d", i);
2671 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2674 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2675 for(i=1; i<100; i+=(i<10?1:5)) {
2676 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2677 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2678 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2680 snprintf(buf, MSG_SIZ, "%d", i);
2681 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2684 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2689 SeekGraphClick (ClickType click, int x, int y, int moving)
2691 static int lastDown = 0, displayed = 0, lastSecond;
2692 if(y < 0) return FALSE;
2693 if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2694 (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2695 if(!seekGraphUp) return FALSE;
2696 seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2697 DrawPosition(TRUE, NULL);
2700 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2701 if(click == Release || moving) return FALSE;
2703 soughtPending = TRUE;
2704 SendToICS(ics_prefix);
2705 SendToICS("sought\n"); // should this be "sought all"?
2706 } else { // issue challenge based on clicked ad
2707 int dist = 10000; int i, closest = 0, second = 0;
2708 for(i=0; i<nrOfSeekAds; i++) {
2709 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2710 if(d < dist) { dist = d; closest = i; }
2711 second += (d - zList[i] < 120); // count in-range ads
2712 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2716 second = (second > 1);
2717 if(displayed != closest || second != lastSecond) {
2718 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2719 lastSecond = second; displayed = closest;
2721 if(click == Press) {
2722 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2725 } // on press 'hit', only show info
2726 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2727 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2728 SendToICS(ics_prefix);
2730 return TRUE; // let incoming board of started game pop down the graph
2731 } else if(click == Release) { // release 'miss' is ignored
2732 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2733 if(moving == 2) { // right up-click
2734 nrOfSeekAds = 0; // refresh graph
2735 soughtPending = TRUE;
2736 SendToICS(ics_prefix);
2737 SendToICS("sought\n"); // should this be "sought all"?
2740 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2741 // press miss or release hit 'pop down' seek graph
2742 seekGraphUp = FALSE;
2743 DrawPosition(TRUE, NULL);
2749 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2751 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2752 #define STARTED_NONE 0
2753 #define STARTED_MOVES 1
2754 #define STARTED_BOARD 2
2755 #define STARTED_OBSERVE 3
2756 #define STARTED_HOLDINGS 4
2757 #define STARTED_CHATTER 5
2758 #define STARTED_COMMENT 6
2759 #define STARTED_MOVES_NOHIDE 7
2761 static int started = STARTED_NONE;
2762 static char parse[20000];
2763 static int parse_pos = 0;
2764 static char buf[BUF_SIZE + 1];
2765 static int firstTime = TRUE, intfSet = FALSE;
2766 static ColorClass prevColor = ColorNormal;
2767 static int savingComment = FALSE;
2768 static int cmatch = 0; // continuation sequence match
2775 int backup; /* [DM] For zippy color lines */
2777 char talker[MSG_SIZ]; // [HGM] chat
2780 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2782 if (appData.debugMode) {
2784 fprintf(debugFP, "<ICS: ");
2785 show_bytes(debugFP, data, count);
2786 fprintf(debugFP, "\n");
2790 if (appData.debugMode) { int f = forwardMostMove;
2791 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2792 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2793 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2796 /* If last read ended with a partial line that we couldn't parse,
2797 prepend it to the new read and try again. */
2798 if (leftover_len > 0) {
2799 for (i=0; i<leftover_len; i++)
2800 buf[i] = buf[leftover_start + i];
2803 /* copy new characters into the buffer */
2804 bp = buf + leftover_len;
2805 buf_len=leftover_len;
2806 for (i=0; i<count; i++)
2809 if (data[i] == '\r')
2812 // join lines split by ICS?
2813 if (!appData.noJoin)
2816 Joining just consists of finding matches against the
2817 continuation sequence, and discarding that sequence
2818 if found instead of copying it. So, until a match
2819 fails, there's nothing to do since it might be the
2820 complete sequence, and thus, something we don't want
2823 if (data[i] == cont_seq[cmatch])
2826 if (cmatch == strlen(cont_seq))
2828 cmatch = 0; // complete match. just reset the counter
2831 it's possible for the ICS to not include the space
2832 at the end of the last word, making our [correct]
2833 join operation fuse two separate words. the server
2834 does this when the space occurs at the width setting.
2836 if (!buf_len || buf[buf_len-1] != ' ')
2847 match failed, so we have to copy what matched before
2848 falling through and copying this character. In reality,
2849 this will only ever be just the newline character, but
2850 it doesn't hurt to be precise.
2852 strncpy(bp, cont_seq, cmatch);
2864 buf[buf_len] = NULLCHAR;
2865 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2870 while (i < buf_len) {
2871 /* Deal with part of the TELNET option negotiation
2872 protocol. We refuse to do anything beyond the
2873 defaults, except that we allow the WILL ECHO option,
2874 which ICS uses to turn off password echoing when we are
2875 directly connected to it. We reject this option
2876 if localLineEditing mode is on (always on in xboard)
2877 and we are talking to port 23, which might be a real
2878 telnet server that will try to keep WILL ECHO on permanently.
2880 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2881 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2882 unsigned char option;
2884 switch ((unsigned char) buf[++i]) {
2886 if (appData.debugMode)
2887 fprintf(debugFP, "\n<WILL ");
2888 switch (option = (unsigned char) buf[++i]) {
2890 if (appData.debugMode)
2891 fprintf(debugFP, "ECHO ");
2892 /* Reply only if this is a change, according
2893 to the protocol rules. */
2894 if (remoteEchoOption) break;
2895 if (appData.localLineEditing &&
2896 atoi(appData.icsPort) == TN_PORT) {
2897 TelnetRequest(TN_DONT, TN_ECHO);
2900 TelnetRequest(TN_DO, TN_ECHO);
2901 remoteEchoOption = TRUE;
2905 if (appData.debugMode)
2906 fprintf(debugFP, "%d ", option);
2907 /* Whatever this is, we don't want it. */
2908 TelnetRequest(TN_DONT, option);
2913 if (appData.debugMode)
2914 fprintf(debugFP, "\n<WONT ");
2915 switch (option = (unsigned char) buf[++i]) {
2917 if (appData.debugMode)
2918 fprintf(debugFP, "ECHO ");
2919 /* Reply only if this is a change, according
2920 to the protocol rules. */
2921 if (!remoteEchoOption) break;
2923 TelnetRequest(TN_DONT, TN_ECHO);
2924 remoteEchoOption = FALSE;
2927 if (appData.debugMode)
2928 fprintf(debugFP, "%d ", (unsigned char) option);
2929 /* Whatever this is, it must already be turned
2930 off, because we never agree to turn on
2931 anything non-default, so according to the
2932 protocol rules, we don't reply. */
2937 if (appData.debugMode)
2938 fprintf(debugFP, "\n<DO ");
2939 switch (option = (unsigned char) buf[++i]) {
2941 /* Whatever this is, we refuse to do it. */
2942 if (appData.debugMode)
2943 fprintf(debugFP, "%d ", option);
2944 TelnetRequest(TN_WONT, option);
2949 if (appData.debugMode)
2950 fprintf(debugFP, "\n<DONT ");
2951 switch (option = (unsigned char) buf[++i]) {
2953 if (appData.debugMode)
2954 fprintf(debugFP, "%d ", option);
2955 /* Whatever this is, we are already not doing
2956 it, because we never agree to do anything
2957 non-default, so according to the protocol
2958 rules, we don't reply. */
2963 if (appData.debugMode)
2964 fprintf(debugFP, "\n<IAC ");
2965 /* Doubled IAC; pass it through */
2969 if (appData.debugMode)
2970 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2971 /* Drop all other telnet commands on the floor */
2974 if (oldi > next_out)
2975 SendToPlayer(&buf[next_out], oldi - next_out);
2981 /* OK, this at least will *usually* work */
2982 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2986 if (loggedOn && !intfSet) {
2987 if (ics_type == ICS_ICC) {
2988 snprintf(str, MSG_SIZ,
2989 "/set-quietly interface %s\n/set-quietly style 12\n",
2991 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2992 strcat(str, "/set-2 51 1\n/set seek 1\n");
2993 } else if (ics_type == ICS_CHESSNET) {
2994 snprintf(str, MSG_SIZ, "/style 12\n");
2996 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2997 strcat(str, programVersion);
2998 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2999 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3000 strcat(str, "$iset seekremove 1\n$set seek 1\n");
3002 strcat(str, "$iset nohighlight 1\n");
3004 strcat(str, "$iset lock 1\n$style 12\n");
3007 NotifyFrontendLogin();
3011 if (started == STARTED_COMMENT) {
3012 /* Accumulate characters in comment */
3013 parse[parse_pos++] = buf[i];
3014 if (buf[i] == '\n') {
3015 parse[parse_pos] = NULLCHAR;
3016 if(chattingPartner>=0) {
3018 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3019 OutputChatMessage(chattingPartner, mess);
3020 chattingPartner = -1;
3021 next_out = i+1; // [HGM] suppress printing in ICS window
3023 if(!suppressKibitz) // [HGM] kibitz
3024 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3025 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3026 int nrDigit = 0, nrAlph = 0, j;
3027 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3028 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3029 parse[parse_pos] = NULLCHAR;
3030 // try to be smart: if it does not look like search info, it should go to
3031 // ICS interaction window after all, not to engine-output window.
3032 for(j=0; j<parse_pos; j++) { // count letters and digits
3033 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3034 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
3035 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
3037 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3038 int depth=0; float score;
3039 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3040 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3041 pvInfoList[forwardMostMove-1].depth = depth;
3042 pvInfoList[forwardMostMove-1].score = 100*score;
3044 OutputKibitz(suppressKibitz, parse);
3047 if(gameMode == IcsObserving) // restore original ICS messages
3048 /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3049 snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3051 /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3052 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3053 SendToPlayer(tmp, strlen(tmp));
3055 next_out = i+1; // [HGM] suppress printing in ICS window
3057 started = STARTED_NONE;
3059 /* Don't match patterns against characters in comment */
3064 if (started == STARTED_CHATTER) {
3065 if (buf[i] != '\n') {
3066 /* Don't match patterns against characters in chatter */
3070 started = STARTED_NONE;
3071 if(suppressKibitz) next_out = i+1;
3074 /* Kludge to deal with rcmd protocol */
3075 if (firstTime && looking_at(buf, &i, "\001*")) {
3076 DisplayFatalError(&buf[1], 0, 1);
3082 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3085 if (appData.debugMode)
3086 fprintf(debugFP, "ics_type %d\n", ics_type);
3089 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3090 ics_type = ICS_FICS;
3092 if (appData.debugMode)
3093 fprintf(debugFP, "ics_type %d\n", ics_type);
3096 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3097 ics_type = ICS_CHESSNET;
3099 if (appData.debugMode)
3100 fprintf(debugFP, "ics_type %d\n", ics_type);
3105 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3106 looking_at(buf, &i, "Logging you in as \"*\"") ||
3107 looking_at(buf, &i, "will be \"*\""))) {
3108 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3112 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3114 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3115 DisplayIcsInteractionTitle(buf);
3116 have_set_title = TRUE;
3119 /* skip finger notes */
3120 if (started == STARTED_NONE &&
3121 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3122 (buf[i] == '1' && buf[i+1] == '0')) &&
3123 buf[i+2] == ':' && buf[i+3] == ' ') {
3124 started = STARTED_CHATTER;
3130 // [HGM] seekgraph: recognize sought lines and end-of-sought message
3131 if(appData.seekGraph) {
3132 if(soughtPending && MatchSoughtLine(buf+i)) {
3133 i = strstr(buf+i, "rated") - buf;
3134 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3135 next_out = leftover_start = i;
3136 started = STARTED_CHATTER;
3137 suppressKibitz = TRUE;
3140 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3141 && looking_at(buf, &i, "* ads displayed")) {
3142 soughtPending = FALSE;
3147 if(appData.autoRefresh) {
3148 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3149 int s = (ics_type == ICS_ICC); // ICC format differs
3151 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3152 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3153 looking_at(buf, &i, "*% "); // eat prompt
3154 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3155 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3156 next_out = i; // suppress
3159 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3160 char *p = star_match[0];
3162 if(seekGraphUp) RemoveSeekAd(atoi(p));
3163 while(*p && *p++ != ' '); // next
3165 looking_at(buf, &i, "*% "); // eat prompt
3166 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3173 /* skip formula vars */
3174 if (started == STARTED_NONE &&
3175 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3176 started = STARTED_CHATTER;
3181 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3182 if (appData.autoKibitz && started == STARTED_NONE &&
3183 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3184 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3185 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3186 looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3187 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3188 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3189 suppressKibitz = TRUE;
3190 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3192 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3193 && (gameMode == IcsPlayingWhite)) ||
3194 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3195 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3196 started = STARTED_CHATTER; // own kibitz we simply discard
3198 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3199 parse_pos = 0; parse[0] = NULLCHAR;
3200 savingComment = TRUE;
3201 suppressKibitz = gameMode != IcsObserving ? 2 :
3202 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3206 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3207 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3208 && atoi(star_match[0])) {
3209 // suppress the acknowledgements of our own autoKibitz
3211 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3212 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3213 SendToPlayer(star_match[0], strlen(star_match[0]));
3214 if(looking_at(buf, &i, "*% ")) // eat prompt
3215 suppressKibitz = FALSE;
3219 } // [HGM] kibitz: end of patch
3221 if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3223 // [HGM] chat: intercept tells by users for which we have an open chat window
3225 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3226 looking_at(buf, &i, "* whispers:") ||
3227 looking_at(buf, &i, "* kibitzes:") ||
3228 looking_at(buf, &i, "* shouts:") ||
3229 looking_at(buf, &i, "* c-shouts:") ||
3230 looking_at(buf, &i, "--> * ") ||
3231 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3232 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3233 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3234 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3236 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3237 chattingPartner = -1;
3239 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3240 for(p=0; p<MAX_CHAT; p++) {
3241 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3242 talker[0] = '['; strcat(talker, "] ");
3243 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3244 chattingPartner = p; break;
3247 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3248 for(p=0; p<MAX_CHAT; p++) {
3249 if(!strcmp("kibitzes", chatPartner[p])) {
3250 talker[0] = '['; strcat(talker, "] ");
3251 chattingPartner = p; break;
3254 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3255 for(p=0; p<MAX_CHAT; p++) {
3256 if(!strcmp("whispers", chatPartner[p])) {
3257 talker[0] = '['; strcat(talker, "] ");
3258 chattingPartner = p; break;
3261 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3262 if(buf[i-8] == '-' && buf[i-3] == 't')
3263 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3264 if(!strcmp("c-shouts", chatPartner[p])) {
3265 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3266 chattingPartner = p; break;
3269 if(chattingPartner < 0)
3270 for(p=0; p<MAX_CHAT; p++) {
3271 if(!strcmp("shouts", chatPartner[p])) {
3272 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3273 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3274 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3275 chattingPartner = p; break;
3279 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3280 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3281 talker[0] = 0; Colorize(ColorTell, FALSE);
3282 chattingPartner = p; break;
3284 if(chattingPartner<0) i = oldi; else {
3285 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3286 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3287 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3288 started = STARTED_COMMENT;
3289 parse_pos = 0; parse[0] = NULLCHAR;
3290 savingComment = 3 + chattingPartner; // counts as TRUE
3291 suppressKibitz = TRUE;
3294 } // [HGM] chat: end of patch
3297 if (appData.zippyTalk || appData.zippyPlay) {
3298 /* [DM] Backup address for color zippy lines */
3300 if (loggedOn == TRUE)
3301 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3302 (appData.zippyPlay && ZippyMatch(buf, &backup)));
3304 } // [DM] 'else { ' deleted
3306 /* Regular tells and says */
3307 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3308 looking_at(buf, &i, "* (your partner) tells you: ") ||
3309 looking_at(buf, &i, "* says: ") ||
3310 /* Don't color "message" or "messages" output */
3311 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3312 looking_at(buf, &i, "*. * at *:*: ") ||
3313 looking_at(buf, &i, "--* (*:*): ") ||
3314 /* Message notifications (same color as tells) */
3315 looking_at(buf, &i, "* has left a message ") ||
3316 looking_at(buf, &i, "* just sent you a message:\n") ||
3317 /* Whispers and kibitzes */
3318 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3319 looking_at(buf, &i, "* kibitzes: ") ||
3321 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3323 if (tkind == 1 && strchr(star_match[0], ':')) {
3324 /* Avoid "tells you:" spoofs in channels */
3327 if (star_match[0][0] == NULLCHAR ||
3328 strchr(star_match[0], ' ') ||
3329 (tkind == 3 && strchr(star_match[1], ' '))) {
3330 /* Reject bogus matches */
3333 if (appData.colorize) {
3334 if (oldi > next_out) {
3335 SendToPlayer(&buf[next_out], oldi - next_out);
3340 Colorize(ColorTell, FALSE);
3341 curColor = ColorTell;
3344 Colorize(ColorKibitz, FALSE);
3345 curColor = ColorKibitz;
3348 p = strrchr(star_match[1], '(');
3355 Colorize(ColorChannel1, FALSE);
3356 curColor = ColorChannel1;
3358 Colorize(ColorChannel, FALSE);
3359 curColor = ColorChannel;
3363 curColor = ColorNormal;
3367 if (started == STARTED_NONE && appData.autoComment &&
3368 (gameMode == IcsObserving ||
3369 gameMode == IcsPlayingWhite ||
3370 gameMode == IcsPlayingBlack)) {
3371 parse_pos = i - oldi;
3372 memcpy(parse, &buf[oldi], parse_pos);
3373 parse[parse_pos] = NULLCHAR;
3374 started = STARTED_COMMENT;
3375 savingComment = TRUE;
3377 started = STARTED_CHATTER;
3378 savingComment = FALSE;
3385 if (looking_at(buf, &i, "* s-shouts: ") ||
3386 looking_at(buf, &i, "* c-shouts: ")) {
3387 if (appData.colorize) {
3388 if (oldi > next_out) {
3389 SendToPlayer(&buf[next_out], oldi - next_out);
3392 Colorize(ColorSShout, FALSE);
3393 curColor = ColorSShout;
3396 started = STARTED_CHATTER;
3400 if (looking_at(buf, &i, "--->")) {
3405 if (looking_at(buf, &i, "* shouts: ") ||
3406 looking_at(buf, &i, "--> ")) {
3407 if (appData.colorize) {
3408 if (oldi > next_out) {
3409 SendToPlayer(&buf[next_out], oldi - next_out);
3412 Colorize(ColorShout, FALSE);
3413 curColor = ColorShout;
3416 started = STARTED_CHATTER;
3420 if (looking_at( buf, &i, "Challenge:")) {
3421 if (appData.colorize) {
3422 if (oldi > next_out) {
3423 SendToPlayer(&buf[next_out], oldi - next_out);
3426 Colorize(ColorChallenge, FALSE);
3427 curColor = ColorChallenge;
3433 if (looking_at(buf, &i, "* offers you") ||
3434 looking_at(buf, &i, "* offers to be") ||
3435 looking_at(buf, &i, "* would like to") ||
3436 looking_at(buf, &i, "* requests to") ||
3437 looking_at(buf, &i, "Your opponent offers") ||
3438 looking_at(buf, &i, "Your opponent requests")) {
3440 if (appData.colorize) {
3441 if (oldi > next_out) {
3442 SendToPlayer(&buf[next_out], oldi - next_out);
3445 Colorize(ColorRequest, FALSE);
3446 curColor = ColorRequest;
3451 if (looking_at(buf, &i, "* (*) seeking")) {
3452 if (appData.colorize) {
3453 if (oldi > next_out) {
3454 SendToPlayer(&buf[next_out], oldi - next_out);
3457 Colorize(ColorSeek, FALSE);
3458 curColor = ColorSeek;
3463 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3465 if (looking_at(buf, &i, "\\ ")) {
3466 if (prevColor != ColorNormal) {
3467 if (oldi > next_out) {
3468 SendToPlayer(&buf[next_out], oldi - next_out);
3471 Colorize(prevColor, TRUE);
3472 curColor = prevColor;
3474 if (savingComment) {
3475 parse_pos = i - oldi;
3476 memcpy(parse, &buf[oldi], parse_pos);
3477 parse[parse_pos] = NULLCHAR;
3478 started = STARTED_COMMENT;
3479 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3480 chattingPartner = savingComment - 3; // kludge to remember the box
3482 started = STARTED_CHATTER;
3487 if (looking_at(buf, &i, "Black Strength :") ||
3488 looking_at(buf, &i, "<<< style 10 board >>>") ||
3489 looking_at(buf, &i, "<10>") ||
3490 looking_at(buf, &i, "#@#")) {
3491 /* Wrong board style */
3493 SendToICS(ics_prefix);
3494 SendToICS("set style 12\n");
3495 SendToICS(ics_prefix);
3496 SendToICS("refresh\n");
3500 if (looking_at(buf, &i, "login:")) {
3501 if (!have_sent_ICS_logon) {
3503 have_sent_ICS_logon = 1;
3504 else // no init script was found
3505 have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3506 } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3507 have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3512 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3513 (looking_at(buf, &i, "\n<12> ") ||
3514 looking_at(buf, &i, "<12> "))) {
3516 if (oldi > next_out) {
3517 SendToPlayer(&buf[next_out], oldi - next_out);
3520 started = STARTED_BOARD;
3525 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3526 looking_at(buf, &i, "<b1> ")) {
3527 if (oldi > next_out) {
3528 SendToPlayer(&buf[next_out], oldi - next_out);
3531 started = STARTED_HOLDINGS;
3536 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3538 /* Header for a move list -- first line */
3540 switch (ics_getting_history) {
3544 case BeginningOfGame:
3545 /* User typed "moves" or "oldmoves" while we
3546 were idle. Pretend we asked for these
3547 moves and soak them up so user can step
3548 through them and/or save them.
3551 gameMode = IcsObserving;
3554 ics_getting_history = H_GOT_UNREQ_HEADER;
3556 case EditGame: /*?*/
3557 case EditPosition: /*?*/
3558 /* Should above feature work in these modes too? */
3559 /* For now it doesn't */
3560 ics_getting_history = H_GOT_UNWANTED_HEADER;
3563 ics_getting_history = H_GOT_UNWANTED_HEADER;
3568 /* Is this the right one? */
3569 if (gameInfo.white && gameInfo.black &&
3570 strcmp(gameInfo.white, star_match[0]) == 0 &&
3571 strcmp(gameInfo.black, star_match[2]) == 0) {
3573 ics_getting_history = H_GOT_REQ_HEADER;
3576 case H_GOT_REQ_HEADER:
3577 case H_GOT_UNREQ_HEADER:
3578 case H_GOT_UNWANTED_HEADER:
3579 case H_GETTING_MOVES:
3580 /* Should not happen */
3581 DisplayError(_("Error gathering move list: two headers"), 0);
3582 ics_getting_history = H_FALSE;
3586 /* Save player ratings into gameInfo if needed */
3587 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3588 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3589 (gameInfo.whiteRating == -1 ||
3590 gameInfo.blackRating == -1)) {
3592 gameInfo.whiteRating = string_to_rating(star_match[1]);
3593 gameInfo.blackRating = string_to_rating(star_match[3]);
3594 if (appData.debugMode)
3595 fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3596 gameInfo.whiteRating, gameInfo.blackRating);
3601 if (looking_at(buf, &i,
3602 "* * match, initial time: * minute*, increment: * second")) {
3603 /* Header for a move list -- second line */
3604 /* Initial board will follow if this is a wild game */
3605 if (gameInfo.event != NULL) free(gameInfo.event);
3606 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3607 gameInfo.event = StrSave(str);
3608 /* [HGM] we switched variant. Translate boards if needed. */
3609 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3613 if (looking_at(buf, &i, "Move ")) {
3614 /* Beginning of a move list */
3615 switch (ics_getting_history) {
3617 /* Normally should not happen */
3618 /* Maybe user hit reset while we were parsing */
3621 /* Happens if we are ignoring a move list that is not
3622 * the one we just requested. Common if the user
3623 * tries to observe two games without turning off
3626 case H_GETTING_MOVES:
3627 /* Should not happen */
3628 DisplayError(_("Error gathering move list: nested"), 0);
3629 ics_getting_history = H_FALSE;
3631 case H_GOT_REQ_HEADER:
3632 ics_getting_history = H_GETTING_MOVES;
3633 started = STARTED_MOVES;
3635 if (oldi > next_out) {
3636 SendToPlayer(&buf[next_out], oldi - next_out);
3639 case H_GOT_UNREQ_HEADER:
3640 ics_getting_history = H_GETTING_MOVES;
3641 started = STARTED_MOVES_NOHIDE;
3644 case H_GOT_UNWANTED_HEADER:
3645 ics_getting_history = H_FALSE;
3651 if (looking_at(buf, &i, "% ") ||
3652 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3653 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3654 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3655 soughtPending = FALSE;
3659 if(suppressKibitz) next_out = i;
3660 savingComment = FALSE;
3664 case STARTED_MOVES_NOHIDE:
3665 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3666 parse[parse_pos + i - oldi] = NULLCHAR;
3667 ParseGameHistory(parse);
3669 if (appData.zippyPlay && first.initDone) {
3670 FeedMovesToProgram(&first, forwardMostMove);
3671 if (gameMode == IcsPlayingWhite) {
3672 if (WhiteOnMove(forwardMostMove)) {
3673 if (first.sendTime) {
3674 if (first.useColors) {
3675 SendToProgram("black\n", &first);
3677 SendTimeRemaining(&first, TRUE);
3679 if (first.useColors) {
3680 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3682 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3683 first.maybeThinking = TRUE;
3685 if (first.usePlayother) {
3686 if (first.sendTime) {
3687 SendTimeRemaining(&first, TRUE);
3689 SendToProgram("playother\n", &first);
3695 } else if (gameMode == IcsPlayingBlack) {
3696 if (!WhiteOnMove(forwardMostMove)) {
3697 if (first.sendTime) {
3698 if (first.useColors) {
3699 SendToProgram("white\n", &first);
3701 SendTimeRemaining(&first, FALSE);
3703 if (first.useColors) {
3704 SendToProgram("black\n", &first);
3706 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3707 first.maybeThinking = TRUE;
3709 if (first.usePlayother) {
3710 if (first.sendTime) {
3711 SendTimeRemaining(&first, FALSE);
3713 SendToProgram("playother\n", &first);
3722 if (gameMode == IcsObserving && ics_gamenum == -1) {
3723 /* Moves came from oldmoves or moves command
3724 while we weren't doing anything else.
3726 currentMove = forwardMostMove;
3727 ClearHighlights();/*!!could figure this out*/
3728 flipView = appData.flipView;
3729 DrawPosition(TRUE, boards[currentMove]);
3730 DisplayBothClocks();
3731 snprintf(str, MSG_SIZ, "%s %s %s",
3732 gameInfo.white, _("vs."), gameInfo.black);
3736 /* Moves were history of an active game */
3737 if (gameInfo.resultDetails != NULL) {
3738 free(gameInfo.resultDetails);
3739 gameInfo.resultDetails = NULL;
3742 HistorySet(parseList, backwardMostMove,
3743 forwardMostMove, currentMove-1);
3744 DisplayMove(currentMove - 1);
3745 if (started == STARTED_MOVES) next_out = i;
3746 started = STARTED_NONE;
3747 ics_getting_history = H_FALSE;
3750 case STARTED_OBSERVE:
3751 started = STARTED_NONE;
3752 SendToICS(ics_prefix);
3753 SendToICS("refresh\n");
3759 if(bookHit) { // [HGM] book: simulate book reply
3760 static char bookMove[MSG_SIZ]; // a bit generous?
3762 programStats.nodes = programStats.depth = programStats.time =
3763 programStats.score = programStats.got_only_move = 0;
3764 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3766 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3767 strcat(bookMove, bookHit);
3768 HandleMachineMove(bookMove, &first);
3773 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3774 started == STARTED_HOLDINGS ||
3775 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3776 /* Accumulate characters in move list or board */
3777 parse[parse_pos++] = buf[i];
3780 /* Start of game messages. Mostly we detect start of game
3781 when the first board image arrives. On some versions
3782 of the ICS, though, we need to do a "refresh" after starting
3783 to observe in order to get the current board right away. */
3784 if (looking_at(buf, &i, "Adding game * to observation list")) {
3785 started = STARTED_OBSERVE;
3789 /* Handle auto-observe */
3790 if (appData.autoObserve &&
3791 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3792 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3794 /* Choose the player that was highlighted, if any. */
3795 if (star_match[0][0] == '\033' ||
3796 star_match[1][0] != '\033') {
3797 player = star_match[0];
3799 player = star_match[2];
3801 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3802 ics_prefix, StripHighlightAndTitle(player));
3805 /* Save ratings from notify string */
3806 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3807 player1Rating = string_to_rating(star_match[1]);
3808 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3809 player2Rating = string_to_rating(star_match[3]);
3811 if (appData.debugMode)
3813 "Ratings from 'Game notification:' %s %d, %s %d\n",
3814 player1Name, player1Rating,
3815 player2Name, player2Rating);
3820 /* Deal with automatic examine mode after a game,
3821 and with IcsObserving -> IcsExamining transition */
3822 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3823 looking_at(buf, &i, "has made you an examiner of game *")) {
3825 int gamenum = atoi(star_match[0]);
3826 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3827 gamenum == ics_gamenum) {
3828 /* We were already playing or observing this game;
3829 no need to refetch history */
3830 gameMode = IcsExamining;
3832 pauseExamForwardMostMove = forwardMostMove;
3833 } else if (currentMove < forwardMostMove) {
3834 ForwardInner(forwardMostMove);
3837 /* I don't think this case really can happen */
3838 SendToICS(ics_prefix);
3839 SendToICS("refresh\n");
3844 /* Error messages */
3845 // if (ics_user_moved) {
3846 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3847 if (looking_at(buf, &i, "Illegal move") ||
3848 looking_at(buf, &i, "Not a legal move") ||
3849 looking_at(buf, &i, "Your king is in check") ||
3850 looking_at(buf, &i, "It isn't your turn") ||
3851 looking_at(buf, &i, "It is not your move")) {
3853 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3854 currentMove = forwardMostMove-1;
3855 DisplayMove(currentMove - 1); /* before DMError */
3856 DrawPosition(FALSE, boards[currentMove]);
3857 SwitchClocks(forwardMostMove-1); // [HGM] race
3858 DisplayBothClocks();
3860 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3866 if (looking_at(buf, &i, "still have time") ||
3867 looking_at(buf, &i, "not out of time") ||
3868 looking_at(buf, &i, "either player is out of time") ||
3869 looking_at(buf, &i, "has timeseal; checking")) {