2 * backend.c -- Common back end for X and Windows NT versions of
4 * Copyright 1991 by Digital Equipment Corporation, Maynard,
7 * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8 * 2007, 2008, 2009, 2010, 2011, 2012, 2013 Free Software Foundation, Inc.
10 * Enhancements Copyright 2005 Alessandro Scotti
12 * The following terms apply to Digital Equipment Corporation's copyright
14 * ------------------------------------------------------------------------
17 * Permission to use, copy, modify, and distribute this software and its
18 * documentation for any purpose and without fee is hereby granted,
19 * provided that the above copyright notice appear in all copies and that
20 * both that copyright notice and this permission notice appear in
21 * supporting documentation, and that the name of Digital not be
22 * used in advertising or publicity pertaining to distribution of the
23 * software without specific, written prior permission.
25 * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27 * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
32 * ------------------------------------------------------------------------
34 * The following terms apply to the enhanced version of XBoard
35 * distributed by the Free Software Foundation:
36 * ------------------------------------------------------------------------
38 * GNU XBoard is free software: you can redistribute it and/or modify
39 * it under the terms of the GNU General Public License as published by
40 * the Free Software Foundation, either version 3 of the License, or (at
41 * your option) any later version.
43 * GNU XBoard is distributed in the hope that it will be useful, but
44 * WITHOUT ANY WARRANTY; without even the implied warranty of
45 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46 * General Public License for more details.
48 * You should have received a copy of the GNU General Public License
49 * along with this program. If not, see http://www.gnu.org/licenses/. *
51 *------------------------------------------------------------------------
52 ** See the file ChangeLog for a revision history. */
54 /* [AS] Also useful here for debugging */
58 int flock(int f, int code);
75 #include <sys/types.h>
84 #else /* not STDC_HEADERS */
87 # else /* not HAVE_STRING_H */
89 # endif /* not HAVE_STRING_H */
90 #endif /* not STDC_HEADERS */
93 # include <sys/fcntl.h>
94 #else /* not HAVE_SYS_FCNTL_H */
97 # endif /* HAVE_FCNTL_H */
98 #endif /* not HAVE_SYS_FCNTL_H */
100 #if TIME_WITH_SYS_TIME
101 # include <sys/time.h>
105 # include <sys/time.h>
111 #if defined(_amigados) && !defined(__GNUC__)
116 extern int gettimeofday(struct timeval *, struct timezone *);
124 #include "frontend.h"
131 #include "backendz.h"
132 #include "evalgraph.h"
136 # define _(s) gettext (s)
137 # define N_(s) gettext_noop (s)
138 # define T_(s) gettext(s)
151 int establish P((void));
152 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
153 char *buf, int count, int error));
154 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
155 char *buf, int count, int error));
156 void SendToICS P((char *s));
157 void SendToICSDelayed P((char *s, long msdelay));
158 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
159 void HandleMachineMove P((char *message, ChessProgramState *cps));
160 int AutoPlayOneMove P((void));
161 int LoadGameOneMove P((ChessMove readAhead));
162 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
163 int LoadPositionFromFile P((char *filename, int n, char *title));
164 int SavePositionToFile P((char *filename));
165 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
166 void ShowMove P((int fromX, int fromY, int toX, int toY));
167 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
168 /*char*/int promoChar));
169 void BackwardInner P((int target));
170 void ForwardInner P((int target));
171 int Adjudicate P((ChessProgramState *cps));
172 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
173 void EditPositionDone P((Boolean fakeRights));
174 void PrintOpponents P((FILE *fp));
175 void PrintPosition P((FILE *fp, int move));
176 void StartChessProgram P((ChessProgramState *cps));
177 void SendToProgram P((char *message, ChessProgramState *cps));
178 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
179 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
180 char *buf, int count, int error));
181 void SendTimeControl P((ChessProgramState *cps,
182 int mps, long tc, int inc, int sd, int st));
183 char *TimeControlTagValue P((void));
184 void Attention P((ChessProgramState *cps));
185 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
186 int ResurrectChessProgram P((void));
187 void DisplayComment P((int moveNumber, char *text));
188 void DisplayMove P((int moveNumber));
190 void ParseGameHistory P((char *game));
191 void ParseBoard12 P((char *string));
192 void KeepAlive P((void));
193 void StartClocks P((void));
194 void SwitchClocks P((int nr));
195 void StopClocks P((void));
196 void ResetClocks P((void));
197 char *PGNDate P((void));
198 void SetGameInfo P((void));
199 int RegisterMove P((void));
200 void MakeRegisteredMove P((void));
201 void TruncateGame P((void));
202 int looking_at P((char *, int *, char *));
203 void CopyPlayerNameIntoFileName P((char **, char *));
204 char *SavePart P((char *));
205 int SaveGameOldStyle P((FILE *));
206 int SaveGamePGN P((FILE *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
220 void NextMatchGame P((void));
221 int NextTourneyGame P((int nr, int *swap));
222 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
223 FILE *WriteTourneyFile P((char *results, FILE *f));
224 void DisplayTwoMachinesTitle P(());
225 static void ExcludeClick P((int index));
226 void ToggleSecond P((void));
227 void PauseEngine P((ChessProgramState *cps));
228 static int NonStandardBoardSize P((void));
231 extern void ConsoleCreate();
234 ChessProgramState *WhitePlayer();
235 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
236 int VerifyDisplayMode P(());
238 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
239 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
240 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
241 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
242 void ics_update_width P((int new_width));
243 extern char installDir[MSG_SIZ];
244 VariantClass startVariant; /* [HGM] nicks: initial variant */
247 extern int tinyLayout, smallLayout;
248 ChessProgramStats programStats;
249 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
251 static int exiting = 0; /* [HGM] moved to top */
252 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
253 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
254 Board partnerBoard; /* [HGM] bughouse: for peeking at partner game */
255 int partnerHighlight[2];
256 Boolean partnerBoardValid = 0;
257 char partnerStatus[MSG_SIZ];
259 Boolean originalFlip;
260 Boolean twoBoards = 0;
261 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
262 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
263 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
264 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
265 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
266 int opponentKibitzes;
267 int lastSavedGame; /* [HGM] save: ID of game */
268 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
269 extern int chatCount;
271 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
272 char legal[BOARD_RANKS][BOARD_FILES]; /* [HGM] legal target squares */
273 char lastMsg[MSG_SIZ];
274 ChessSquare pieceSweep = EmptySquare;
275 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
276 int promoDefaultAltered;
277 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
279 /* States for ics_getting_history */
281 #define H_REQUESTED 1
282 #define H_GOT_REQ_HEADER 2
283 #define H_GOT_UNREQ_HEADER 3
284 #define H_GETTING_MOVES 4
285 #define H_GOT_UNWANTED_HEADER 5
287 /* whosays values for GameEnds */
296 /* Maximum number of games in a cmail message */
297 #define CMAIL_MAX_GAMES 20
299 /* Different types of move when calling RegisterMove */
301 #define CMAIL_RESIGN 1
303 #define CMAIL_ACCEPT 3
305 /* Different types of result to remember for each game */
306 #define CMAIL_NOT_RESULT 0
307 #define CMAIL_OLD_RESULT 1
308 #define CMAIL_NEW_RESULT 2
310 /* Telnet protocol constants */
321 safeStrCpy (char *dst, const char *src, size_t count)
324 assert( dst != NULL );
325 assert( src != NULL );
328 for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
329 if( i == count && dst[count-1] != NULLCHAR)
331 dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
332 if(appData.debugMode)
333 fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
339 /* Some compiler can't cast u64 to double
340 * This function do the job for us:
342 * We use the highest bit for cast, this only
343 * works if the highest bit is not
344 * in use (This should not happen)
346 * We used this for all compiler
349 u64ToDouble (u64 value)
352 u64 tmp = value & u64Const(0x7fffffffffffffff);
353 r = (double)(s64)tmp;
354 if (value & u64Const(0x8000000000000000))
355 r += 9.2233720368547758080e18; /* 2^63 */
359 /* Fake up flags for now, as we aren't keeping track of castling
360 availability yet. [HGM] Change of logic: the flag now only
361 indicates the type of castlings allowed by the rule of the game.
362 The actual rights themselves are maintained in the array
363 castlingRights, as part of the game history, and are not probed
369 int flags = F_ALL_CASTLE_OK;
370 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
371 switch (gameInfo.variant) {
373 flags &= ~F_ALL_CASTLE_OK;
374 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
375 flags |= F_IGNORE_CHECK;
377 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
380 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
382 case VariantKriegspiel:
383 flags |= F_KRIEGSPIEL_CAPTURE;
385 case VariantCapaRandom:
386 case VariantFischeRandom:
387 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
388 case VariantNoCastle:
389 case VariantShatranj:
394 flags &= ~F_ALL_CASTLE_OK;
402 FILE *gameFileFP, *debugFP, *serverFP;
403 char *currentDebugFile; // [HGM] debug split: to remember name
406 [AS] Note: sometimes, the sscanf() function is used to parse the input
407 into a fixed-size buffer. Because of this, we must be prepared to
408 receive strings as long as the size of the input buffer, which is currently
409 set to 4K for Windows and 8K for the rest.
410 So, we must either allocate sufficiently large buffers here, or
411 reduce the size of the input buffer in the input reading part.
414 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
415 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
416 char thinkOutput1[MSG_SIZ*10];
418 ChessProgramState first, second, pairing;
420 /* premove variables */
423 int premoveFromX = 0;
424 int premoveFromY = 0;
425 int premovePromoChar = 0;
427 Boolean alarmSounded;
428 /* end premove variables */
430 char *ics_prefix = "$";
431 enum ICS_TYPE ics_type = ICS_GENERIC;
433 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
434 int pauseExamForwardMostMove = 0;
435 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
436 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
437 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
438 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
439 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
440 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
441 int whiteFlag = FALSE, blackFlag = FALSE;
442 int userOfferedDraw = FALSE;
443 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
444 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
445 int cmailMoveType[CMAIL_MAX_GAMES];
446 long ics_clock_paused = 0;
447 ProcRef icsPR = NoProc, cmailPR = NoProc;
448 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
449 GameMode gameMode = BeginningOfGame;
450 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
451 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
452 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
453 int hiddenThinkOutputState = 0; /* [AS] */
454 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
455 int adjudicateLossPlies = 6;
456 char white_holding[64], black_holding[64];
457 TimeMark lastNodeCountTime;
458 long lastNodeCount=0;
459 int shiftKey, controlKey; // [HGM] set by mouse handler
461 int have_sent_ICS_logon = 0;
463 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
464 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
465 Boolean adjustedClock;
466 long timeControl_2; /* [AS] Allow separate time controls */
467 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
468 long timeRemaining[2][MAX_MOVES];
469 int matchGame = 0, nextGame = 0, roundNr = 0;
470 Boolean waitingForGame = FALSE, startingEngine = FALSE;
471 TimeMark programStartTime, pauseStart;
472 char ics_handle[MSG_SIZ];
473 int have_set_title = 0;
475 /* animateTraining preserves the state of appData.animate
476 * when Training mode is activated. This allows the
477 * response to be animated when appData.animate == TRUE and
478 * appData.animateDragging == TRUE.
480 Boolean animateTraining;
486 Board boards[MAX_MOVES];
487 /* [HGM] Following 7 needed for accurate legality tests: */
488 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
489 signed char initialRights[BOARD_FILES];
490 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
491 int initialRulePlies, FENrulePlies;
492 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
494 Boolean shuffleOpenings;
495 int mute; // mute all sounds
497 // [HGM] vari: next 12 to save and restore variations
498 #define MAX_VARIATIONS 10
499 int framePtr = MAX_MOVES-1; // points to free stack entry
501 int savedFirst[MAX_VARIATIONS];
502 int savedLast[MAX_VARIATIONS];
503 int savedFramePtr[MAX_VARIATIONS];
504 char *savedDetails[MAX_VARIATIONS];
505 ChessMove savedResult[MAX_VARIATIONS];
507 void PushTail P((int firstMove, int lastMove));
508 Boolean PopTail P((Boolean annotate));
509 void PushInner P((int firstMove, int lastMove));
510 void PopInner P((Boolean annotate));
511 void CleanupTail P((void));
513 ChessSquare FIDEArray[2][BOARD_FILES] = {
514 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
515 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
516 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
517 BlackKing, BlackBishop, BlackKnight, BlackRook }
520 ChessSquare twoKingsArray[2][BOARD_FILES] = {
521 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
522 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
523 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
524 BlackKing, BlackKing, BlackKnight, BlackRook }
527 ChessSquare KnightmateArray[2][BOARD_FILES] = {
528 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
529 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
530 { BlackRook, BlackMan, BlackBishop, BlackQueen,
531 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
534 ChessSquare SpartanArray[2][BOARD_FILES] = {
535 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
536 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
537 { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
538 BlackDragon, BlackKing, BlackAngel, BlackAlfil }
541 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
542 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
543 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
544 { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
545 BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
548 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
549 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
550 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
551 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
552 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
555 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
556 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
557 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
558 { BlackRook, BlackKnight, BlackMan, BlackFerz,
559 BlackKing, BlackMan, BlackKnight, BlackRook }
562 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
563 { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
564 WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
565 { BlackRook, BlackKnight, BlackMan, BlackFerz,
566 BlackKing, BlackMan, BlackKnight, BlackRook }
570 #if (BOARD_FILES>=10)
571 ChessSquare ShogiArray[2][BOARD_FILES] = {
572 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
573 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
574 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
575 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
578 ChessSquare XiangqiArray[2][BOARD_FILES] = {
579 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
580 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
581 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
582 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
585 ChessSquare CapablancaArray[2][BOARD_FILES] = {
586 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
587 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
588 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
589 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
592 ChessSquare GreatArray[2][BOARD_FILES] = {
593 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
594 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
595 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
596 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
599 ChessSquare JanusArray[2][BOARD_FILES] = {
600 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
601 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
602 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
603 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
606 ChessSquare GrandArray[2][BOARD_FILES] = {
607 { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
608 WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
609 { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
610 BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
614 ChessSquare GothicArray[2][BOARD_FILES] = {
615 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
616 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
617 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
618 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
621 #define GothicArray CapablancaArray
625 ChessSquare FalconArray[2][BOARD_FILES] = {
626 { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
627 WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
628 { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
629 BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
632 #define FalconArray CapablancaArray
635 #else // !(BOARD_FILES>=10)
636 #define XiangqiPosition FIDEArray
637 #define CapablancaArray FIDEArray
638 #define GothicArray FIDEArray
639 #define GreatArray FIDEArray
640 #endif // !(BOARD_FILES>=10)
642 #if (BOARD_FILES>=12)
643 ChessSquare CourierArray[2][BOARD_FILES] = {
644 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
645 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
646 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
647 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
649 #else // !(BOARD_FILES>=12)
650 #define CourierArray CapablancaArray
651 #endif // !(BOARD_FILES>=12)
654 Board initialPosition;
657 /* Convert str to a rating. Checks for special cases of "----",
659 "++++", etc. Also strips ()'s */
661 string_to_rating (char *str)
663 while(*str && !isdigit(*str)) ++str;
665 return 0; /* One of the special "no rating" cases */
673 /* Init programStats */
674 programStats.movelist[0] = 0;
675 programStats.depth = 0;
676 programStats.nr_moves = 0;
677 programStats.moves_left = 0;
678 programStats.nodes = 0;
679 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
680 programStats.score = 0;
681 programStats.got_only_move = 0;
682 programStats.got_fail = 0;
683 programStats.line_is_book = 0;
688 { // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
689 if (appData.firstPlaysBlack) {
690 first.twoMachinesColor = "black\n";
691 second.twoMachinesColor = "white\n";
693 first.twoMachinesColor = "white\n";
694 second.twoMachinesColor = "black\n";
697 first.other = &second;
698 second.other = &first;
701 if(appData.timeOddsMode) {
702 norm = appData.timeOdds[0];
703 if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
705 first.timeOdds = appData.timeOdds[0]/norm;
706 second.timeOdds = appData.timeOdds[1]/norm;
709 if(programVersion) free(programVersion);
710 if (appData.noChessProgram) {
711 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
712 sprintf(programVersion, "%s", PACKAGE_STRING);
714 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
715 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
716 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
721 UnloadEngine (ChessProgramState *cps)
723 /* Kill off first chess program */
724 if (cps->isr != NULL)
725 RemoveInputSource(cps->isr);
728 if (cps->pr != NoProc) {
730 DoSleep( appData.delayBeforeQuit );
731 SendToProgram("quit\n", cps);
732 DoSleep( appData.delayAfterQuit );
733 DestroyChildProcess(cps->pr, cps->useSigterm);
736 if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
740 ClearOptions (ChessProgramState *cps)
743 cps->nrOptions = cps->comboCnt = 0;
744 for(i=0; i<MAX_OPTIONS; i++) {
745 cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
746 cps->option[i].textValue = 0;
750 char *engineNames[] = {
751 /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
752 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
754 /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
755 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
760 InitEngine (ChessProgramState *cps, int n)
761 { // [HGM] all engine initialiation put in a function that does one engine
765 cps->which = engineNames[n];
766 cps->maybeThinking = FALSE;
770 cps->sendDrawOffers = 1;
772 cps->program = appData.chessProgram[n];
773 cps->host = appData.host[n];
774 cps->dir = appData.directory[n];
775 cps->initString = appData.engInitString[n];
776 cps->computerString = appData.computerString[n];
777 cps->useSigint = TRUE;
778 cps->useSigterm = TRUE;
779 cps->reuse = appData.reuse[n];
780 cps->nps = appData.NPS[n]; // [HGM] nps: copy nodes per second
781 cps->useSetboard = FALSE;
783 cps->usePing = FALSE;
786 cps->usePlayother = FALSE;
787 cps->useColors = TRUE;
788 cps->useUsermove = FALSE;
789 cps->sendICS = FALSE;
790 cps->sendName = appData.icsActive;
791 cps->sdKludge = FALSE;
792 cps->stKludge = FALSE;
793 if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
794 TidyProgramName(cps->program, cps->host, cps->tidy);
796 ASSIGN(cps->variants, appData.variant);
797 cps->analysisSupport = 2; /* detect */
798 cps->analyzing = FALSE;
799 cps->initDone = FALSE;
802 /* New features added by Tord: */
803 cps->useFEN960 = FALSE;
804 cps->useOOCastle = TRUE;
805 /* End of new features added by Tord. */
806 cps->fenOverride = appData.fenOverride[n];
808 /* [HGM] time odds: set factor for each machine */
809 cps->timeOdds = appData.timeOdds[n];
811 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
812 cps->accumulateTC = appData.accumulateTC[n];
813 cps->maxNrOfSessions = 1;
818 cps->supportsNPS = UNKNOWN;
819 cps->memSize = FALSE;
820 cps->maxCores = FALSE;
821 ASSIGN(cps->egtFormats, "");
824 cps->optionSettings = appData.engOptions[n];
826 cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
827 cps->isUCI = appData.isUCI[n]; /* [AS] */
828 cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
831 if (appData.protocolVersion[n] > PROTOVER
832 || appData.protocolVersion[n] < 1)
837 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
838 appData.protocolVersion[n]);
839 if( (len >= MSG_SIZ) && appData.debugMode )
840 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
842 DisplayFatalError(buf, 0, 2);
846 cps->protocolVersion = appData.protocolVersion[n];
849 InitEngineUCI( installDir, cps ); // [HGM] moved here from winboard.c, to make available in xboard
850 ParseFeatures(appData.featureDefaults, cps);
853 ChessProgramState *savCps;
861 if(WaitForEngine(savCps, LoadEngine)) return;
862 CommonEngineInit(); // recalculate time odds
863 if(gameInfo.variant != StringToVariant(appData.variant)) {
864 // we changed variant when loading the engine; this forces us to reset
865 Reset(TRUE, savCps != &first);
866 oldMode = BeginningOfGame; // to prevent restoring old mode
868 InitChessProgram(savCps, FALSE);
869 if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
870 DisplayMessage("", "");
871 if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
872 for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
875 if(oldMode == AnalyzeMode) AnalyzeModeEvent();
879 ReplaceEngine (ChessProgramState *cps, int n)
881 oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
883 if(oldMode != BeginningOfGame) EditGameEvent();
886 appData.noChessProgram = FALSE;
887 appData.clockMode = TRUE;
890 if(n) return; // only startup first engine immediately; second can wait
891 savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
895 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
896 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
898 static char resetOptions[] =
899 "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
900 "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
901 "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
902 "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
905 FloatToFront(char **list, char *engineLine)
907 char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
909 if(appData.recentEngines <= 0) return;
910 TidyProgramName(engineLine, "localhost", tidy+1);
911 tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
912 strncpy(buf+1, *list, MSG_SIZ-50);
913 if(p = strstr(buf, tidy)) { // tidy name appears in list
914 q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
915 while(*p++ = *++q); // squeeze out
917 strcat(tidy, buf+1); // put list behind tidy name
918 p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
919 if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
920 ASSIGN(*list, tidy+1);
923 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
926 Load (ChessProgramState *cps, int i)
928 char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
929 if(engineLine && engineLine[0]) { // an engine was selected from the combo box
930 snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
931 SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
932 ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
933 FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
934 appData.firstProtocolVersion = PROTOVER;
935 ParseArgsFromString(buf);
937 ReplaceEngine(cps, i);
938 FloatToFront(&appData.recentEngineList, engineLine);
942 while(q = strchr(p, SLASH)) p = q+1;
943 if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
944 if(engineDir[0] != NULLCHAR) {
945 ASSIGN(appData.directory[i], engineDir); p = engineName;
946 } else if(p != engineName) { // derive directory from engine path, when not given
948 ASSIGN(appData.directory[i], engineName);
950 if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
951 } else { ASSIGN(appData.directory[i], "."); }
953 if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
954 snprintf(command, MSG_SIZ, "%s %s", p, params);
957 ASSIGN(appData.chessProgram[i], p);
958 appData.isUCI[i] = isUCI;
959 appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
960 appData.hasOwnBookUCI[i] = hasBook;
961 if(!nickName[0]) useNick = FALSE;
962 if(useNick) ASSIGN(appData.pgnName[i], nickName);
966 q = firstChessProgramNames;
967 if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
968 quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
969 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
970 quote, p, quote, appData.directory[i],
971 useNick ? " -fn \"" : "",
972 useNick ? nickName : "",
974 v1 ? " -firstProtocolVersion 1" : "",
975 hasBook ? "" : " -fNoOwnBookUCI",
976 isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
977 storeVariant ? " -variant " : "",
978 storeVariant ? VariantName(gameInfo.variant) : "");
979 if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
980 firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
981 if(insert != q) insert[-1] = NULLCHAR;
982 snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
984 FloatToFront(&appData.recentEngineList, buf);
986 ReplaceEngine(cps, i);
992 int matched, min, sec;
994 * Parse timeControl resource
996 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
997 appData.movesPerSession)) {
999 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1000 DisplayFatalError(buf, 0, 2);
1004 * Parse searchTime resource
1006 if (*appData.searchTime != NULLCHAR) {
1007 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1009 searchTime = min * 60;
1010 } else if (matched == 2) {
1011 searchTime = min * 60 + sec;
1014 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1015 DisplayFatalError(buf, 0, 2);
1024 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1025 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1027 GetTimeMark(&programStartTime);
1028 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1029 appData.seedBase = random() + (random()<<15);
1030 pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1032 ClearProgramStats();
1033 programStats.ok_to_send = 1;
1034 programStats.seen_stat = 0;
1037 * Initialize game list
1043 * Internet chess server status
1045 if (appData.icsActive) {
1046 appData.matchMode = FALSE;
1047 appData.matchGames = 0;
1049 appData.noChessProgram = !appData.zippyPlay;
1051 appData.zippyPlay = FALSE;
1052 appData.zippyTalk = FALSE;
1053 appData.noChessProgram = TRUE;
1055 if (*appData.icsHelper != NULLCHAR) {
1056 appData.useTelnet = TRUE;
1057 appData.telnetProgram = appData.icsHelper;
1060 appData.zippyTalk = appData.zippyPlay = FALSE;
1063 /* [AS] Initialize pv info list [HGM] and game state */
1067 for( i=0; i<=framePtr; i++ ) {
1068 pvInfoList[i].depth = -1;
1069 boards[i][EP_STATUS] = EP_NONE;
1070 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1076 /* [AS] Adjudication threshold */
1077 adjudicateLossThreshold = appData.adjudicateLossThreshold;
1079 InitEngine(&first, 0);
1080 InitEngine(&second, 1);
1083 pairing.which = "pairing"; // pairing engine
1084 pairing.pr = NoProc;
1086 pairing.program = appData.pairingEngine;
1087 pairing.host = "localhost";
1090 if (appData.icsActive) {
1091 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
1092 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1093 appData.clockMode = FALSE;
1094 first.sendTime = second.sendTime = 0;
1098 /* Override some settings from environment variables, for backward
1099 compatibility. Unfortunately it's not feasible to have the env
1100 vars just set defaults, at least in xboard. Ugh.
1102 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1107 if (!appData.icsActive) {
1111 /* Check for variants that are supported only in ICS mode,
1112 or not at all. Some that are accepted here nevertheless
1113 have bugs; see comments below.
1115 VariantClass variant = StringToVariant(appData.variant);
1117 case VariantBughouse: /* need four players and two boards */
1118 case VariantKriegspiel: /* need to hide pieces and move details */
1119 /* case VariantFischeRandom: (Fabien: moved below) */
1120 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1121 if( (len >= MSG_SIZ) && appData.debugMode )
1122 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1124 DisplayFatalError(buf, 0, 2);
1127 case VariantUnknown:
1128 case VariantLoadable:
1138 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1139 if( (len >= MSG_SIZ) && appData.debugMode )
1140 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1142 DisplayFatalError(buf, 0, 2);
1145 case VariantXiangqi: /* [HGM] repetition rules not implemented */
1146 case VariantFairy: /* [HGM] TestLegality definitely off! */
1147 case VariantGothic: /* [HGM] should work */
1148 case VariantCapablanca: /* [HGM] should work */
1149 case VariantCourier: /* [HGM] initial forced moves not implemented */
1150 case VariantShogi: /* [HGM] could still mate with pawn drop */
1151 case VariantKnightmate: /* [HGM] should work */
1152 case VariantCylinder: /* [HGM] untested */
1153 case VariantFalcon: /* [HGM] untested */
1154 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1155 offboard interposition not understood */
1156 case VariantNormal: /* definitely works! */
1157 case VariantWildCastle: /* pieces not automatically shuffled */
1158 case VariantNoCastle: /* pieces not automatically shuffled */
1159 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1160 case VariantLosers: /* should work except for win condition,
1161 and doesn't know captures are mandatory */
1162 case VariantSuicide: /* should work except for win condition,
1163 and doesn't know captures are mandatory */
1164 case VariantGiveaway: /* should work except for win condition,
1165 and doesn't know captures are mandatory */
1166 case VariantTwoKings: /* should work */
1167 case VariantAtomic: /* should work except for win condition */
1168 case Variant3Check: /* should work except for win condition */
1169 case VariantShatranj: /* should work except for all win conditions */
1170 case VariantMakruk: /* should work except for draw countdown */
1171 case VariantASEAN : /* should work except for draw countdown */
1172 case VariantBerolina: /* might work if TestLegality is off */
1173 case VariantCapaRandom: /* should work */
1174 case VariantJanus: /* should work */
1175 case VariantSuper: /* experimental */
1176 case VariantGreat: /* experimental, requires legality testing to be off */
1177 case VariantSChess: /* S-Chess, should work */
1178 case VariantGrand: /* should work */
1179 case VariantSpartan: /* should work */
1187 NextIntegerFromString (char ** str, long * value)
1192 while( *s == ' ' || *s == '\t' ) {
1198 if( *s >= '0' && *s <= '9' ) {
1199 while( *s >= '0' && *s <= '9' ) {
1200 *value = *value * 10 + (*s - '0');
1213 NextTimeControlFromString (char ** str, long * value)
1216 int result = NextIntegerFromString( str, &temp );
1219 *value = temp * 60; /* Minutes */
1220 if( **str == ':' ) {
1222 result = NextIntegerFromString( str, &temp );
1223 *value += temp; /* Seconds */
1231 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1232 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1233 int result = -1, type = 0; long temp, temp2;
1235 if(**str != ':') return -1; // old params remain in force!
1237 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1238 if( NextIntegerFromString( str, &temp ) ) return -1;
1239 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1242 /* time only: incremental or sudden-death time control */
1243 if(**str == '+') { /* increment follows; read it */
1245 if(**str == '!') type = *(*str)++; // Bronstein TC
1246 if(result = NextIntegerFromString( str, &temp2)) return -1;
1247 *inc = temp2 * 1000;
1248 if(**str == '.') { // read fraction of increment
1249 char *start = ++(*str);
1250 if(result = NextIntegerFromString( str, &temp2)) return -1;
1252 while(start++ < *str) temp2 /= 10;
1256 *moves = 0; *tc = temp * 1000; *incType = type;
1260 (*str)++; /* classical time control */
1261 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1273 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1274 { /* [HGM] get time to add from the multi-session time-control string */
1275 int incType, moves=1; /* kludge to force reading of first session */
1276 long time, increment;
1279 if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1281 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1282 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1283 if(movenr == -1) return time; /* last move before new session */
1284 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1285 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1286 if(!moves) return increment; /* current session is incremental */
1287 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1288 } while(movenr >= -1); /* try again for next session */
1290 return 0; // no new time quota on this move
1294 ParseTimeControl (char *tc, float ti, int mps)
1298 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1301 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1302 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1303 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1307 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1309 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1312 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1314 snprintf(buf, MSG_SIZ, ":%s", mytc);
1316 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1318 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1323 /* Parse second time control */
1326 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1334 timeControl_2 = tc2 * 1000;
1344 timeControl = tc1 * 1000;
1347 timeIncrement = ti * 1000; /* convert to ms */
1348 movesPerSession = 0;
1351 movesPerSession = mps;
1359 if (appData.debugMode) {
1360 # ifdef __GIT_VERSION
1361 fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1363 fprintf(debugFP, "Version: %s\n", programVersion);
1366 ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1368 set_cont_sequence(appData.wrapContSeq);
1369 if (appData.matchGames > 0) {
1370 appData.matchMode = TRUE;
1371 } else if (appData.matchMode) {
1372 appData.matchGames = 1;
1374 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1375 appData.matchGames = appData.sameColorGames;
1376 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1377 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1378 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1381 if (appData.noChessProgram || first.protocolVersion == 1) {
1384 /* kludge: allow timeout for initial "feature" commands */
1386 DisplayMessage("", _("Starting chess program"));
1387 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1392 CalculateIndex (int index, int gameNr)
1393 { // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1395 if(index > 0) return index; // fixed nmber
1396 if(index == 0) return 1;
1397 res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1398 if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1403 LoadGameOrPosition (int gameNr)
1404 { // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1405 if (*appData.loadGameFile != NULLCHAR) {
1406 if (!LoadGameFromFile(appData.loadGameFile,
1407 CalculateIndex(appData.loadGameIndex, gameNr),
1408 appData.loadGameFile, FALSE)) {
1409 DisplayFatalError(_("Bad game file"), 0, 1);
1412 } else if (*appData.loadPositionFile != NULLCHAR) {
1413 if (!LoadPositionFromFile(appData.loadPositionFile,
1414 CalculateIndex(appData.loadPositionIndex, gameNr),
1415 appData.loadPositionFile)) {
1416 DisplayFatalError(_("Bad position file"), 0, 1);
1424 ReserveGame (int gameNr, char resChar)
1426 FILE *tf = fopen(appData.tourneyFile, "r+");
1427 char *p, *q, c, buf[MSG_SIZ];
1428 if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1429 safeStrCpy(buf, lastMsg, MSG_SIZ);
1430 DisplayMessage(_("Pick new game"), "");
1431 flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1432 ParseArgsFromFile(tf);
1433 p = q = appData.results;
1434 if(appData.debugMode) {
1435 char *r = appData.participants;
1436 fprintf(debugFP, "results = '%s'\n", p);
1437 while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1438 fprintf(debugFP, "\n");
1440 while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1442 q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1443 safeStrCpy(q, p, strlen(p) + 2);
1444 if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1445 if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1446 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1447 if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1450 fseek(tf, -(strlen(p)+4), SEEK_END);
1452 if(c != '"') // depending on DOS or Unix line endings we can be one off
1453 fseek(tf, -(strlen(p)+2), SEEK_END);
1454 else fseek(tf, -(strlen(p)+3), SEEK_END);
1455 fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1456 DisplayMessage(buf, "");
1457 free(p); appData.results = q;
1458 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1459 (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1460 int round = appData.defaultMatchGames * appData.tourneyType;
1461 if(gameNr < 0 || appData.tourneyType < 1 || // gauntlet engine can always stay loaded as first engine
1462 appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1463 UnloadEngine(&first); // next game belongs to other pairing;
1464 UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1466 if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1470 MatchEvent (int mode)
1471 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1473 if(matchMode) { // already in match mode: switch it off
1475 if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1478 // if(gameMode != BeginningOfGame) {
1479 // DisplayError(_("You can only start a match from the initial position."), 0);
1483 if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1484 /* Set up machine vs. machine match */
1486 NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1487 if(appData.tourneyFile[0]) {
1489 if(nextGame > appData.matchGames) {
1491 if(strchr(appData.results, '*') == NULL) {
1493 appData.tourneyCycles++;
1494 if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1496 NextTourneyGame(-1, &dummy);
1498 if(nextGame <= appData.matchGames) {
1499 DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1501 ScheduleDelayedEvent(NextMatchGame, 10000);
1506 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1507 DisplayError(buf, 0);
1508 appData.tourneyFile[0] = 0;
1512 if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically
1513 DisplayFatalError(_("Can't have a match with no chess programs"),
1518 matchGame = roundNr = 1;
1519 first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1523 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1526 InitBackEnd3 P((void))
1528 GameMode initialMode;
1532 InitChessProgram(&first, startedFromSetupPosition);
1534 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1535 free(programVersion);
1536 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1537 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1538 FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1541 if (appData.icsActive) {
1543 /* [DM] Make a console window if needed [HGM] merged ifs */
1549 if (*appData.icsCommPort != NULLCHAR)
1550 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1551 appData.icsCommPort);
1553 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1554 appData.icsHost, appData.icsPort);
1556 if( (len >= MSG_SIZ) && appData.debugMode )
1557 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1559 DisplayFatalError(buf, err, 1);
1564 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1566 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1567 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1568 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1569 } else if (appData.noChessProgram) {
1575 if (*appData.cmailGameName != NULLCHAR) {
1577 OpenLoopback(&cmailPR);
1579 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1583 DisplayMessage("", "");
1584 if (StrCaseCmp(appData.initialMode, "") == 0) {
1585 initialMode = BeginningOfGame;
1586 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1587 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1588 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1589 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1592 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1593 initialMode = TwoMachinesPlay;
1594 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1595 initialMode = AnalyzeFile;
1596 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1597 initialMode = AnalyzeMode;
1598 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1599 initialMode = MachinePlaysWhite;
1600 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1601 initialMode = MachinePlaysBlack;
1602 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1603 initialMode = EditGame;
1604 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1605 initialMode = EditPosition;
1606 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1607 initialMode = Training;
1609 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1610 if( (len >= MSG_SIZ) && appData.debugMode )
1611 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1613 DisplayFatalError(buf, 0, 2);
1617 if (appData.matchMode) {
1618 if(appData.tourneyFile[0]) { // start tourney from command line
1620 if(f = fopen(appData.tourneyFile, "r")) {
1621 ParseArgsFromFile(f); // make sure tourney parmeters re known
1623 appData.clockMode = TRUE;
1625 } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1628 } else if (*appData.cmailGameName != NULLCHAR) {
1629 /* Set up cmail mode */
1630 ReloadCmailMsgEvent(TRUE);
1632 /* Set up other modes */
1633 if (initialMode == AnalyzeFile) {
1634 if (*appData.loadGameFile == NULLCHAR) {
1635 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1639 if (*appData.loadGameFile != NULLCHAR) {
1640 (void) LoadGameFromFile(appData.loadGameFile,
1641 appData.loadGameIndex,
1642 appData.loadGameFile, TRUE);
1643 } else if (*appData.loadPositionFile != NULLCHAR) {
1644 (void) LoadPositionFromFile(appData.loadPositionFile,
1645 appData.loadPositionIndex,
1646 appData.loadPositionFile);
1647 /* [HGM] try to make self-starting even after FEN load */
1648 /* to allow automatic setup of fairy variants with wtm */
1649 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1650 gameMode = BeginningOfGame;
1651 setboardSpoiledMachineBlack = 1;
1653 /* [HGM] loadPos: make that every new game uses the setup */
1654 /* from file as long as we do not switch variant */
1655 if(!blackPlaysFirst) {
1656 startedFromPositionFile = TRUE;
1657 CopyBoard(filePosition, boards[0]);
1660 if (initialMode == AnalyzeMode) {
1661 if (appData.noChessProgram) {
1662 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1665 if (appData.icsActive) {
1666 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1670 } else if (initialMode == AnalyzeFile) {
1671 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1672 ShowThinkingEvent();
1674 AnalysisPeriodicEvent(1);
1675 } else if (initialMode == MachinePlaysWhite) {
1676 if (appData.noChessProgram) {
1677 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1681 if (appData.icsActive) {
1682 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1686 MachineWhiteEvent();
1687 } else if (initialMode == MachinePlaysBlack) {
1688 if (appData.noChessProgram) {
1689 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1693 if (appData.icsActive) {
1694 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1698 MachineBlackEvent();
1699 } else if (initialMode == TwoMachinesPlay) {
1700 if (appData.noChessProgram) {
1701 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1705 if (appData.icsActive) {
1706 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1711 } else if (initialMode == EditGame) {
1713 } else if (initialMode == EditPosition) {
1714 EditPositionEvent();
1715 } else if (initialMode == Training) {
1716 if (*appData.loadGameFile == NULLCHAR) {
1717 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1726 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1728 DisplayBook(current+1);
1730 MoveHistorySet( movelist, first, last, current, pvInfoList );
1732 EvalGraphSet( first, last, current, pvInfoList );
1734 MakeEngineOutputTitle();
1738 * Establish will establish a contact to a remote host.port.
1739 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1740 * used to talk to the host.
1741 * Returns 0 if okay, error code if not.
1748 if (*appData.icsCommPort != NULLCHAR) {
1749 /* Talk to the host through a serial comm port */
1750 return OpenCommPort(appData.icsCommPort, &icsPR);
1752 } else if (*appData.gateway != NULLCHAR) {
1753 if (*appData.remoteShell == NULLCHAR) {
1754 /* Use the rcmd protocol to run telnet program on a gateway host */
1755 snprintf(buf, sizeof(buf), "%s %s %s",
1756 appData.telnetProgram, appData.icsHost, appData.icsPort);
1757 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1760 /* Use the rsh program to run telnet program on a gateway host */
1761 if (*appData.remoteUser == NULLCHAR) {
1762 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1763 appData.gateway, appData.telnetProgram,
1764 appData.icsHost, appData.icsPort);
1766 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1767 appData.remoteShell, appData.gateway,
1768 appData.remoteUser, appData.telnetProgram,
1769 appData.icsHost, appData.icsPort);
1771 return StartChildProcess(buf, "", &icsPR);
1774 } else if (appData.useTelnet) {
1775 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1778 /* TCP socket interface differs somewhat between
1779 Unix and NT; handle details in the front end.
1781 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1786 EscapeExpand (char *p, char *q)
1787 { // [HGM] initstring: routine to shape up string arguments
1788 while(*p++ = *q++) if(p[-1] == '\\')
1790 case 'n': p[-1] = '\n'; break;
1791 case 'r': p[-1] = '\r'; break;
1792 case 't': p[-1] = '\t'; break;
1793 case '\\': p[-1] = '\\'; break;
1794 case 0: *p = 0; return;
1795 default: p[-1] = q[-1]; break;
1800 show_bytes (FILE *fp, char *buf, int count)
1803 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1804 fprintf(fp, "\\%03o", *buf & 0xff);
1813 /* Returns an errno value */
1815 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1817 char buf[8192], *p, *q, *buflim;
1818 int left, newcount, outcount;
1820 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1821 *appData.gateway != NULLCHAR) {
1822 if (appData.debugMode) {
1823 fprintf(debugFP, ">ICS: ");
1824 show_bytes(debugFP, message, count);
1825 fprintf(debugFP, "\n");
1827 return OutputToProcess(pr, message, count, outError);
1830 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1837 if (appData.debugMode) {
1838 fprintf(debugFP, ">ICS: ");
1839 show_bytes(debugFP, buf, newcount);
1840 fprintf(debugFP, "\n");
1842 outcount = OutputToProcess(pr, buf, newcount, outError);
1843 if (outcount < newcount) return -1; /* to be sure */
1850 } else if (((unsigned char) *p) == TN_IAC) {
1851 *q++ = (char) TN_IAC;
1858 if (appData.debugMode) {
1859 fprintf(debugFP, ">ICS: ");
1860 show_bytes(debugFP, buf, newcount);
1861 fprintf(debugFP, "\n");
1863 outcount = OutputToProcess(pr, buf, newcount, outError);
1864 if (outcount < newcount) return -1; /* to be sure */
1869 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1871 int outError, outCount;
1872 static int gotEof = 0;
1875 /* Pass data read from player on to ICS */
1878 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1879 if (outCount < count) {
1880 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1882 if(have_sent_ICS_logon == 2) {
1883 if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1884 fprintf(ini, "%s", message);
1885 have_sent_ICS_logon = 3;
1887 have_sent_ICS_logon = 1;
1888 } else if(have_sent_ICS_logon == 3) {
1889 fprintf(ini, "%s", message);
1891 have_sent_ICS_logon = 1;
1893 } else if (count < 0) {
1894 RemoveInputSource(isr);
1895 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1896 } else if (gotEof++ > 0) {
1897 RemoveInputSource(isr);
1898 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1904 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1905 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1906 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1907 SendToICS("date\n");
1908 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1911 /* added routine for printf style output to ics */
1913 ics_printf (char *format, ...)
1915 char buffer[MSG_SIZ];
1918 va_start(args, format);
1919 vsnprintf(buffer, sizeof(buffer), format, args);
1920 buffer[sizeof(buffer)-1] = '\0';
1928 int count, outCount, outError;
1930 if (icsPR == NoProc) return;
1933 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1934 if (outCount < count) {
1935 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1939 /* This is used for sending logon scripts to the ICS. Sending
1940 without a delay causes problems when using timestamp on ICC
1941 (at least on my machine). */
1943 SendToICSDelayed (char *s, long msdelay)
1945 int count, outCount, outError;
1947 if (icsPR == NoProc) return;
1950 if (appData.debugMode) {
1951 fprintf(debugFP, ">ICS: ");
1952 show_bytes(debugFP, s, count);
1953 fprintf(debugFP, "\n");
1955 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1957 if (outCount < count) {
1958 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1963 /* Remove all highlighting escape sequences in s
1964 Also deletes any suffix starting with '('
1967 StripHighlightAndTitle (char *s)
1969 static char retbuf[MSG_SIZ];
1972 while (*s != NULLCHAR) {
1973 while (*s == '\033') {
1974 while (*s != NULLCHAR && !isalpha(*s)) s++;
1975 if (*s != NULLCHAR) s++;
1977 while (*s != NULLCHAR && *s != '\033') {
1978 if (*s == '(' || *s == '[') {
1989 /* Remove all highlighting escape sequences in s */
1991 StripHighlight (char *s)
1993 static char retbuf[MSG_SIZ];
1996 while (*s != NULLCHAR) {
1997 while (*s == '\033') {
1998 while (*s != NULLCHAR && !isalpha(*s)) s++;
1999 if (*s != NULLCHAR) s++;
2001 while (*s != NULLCHAR && *s != '\033') {
2009 char engineVariant[MSG_SIZ];
2010 char *variantNames[] = VARIANT_NAMES;
2012 VariantName (VariantClass v)
2014 if(v == VariantUnknown) return engineVariant;
2015 return variantNames[v];
2019 /* Identify a variant from the strings the chess servers use or the
2020 PGN Variant tag names we use. */
2022 StringToVariant (char *e)
2026 VariantClass v = VariantNormal;
2027 int i, found = FALSE;
2033 /* [HGM] skip over optional board-size prefixes */
2034 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
2035 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
2036 while( *e++ != '_');
2039 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2043 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2044 if (StrCaseStr(e, variantNames[i])) {
2045 v = (VariantClass) i;
2052 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2053 || StrCaseStr(e, "wild/fr")
2054 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2055 v = VariantFischeRandom;
2056 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2057 (i = 1, p = StrCaseStr(e, "w"))) {
2059 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2066 case 0: /* FICS only, actually */
2068 /* Castling legal even if K starts on d-file */
2069 v = VariantWildCastle;
2074 /* Castling illegal even if K & R happen to start in
2075 normal positions. */
2076 v = VariantNoCastle;
2089 /* Castling legal iff K & R start in normal positions */
2095 /* Special wilds for position setup; unclear what to do here */
2096 v = VariantLoadable;
2099 /* Bizarre ICC game */
2100 v = VariantTwoKings;
2103 v = VariantKriegspiel;
2109 v = VariantFischeRandom;
2112 v = VariantCrazyhouse;
2115 v = VariantBughouse;
2121 /* Not quite the same as FICS suicide! */
2122 v = VariantGiveaway;
2128 v = VariantShatranj;
2131 /* Temporary names for future ICC types. The name *will* change in
2132 the next xboard/WinBoard release after ICC defines it. */
2170 v = VariantCapablanca;
2173 v = VariantKnightmate;
2179 v = VariantCylinder;
2185 v = VariantCapaRandom;
2188 v = VariantBerolina;
2200 /* Found "wild" or "w" in the string but no number;
2201 must assume it's normal chess. */
2205 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2206 if( (len >= MSG_SIZ) && appData.debugMode )
2207 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2209 DisplayError(buf, 0);
2215 if (appData.debugMode) {
2216 fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2217 e, wnum, VariantName(v));
2222 static int leftover_start = 0, leftover_len = 0;
2223 char star_match[STAR_MATCH_N][MSG_SIZ];
2225 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2226 advance *index beyond it, and set leftover_start to the new value of
2227 *index; else return FALSE. If pattern contains the character '*', it
2228 matches any sequence of characters not containing '\r', '\n', or the
2229 character following the '*' (if any), and the matched sequence(s) are
2230 copied into star_match.
2233 looking_at ( char *buf, int *index, char *pattern)
2235 char *bufp = &buf[*index], *patternp = pattern;
2237 char *matchp = star_match[0];
2240 if (*patternp == NULLCHAR) {
2241 *index = leftover_start = bufp - buf;
2245 if (*bufp == NULLCHAR) return FALSE;
2246 if (*patternp == '*') {
2247 if (*bufp == *(patternp + 1)) {
2249 matchp = star_match[++star_count];
2253 } else if (*bufp == '\n' || *bufp == '\r') {
2255 if (*patternp == NULLCHAR)
2260 *matchp++ = *bufp++;
2264 if (*patternp != *bufp) return FALSE;
2271 SendToPlayer (char *data, int length)
2273 int error, outCount;
2274 outCount = OutputToProcess(NoProc, data, length, &error);
2275 if (outCount < length) {
2276 DisplayFatalError(_("Error writing to display"), error, 1);
2281 PackHolding (char packed[], char *holding)
2291 switch (runlength) {
2302 sprintf(q, "%d", runlength);
2314 /* Telnet protocol requests from the front end */
2316 TelnetRequest (unsigned char ddww, unsigned char option)
2318 unsigned char msg[3];
2319 int outCount, outError;
2321 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2323 if (appData.debugMode) {
2324 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2340 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2349 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2352 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2357 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2359 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2366 if (!appData.icsActive) return;
2367 TelnetRequest(TN_DO, TN_ECHO);
2373 if (!appData.icsActive) return;
2374 TelnetRequest(TN_DONT, TN_ECHO);
2378 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2380 /* put the holdings sent to us by the server on the board holdings area */
2381 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2385 if(gameInfo.holdingsWidth < 2) return;
2386 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2387 return; // prevent overwriting by pre-board holdings
2389 if( (int)lowestPiece >= BlackPawn ) {
2392 holdingsStartRow = BOARD_HEIGHT-1;
2395 holdingsColumn = BOARD_WIDTH-1;
2396 countsColumn = BOARD_WIDTH-2;
2397 holdingsStartRow = 0;
2401 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2402 board[i][holdingsColumn] = EmptySquare;
2403 board[i][countsColumn] = (ChessSquare) 0;
2405 while( (p=*holdings++) != NULLCHAR ) {
2406 piece = CharToPiece( ToUpper(p) );
2407 if(piece == EmptySquare) continue;
2408 /*j = (int) piece - (int) WhitePawn;*/
2409 j = PieceToNumber(piece);
2410 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2411 if(j < 0) continue; /* should not happen */
2412 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2413 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2414 board[holdingsStartRow+j*direction][countsColumn]++;
2420 VariantSwitch (Board board, VariantClass newVariant)
2422 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2423 static Board oldBoard;
2425 startedFromPositionFile = FALSE;
2426 if(gameInfo.variant == newVariant) return;
2428 /* [HGM] This routine is called each time an assignment is made to
2429 * gameInfo.variant during a game, to make sure the board sizes
2430 * are set to match the new variant. If that means adding or deleting
2431 * holdings, we shift the playing board accordingly
2432 * This kludge is needed because in ICS observe mode, we get boards
2433 * of an ongoing game without knowing the variant, and learn about the
2434 * latter only later. This can be because of the move list we requested,
2435 * in which case the game history is refilled from the beginning anyway,
2436 * but also when receiving holdings of a crazyhouse game. In the latter
2437 * case we want to add those holdings to the already received position.
2441 if (appData.debugMode) {
2442 fprintf(debugFP, "Switch board from %s to %s\n",
2443 VariantName(gameInfo.variant), VariantName(newVariant));
2444 setbuf(debugFP, NULL);
2446 shuffleOpenings = 0; /* [HGM] shuffle */
2447 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2451 newWidth = 9; newHeight = 9;
2452 gameInfo.holdingsSize = 7;
2453 case VariantBughouse:
2454 case VariantCrazyhouse:
2455 newHoldingsWidth = 2; break;
2459 newHoldingsWidth = 2;
2460 gameInfo.holdingsSize = 8;
2463 case VariantCapablanca:
2464 case VariantCapaRandom:
2467 newHoldingsWidth = gameInfo.holdingsSize = 0;
2470 if(newWidth != gameInfo.boardWidth ||
2471 newHeight != gameInfo.boardHeight ||
2472 newHoldingsWidth != gameInfo.holdingsWidth ) {
2474 /* shift position to new playing area, if needed */
2475 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2476 for(i=0; i<BOARD_HEIGHT; i++)
2477 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2478 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2480 for(i=0; i<newHeight; i++) {
2481 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2482 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2484 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2485 for(i=0; i<BOARD_HEIGHT; i++)
2486 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2487 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2490 board[HOLDINGS_SET] = 0;
2491 gameInfo.boardWidth = newWidth;
2492 gameInfo.boardHeight = newHeight;
2493 gameInfo.holdingsWidth = newHoldingsWidth;
2494 gameInfo.variant = newVariant;
2495 InitDrawingSizes(-2, 0);
2496 } else gameInfo.variant = newVariant;
2497 CopyBoard(oldBoard, board); // remember correctly formatted board
2498 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2499 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2502 static int loggedOn = FALSE;
2504 /*-- Game start info cache: --*/
2506 char gs_kind[MSG_SIZ];
2507 static char player1Name[128] = "";
2508 static char player2Name[128] = "";
2509 static char cont_seq[] = "\n\\ ";
2510 static int player1Rating = -1;
2511 static int player2Rating = -1;
2512 /*----------------------------*/
2514 ColorClass curColor = ColorNormal;
2515 int suppressKibitz = 0;
2518 Boolean soughtPending = FALSE;
2519 Boolean seekGraphUp;
2520 #define MAX_SEEK_ADS 200
2522 char *seekAdList[MAX_SEEK_ADS];
2523 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2524 float tcList[MAX_SEEK_ADS];
2525 char colorList[MAX_SEEK_ADS];
2526 int nrOfSeekAds = 0;
2527 int minRating = 1010, maxRating = 2800;
2528 int hMargin = 10, vMargin = 20, h, w;
2529 extern int squareSize, lineGap;
2534 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2535 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2536 if(r < minRating+100 && r >=0 ) r = minRating+100;
2537 if(r > maxRating) r = maxRating;
2538 if(tc < 1.f) tc = 1.f;
2539 if(tc > 95.f) tc = 95.f;
2540 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2541 y = ((double)r - minRating)/(maxRating - minRating)
2542 * (h-vMargin-squareSize/8-1) + vMargin;
2543 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2544 if(strstr(seekAdList[i], " u ")) color = 1;
2545 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2546 !strstr(seekAdList[i], "bullet") &&
2547 !strstr(seekAdList[i], "blitz") &&
2548 !strstr(seekAdList[i], "standard") ) color = 2;
2549 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2550 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2554 PlotSingleSeekAd (int i)
2560 AddAd (char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2562 char buf[MSG_SIZ], *ext = "";
2563 VariantClass v = StringToVariant(type);
2564 if(strstr(type, "wild")) {
2565 ext = type + 4; // append wild number
2566 if(v == VariantFischeRandom) type = "chess960"; else
2567 if(v == VariantLoadable) type = "setup"; else
2568 type = VariantName(v);
2570 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2571 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2572 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2573 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2574 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2575 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2576 seekNrList[nrOfSeekAds] = nr;
2577 zList[nrOfSeekAds] = 0;
2578 seekAdList[nrOfSeekAds++] = StrSave(buf);
2579 if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2584 EraseSeekDot (int i)
2586 int x = xList[i], y = yList[i], d=squareSize/4, k;
2587 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2588 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2589 // now replot every dot that overlapped
2590 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2591 int xx = xList[k], yy = yList[k];
2592 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2593 DrawSeekDot(xx, yy, colorList[k]);
2598 RemoveSeekAd (int nr)
2601 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2603 if(seekAdList[i]) free(seekAdList[i]);
2604 seekAdList[i] = seekAdList[--nrOfSeekAds];
2605 seekNrList[i] = seekNrList[nrOfSeekAds];
2606 ratingList[i] = ratingList[nrOfSeekAds];
2607 colorList[i] = colorList[nrOfSeekAds];
2608 tcList[i] = tcList[nrOfSeekAds];
2609 xList[i] = xList[nrOfSeekAds];
2610 yList[i] = yList[nrOfSeekAds];
2611 zList[i] = zList[nrOfSeekAds];
2612 seekAdList[nrOfSeekAds] = NULL;
2618 MatchSoughtLine (char *line)
2620 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2621 int nr, base, inc, u=0; char dummy;
2623 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2624 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2626 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2627 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2628 // match: compact and save the line
2629 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2639 if(!seekGraphUp) return FALSE;
2640 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2641 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2643 DrawSeekBackground(0, 0, w, h);
2644 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2645 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2646 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2647 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2649 DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2652 snprintf(buf, MSG_SIZ, "%d", i);
2653 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2656 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2657 for(i=1; i<100; i+=(i<10?1:5)) {
2658 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2659 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2660 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2662 snprintf(buf, MSG_SIZ, "%d", i);
2663 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2666 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2671 SeekGraphClick (ClickType click, int x, int y, int moving)
2673 static int lastDown = 0, displayed = 0, lastSecond;
2674 if(y < 0) return FALSE;
2675 if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2676 (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2677 if(!seekGraphUp) return FALSE;
2678 seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2679 DrawPosition(TRUE, NULL);
2682 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2683 if(click == Release || moving) return FALSE;
2685 soughtPending = TRUE;
2686 SendToICS(ics_prefix);
2687 SendToICS("sought\n"); // should this be "sought all"?
2688 } else { // issue challenge based on clicked ad
2689 int dist = 10000; int i, closest = 0, second = 0;
2690 for(i=0; i<nrOfSeekAds; i++) {
2691 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2692 if(d < dist) { dist = d; closest = i; }
2693 second += (d - zList[i] < 120); // count in-range ads
2694 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2698 second = (second > 1);
2699 if(displayed != closest || second != lastSecond) {
2700 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2701 lastSecond = second; displayed = closest;
2703 if(click == Press) {
2704 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2707 } // on press 'hit', only show info
2708 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2709 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2710 SendToICS(ics_prefix);
2712 return TRUE; // let incoming board of started game pop down the graph
2713 } else if(click == Release) { // release 'miss' is ignored
2714 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2715 if(moving == 2) { // right up-click
2716 nrOfSeekAds = 0; // refresh graph
2717 soughtPending = TRUE;
2718 SendToICS(ics_prefix);
2719 SendToICS("sought\n"); // should this be "sought all"?
2722 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2723 // press miss or release hit 'pop down' seek graph
2724 seekGraphUp = FALSE;
2725 DrawPosition(TRUE, NULL);
2731 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2733 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2734 #define STARTED_NONE 0
2735 #define STARTED_MOVES 1
2736 #define STARTED_BOARD 2
2737 #define STARTED_OBSERVE 3
2738 #define STARTED_HOLDINGS 4
2739 #define STARTED_CHATTER 5
2740 #define STARTED_COMMENT 6
2741 #define STARTED_MOVES_NOHIDE 7
2743 static int started = STARTED_NONE;
2744 static char parse[20000];
2745 static int parse_pos = 0;
2746 static char buf[BUF_SIZE + 1];
2747 static int firstTime = TRUE, intfSet = FALSE;
2748 static ColorClass prevColor = ColorNormal;
2749 static int savingComment = FALSE;
2750 static int cmatch = 0; // continuation sequence match
2757 int backup; /* [DM] For zippy color lines */
2759 char talker[MSG_SIZ]; // [HGM] chat
2762 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2764 if (appData.debugMode) {
2766 fprintf(debugFP, "<ICS: ");
2767 show_bytes(debugFP, data, count);
2768 fprintf(debugFP, "\n");
2772 if (appData.debugMode) { int f = forwardMostMove;
2773 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2774 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2775 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2778 /* If last read ended with a partial line that we couldn't parse,
2779 prepend it to the new read and try again. */
2780 if (leftover_len > 0) {
2781 for (i=0; i<leftover_len; i++)
2782 buf[i] = buf[leftover_start + i];
2785 /* copy new characters into the buffer */
2786 bp = buf + leftover_len;
2787 buf_len=leftover_len;
2788 for (i=0; i<count; i++)
2791 if (data[i] == '\r')
2794 // join lines split by ICS?
2795 if (!appData.noJoin)
2798 Joining just consists of finding matches against the
2799 continuation sequence, and discarding that sequence
2800 if found instead of copying it. So, until a match
2801 fails, there's nothing to do since it might be the
2802 complete sequence, and thus, something we don't want
2805 if (data[i] == cont_seq[cmatch])
2808 if (cmatch == strlen(cont_seq))
2810 cmatch = 0; // complete match. just reset the counter
2813 it's possible for the ICS to not include the space
2814 at the end of the last word, making our [correct]
2815 join operation fuse two separate words. the server
2816 does this when the space occurs at the width setting.
2818 if (!buf_len || buf[buf_len-1] != ' ')
2829 match failed, so we have to copy what matched before
2830 falling through and copying this character. In reality,
2831 this will only ever be just the newline character, but
2832 it doesn't hurt to be precise.
2834 strncpy(bp, cont_seq, cmatch);
2846 buf[buf_len] = NULLCHAR;
2847 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2852 while (i < buf_len) {
2853 /* Deal with part of the TELNET option negotiation
2854 protocol. We refuse to do anything beyond the
2855 defaults, except that we allow the WILL ECHO option,
2856 which ICS uses to turn off password echoing when we are
2857 directly connected to it. We reject this option
2858 if localLineEditing mode is on (always on in xboard)
2859 and we are talking to port 23, which might be a real
2860 telnet server that will try to keep WILL ECHO on permanently.
2862 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2863 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2864 unsigned char option;
2866 switch ((unsigned char) buf[++i]) {
2868 if (appData.debugMode)
2869 fprintf(debugFP, "\n<WILL ");
2870 switch (option = (unsigned char) buf[++i]) {
2872 if (appData.debugMode)
2873 fprintf(debugFP, "ECHO ");
2874 /* Reply only if this is a change, according
2875 to the protocol rules. */
2876 if (remoteEchoOption) break;
2877 if (appData.localLineEditing &&
2878 atoi(appData.icsPort) == TN_PORT) {
2879 TelnetRequest(TN_DONT, TN_ECHO);
2882 TelnetRequest(TN_DO, TN_ECHO);
2883 remoteEchoOption = TRUE;
2887 if (appData.debugMode)
2888 fprintf(debugFP, "%d ", option);
2889 /* Whatever this is, we don't want it. */
2890 TelnetRequest(TN_DONT, option);
2895 if (appData.debugMode)
2896 fprintf(debugFP, "\n<WONT ");
2897 switch (option = (unsigned char) buf[++i]) {
2899 if (appData.debugMode)
2900 fprintf(debugFP, "ECHO ");
2901 /* Reply only if this is a change, according
2902 to the protocol rules. */
2903 if (!remoteEchoOption) break;
2905 TelnetRequest(TN_DONT, TN_ECHO);
2906 remoteEchoOption = FALSE;
2909 if (appData.debugMode)
2910 fprintf(debugFP, "%d ", (unsigned char) option);
2911 /* Whatever this is, it must already be turned
2912 off, because we never agree to turn on
2913 anything non-default, so according to the
2914 protocol rules, we don't reply. */
2919 if (appData.debugMode)
2920 fprintf(debugFP, "\n<DO ");
2921 switch (option = (unsigned char) buf[++i]) {
2923 /* Whatever this is, we refuse to do it. */
2924 if (appData.debugMode)
2925 fprintf(debugFP, "%d ", option);
2926 TelnetRequest(TN_WONT, option);
2931 if (appData.debugMode)
2932 fprintf(debugFP, "\n<DONT ");
2933 switch (option = (unsigned char) buf[++i]) {
2935 if (appData.debugMode)
2936 fprintf(debugFP, "%d ", option);
2937 /* Whatever this is, we are already not doing
2938 it, because we never agree to do anything
2939 non-default, so according to the protocol
2940 rules, we don't reply. */
2945 if (appData.debugMode)
2946 fprintf(debugFP, "\n<IAC ");
2947 /* Doubled IAC; pass it through */
2951 if (appData.debugMode)
2952 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2953 /* Drop all other telnet commands on the floor */
2956 if (oldi > next_out)
2957 SendToPlayer(&buf[next_out], oldi - next_out);
2963 /* OK, this at least will *usually* work */
2964 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2968 if (loggedOn && !intfSet) {
2969 if (ics_type == ICS_ICC) {
2970 snprintf(str, MSG_SIZ,
2971 "/set-quietly interface %s\n/set-quietly style 12\n",
2973 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2974 strcat(str, "/set-2 51 1\n/set seek 1\n");
2975 } else if (ics_type == ICS_CHESSNET) {
2976 snprintf(str, MSG_SIZ, "/style 12\n");
2978 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2979 strcat(str, programVersion);
2980 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2981 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2982 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2984 strcat(str, "$iset nohighlight 1\n");
2986 strcat(str, "$iset lock 1\n$style 12\n");
2989 NotifyFrontendLogin();
2993 if (started == STARTED_COMMENT) {
2994 /* Accumulate characters in comment */
2995 parse[parse_pos++] = buf[i];
2996 if (buf[i] == '\n') {
2997 parse[parse_pos] = NULLCHAR;
2998 if(chattingPartner>=0) {
3000 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3001 OutputChatMessage(chattingPartner, mess);
3002 chattingPartner = -1;
3003 next_out = i+1; // [HGM] suppress printing in ICS window
3005 if(!suppressKibitz) // [HGM] kibitz
3006 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3007 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3008 int nrDigit = 0, nrAlph = 0, j;
3009 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3010 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3011 parse[parse_pos] = NULLCHAR;
3012 // try to be smart: if it does not look like search info, it should go to
3013 // ICS interaction window after all, not to engine-output window.
3014 for(j=0; j<parse_pos; j++) { // count letters and digits
3015 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3016 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
3017 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
3019 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3020 int depth=0; float score;
3021 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3022 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3023 pvInfoList[forwardMostMove-1].depth = depth;
3024 pvInfoList[forwardMostMove-1].score = 100*score;
3026 OutputKibitz(suppressKibitz, parse);
3029 if(gameMode == IcsObserving) // restore original ICS messages
3030 snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3032 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3033 SendToPlayer(tmp, strlen(tmp));
3035 next_out = i+1; // [HGM] suppress printing in ICS window
3037 started = STARTED_NONE;
3039 /* Don't match patterns against characters in comment */
3044 if (started == STARTED_CHATTER) {
3045 if (buf[i] != '\n') {
3046 /* Don't match patterns against characters in chatter */
3050 started = STARTED_NONE;
3051 if(suppressKibitz) next_out = i+1;
3054 /* Kludge to deal with rcmd protocol */
3055 if (firstTime && looking_at(buf, &i, "\001*")) {
3056 DisplayFatalError(&buf[1], 0, 1);
3062 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3065 if (appData.debugMode)
3066 fprintf(debugFP, "ics_type %d\n", ics_type);
3069 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3070 ics_type = ICS_FICS;
3072 if (appData.debugMode)
3073 fprintf(debugFP, "ics_type %d\n", ics_type);
3076 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3077 ics_type = ICS_CHESSNET;
3079 if (appData.debugMode)
3080 fprintf(debugFP, "ics_type %d\n", ics_type);
3085 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3086 looking_at(buf, &i, "Logging you in as \"*\"") ||
3087 looking_at(buf, &i, "will be \"*\""))) {
3088 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3092 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3094 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3095 DisplayIcsInteractionTitle(buf);
3096 have_set_title = TRUE;
3099 /* skip finger notes */
3100 if (started == STARTED_NONE &&
3101 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3102 (buf[i] == '1' && buf[i+1] == '0')) &&
3103 buf[i+2] == ':' && buf[i+3] == ' ') {
3104 started = STARTED_CHATTER;
3110 // [HGM] seekgraph: recognize sought lines and end-of-sought message
3111 if(appData.seekGraph) {
3112 if(soughtPending && MatchSoughtLine(buf+i)) {
3113 i = strstr(buf+i, "rated") - buf;
3114 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3115 next_out = leftover_start = i;
3116 started = STARTED_CHATTER;
3117 suppressKibitz = TRUE;
3120 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3121 && looking_at(buf, &i, "* ads displayed")) {
3122 soughtPending = FALSE;
3127 if(appData.autoRefresh) {
3128 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3129 int s = (ics_type == ICS_ICC); // ICC format differs
3131 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3132 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3133 looking_at(buf, &i, "*% "); // eat prompt
3134 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3135 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3136 next_out = i; // suppress
3139 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3140 char *p = star_match[0];
3142 if(seekGraphUp) RemoveSeekAd(atoi(p));
3143 while(*p && *p++ != ' '); // next
3145 looking_at(buf, &i, "*% "); // eat prompt
3146 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3153 /* skip formula vars */
3154 if (started == STARTED_NONE &&
3155 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3156 started = STARTED_CHATTER;
3161 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3162 if (appData.autoKibitz && started == STARTED_NONE &&
3163 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3164 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3165 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3166 looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3167 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3168 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3169 suppressKibitz = TRUE;
3170 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3172 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3173 && (gameMode == IcsPlayingWhite)) ||
3174 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3175 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3176 started = STARTED_CHATTER; // own kibitz we simply discard
3178 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3179 parse_pos = 0; parse[0] = NULLCHAR;
3180 savingComment = TRUE;
3181 suppressKibitz = gameMode != IcsObserving ? 2 :
3182 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3186 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3187 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3188 && atoi(star_match[0])) {
3189 // suppress the acknowledgements of our own autoKibitz
3191 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3192 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3193 SendToPlayer(star_match[0], strlen(star_match[0]));
3194 if(looking_at(buf, &i, "*% ")) // eat prompt
3195 suppressKibitz = FALSE;
3199 } // [HGM] kibitz: end of patch
3201 if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3203 // [HGM] chat: intercept tells by users for which we have an open chat window
3205 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3206 looking_at(buf, &i, "* whispers:") ||
3207 looking_at(buf, &i, "* kibitzes:") ||
3208 looking_at(buf, &i, "* shouts:") ||
3209 looking_at(buf, &i, "* c-shouts:") ||
3210 looking_at(buf, &i, "--> * ") ||
3211 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3212 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3213 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3214 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3216 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3217 chattingPartner = -1;
3219 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3220 for(p=0; p<MAX_CHAT; p++) {
3221 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3222 talker[0] = '['; strcat(talker, "] ");
3223 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3224 chattingPartner = p; break;
3227 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3228 for(p=0; p<MAX_CHAT; p++) {
3229 if(!strcmp("kibitzes", chatPartner[p])) {
3230 talker[0] = '['; strcat(talker, "] ");
3231 chattingPartner = p; break;
3234 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3235 for(p=0; p<MAX_CHAT; p++) {
3236 if(!strcmp("whispers", chatPartner[p])) {
3237 talker[0] = '['; strcat(talker, "] ");
3238 chattingPartner = p; break;
3241 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3242 if(buf[i-8] == '-' && buf[i-3] == 't')
3243 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3244 if(!strcmp("c-shouts", chatPartner[p])) {
3245 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3246 chattingPartner = p; break;
3249 if(chattingPartner < 0)
3250 for(p=0; p<MAX_CHAT; p++) {
3251 if(!strcmp("shouts", chatPartner[p])) {
3252 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3253 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3254 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3255 chattingPartner = p; break;
3259 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3260 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3261 talker[0] = 0; Colorize(ColorTell, FALSE);
3262 chattingPartner = p; break;
3264 if(chattingPartner<0) i = oldi; else {
3265 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3266 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3267 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3268 started = STARTED_COMMENT;
3269 parse_pos = 0; parse[0] = NULLCHAR;
3270 savingComment = 3 + chattingPartner; // counts as TRUE
3271 suppressKibitz = TRUE;
3274 } // [HGM] chat: end of patch
3277 if (appData.zippyTalk || appData.zippyPlay) {
3278 /* [DM] Backup address for color zippy lines */
3280 if (loggedOn == TRUE)
3281 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3282 (appData.zippyPlay && ZippyMatch(buf, &backup)));
3284 } // [DM] 'else { ' deleted
3286 /* Regular tells and says */
3287 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3288 looking_at(buf, &i, "* (your partner) tells you: ") ||
3289 looking_at(buf, &i, "* says: ") ||
3290 /* Don't color "message" or "messages" output */
3291 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3292 looking_at(buf, &i, "*. * at *:*: ") ||
3293 looking_at(buf, &i, "--* (*:*): ") ||
3294 /* Message notifications (same color as tells) */
3295 looking_at(buf, &i, "* has left a message ") ||
3296 looking_at(buf, &i, "* just sent you a message:\n") ||
3297 /* Whispers and kibitzes */
3298 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3299 looking_at(buf, &i, "* kibitzes: ") ||
3301 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3303 if (tkind == 1 && strchr(star_match[0], ':')) {
3304 /* Avoid "tells you:" spoofs in channels */
3307 if (star_match[0][0] == NULLCHAR ||
3308 strchr(star_match[0], ' ') ||
3309 (tkind == 3 && strchr(star_match[1], ' '))) {
3310 /* Reject bogus matches */
3313 if (appData.colorize) {
3314 if (oldi > next_out) {
3315 SendToPlayer(&buf[next_out], oldi - next_out);
3320 Colorize(ColorTell, FALSE);
3321 curColor = ColorTell;
3324 Colorize(ColorKibitz, FALSE);
3325 curColor = ColorKibitz;
3328 p = strrchr(star_match[1], '(');
3335 Colorize(ColorChannel1, FALSE);
3336 curColor = ColorChannel1;
3338 Colorize(ColorChannel, FALSE);
3339 curColor = ColorChannel;
3343 curColor = ColorNormal;
3347 if (started == STARTED_NONE && appData.autoComment &&
3348 (gameMode == IcsObserving ||
3349 gameMode == IcsPlayingWhite ||
3350 gameMode == IcsPlayingBlack)) {
3351 parse_pos = i - oldi;
3352 memcpy(parse, &buf[oldi], parse_pos);
3353 parse[parse_pos] = NULLCHAR;
3354 started = STARTED_COMMENT;
3355 savingComment = TRUE;
3357 started = STARTED_CHATTER;
3358 savingComment = FALSE;
3365 if (looking_at(buf, &i, "* s-shouts: ") ||
3366 looking_at(buf, &i, "* c-shouts: ")) {
3367 if (appData.colorize) {
3368 if (oldi > next_out) {
3369 SendToPlayer(&buf[next_out], oldi - next_out);
3372 Colorize(ColorSShout, FALSE);
3373 curColor = ColorSShout;
3376 started = STARTED_CHATTER;
3380 if (looking_at(buf, &i, "--->")) {
3385 if (looking_at(buf, &i, "* shouts: ") ||
3386 looking_at(buf, &i, "--> ")) {
3387 if (appData.colorize) {
3388 if (oldi > next_out) {
3389 SendToPlayer(&buf[next_out], oldi - next_out);
3392 Colorize(ColorShout, FALSE);
3393 curColor = ColorShout;
3396 started = STARTED_CHATTER;
3400 if (looking_at( buf, &i, "Challenge:")) {
3401 if (appData.colorize) {
3402 if (oldi > next_out) {
3403 SendToPlayer(&buf[next_out], oldi - next_out);
3406 Colorize(ColorChallenge, FALSE);
3407 curColor = ColorChallenge;
3413 if (looking_at(buf, &i, "* offers you") ||
3414 looking_at(buf, &i, "* offers to be") ||
3415 looking_at(buf, &i, "* would like to") ||
3416 looking_at(buf, &i, "* requests to") ||
3417 looking_at(buf, &i, "Your opponent offers") ||
3418 looking_at(buf, &i, "Your opponent requests")) {
3420 if (appData.colorize) {
3421 if (oldi > next_out) {
3422 SendToPlayer(&buf[next_out], oldi - next_out);
3425 Colorize(ColorRequest, FALSE);
3426 curColor = ColorRequest;
3431 if (looking_at(buf, &i, "* (*) seeking")) {
3432 if (appData.colorize) {
3433 if (oldi > next_out) {
3434 SendToPlayer(&buf[next_out], oldi - next_out);
3437 Colorize(ColorSeek, FALSE);
3438 curColor = ColorSeek;
3443 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3445 if (looking_at(buf, &i, "\\ ")) {
3446 if (prevColor != ColorNormal) {
3447 if (oldi > next_out) {
3448 SendToPlayer(&buf[next_out], oldi - next_out);
3451 Colorize(prevColor, TRUE);
3452 curColor = prevColor;
3454 if (savingComment) {
3455 parse_pos = i - oldi;
3456 memcpy(parse, &buf[oldi], parse_pos);
3457 parse[parse_pos] = NULLCHAR;
3458 started = STARTED_COMMENT;
3459 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3460 chattingPartner = savingComment - 3; // kludge to remember the box
3462 started = STARTED_CHATTER;
3467 if (looking_at(buf, &i, "Black Strength :") ||
3468 looking_at(buf, &i, "<<< style 10 board >>>") ||
3469 looking_at(buf, &i, "<10>") ||
3470 looking_at(buf, &i, "#@#")) {
3471 /* Wrong board style */
3473 SendToICS(ics_prefix);
3474 SendToICS("set style 12\n");
3475 SendToICS(ics_prefix);
3476 SendToICS("refresh\n");
3480 if (looking_at(buf, &i, "login:")) {
3481 if (!have_sent_ICS_logon) {
3483 have_sent_ICS_logon = 1;
3484 else // no init script was found
3485 have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3486 } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3487 have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3492 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3493 (looking_at(buf, &i, "\n<12> ") ||
3494 looking_at(buf, &i, "<12> "))) {
3496 if (oldi > next_out) {
3497 SendToPlayer(&buf[next_out], oldi - next_out);
3500 started = STARTED_BOARD;
3505 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3506 looking_at(buf, &i, "<b1> ")) {
3507 if (oldi > next_out) {
3508 SendToPlayer(&buf[next_out], oldi - next_out);
3511 started = STARTED_HOLDINGS;
3516 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3518 /* Header for a move list -- first line */
3520 switch (ics_getting_history) {
3524 case BeginningOfGame:
3525 /* User typed "moves" or "oldmoves" while we
3526 were idle. Pretend we asked for these
3527 moves and soak them up so user can step
3528 through them and/or save them.
3531 gameMode = IcsObserving;
3534 ics_getting_history = H_GOT_UNREQ_HEADER;
3536 case EditGame: /*?*/
3537 case EditPosition: /*?*/
3538 /* Should above feature work in these modes too? */
3539 /* For now it doesn't */
3540 ics_getting_history = H_GOT_UNWANTED_HEADER;
3543 ics_getting_history = H_GOT_UNWANTED_HEADER;
3548 /* Is this the right one? */
3549 if (gameInfo.white && gameInfo.black &&
3550 strcmp(gameInfo.white, star_match[0]) == 0 &&
3551 strcmp(gameInfo.black, star_match[2]) == 0) {
3553 ics_getting_history = H_GOT_REQ_HEADER;
3556 case H_GOT_REQ_HEADER:
3557 case H_GOT_UNREQ_HEADER:
3558 case H_GOT_UNWANTED_HEADER:
3559 case H_GETTING_MOVES:
3560 /* Should not happen */
3561 DisplayError(_("Error gathering move list: two headers"), 0);
3562 ics_getting_history = H_FALSE;
3566 /* Save player ratings into gameInfo if needed */
3567 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3568 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3569 (gameInfo.whiteRating == -1 ||
3570 gameInfo.blackRating == -1)) {
3572 gameInfo.whiteRating = string_to_rating(star_match[1]);
3573 gameInfo.blackRating = string_to_rating(star_match[3]);
3574 if (appData.debugMode)
3575 fprintf(debugFP, "Ratings from header: W %d, B %d\n",
3576 gameInfo.whiteRating, gameInfo.blackRating);
3581 if (looking_at(buf, &i,
3582 "* * match, initial time: * minute*, increment: * second")) {
3583 /* Header for a move list -- second line */
3584 /* Initial board will follow if this is a wild game */
3585 if (gameInfo.event != NULL) free(gameInfo.event);
3586 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3587 gameInfo.event = StrSave(str);
3588 /* [HGM] we switched variant. Translate boards if needed. */
3589 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3593 if (looking_at(buf, &i, "Move ")) {
3594 /* Beginning of a move list */
3595 switch (ics_getting_history) {
3597 /* Normally should not happen */
3598 /* Maybe user hit reset while we were parsing */
3601 /* Happens if we are ignoring a move list that is not
3602 * the one we just requested. Common if the user
3603 * tries to observe two games without turning off
3606 case H_GETTING_MOVES:
3607 /* Should not happen */
3608 DisplayError(_("Error gathering move list: nested"), 0);
3609 ics_getting_history = H_FALSE;
3611 case H_GOT_REQ_HEADER:
3612 ics_getting_history = H_GETTING_MOVES;
3613 started = STARTED_MOVES;
3615 if (oldi > next_out) {
3616 SendToPlayer(&buf[next_out], oldi - next_out);
3619 case H_GOT_UNREQ_HEADER:
3620 ics_getting_history = H_GETTING_MOVES;
3621 started = STARTED_MOVES_NOHIDE;
3624 case H_GOT_UNWANTED_HEADER:
3625 ics_getting_history = H_FALSE;
3631 if (looking_at(buf, &i, "% ") ||
3632 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3633 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3634 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3635 soughtPending = FALSE;
3639 if(suppressKibitz) next_out = i;
3640 savingComment = FALSE;
3644 case STARTED_MOVES_NOHIDE:
3645 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3646 parse[parse_pos + i - oldi] = NULLCHAR;
3647 ParseGameHistory(parse);
3649 if (appData.zippyPlay && first.initDone) {
3650 FeedMovesToProgram(&first, forwardMostMove);
3651 if (gameMode == IcsPlayingWhite) {
3652 if (WhiteOnMove(forwardMostMove)) {
3653 if (first.sendTime) {
3654 if (first.useColors) {
3655 SendToProgram("black\n", &first);
3657 SendTimeRemaining(&first, TRUE);
3659 if (first.useColors) {
3660 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3662 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3663 first.maybeThinking = TRUE;
3665 if (first.usePlayother) {
3666 if (first.sendTime) {
3667 SendTimeRemaining(&first, TRUE);
3669 SendToProgram("playother\n", &first);
3675 } else if (gameMode == IcsPlayingBlack) {
3676 if (!WhiteOnMove(forwardMostMove)) {
3677 if (first.sendTime) {
3678 if (first.useColors) {
3679 SendToProgram("white\n", &first);
3681 SendTimeRemaining(&first, FALSE);
3683 if (first.useColors) {
3684 SendToProgram("black\n", &first);
3686 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3687 first.maybeThinking = TRUE;
3689 if (first.usePlayother) {
3690 if (first.sendTime) {
3691 SendTimeRemaining(&first, FALSE);
3693 SendToProgram("playother\n", &first);
3702 if (gameMode == IcsObserving && ics_gamenum == -1) {
3703 /* Moves came from oldmoves or moves command
3704 while we weren't doing anything else.
3706 currentMove = forwardMostMove;
3707 ClearHighlights();/*!!could figure this out*/
3708 flipView = appData.flipView;
3709 DrawPosition(TRUE, boards[currentMove]);
3710 DisplayBothClocks();
3711 snprintf(str, MSG_SIZ, "%s %s %s",
3712 gameInfo.white, _("vs."), gameInfo.black);
3716 /* Moves were history of an active game */
3717 if (gameInfo.resultDetails != NULL) {
3718 free(gameInfo.resultDetails);
3719 gameInfo.resultDetails = NULL;
3722 HistorySet(parseList, backwardMostMove,
3723 forwardMostMove, currentMove-1);
3724 DisplayMove(currentMove - 1);
3725 if (started == STARTED_MOVES) next_out = i;
3726 started = STARTED_NONE;
3727 ics_getting_history = H_FALSE;
3730 case STARTED_OBSERVE:
3731 started = STARTED_NONE;
3732 SendToICS(ics_prefix);
3733 SendToICS("refresh\n");
3739 if(bookHit) { // [HGM] book: simulate book reply
3740 static char bookMove[MSG_SIZ]; // a bit generous?
3742 programStats.nodes = programStats.depth = programStats.time =
3743 programStats.score = programStats.got_only_move = 0;
3744 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3746 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3747 strcat(bookMove, bookHit);
3748 HandleMachineMove(bookMove, &first);
3753 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3754 started == STARTED_HOLDINGS ||
3755 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3756 /* Accumulate characters in move list or board */
3757 parse[parse_pos++] = buf[i];
3760 /* Start of game messages. Mostly we detect start of game
3761 when the first board image arrives. On some versions
3762 of the ICS, though, we need to do a "refresh" after starting
3763 to observe in order to get the current board right away. */
3764 if (looking_at(buf, &i, "Adding game * to observation list")) {
3765 started = STARTED_OBSERVE;
3769 /* Handle auto-observe */
3770 if (appData.autoObserve &&
3771 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3772 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3774 /* Choose the player that was highlighted, if any. */
3775 if (star_match[0][0] == '\033' ||
3776 star_match[1][0] != '\033') {
3777 player = star_match[0];
3779 player = star_match[2];
3781 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3782 ics_prefix, StripHighlightAndTitle(player));
3785 /* Save ratings from notify string */
3786 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3787 player1Rating = string_to_rating(star_match[1]);
3788 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3789 player2Rating = string_to_rating(star_match[3]);
3791 if (appData.debugMode)
3793 "Ratings from 'Game notification:' %s %d, %s %d\n",
3794 player1Name, player1Rating,
3795 player2Name, player2Rating);
3800 /* Deal with automatic examine mode after a game,
3801 and with IcsObserving -> IcsExamining transition */
3802 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3803 looking_at(buf, &i, "has made you an examiner of game *")) {
3805 int gamenum = atoi(star_match[0]);
3806 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3807 gamenum == ics_gamenum) {
3808 /* We were already playing or observing this game;
3809 no need to refetch history */
3810 gameMode = IcsExamining;
3812 pauseExamForwardMostMove = forwardMostMove;
3813 } else if (currentMove < forwardMostMove) {
3814 ForwardInner(forwardMostMove);
3817 /* I don't think this case really can happen */
3818 SendToICS(ics_prefix);
3819 SendToICS("refresh\n");
3824 /* Error messages */
3825 // if (ics_user_moved) {
3826 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3827 if (looking_at(buf, &i, "Illegal move") ||
3828 looking_at(buf, &i, "Not a legal move") ||
3829 looking_at(buf, &i, "Your king is in check") ||
3830 looking_at(buf, &i, "It isn't your turn") ||
3831 looking_at(buf, &i, "It is not your move")) {
3833 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3834 currentMove = forwardMostMove-1;
3835 DisplayMove(currentMove - 1); /* before DMError */
3836 DrawPosition(FALSE, boards[currentMove]);
3837 SwitchClocks(forwardMostMove-1); // [HGM] race
3838 DisplayBothClocks();
3840 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3846 if (looking_at(buf, &i, "still have time") ||
3847 looking_at(buf, &i, "not out of time") ||
3848 looking_at(buf, &i, "either player is out of time") ||
3849 looking_at(buf, &i, "has timeseal; checking")) {
3850 /* We must have called his flag a little too soon */
3851 whiteFlag = blackFlag = FALSE;
3855 if (looking_at(buf, &i, "added * seconds to") ||
3856 looking_at(buf, &i, "seconds were added to")) {
3857 /* Update the clocks */
3858 SendToICS(ics_prefix);
3859 SendToICS("refresh\n");
3863 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3864 ics_clock_paused = TRUE;
3869 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3870 ics_clock_paused = FALSE;