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 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"
135 # define _(s) gettext (s)
136 # define N_(s) gettext_noop (s)
137 # define T_(s) gettext(s)
150 int establish P((void));
151 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
152 char *buf, int count, int error));
153 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
154 char *buf, int count, int error));
155 void ics_printf P((char *format, ...));
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(());
227 extern void ConsoleCreate();
230 ChessProgramState *WhitePlayer();
231 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
232 int VerifyDisplayMode P(());
234 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
235 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
236 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
237 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
238 void ics_update_width P((int new_width));
239 extern char installDir[MSG_SIZ];
240 VariantClass startVariant; /* [HGM] nicks: initial variant */
243 extern int tinyLayout, smallLayout;
244 ChessProgramStats programStats;
245 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
247 static int exiting = 0; /* [HGM] moved to top */
248 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
249 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
250 Board partnerBoard; /* [HGM] bughouse: for peeking at partner game */
251 int partnerHighlight[2];
252 Boolean partnerBoardValid = 0;
253 char partnerStatus[MSG_SIZ];
255 Boolean originalFlip;
256 Boolean twoBoards = 0;
257 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
258 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
259 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
260 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
261 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
262 int opponentKibitzes;
263 int lastSavedGame; /* [HGM] save: ID of game */
264 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
265 extern int chatCount;
267 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
268 char lastMsg[MSG_SIZ];
269 ChessSquare pieceSweep = EmptySquare;
270 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
271 int promoDefaultAltered;
273 /* States for ics_getting_history */
275 #define H_REQUESTED 1
276 #define H_GOT_REQ_HEADER 2
277 #define H_GOT_UNREQ_HEADER 3
278 #define H_GETTING_MOVES 4
279 #define H_GOT_UNWANTED_HEADER 5
281 /* whosays values for GameEnds */
290 /* Maximum number of games in a cmail message */
291 #define CMAIL_MAX_GAMES 20
293 /* Different types of move when calling RegisterMove */
295 #define CMAIL_RESIGN 1
297 #define CMAIL_ACCEPT 3
299 /* Different types of result to remember for each game */
300 #define CMAIL_NOT_RESULT 0
301 #define CMAIL_OLD_RESULT 1
302 #define CMAIL_NEW_RESULT 2
304 /* Telnet protocol constants */
315 safeStrCpy (char *dst, const char *src, size_t count)
318 assert( dst != NULL );
319 assert( src != NULL );
322 for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
323 if( i == count && dst[count-1] != NULLCHAR)
325 dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
326 if(appData.debugMode)
327 fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
333 /* Some compiler can't cast u64 to double
334 * This function do the job for us:
336 * We use the highest bit for cast, this only
337 * works if the highest bit is not
338 * in use (This should not happen)
340 * We used this for all compiler
343 u64ToDouble (u64 value)
346 u64 tmp = value & u64Const(0x7fffffffffffffff);
347 r = (double)(s64)tmp;
348 if (value & u64Const(0x8000000000000000))
349 r += 9.2233720368547758080e18; /* 2^63 */
353 /* Fake up flags for now, as we aren't keeping track of castling
354 availability yet. [HGM] Change of logic: the flag now only
355 indicates the type of castlings allowed by the rule of the game.
356 The actual rights themselves are maintained in the array
357 castlingRights, as part of the game history, and are not probed
363 int flags = F_ALL_CASTLE_OK;
364 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
365 switch (gameInfo.variant) {
367 flags &= ~F_ALL_CASTLE_OK;
368 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
369 flags |= F_IGNORE_CHECK;
371 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
374 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
376 case VariantKriegspiel:
377 flags |= F_KRIEGSPIEL_CAPTURE;
379 case VariantCapaRandom:
380 case VariantFischeRandom:
381 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
382 case VariantNoCastle:
383 case VariantShatranj:
387 flags &= ~F_ALL_CASTLE_OK;
395 FILE *gameFileFP, *debugFP;
396 char *currentDebugFile; // [HGM] debug split: to remember name
399 [AS] Note: sometimes, the sscanf() function is used to parse the input
400 into a fixed-size buffer. Because of this, we must be prepared to
401 receive strings as long as the size of the input buffer, which is currently
402 set to 4K for Windows and 8K for the rest.
403 So, we must either allocate sufficiently large buffers here, or
404 reduce the size of the input buffer in the input reading part.
407 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
408 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
409 char thinkOutput1[MSG_SIZ*10];
411 ChessProgramState first, second, pairing;
413 /* premove variables */
416 int premoveFromX = 0;
417 int premoveFromY = 0;
418 int premovePromoChar = 0;
420 Boolean alarmSounded;
421 /* end premove variables */
423 char *ics_prefix = "$";
424 int ics_type = ICS_GENERIC;
426 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
427 int pauseExamForwardMostMove = 0;
428 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
429 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
430 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
431 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
432 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
433 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
434 int whiteFlag = FALSE, blackFlag = FALSE;
435 int userOfferedDraw = FALSE;
436 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
437 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
438 int cmailMoveType[CMAIL_MAX_GAMES];
439 long ics_clock_paused = 0;
440 ProcRef icsPR = NoProc, cmailPR = NoProc;
441 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
442 GameMode gameMode = BeginningOfGame;
443 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
444 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
445 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
446 int hiddenThinkOutputState = 0; /* [AS] */
447 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
448 int adjudicateLossPlies = 6;
449 char white_holding[64], black_holding[64];
450 TimeMark lastNodeCountTime;
451 long lastNodeCount=0;
452 int shiftKey; // [HGM] set by mouse handler
454 int have_sent_ICS_logon = 0;
456 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
457 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
458 Boolean adjustedClock;
459 long timeControl_2; /* [AS] Allow separate time controls */
460 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
461 long timeRemaining[2][MAX_MOVES];
462 int matchGame = 0, nextGame = 0, roundNr = 0;
463 Boolean waitingForGame = FALSE;
464 TimeMark programStartTime, pauseStart;
465 char ics_handle[MSG_SIZ];
466 int have_set_title = 0;
468 /* animateTraining preserves the state of appData.animate
469 * when Training mode is activated. This allows the
470 * response to be animated when appData.animate == TRUE and
471 * appData.animateDragging == TRUE.
473 Boolean animateTraining;
479 Board boards[MAX_MOVES];
480 /* [HGM] Following 7 needed for accurate legality tests: */
481 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
482 signed char initialRights[BOARD_FILES];
483 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
484 int initialRulePlies, FENrulePlies;
485 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
487 Boolean shuffleOpenings;
488 int mute; // mute all sounds
490 // [HGM] vari: next 12 to save and restore variations
491 #define MAX_VARIATIONS 10
492 int framePtr = MAX_MOVES-1; // points to free stack entry
494 int savedFirst[MAX_VARIATIONS];
495 int savedLast[MAX_VARIATIONS];
496 int savedFramePtr[MAX_VARIATIONS];
497 char *savedDetails[MAX_VARIATIONS];
498 ChessMove savedResult[MAX_VARIATIONS];
500 void PushTail P((int firstMove, int lastMove));
501 Boolean PopTail P((Boolean annotate));
502 void PushInner P((int firstMove, int lastMove));
503 void PopInner P((Boolean annotate));
504 void CleanupTail P((void));
506 ChessSquare FIDEArray[2][BOARD_FILES] = {
507 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
508 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
509 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
510 BlackKing, BlackBishop, BlackKnight, BlackRook }
513 ChessSquare twoKingsArray[2][BOARD_FILES] = {
514 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
515 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
516 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
517 BlackKing, BlackKing, BlackKnight, BlackRook }
520 ChessSquare KnightmateArray[2][BOARD_FILES] = {
521 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
522 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
523 { BlackRook, BlackMan, BlackBishop, BlackQueen,
524 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
527 ChessSquare SpartanArray[2][BOARD_FILES] = {
528 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
529 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
530 { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
531 BlackDragon, BlackKing, BlackAngel, BlackAlfil }
534 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
535 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
536 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
537 { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
538 BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
541 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
542 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
543 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
544 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
545 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
548 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
549 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
550 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
551 { BlackRook, BlackKnight, BlackMan, BlackFerz,
552 BlackKing, BlackMan, BlackKnight, BlackRook }
556 #if (BOARD_FILES>=10)
557 ChessSquare ShogiArray[2][BOARD_FILES] = {
558 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
559 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
560 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
561 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
564 ChessSquare XiangqiArray[2][BOARD_FILES] = {
565 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
566 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
567 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
568 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
571 ChessSquare CapablancaArray[2][BOARD_FILES] = {
572 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
573 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
574 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
575 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
578 ChessSquare GreatArray[2][BOARD_FILES] = {
579 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
580 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
581 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
582 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
585 ChessSquare JanusArray[2][BOARD_FILES] = {
586 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
587 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
588 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
589 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
592 ChessSquare GrandArray[2][BOARD_FILES] = {
593 { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
594 WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
595 { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
596 BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
600 ChessSquare GothicArray[2][BOARD_FILES] = {
601 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
602 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
603 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
604 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
607 #define GothicArray CapablancaArray
611 ChessSquare FalconArray[2][BOARD_FILES] = {
612 { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
613 WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
614 { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
615 BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
618 #define FalconArray CapablancaArray
621 #else // !(BOARD_FILES>=10)
622 #define XiangqiPosition FIDEArray
623 #define CapablancaArray FIDEArray
624 #define GothicArray FIDEArray
625 #define GreatArray FIDEArray
626 #endif // !(BOARD_FILES>=10)
628 #if (BOARD_FILES>=12)
629 ChessSquare CourierArray[2][BOARD_FILES] = {
630 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
631 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
632 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
633 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
635 #else // !(BOARD_FILES>=12)
636 #define CourierArray CapablancaArray
637 #endif // !(BOARD_FILES>=12)
640 Board initialPosition;
643 /* Convert str to a rating. Checks for special cases of "----",
645 "++++", etc. Also strips ()'s */
647 string_to_rating (char *str)
649 while(*str && !isdigit(*str)) ++str;
651 return 0; /* One of the special "no rating" cases */
659 /* Init programStats */
660 programStats.movelist[0] = 0;
661 programStats.depth = 0;
662 programStats.nr_moves = 0;
663 programStats.moves_left = 0;
664 programStats.nodes = 0;
665 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
666 programStats.score = 0;
667 programStats.got_only_move = 0;
668 programStats.got_fail = 0;
669 programStats.line_is_book = 0;
674 { // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
675 if (appData.firstPlaysBlack) {
676 first.twoMachinesColor = "black\n";
677 second.twoMachinesColor = "white\n";
679 first.twoMachinesColor = "white\n";
680 second.twoMachinesColor = "black\n";
683 first.other = &second;
684 second.other = &first;
687 if(appData.timeOddsMode) {
688 norm = appData.timeOdds[0];
689 if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
691 first.timeOdds = appData.timeOdds[0]/norm;
692 second.timeOdds = appData.timeOdds[1]/norm;
695 if(programVersion) free(programVersion);
696 if (appData.noChessProgram) {
697 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
698 sprintf(programVersion, "%s", PACKAGE_STRING);
700 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
701 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
702 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
707 UnloadEngine (ChessProgramState *cps)
709 /* Kill off first chess program */
710 if (cps->isr != NULL)
711 RemoveInputSource(cps->isr);
714 if (cps->pr != NoProc) {
716 DoSleep( appData.delayBeforeQuit );
717 SendToProgram("quit\n", cps);
718 DoSleep( appData.delayAfterQuit );
719 DestroyChildProcess(cps->pr, cps->useSigterm);
722 if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
726 ClearOptions (ChessProgramState *cps)
729 cps->nrOptions = cps->comboCnt = 0;
730 for(i=0; i<MAX_OPTIONS; i++) {
731 cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
732 cps->option[i].textValue = 0;
736 char *engineNames[] = {
737 /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
738 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
740 /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
741 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
746 InitEngine (ChessProgramState *cps, int n)
747 { // [HGM] all engine initialiation put in a function that does one engine
751 cps->which = _(engineNames[n]);
752 cps->maybeThinking = FALSE;
756 cps->sendDrawOffers = 1;
758 cps->program = appData.chessProgram[n];
759 cps->host = appData.host[n];
760 cps->dir = appData.directory[n];
761 cps->initString = appData.engInitString[n];
762 cps->computerString = appData.computerString[n];
763 cps->useSigint = TRUE;
764 cps->useSigterm = TRUE;
765 cps->reuse = appData.reuse[n];
766 cps->nps = appData.NPS[n]; // [HGM] nps: copy nodes per second
767 cps->useSetboard = FALSE;
769 cps->usePing = FALSE;
772 cps->usePlayother = FALSE;
773 cps->useColors = TRUE;
774 cps->useUsermove = FALSE;
775 cps->sendICS = FALSE;
776 cps->sendName = appData.icsActive;
777 cps->sdKludge = FALSE;
778 cps->stKludge = FALSE;
779 TidyProgramName(cps->program, cps->host, cps->tidy);
781 safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
782 cps->analysisSupport = 2; /* detect */
783 cps->analyzing = FALSE;
784 cps->initDone = FALSE;
786 /* New features added by Tord: */
787 cps->useFEN960 = FALSE;
788 cps->useOOCastle = TRUE;
789 /* End of new features added by Tord. */
790 cps->fenOverride = appData.fenOverride[n];
792 /* [HGM] time odds: set factor for each machine */
793 cps->timeOdds = appData.timeOdds[n];
795 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
796 cps->accumulateTC = appData.accumulateTC[n];
797 cps->maxNrOfSessions = 1;
802 cps->supportsNPS = UNKNOWN;
803 cps->memSize = FALSE;
804 cps->maxCores = FALSE;
805 cps->egtFormats[0] = NULLCHAR;
808 cps->optionSettings = appData.engOptions[n];
810 cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
811 cps->isUCI = appData.isUCI[n]; /* [AS] */
812 cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
814 if (appData.protocolVersion[n] > PROTOVER
815 || appData.protocolVersion[n] < 1)
820 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
821 appData.protocolVersion[n]);
822 if( (len >= MSG_SIZ) && appData.debugMode )
823 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
825 DisplayFatalError(buf, 0, 2);
829 cps->protocolVersion = appData.protocolVersion[n];
832 InitEngineUCI( installDir, cps ); // [HGM] moved here from winboard.c, to make available in xboard
833 ParseFeatures(appData.featureDefaults, cps);
836 ChessProgramState *savCps;
842 if(WaitForEngine(savCps, LoadEngine)) return;
843 CommonEngineInit(); // recalculate time odds
844 if(gameInfo.variant != StringToVariant(appData.variant)) {
845 // we changed variant when loading the engine; this forces us to reset
846 Reset(TRUE, savCps != &first);
847 EditGameEvent(); // for consistency with other path, as Reset changes mode
849 InitChessProgram(savCps, FALSE);
850 SendToProgram("force\n", savCps);
851 DisplayMessage("", "");
852 if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
853 for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
859 ReplaceEngine (ChessProgramState *cps, int n)
863 appData.noChessProgram = FALSE;
864 appData.clockMode = TRUE;
867 if(n) return; // only startup first engine immediately; second can wait
868 savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
872 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
873 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
875 static char resetOptions[] =
876 "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
877 "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
878 "-firstOptions \"\" -firstNPS -1 -fn \"\"";
881 FloatToFront(char **list, char *engineLine)
883 char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
885 if(appData.recentEngines <= 0) return;
886 TidyProgramName(engineLine, "localhost", tidy+1);
887 tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
888 strncpy(buf+1, *list, MSG_SIZ-50);
889 if(p = strstr(buf, tidy)) { // tidy name appears in list
890 q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
891 while(*p++ = *++q); // squeeze out
893 strcat(tidy, buf+1); // put list behind tidy name
894 p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
895 if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
896 ASSIGN(*list, tidy+1);
900 Load (ChessProgramState *cps, int i)
902 char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
903 if(engineLine && engineLine[0]) { // an engine was selected from the combo box
904 snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
905 SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
906 ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
907 appData.firstProtocolVersion = PROTOVER;
908 ParseArgsFromString(buf);
910 ReplaceEngine(cps, i);
911 FloatToFront(&appData.recentEngineList, engineLine);
915 while(q = strchr(p, SLASH)) p = q+1;
916 if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
917 if(engineDir[0] != NULLCHAR)
918 appData.directory[i] = engineDir;
919 else if(p != engineName) { // derive directory from engine path, when not given
921 appData.directory[i] = strdup(engineName);
923 } else appData.directory[i] = ".";
925 if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
926 snprintf(command, MSG_SIZ, "%s %s", p, params);
929 appData.chessProgram[i] = strdup(p);
930 appData.isUCI[i] = isUCI;
931 appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
932 appData.hasOwnBookUCI[i] = hasBook;
933 if(!nickName[0]) useNick = FALSE;
934 if(useNick) ASSIGN(appData.pgnName[i], nickName);
938 q = firstChessProgramNames;
939 if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
940 quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
941 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
942 quote, p, quote, appData.directory[i],
943 useNick ? " -fn \"" : "",
944 useNick ? nickName : "",
946 v1 ? " -firstProtocolVersion 1" : "",
947 hasBook ? "" : " -fNoOwnBookUCI",
948 isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
949 storeVariant ? " -variant " : "",
950 storeVariant ? VariantName(gameInfo.variant) : "");
951 firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
952 snprintf(firstChessProgramNames, len, "%s%s", q, buf);
954 FloatToFront(&appData.recentEngineList, buf);
956 ReplaceEngine(cps, i);
962 int matched, min, sec;
964 * Parse timeControl resource
966 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
967 appData.movesPerSession)) {
969 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
970 DisplayFatalError(buf, 0, 2);
974 * Parse searchTime resource
976 if (*appData.searchTime != NULLCHAR) {
977 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
979 searchTime = min * 60;
980 } else if (matched == 2) {
981 searchTime = min * 60 + sec;
984 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
985 DisplayFatalError(buf, 0, 2);
994 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
995 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
997 GetTimeMark(&programStartTime);
998 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
999 appData.seedBase = random() + (random()<<15);
1000 pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1002 ClearProgramStats();
1003 programStats.ok_to_send = 1;
1004 programStats.seen_stat = 0;
1007 * Initialize game list
1013 * Internet chess server status
1015 if (appData.icsActive) {
1016 appData.matchMode = FALSE;
1017 appData.matchGames = 0;
1019 appData.noChessProgram = !appData.zippyPlay;
1021 appData.zippyPlay = FALSE;
1022 appData.zippyTalk = FALSE;
1023 appData.noChessProgram = TRUE;
1025 if (*appData.icsHelper != NULLCHAR) {
1026 appData.useTelnet = TRUE;
1027 appData.telnetProgram = appData.icsHelper;
1030 appData.zippyTalk = appData.zippyPlay = FALSE;
1033 /* [AS] Initialize pv info list [HGM] and game state */
1037 for( i=0; i<=framePtr; i++ ) {
1038 pvInfoList[i].depth = -1;
1039 boards[i][EP_STATUS] = EP_NONE;
1040 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1046 /* [AS] Adjudication threshold */
1047 adjudicateLossThreshold = appData.adjudicateLossThreshold;
1049 InitEngine(&first, 0);
1050 InitEngine(&second, 1);
1053 pairing.which = "pairing"; // pairing engine
1054 pairing.pr = NoProc;
1056 pairing.program = appData.pairingEngine;
1057 pairing.host = "localhost";
1060 if (appData.icsActive) {
1061 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
1062 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1063 appData.clockMode = FALSE;
1064 first.sendTime = second.sendTime = 0;
1068 /* Override some settings from environment variables, for backward
1069 compatibility. Unfortunately it's not feasible to have the env
1070 vars just set defaults, at least in xboard. Ugh.
1072 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1077 if (!appData.icsActive) {
1081 /* Check for variants that are supported only in ICS mode,
1082 or not at all. Some that are accepted here nevertheless
1083 have bugs; see comments below.
1085 VariantClass variant = StringToVariant(appData.variant);
1087 case VariantBughouse: /* need four players and two boards */
1088 case VariantKriegspiel: /* need to hide pieces and move details */
1089 /* case VariantFischeRandom: (Fabien: moved below) */
1090 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1091 if( (len >= MSG_SIZ) && appData.debugMode )
1092 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1094 DisplayFatalError(buf, 0, 2);
1097 case VariantUnknown:
1098 case VariantLoadable:
1108 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1109 if( (len >= MSG_SIZ) && appData.debugMode )
1110 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1112 DisplayFatalError(buf, 0, 2);
1115 case VariantXiangqi: /* [HGM] repetition rules not implemented */
1116 case VariantFairy: /* [HGM] TestLegality definitely off! */
1117 case VariantGothic: /* [HGM] should work */
1118 case VariantCapablanca: /* [HGM] should work */
1119 case VariantCourier: /* [HGM] initial forced moves not implemented */
1120 case VariantShogi: /* [HGM] could still mate with pawn drop */
1121 case VariantKnightmate: /* [HGM] should work */
1122 case VariantCylinder: /* [HGM] untested */
1123 case VariantFalcon: /* [HGM] untested */
1124 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1125 offboard interposition not understood */
1126 case VariantNormal: /* definitely works! */
1127 case VariantWildCastle: /* pieces not automatically shuffled */
1128 case VariantNoCastle: /* pieces not automatically shuffled */
1129 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1130 case VariantLosers: /* should work except for win condition,
1131 and doesn't know captures are mandatory */
1132 case VariantSuicide: /* should work except for win condition,
1133 and doesn't know captures are mandatory */
1134 case VariantGiveaway: /* should work except for win condition,
1135 and doesn't know captures are mandatory */
1136 case VariantTwoKings: /* should work */
1137 case VariantAtomic: /* should work except for win condition */
1138 case Variant3Check: /* should work except for win condition */
1139 case VariantShatranj: /* should work except for all win conditions */
1140 case VariantMakruk: /* should work except for draw countdown */
1141 case VariantBerolina: /* might work if TestLegality is off */
1142 case VariantCapaRandom: /* should work */
1143 case VariantJanus: /* should work */
1144 case VariantSuper: /* experimental */
1145 case VariantGreat: /* experimental, requires legality testing to be off */
1146 case VariantSChess: /* S-Chess, should work */
1147 case VariantGrand: /* should work */
1148 case VariantSpartan: /* should work */
1156 NextIntegerFromString (char ** str, long * value)
1161 while( *s == ' ' || *s == '\t' ) {
1167 if( *s >= '0' && *s <= '9' ) {
1168 while( *s >= '0' && *s <= '9' ) {
1169 *value = *value * 10 + (*s - '0');
1182 NextTimeControlFromString (char ** str, long * value)
1185 int result = NextIntegerFromString( str, &temp );
1188 *value = temp * 60; /* Minutes */
1189 if( **str == ':' ) {
1191 result = NextIntegerFromString( str, &temp );
1192 *value += temp; /* Seconds */
1200 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1201 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1202 int result = -1, type = 0; long temp, temp2;
1204 if(**str != ':') return -1; // old params remain in force!
1206 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1207 if( NextIntegerFromString( str, &temp ) ) return -1;
1208 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1211 /* time only: incremental or sudden-death time control */
1212 if(**str == '+') { /* increment follows; read it */
1214 if(**str == '!') type = *(*str)++; // Bronstein TC
1215 if(result = NextIntegerFromString( str, &temp2)) return -1;
1216 *inc = temp2 * 1000;
1217 if(**str == '.') { // read fraction of increment
1218 char *start = ++(*str);
1219 if(result = NextIntegerFromString( str, &temp2)) return -1;
1221 while(start++ < *str) temp2 /= 10;
1225 *moves = 0; *tc = temp * 1000; *incType = type;
1229 (*str)++; /* classical time control */
1230 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1242 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1243 { /* [HGM] get time to add from the multi-session time-control string */
1244 int incType, moves=1; /* kludge to force reading of first session */
1245 long time, increment;
1248 if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1250 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1251 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1252 if(movenr == -1) return time; /* last move before new session */
1253 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1254 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1255 if(!moves) return increment; /* current session is incremental */
1256 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1257 } while(movenr >= -1); /* try again for next session */
1259 return 0; // no new time quota on this move
1263 ParseTimeControl (char *tc, float ti, int mps)
1267 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1270 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1271 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1272 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1276 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1278 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1281 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1283 snprintf(buf, MSG_SIZ, ":%s", mytc);
1285 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1287 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1292 /* Parse second time control */
1295 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1303 timeControl_2 = tc2 * 1000;
1313 timeControl = tc1 * 1000;
1316 timeIncrement = ti * 1000; /* convert to ms */
1317 movesPerSession = 0;
1320 movesPerSession = mps;
1328 if (appData.debugMode) {
1329 fprintf(debugFP, "%s\n", programVersion);
1331 ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1333 set_cont_sequence(appData.wrapContSeq);
1334 if (appData.matchGames > 0) {
1335 appData.matchMode = TRUE;
1336 } else if (appData.matchMode) {
1337 appData.matchGames = 1;
1339 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1340 appData.matchGames = appData.sameColorGames;
1341 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1342 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1343 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1346 if (appData.noChessProgram || first.protocolVersion == 1) {
1349 /* kludge: allow timeout for initial "feature" commands */
1351 DisplayMessage("", _("Starting chess program"));
1352 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1357 CalculateIndex (int index, int gameNr)
1358 { // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1360 if(index > 0) return index; // fixed nmber
1361 if(index == 0) return 1;
1362 res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1363 if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1368 LoadGameOrPosition (int gameNr)
1369 { // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1370 if (*appData.loadGameFile != NULLCHAR) {
1371 if (!LoadGameFromFile(appData.loadGameFile,
1372 CalculateIndex(appData.loadGameIndex, gameNr),
1373 appData.loadGameFile, FALSE)) {
1374 DisplayFatalError(_("Bad game file"), 0, 1);
1377 } else if (*appData.loadPositionFile != NULLCHAR) {
1378 if (!LoadPositionFromFile(appData.loadPositionFile,
1379 CalculateIndex(appData.loadPositionIndex, gameNr),
1380 appData.loadPositionFile)) {
1381 DisplayFatalError(_("Bad position file"), 0, 1);
1389 ReserveGame (int gameNr, char resChar)
1391 FILE *tf = fopen(appData.tourneyFile, "r+");
1392 char *p, *q, c, buf[MSG_SIZ];
1393 if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1394 safeStrCpy(buf, lastMsg, MSG_SIZ);
1395 DisplayMessage(_("Pick new game"), "");
1396 flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1397 ParseArgsFromFile(tf);
1398 p = q = appData.results;
1399 if(appData.debugMode) {
1400 char *r = appData.participants;
1401 fprintf(debugFP, "results = '%s'\n", p);
1402 while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1403 fprintf(debugFP, "\n");
1405 while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1407 q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1408 safeStrCpy(q, p, strlen(p) + 2);
1409 if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1410 if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1411 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1412 if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1415 fseek(tf, -(strlen(p)+4), SEEK_END);
1417 if(c != '"') // depending on DOS or Unix line endings we can be one off
1418 fseek(tf, -(strlen(p)+2), SEEK_END);
1419 else fseek(tf, -(strlen(p)+3), SEEK_END);
1420 fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1421 DisplayMessage(buf, "");
1422 free(p); appData.results = q;
1423 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1424 (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1425 int round = appData.defaultMatchGames * appData.tourneyType;
1426 if(gameNr < 0 || appData.tourneyType < 1 || // gauntlet engine can always stay loaded as first engine
1427 appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1428 UnloadEngine(&first); // next game belongs to other pairing;
1429 UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1431 if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1435 MatchEvent (int mode)
1436 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1438 if(matchMode) { // already in match mode: switch it off
1440 if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1443 // if(gameMode != BeginningOfGame) {
1444 // DisplayError(_("You can only start a match from the initial position."), 0);
1448 if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1449 /* Set up machine vs. machine match */
1451 NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1452 if(appData.tourneyFile[0]) {
1454 if(nextGame > appData.matchGames) {
1456 if(strchr(appData.results, '*') == NULL) {
1458 appData.tourneyCycles++;
1459 if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1461 NextTourneyGame(-1, &dummy);
1463 if(nextGame <= appData.matchGames) {
1464 DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1466 ScheduleDelayedEvent(NextMatchGame, 10000);
1471 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1472 DisplayError(buf, 0);
1473 appData.tourneyFile[0] = 0;
1477 if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically
1478 DisplayFatalError(_("Can't have a match with no chess programs"),
1483 matchGame = roundNr = 1;
1484 first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1489 InitBackEnd3 P((void))
1491 GameMode initialMode;
1495 InitChessProgram(&first, startedFromSetupPosition);
1497 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1498 free(programVersion);
1499 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1500 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1501 FloatToFront(&appData.recentEngineList, appData.firstChessProgram);
1504 if (appData.icsActive) {
1506 /* [DM] Make a console window if needed [HGM] merged ifs */
1512 if (*appData.icsCommPort != NULLCHAR)
1513 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1514 appData.icsCommPort);
1516 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1517 appData.icsHost, appData.icsPort);
1519 if( (len >= MSG_SIZ) && appData.debugMode )
1520 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1522 DisplayFatalError(buf, err, 1);
1527 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1529 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1530 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1531 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1532 } else if (appData.noChessProgram) {
1538 if (*appData.cmailGameName != NULLCHAR) {
1540 OpenLoopback(&cmailPR);
1542 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1546 DisplayMessage("", "");
1547 if (StrCaseCmp(appData.initialMode, "") == 0) {
1548 initialMode = BeginningOfGame;
1549 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1550 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1551 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1552 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1555 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1556 initialMode = TwoMachinesPlay;
1557 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1558 initialMode = AnalyzeFile;
1559 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1560 initialMode = AnalyzeMode;
1561 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1562 initialMode = MachinePlaysWhite;
1563 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1564 initialMode = MachinePlaysBlack;
1565 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1566 initialMode = EditGame;
1567 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1568 initialMode = EditPosition;
1569 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1570 initialMode = Training;
1572 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1573 if( (len >= MSG_SIZ) && appData.debugMode )
1574 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1576 DisplayFatalError(buf, 0, 2);
1580 if (appData.matchMode) {
1581 if(appData.tourneyFile[0]) { // start tourney from command line
1583 if(f = fopen(appData.tourneyFile, "r")) {
1584 ParseArgsFromFile(f); // make sure tourney parmeters re known
1586 appData.clockMode = TRUE;
1588 } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1591 } else if (*appData.cmailGameName != NULLCHAR) {
1592 /* Set up cmail mode */
1593 ReloadCmailMsgEvent(TRUE);
1595 /* Set up other modes */
1596 if (initialMode == AnalyzeFile) {
1597 if (*appData.loadGameFile == NULLCHAR) {
1598 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1602 if (*appData.loadGameFile != NULLCHAR) {
1603 (void) LoadGameFromFile(appData.loadGameFile,
1604 appData.loadGameIndex,
1605 appData.loadGameFile, TRUE);
1606 } else if (*appData.loadPositionFile != NULLCHAR) {
1607 (void) LoadPositionFromFile(appData.loadPositionFile,
1608 appData.loadPositionIndex,
1609 appData.loadPositionFile);
1610 /* [HGM] try to make self-starting even after FEN load */
1611 /* to allow automatic setup of fairy variants with wtm */
1612 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1613 gameMode = BeginningOfGame;
1614 setboardSpoiledMachineBlack = 1;
1616 /* [HGM] loadPos: make that every new game uses the setup */
1617 /* from file as long as we do not switch variant */
1618 if(!blackPlaysFirst) {
1619 startedFromPositionFile = TRUE;
1620 CopyBoard(filePosition, boards[0]);
1623 if (initialMode == AnalyzeMode) {
1624 if (appData.noChessProgram) {
1625 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1628 if (appData.icsActive) {
1629 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1633 } else if (initialMode == AnalyzeFile) {
1634 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1635 ShowThinkingEvent();
1637 AnalysisPeriodicEvent(1);
1638 } else if (initialMode == MachinePlaysWhite) {
1639 if (appData.noChessProgram) {
1640 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1644 if (appData.icsActive) {
1645 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1649 MachineWhiteEvent();
1650 } else if (initialMode == MachinePlaysBlack) {
1651 if (appData.noChessProgram) {
1652 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1656 if (appData.icsActive) {
1657 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1661 MachineBlackEvent();
1662 } else if (initialMode == TwoMachinesPlay) {
1663 if (appData.noChessProgram) {
1664 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1668 if (appData.icsActive) {
1669 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1674 } else if (initialMode == EditGame) {
1676 } else if (initialMode == EditPosition) {
1677 EditPositionEvent();
1678 } else if (initialMode == Training) {
1679 if (*appData.loadGameFile == NULLCHAR) {
1680 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1689 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1691 DisplayBook(current+1);
1693 MoveHistorySet( movelist, first, last, current, pvInfoList );
1695 EvalGraphSet( first, last, current, pvInfoList );
1697 MakeEngineOutputTitle();
1701 * Establish will establish a contact to a remote host.port.
1702 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1703 * used to talk to the host.
1704 * Returns 0 if okay, error code if not.
1711 if (*appData.icsCommPort != NULLCHAR) {
1712 /* Talk to the host through a serial comm port */
1713 return OpenCommPort(appData.icsCommPort, &icsPR);
1715 } else if (*appData.gateway != NULLCHAR) {
1716 if (*appData.remoteShell == NULLCHAR) {
1717 /* Use the rcmd protocol to run telnet program on a gateway host */
1718 snprintf(buf, sizeof(buf), "%s %s %s",
1719 appData.telnetProgram, appData.icsHost, appData.icsPort);
1720 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1723 /* Use the rsh program to run telnet program on a gateway host */
1724 if (*appData.remoteUser == NULLCHAR) {
1725 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1726 appData.gateway, appData.telnetProgram,
1727 appData.icsHost, appData.icsPort);
1729 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1730 appData.remoteShell, appData.gateway,
1731 appData.remoteUser, appData.telnetProgram,
1732 appData.icsHost, appData.icsPort);
1734 return StartChildProcess(buf, "", &icsPR);
1737 } else if (appData.useTelnet) {
1738 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1741 /* TCP socket interface differs somewhat between
1742 Unix and NT; handle details in the front end.
1744 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1749 EscapeExpand (char *p, char *q)
1750 { // [HGM] initstring: routine to shape up string arguments
1751 while(*p++ = *q++) if(p[-1] == '\\')
1753 case 'n': p[-1] = '\n'; break;
1754 case 'r': p[-1] = '\r'; break;
1755 case 't': p[-1] = '\t'; break;
1756 case '\\': p[-1] = '\\'; break;
1757 case 0: *p = 0; return;
1758 default: p[-1] = q[-1]; break;
1763 show_bytes (FILE *fp, char *buf, int count)
1766 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1767 fprintf(fp, "\\%03o", *buf & 0xff);
1776 /* Returns an errno value */
1778 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1780 char buf[8192], *p, *q, *buflim;
1781 int left, newcount, outcount;
1783 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1784 *appData.gateway != NULLCHAR) {
1785 if (appData.debugMode) {
1786 fprintf(debugFP, ">ICS: ");
1787 show_bytes(debugFP, message, count);
1788 fprintf(debugFP, "\n");
1790 return OutputToProcess(pr, message, count, outError);
1793 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1800 if (appData.debugMode) {
1801 fprintf(debugFP, ">ICS: ");
1802 show_bytes(debugFP, buf, newcount);
1803 fprintf(debugFP, "\n");
1805 outcount = OutputToProcess(pr, buf, newcount, outError);
1806 if (outcount < newcount) return -1; /* to be sure */
1813 } else if (((unsigned char) *p) == TN_IAC) {
1814 *q++ = (char) TN_IAC;
1821 if (appData.debugMode) {
1822 fprintf(debugFP, ">ICS: ");
1823 show_bytes(debugFP, buf, newcount);
1824 fprintf(debugFP, "\n");
1826 outcount = OutputToProcess(pr, buf, newcount, outError);
1827 if (outcount < newcount) return -1; /* to be sure */
1832 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1834 int outError, outCount;
1835 static int gotEof = 0;
1837 /* Pass data read from player on to ICS */
1840 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1841 if (outCount < count) {
1842 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1844 } else if (count < 0) {
1845 RemoveInputSource(isr);
1846 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1847 } else if (gotEof++ > 0) {
1848 RemoveInputSource(isr);
1849 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1855 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1856 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1857 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1858 SendToICS("date\n");
1859 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1862 /* added routine for printf style output to ics */
1864 ics_printf (char *format, ...)
1866 char buffer[MSG_SIZ];
1869 va_start(args, format);
1870 vsnprintf(buffer, sizeof(buffer), format, args);
1871 buffer[sizeof(buffer)-1] = '\0';
1879 int count, outCount, outError;
1881 if (icsPR == NoProc) return;
1884 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1885 if (outCount < count) {
1886 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1890 /* This is used for sending logon scripts to the ICS. Sending
1891 without a delay causes problems when using timestamp on ICC
1892 (at least on my machine). */
1894 SendToICSDelayed (char *s, long msdelay)
1896 int count, outCount, outError;
1898 if (icsPR == NoProc) return;
1901 if (appData.debugMode) {
1902 fprintf(debugFP, ">ICS: ");
1903 show_bytes(debugFP, s, count);
1904 fprintf(debugFP, "\n");
1906 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1908 if (outCount < count) {
1909 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1914 /* Remove all highlighting escape sequences in s
1915 Also deletes any suffix starting with '('
1918 StripHighlightAndTitle (char *s)
1920 static char retbuf[MSG_SIZ];
1923 while (*s != NULLCHAR) {
1924 while (*s == '\033') {
1925 while (*s != NULLCHAR && !isalpha(*s)) s++;
1926 if (*s != NULLCHAR) s++;
1928 while (*s != NULLCHAR && *s != '\033') {
1929 if (*s == '(' || *s == '[') {
1940 /* Remove all highlighting escape sequences in s */
1942 StripHighlight (char *s)
1944 static char retbuf[MSG_SIZ];
1947 while (*s != NULLCHAR) {
1948 while (*s == '\033') {
1949 while (*s != NULLCHAR && !isalpha(*s)) s++;
1950 if (*s != NULLCHAR) s++;
1952 while (*s != NULLCHAR && *s != '\033') {
1960 char *variantNames[] = VARIANT_NAMES;
1962 VariantName (VariantClass v)
1964 return variantNames[v];
1968 /* Identify a variant from the strings the chess servers use or the
1969 PGN Variant tag names we use. */
1971 StringToVariant (char *e)
1975 VariantClass v = VariantNormal;
1976 int i, found = FALSE;
1982 /* [HGM] skip over optional board-size prefixes */
1983 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1984 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1985 while( *e++ != '_');
1988 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1992 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1993 if (StrCaseStr(e, variantNames[i])) {
1994 v = (VariantClass) i;
2001 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2002 || StrCaseStr(e, "wild/fr")
2003 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2004 v = VariantFischeRandom;
2005 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2006 (i = 1, p = StrCaseStr(e, "w"))) {
2008 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2015 case 0: /* FICS only, actually */
2017 /* Castling legal even if K starts on d-file */
2018 v = VariantWildCastle;
2023 /* Castling illegal even if K & R happen to start in
2024 normal positions. */
2025 v = VariantNoCastle;
2038 /* Castling legal iff K & R start in normal positions */
2044 /* Special wilds for position setup; unclear what to do here */
2045 v = VariantLoadable;
2048 /* Bizarre ICC game */
2049 v = VariantTwoKings;
2052 v = VariantKriegspiel;
2058 v = VariantFischeRandom;
2061 v = VariantCrazyhouse;
2064 v = VariantBughouse;
2070 /* Not quite the same as FICS suicide! */
2071 v = VariantGiveaway;
2077 v = VariantShatranj;
2080 /* Temporary names for future ICC types. The name *will* change in
2081 the next xboard/WinBoard release after ICC defines it. */
2119 v = VariantCapablanca;
2122 v = VariantKnightmate;
2128 v = VariantCylinder;
2134 v = VariantCapaRandom;
2137 v = VariantBerolina;
2149 /* Found "wild" or "w" in the string but no number;
2150 must assume it's normal chess. */
2154 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2155 if( (len >= MSG_SIZ) && appData.debugMode )
2156 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2158 DisplayError(buf, 0);
2164 if (appData.debugMode) {
2165 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2166 e, wnum, VariantName(v));
2171 static int leftover_start = 0, leftover_len = 0;
2172 char star_match[STAR_MATCH_N][MSG_SIZ];
2174 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2175 advance *index beyond it, and set leftover_start to the new value of
2176 *index; else return FALSE. If pattern contains the character '*', it
2177 matches any sequence of characters not containing '\r', '\n', or the
2178 character following the '*' (if any), and the matched sequence(s) are
2179 copied into star_match.
2182 looking_at ( char *buf, int *index, char *pattern)
2184 char *bufp = &buf[*index], *patternp = pattern;
2186 char *matchp = star_match[0];
2189 if (*patternp == NULLCHAR) {
2190 *index = leftover_start = bufp - buf;
2194 if (*bufp == NULLCHAR) return FALSE;
2195 if (*patternp == '*') {
2196 if (*bufp == *(patternp + 1)) {
2198 matchp = star_match[++star_count];
2202 } else if (*bufp == '\n' || *bufp == '\r') {
2204 if (*patternp == NULLCHAR)
2209 *matchp++ = *bufp++;
2213 if (*patternp != *bufp) return FALSE;
2220 SendToPlayer (char *data, int length)
2222 int error, outCount;
2223 outCount = OutputToProcess(NoProc, data, length, &error);
2224 if (outCount < length) {
2225 DisplayFatalError(_("Error writing to display"), error, 1);
2230 PackHolding (char packed[], char *holding)
2240 switch (runlength) {
2251 sprintf(q, "%d", runlength);
2263 /* Telnet protocol requests from the front end */
2265 TelnetRequest (unsigned char ddww, unsigned char option)
2267 unsigned char msg[3];
2268 int outCount, outError;
2270 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2272 if (appData.debugMode) {
2273 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2289 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2298 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2301 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2306 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2308 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2315 if (!appData.icsActive) return;
2316 TelnetRequest(TN_DO, TN_ECHO);
2322 if (!appData.icsActive) return;
2323 TelnetRequest(TN_DONT, TN_ECHO);
2327 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2329 /* put the holdings sent to us by the server on the board holdings area */
2330 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2334 if(gameInfo.holdingsWidth < 2) return;
2335 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2336 return; // prevent overwriting by pre-board holdings
2338 if( (int)lowestPiece >= BlackPawn ) {
2341 holdingsStartRow = BOARD_HEIGHT-1;
2344 holdingsColumn = BOARD_WIDTH-1;
2345 countsColumn = BOARD_WIDTH-2;
2346 holdingsStartRow = 0;
2350 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2351 board[i][holdingsColumn] = EmptySquare;
2352 board[i][countsColumn] = (ChessSquare) 0;
2354 while( (p=*holdings++) != NULLCHAR ) {
2355 piece = CharToPiece( ToUpper(p) );
2356 if(piece == EmptySquare) continue;
2357 /*j = (int) piece - (int) WhitePawn;*/
2358 j = PieceToNumber(piece);
2359 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2360 if(j < 0) continue; /* should not happen */
2361 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2362 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2363 board[holdingsStartRow+j*direction][countsColumn]++;
2369 VariantSwitch (Board board, VariantClass newVariant)
2371 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2372 static Board oldBoard;
2374 startedFromPositionFile = FALSE;
2375 if(gameInfo.variant == newVariant) return;
2377 /* [HGM] This routine is called each time an assignment is made to
2378 * gameInfo.variant during a game, to make sure the board sizes
2379 * are set to match the new variant. If that means adding or deleting
2380 * holdings, we shift the playing board accordingly
2381 * This kludge is needed because in ICS observe mode, we get boards
2382 * of an ongoing game without knowing the variant, and learn about the
2383 * latter only later. This can be because of the move list we requested,
2384 * in which case the game history is refilled from the beginning anyway,
2385 * but also when receiving holdings of a crazyhouse game. In the latter
2386 * case we want to add those holdings to the already received position.
2390 if (appData.debugMode) {
2391 fprintf(debugFP, "Switch board from %s to %s\n",
2392 VariantName(gameInfo.variant), VariantName(newVariant));
2393 setbuf(debugFP, NULL);
2395 shuffleOpenings = 0; /* [HGM] shuffle */
2396 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2400 newWidth = 9; newHeight = 9;
2401 gameInfo.holdingsSize = 7;
2402 case VariantBughouse:
2403 case VariantCrazyhouse:
2404 newHoldingsWidth = 2; break;
2408 newHoldingsWidth = 2;
2409 gameInfo.holdingsSize = 8;
2412 case VariantCapablanca:
2413 case VariantCapaRandom:
2416 newHoldingsWidth = gameInfo.holdingsSize = 0;
2419 if(newWidth != gameInfo.boardWidth ||
2420 newHeight != gameInfo.boardHeight ||
2421 newHoldingsWidth != gameInfo.holdingsWidth ) {
2423 /* shift position to new playing area, if needed */
2424 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2425 for(i=0; i<BOARD_HEIGHT; i++)
2426 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2427 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2429 for(i=0; i<newHeight; i++) {
2430 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2431 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2433 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2434 for(i=0; i<BOARD_HEIGHT; i++)
2435 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2436 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2439 gameInfo.boardWidth = newWidth;
2440 gameInfo.boardHeight = newHeight;
2441 gameInfo.holdingsWidth = newHoldingsWidth;
2442 gameInfo.variant = newVariant;
2443 InitDrawingSizes(-2, 0);
2444 } else gameInfo.variant = newVariant;
2445 CopyBoard(oldBoard, board); // remember correctly formatted board
2446 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2447 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2450 static int loggedOn = FALSE;
2452 /*-- Game start info cache: --*/
2454 char gs_kind[MSG_SIZ];
2455 static char player1Name[128] = "";
2456 static char player2Name[128] = "";
2457 static char cont_seq[] = "\n\\ ";
2458 static int player1Rating = -1;
2459 static int player2Rating = -1;
2460 /*----------------------------*/
2462 ColorClass curColor = ColorNormal;
2463 int suppressKibitz = 0;
2466 Boolean soughtPending = FALSE;
2467 Boolean seekGraphUp;
2468 #define MAX_SEEK_ADS 200
2470 char *seekAdList[MAX_SEEK_ADS];
2471 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2472 float tcList[MAX_SEEK_ADS];
2473 char colorList[MAX_SEEK_ADS];
2474 int nrOfSeekAds = 0;
2475 int minRating = 1010, maxRating = 2800;
2476 int hMargin = 10, vMargin = 20, h, w;
2477 extern int squareSize, lineGap;
2482 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2483 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2484 if(r < minRating+100 && r >=0 ) r = minRating+100;
2485 if(r > maxRating) r = maxRating;
2486 if(tc < 1.) tc = 1.;
2487 if(tc > 95.) tc = 95.;
2488 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2489 y = ((double)r - minRating)/(maxRating - minRating)
2490 * (h-vMargin-squareSize/8-1) + vMargin;
2491 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2492 if(strstr(seekAdList[i], " u ")) color = 1;
2493 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2494 !strstr(seekAdList[i], "bullet") &&
2495 !strstr(seekAdList[i], "blitz") &&
2496 !strstr(seekAdList[i], "standard") ) color = 2;
2497 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2498 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2502 AddAd (char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2504 char buf[MSG_SIZ], *ext = "";
2505 VariantClass v = StringToVariant(type);
2506 if(strstr(type, "wild")) {
2507 ext = type + 4; // append wild number
2508 if(v == VariantFischeRandom) type = "chess960"; else
2509 if(v == VariantLoadable) type = "setup"; else
2510 type = VariantName(v);
2512 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2513 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2514 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2515 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2516 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2517 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2518 seekNrList[nrOfSeekAds] = nr;
2519 zList[nrOfSeekAds] = 0;
2520 seekAdList[nrOfSeekAds++] = StrSave(buf);
2521 if(plot) PlotSeekAd(nrOfSeekAds-1);
2526 EraseSeekDot (int i)
2528 int x = xList[i], y = yList[i], d=squareSize/4, k;
2529 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2530 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2531 // now replot every dot that overlapped
2532 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2533 int xx = xList[k], yy = yList[k];
2534 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2535 DrawSeekDot(xx, yy, colorList[k]);
2540 RemoveSeekAd (int nr)
2543 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2545 if(seekAdList[i]) free(seekAdList[i]);
2546 seekAdList[i] = seekAdList[--nrOfSeekAds];
2547 seekNrList[i] = seekNrList[nrOfSeekAds];
2548 ratingList[i] = ratingList[nrOfSeekAds];
2549 colorList[i] = colorList[nrOfSeekAds];
2550 tcList[i] = tcList[nrOfSeekAds];
2551 xList[i] = xList[nrOfSeekAds];
2552 yList[i] = yList[nrOfSeekAds];
2553 zList[i] = zList[nrOfSeekAds];
2554 seekAdList[nrOfSeekAds] = NULL;
2560 MatchSoughtLine (char *line)
2562 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2563 int nr, base, inc, u=0; char dummy;
2565 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2566 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2568 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2569 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2570 // match: compact and save the line
2571 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2581 if(!seekGraphUp) return FALSE;
2582 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2583 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2585 DrawSeekBackground(0, 0, w, h);
2586 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2587 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2588 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2589 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2591 DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2594 snprintf(buf, MSG_SIZ, "%d", i);
2595 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2598 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2599 for(i=1; i<100; i+=(i<10?1:5)) {
2600 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2601 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2602 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2604 snprintf(buf, MSG_SIZ, "%d", i);
2605 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2608 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2613 SeekGraphClick (ClickType click, int x, int y, int moving)
2615 static int lastDown = 0, displayed = 0, lastSecond;
2616 if(y < 0) return FALSE;
2617 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2618 if(click == Release || moving) return FALSE;
2620 soughtPending = TRUE;
2621 SendToICS(ics_prefix);
2622 SendToICS("sought\n"); // should this be "sought all"?
2623 } else { // issue challenge based on clicked ad
2624 int dist = 10000; int i, closest = 0, second = 0;
2625 for(i=0; i<nrOfSeekAds; i++) {
2626 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2627 if(d < dist) { dist = d; closest = i; }
2628 second += (d - zList[i] < 120); // count in-range ads
2629 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2633 second = (second > 1);
2634 if(displayed != closest || second != lastSecond) {
2635 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2636 lastSecond = second; displayed = closest;
2638 if(click == Press) {
2639 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2642 } // on press 'hit', only show info
2643 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2644 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2645 SendToICS(ics_prefix);
2647 return TRUE; // let incoming board of started game pop down the graph
2648 } else if(click == Release) { // release 'miss' is ignored
2649 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2650 if(moving == 2) { // right up-click
2651 nrOfSeekAds = 0; // refresh graph
2652 soughtPending = TRUE;
2653 SendToICS(ics_prefix);
2654 SendToICS("sought\n"); // should this be "sought all"?
2657 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2658 // press miss or release hit 'pop down' seek graph
2659 seekGraphUp = FALSE;
2660 DrawPosition(TRUE, NULL);
2666 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2668 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2669 #define STARTED_NONE 0
2670 #define STARTED_MOVES 1
2671 #define STARTED_BOARD 2
2672 #define STARTED_OBSERVE 3
2673 #define STARTED_HOLDINGS 4
2674 #define STARTED_CHATTER 5
2675 #define STARTED_COMMENT 6
2676 #define STARTED_MOVES_NOHIDE 7
2678 static int started = STARTED_NONE;
2679 static char parse[20000];
2680 static int parse_pos = 0;
2681 static char buf[BUF_SIZE + 1];
2682 static int firstTime = TRUE, intfSet = FALSE;
2683 static ColorClass prevColor = ColorNormal;
2684 static int savingComment = FALSE;
2685 static int cmatch = 0; // continuation sequence match
2692 int backup; /* [DM] For zippy color lines */
2694 char talker[MSG_SIZ]; // [HGM] chat
2697 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2699 if (appData.debugMode) {
2701 fprintf(debugFP, "<ICS: ");
2702 show_bytes(debugFP, data, count);
2703 fprintf(debugFP, "\n");
2707 if (appData.debugMode) { int f = forwardMostMove;
2708 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2709 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2710 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2713 /* If last read ended with a partial line that we couldn't parse,
2714 prepend it to the new read and try again. */
2715 if (leftover_len > 0) {
2716 for (i=0; i<leftover_len; i++)
2717 buf[i] = buf[leftover_start + i];
2720 /* copy new characters into the buffer */
2721 bp = buf + leftover_len;
2722 buf_len=leftover_len;
2723 for (i=0; i<count; i++)
2726 if (data[i] == '\r')
2729 // join lines split by ICS?
2730 if (!appData.noJoin)
2733 Joining just consists of finding matches against the
2734 continuation sequence, and discarding that sequence
2735 if found instead of copying it. So, until a match
2736 fails, there's nothing to do since it might be the
2737 complete sequence, and thus, something we don't want
2740 if (data[i] == cont_seq[cmatch])
2743 if (cmatch == strlen(cont_seq))
2745 cmatch = 0; // complete match. just reset the counter
2748 it's possible for the ICS to not include the space
2749 at the end of the last word, making our [correct]
2750 join operation fuse two separate words. the server
2751 does this when the space occurs at the width setting.
2753 if (!buf_len || buf[buf_len-1] != ' ')
2764 match failed, so we have to copy what matched before
2765 falling through and copying this character. In reality,
2766 this will only ever be just the newline character, but
2767 it doesn't hurt to be precise.
2769 strncpy(bp, cont_seq, cmatch);
2781 buf[buf_len] = NULLCHAR;
2782 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2787 while (i < buf_len) {
2788 /* Deal with part of the TELNET option negotiation
2789 protocol. We refuse to do anything beyond the
2790 defaults, except that we allow the WILL ECHO option,
2791 which ICS uses to turn off password echoing when we are
2792 directly connected to it. We reject this option
2793 if localLineEditing mode is on (always on in xboard)
2794 and we are talking to port 23, which might be a real
2795 telnet server that will try to keep WILL ECHO on permanently.
2797 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2798 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2799 unsigned char option;
2801 switch ((unsigned char) buf[++i]) {
2803 if (appData.debugMode)
2804 fprintf(debugFP, "\n<WILL ");
2805 switch (option = (unsigned char) buf[++i]) {
2807 if (appData.debugMode)
2808 fprintf(debugFP, "ECHO ");
2809 /* Reply only if this is a change, according
2810 to the protocol rules. */
2811 if (remoteEchoOption) break;
2812 if (appData.localLineEditing &&
2813 atoi(appData.icsPort) == TN_PORT) {
2814 TelnetRequest(TN_DONT, TN_ECHO);
2817 TelnetRequest(TN_DO, TN_ECHO);
2818 remoteEchoOption = TRUE;
2822 if (appData.debugMode)
2823 fprintf(debugFP, "%d ", option);
2824 /* Whatever this is, we don't want it. */
2825 TelnetRequest(TN_DONT, option);
2830 if (appData.debugMode)
2831 fprintf(debugFP, "\n<WONT ");
2832 switch (option = (unsigned char) buf[++i]) {
2834 if (appData.debugMode)
2835 fprintf(debugFP, "ECHO ");
2836 /* Reply only if this is a change, according
2837 to the protocol rules. */
2838 if (!remoteEchoOption) break;
2840 TelnetRequest(TN_DONT, TN_ECHO);
2841 remoteEchoOption = FALSE;
2844 if (appData.debugMode)
2845 fprintf(debugFP, "%d ", (unsigned char) option);
2846 /* Whatever this is, it must already be turned
2847 off, because we never agree to turn on
2848 anything non-default, so according to the
2849 protocol rules, we don't reply. */
2854 if (appData.debugMode)
2855 fprintf(debugFP, "\n<DO ");
2856 switch (option = (unsigned char) buf[++i]) {
2858 /* Whatever this is, we refuse to do it. */
2859 if (appData.debugMode)
2860 fprintf(debugFP, "%d ", option);
2861 TelnetRequest(TN_WONT, option);
2866 if (appData.debugMode)
2867 fprintf(debugFP, "\n<DONT ");
2868 switch (option = (unsigned char) buf[++i]) {
2870 if (appData.debugMode)
2871 fprintf(debugFP, "%d ", option);
2872 /* Whatever this is, we are already not doing
2873 it, because we never agree to do anything
2874 non-default, so according to the protocol
2875 rules, we don't reply. */
2880 if (appData.debugMode)
2881 fprintf(debugFP, "\n<IAC ");
2882 /* Doubled IAC; pass it through */
2886 if (appData.debugMode)
2887 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2888 /* Drop all other telnet commands on the floor */
2891 if (oldi > next_out)
2892 SendToPlayer(&buf[next_out], oldi - next_out);
2898 /* OK, this at least will *usually* work */
2899 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2903 if (loggedOn && !intfSet) {
2904 if (ics_type == ICS_ICC) {
2905 snprintf(str, MSG_SIZ,
2906 "/set-quietly interface %s\n/set-quietly style 12\n",
2908 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2909 strcat(str, "/set-2 51 1\n/set seek 1\n");
2910 } else if (ics_type == ICS_CHESSNET) {
2911 snprintf(str, MSG_SIZ, "/style 12\n");
2913 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2914 strcat(str, programVersion);
2915 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2916 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2917 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2919 strcat(str, "$iset nohighlight 1\n");
2921 strcat(str, "$iset lock 1\n$style 12\n");
2924 NotifyFrontendLogin();
2928 if (started == STARTED_COMMENT) {
2929 /* Accumulate characters in comment */
2930 parse[parse_pos++] = buf[i];
2931 if (buf[i] == '\n') {
2932 parse[parse_pos] = NULLCHAR;
2933 if(chattingPartner>=0) {
2935 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2936 OutputChatMessage(chattingPartner, mess);
2937 chattingPartner = -1;
2938 next_out = i+1; // [HGM] suppress printing in ICS window
2940 if(!suppressKibitz) // [HGM] kibitz
2941 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2942 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2943 int nrDigit = 0, nrAlph = 0, j;
2944 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2945 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2946 parse[parse_pos] = NULLCHAR;
2947 // try to be smart: if it does not look like search info, it should go to
2948 // ICS interaction window after all, not to engine-output window.
2949 for(j=0; j<parse_pos; j++) { // count letters and digits
2950 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2951 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2952 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2954 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2955 int depth=0; float score;
2956 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2957 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2958 pvInfoList[forwardMostMove-1].depth = depth;
2959 pvInfoList[forwardMostMove-1].score = 100*score;
2961 OutputKibitz(suppressKibitz, parse);
2964 if(gameMode == IcsObserving) // restore original ICS messages
2965 snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
2967 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2968 SendToPlayer(tmp, strlen(tmp));
2970 next_out = i+1; // [HGM] suppress printing in ICS window
2972 started = STARTED_NONE;
2974 /* Don't match patterns against characters in comment */
2979 if (started == STARTED_CHATTER) {
2980 if (buf[i] != '\n') {
2981 /* Don't match patterns against characters in chatter */
2985 started = STARTED_NONE;
2986 if(suppressKibitz) next_out = i+1;
2989 /* Kludge to deal with rcmd protocol */
2990 if (firstTime && looking_at(buf, &i, "\001*")) {
2991 DisplayFatalError(&buf[1], 0, 1);
2997 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3000 if (appData.debugMode)
3001 fprintf(debugFP, "ics_type %d\n", ics_type);
3004 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3005 ics_type = ICS_FICS;
3007 if (appData.debugMode)
3008 fprintf(debugFP, "ics_type %d\n", ics_type);
3011 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3012 ics_type = ICS_CHESSNET;
3014 if (appData.debugMode)
3015 fprintf(debugFP, "ics_type %d\n", ics_type);
3020 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3021 looking_at(buf, &i, "Logging you in as \"*\"") ||
3022 looking_at(buf, &i, "will be \"*\""))) {
3023 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3027 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3029 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3030 DisplayIcsInteractionTitle(buf);
3031 have_set_title = TRUE;
3034 /* skip finger notes */
3035 if (started == STARTED_NONE &&
3036 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3037 (buf[i] == '1' && buf[i+1] == '0')) &&
3038 buf[i+2] == ':' && buf[i+3] == ' ') {
3039 started = STARTED_CHATTER;
3045 // [HGM] seekgraph: recognize sought lines and end-of-sought message
3046 if(appData.seekGraph) {
3047 if(soughtPending && MatchSoughtLine(buf+i)) {
3048 i = strstr(buf+i, "rated") - buf;
3049 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3050 next_out = leftover_start = i;
3051 started = STARTED_CHATTER;
3052 suppressKibitz = TRUE;
3055 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3056 && looking_at(buf, &i, "* ads displayed")) {
3057 soughtPending = FALSE;
3062 if(appData.autoRefresh) {
3063 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3064 int s = (ics_type == ICS_ICC); // ICC format differs
3066 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3067 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3068 looking_at(buf, &i, "*% "); // eat prompt
3069 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3070 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3071 next_out = i; // suppress
3074 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3075 char *p = star_match[0];
3077 if(seekGraphUp) RemoveSeekAd(atoi(p));
3078 while(*p && *p++ != ' '); // next
3080 looking_at(buf, &i, "*% "); // eat prompt
3081 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3088 /* skip formula vars */
3089 if (started == STARTED_NONE &&
3090 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3091 started = STARTED_CHATTER;
3096 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3097 if (appData.autoKibitz && started == STARTED_NONE &&
3098 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3099 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3100 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3101 looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3102 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3103 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3104 suppressKibitz = TRUE;
3105 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3107 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3108 && (gameMode == IcsPlayingWhite)) ||
3109 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3110 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3111 started = STARTED_CHATTER; // own kibitz we simply discard
3113 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3114 parse_pos = 0; parse[0] = NULLCHAR;
3115 savingComment = TRUE;
3116 suppressKibitz = gameMode != IcsObserving ? 2 :
3117 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3121 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3122 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3123 && atoi(star_match[0])) {
3124 // suppress the acknowledgements of our own autoKibitz
3126 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3127 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3128 SendToPlayer(star_match[0], strlen(star_match[0]));
3129 if(looking_at(buf, &i, "*% ")) // eat prompt
3130 suppressKibitz = FALSE;
3134 } // [HGM] kibitz: end of patch
3136 if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3138 // [HGM] chat: intercept tells by users for which we have an open chat window
3140 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3141 looking_at(buf, &i, "* whispers:") ||
3142 looking_at(buf, &i, "* kibitzes:") ||
3143 looking_at(buf, &i, "* shouts:") ||
3144 looking_at(buf, &i, "* c-shouts:") ||
3145 looking_at(buf, &i, "--> * ") ||
3146 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3147 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3148 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3149 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3151 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3152 chattingPartner = -1;
3154 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3155 for(p=0; p<MAX_CHAT; p++) {
3156 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3157 talker[0] = '['; strcat(talker, "] ");
3158 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3159 chattingPartner = p; break;
3162 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3163 for(p=0; p<MAX_CHAT; p++) {
3164 if(!strcmp("kibitzes", chatPartner[p])) {
3165 talker[0] = '['; strcat(talker, "] ");
3166 chattingPartner = p; break;
3169 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3170 for(p=0; p<MAX_CHAT; p++) {
3171 if(!strcmp("whispers", chatPartner[p])) {
3172 talker[0] = '['; strcat(talker, "] ");
3173 chattingPartner = p; break;
3176 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3177 if(buf[i-8] == '-' && buf[i-3] == 't')
3178 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3179 if(!strcmp("c-shouts", chatPartner[p])) {
3180 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3181 chattingPartner = p; break;
3184 if(chattingPartner < 0)
3185 for(p=0; p<MAX_CHAT; p++) {
3186 if(!strcmp("shouts", chatPartner[p])) {
3187 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3188 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3189 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3190 chattingPartner = p; break;
3194 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3195 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3196 talker[0] = 0; Colorize(ColorTell, FALSE);
3197 chattingPartner = p; break;
3199 if(chattingPartner<0) i = oldi; else {
3200 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3201 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3202 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3203 started = STARTED_COMMENT;
3204 parse_pos = 0; parse[0] = NULLCHAR;
3205 savingComment = 3 + chattingPartner; // counts as TRUE
3206 suppressKibitz = TRUE;
3209 } // [HGM] chat: end of patch
3212 if (appData.zippyTalk || appData.zippyPlay) {
3213 /* [DM] Backup address for color zippy lines */
3215 if (loggedOn == TRUE)
3216 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3217 (appData.zippyPlay && ZippyMatch(buf, &backup)));
3219 } // [DM] 'else { ' deleted
3221 /* Regular tells and says */
3222 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3223 looking_at(buf, &i, "* (your partner) tells you: ") ||
3224 looking_at(buf, &i, "* says: ") ||
3225 /* Don't color "message" or "messages" output */
3226 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3227 looking_at(buf, &i, "*. * at *:*: ") ||
3228 looking_at(buf, &i, "--* (*:*): ") ||
3229 /* Message notifications (same color as tells) */
3230 looking_at(buf, &i, "* has left a message ") ||
3231 looking_at(buf, &i, "* just sent you a message:\n") ||
3232 /* Whispers and kibitzes */
3233 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3234 looking_at(buf, &i, "* kibitzes: ") ||
3236 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3238 if (tkind == 1 && strchr(star_match[0], ':')) {
3239 /* Avoid "tells you:" spoofs in channels */
3242 if (star_match[0][0] == NULLCHAR ||
3243 strchr(star_match[0], ' ') ||
3244 (tkind == 3 && strchr(star_match[1], ' '))) {
3245 /* Reject bogus matches */
3248 if (appData.colorize) {
3249 if (oldi > next_out) {
3250 SendToPlayer(&buf[next_out], oldi - next_out);
3255 Colorize(ColorTell, FALSE);
3256 curColor = ColorTell;
3259 Colorize(ColorKibitz, FALSE);
3260 curColor = ColorKibitz;
3263 p = strrchr(star_match[1], '(');
3270 Colorize(ColorChannel1, FALSE);
3271 curColor = ColorChannel1;
3273 Colorize(ColorChannel, FALSE);
3274 curColor = ColorChannel;
3278 curColor = ColorNormal;
3282 if (started == STARTED_NONE && appData.autoComment &&
3283 (gameMode == IcsObserving ||
3284 gameMode == IcsPlayingWhite ||
3285 gameMode == IcsPlayingBlack)) {
3286 parse_pos = i - oldi;
3287 memcpy(parse, &buf[oldi], parse_pos);
3288 parse[parse_pos] = NULLCHAR;
3289 started = STARTED_COMMENT;
3290 savingComment = TRUE;
3292 started = STARTED_CHATTER;
3293 savingComment = FALSE;
3300 if (looking_at(buf, &i, "* s-shouts: ") ||
3301 looking_at(buf, &i, "* c-shouts: ")) {
3302 if (appData.colorize) {
3303 if (oldi > next_out) {
3304 SendToPlayer(&buf[next_out], oldi - next_out);
3307 Colorize(ColorSShout, FALSE);
3308 curColor = ColorSShout;
3311 started = STARTED_CHATTER;
3315 if (looking_at(buf, &i, "--->")) {
3320 if (looking_at(buf, &i, "* shouts: ") ||
3321 looking_at(buf, &i, "--> ")) {
3322 if (appData.colorize) {
3323 if (oldi > next_out) {
3324 SendToPlayer(&buf[next_out], oldi - next_out);
3327 Colorize(ColorShout, FALSE);
3328 curColor = ColorShout;
3331 started = STARTED_CHATTER;
3335 if (looking_at( buf, &i, "Challenge:")) {
3336 if (appData.colorize) {
3337 if (oldi > next_out) {
3338 SendToPlayer(&buf[next_out], oldi - next_out);
3341 Colorize(ColorChallenge, FALSE);
3342 curColor = ColorChallenge;
3348 if (looking_at(buf, &i, "* offers you") ||
3349 looking_at(buf, &i, "* offers to be") ||
3350 looking_at(buf, &i, "* would like to") ||
3351 looking_at(buf, &i, "* requests to") ||
3352 looking_at(buf, &i, "Your opponent offers") ||
3353 looking_at(buf, &i, "Your opponent requests")) {
3355 if (appData.colorize) {
3356 if (oldi > next_out) {
3357 SendToPlayer(&buf[next_out], oldi - next_out);
3360 Colorize(ColorRequest, FALSE);
3361 curColor = ColorRequest;
3366 if (looking_at(buf, &i, "* (*) seeking")) {
3367 if (appData.colorize) {
3368 if (oldi > next_out) {
3369 SendToPlayer(&buf[next_out], oldi - next_out);
3372 Colorize(ColorSeek, FALSE);
3373 curColor = ColorSeek;
3378 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3380 if (looking_at(buf, &i, "\\ ")) {
3381 if (prevColor != ColorNormal) {
3382 if (oldi > next_out) {
3383 SendToPlayer(&buf[next_out], oldi - next_out);
3386 Colorize(prevColor, TRUE);
3387 curColor = prevColor;
3389 if (savingComment) {
3390 parse_pos = i - oldi;
3391 memcpy(parse, &buf[oldi], parse_pos);
3392 parse[parse_pos] = NULLCHAR;
3393 started = STARTED_COMMENT;
3394 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3395 chattingPartner = savingComment - 3; // kludge to remember the box
3397 started = STARTED_CHATTER;
3402 if (looking_at(buf, &i, "Black Strength :") ||
3403 looking_at(buf, &i, "<<< style 10 board >>>") ||
3404 looking_at(buf, &i, "<10>") ||
3405 looking_at(buf, &i, "#@#")) {
3406 /* Wrong board style */
3408 SendToICS(ics_prefix);
3409 SendToICS("set style 12\n");
3410 SendToICS(ics_prefix);
3411 SendToICS("refresh\n");
3415 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3417 have_sent_ICS_logon = 1;
3421 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3422 (looking_at(buf, &i, "\n<12> ") ||
3423 looking_at(buf, &i, "<12> "))) {
3425 if (oldi > next_out) {
3426 SendToPlayer(&buf[next_out], oldi - next_out);
3429 started = STARTED_BOARD;
3434 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3435 looking_at(buf, &i, "<b1> ")) {
3436 if (oldi > next_out) {
3437 SendToPlayer(&buf[next_out], oldi - next_out);
3440 started = STARTED_HOLDINGS;
3445 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3447 /* Header for a move list -- first line */
3449 switch (ics_getting_history) {
3453 case BeginningOfGame:
3454 /* User typed "moves" or "oldmoves" while we
3455 were idle. Pretend we asked for these
3456 moves and soak them up so user can step
3457 through them and/or save them.
3460 gameMode = IcsObserving;
3463 ics_getting_history = H_GOT_UNREQ_HEADER;
3465 case EditGame: /*?*/
3466 case EditPosition: /*?*/
3467 /* Should above feature work in these modes too? */
3468 /* For now it doesn't */
3469 ics_getting_history = H_GOT_UNWANTED_HEADER;
3472 ics_getting_history = H_GOT_UNWANTED_HEADER;
3477 /* Is this the right one? */
3478 if (gameInfo.white && gameInfo.black &&
3479 strcmp(gameInfo.white, star_match[0]) == 0 &&
3480 strcmp(gameInfo.black, star_match[2]) == 0) {
3482 ics_getting_history = H_GOT_REQ_HEADER;
3485 case H_GOT_REQ_HEADER:
3486 case H_GOT_UNREQ_HEADER:
3487 case H_GOT_UNWANTED_HEADER:
3488 case H_GETTING_MOVES:
3489 /* Should not happen */
3490 DisplayError(_("Error gathering move list: two headers"), 0);
3491 ics_getting_history = H_FALSE;
3495 /* Save player ratings into gameInfo if needed */
3496 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3497 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3498 (gameInfo.whiteRating == -1 ||
3499 gameInfo.blackRating == -1)) {
3501 gameInfo.whiteRating = string_to_rating(star_match[1]);
3502 gameInfo.blackRating = string_to_rating(star_match[3]);
3503 if (appData.debugMode)
3504 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3505 gameInfo.whiteRating, gameInfo.blackRating);
3510 if (looking_at(buf, &i,
3511 "* * match, initial time: * minute*, increment: * second")) {
3512 /* Header for a move list -- second line */
3513 /* Initial board will follow if this is a wild game */
3514 if (gameInfo.event != NULL) free(gameInfo.event);
3515 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3516 gameInfo.event = StrSave(str);
3517 /* [HGM] we switched variant. Translate boards if needed. */
3518 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3522 if (looking_at(buf, &i, "Move ")) {
3523 /* Beginning of a move list */
3524 switch (ics_getting_history) {
3526 /* Normally should not happen */
3527 /* Maybe user hit reset while we were parsing */
3530 /* Happens if we are ignoring a move list that is not
3531 * the one we just requested. Common if the user
3532 * tries to observe two games without turning off
3535 case H_GETTING_MOVES:
3536 /* Should not happen */
3537 DisplayError(_("Error gathering move list: nested"), 0);
3538 ics_getting_history = H_FALSE;
3540 case H_GOT_REQ_HEADER:
3541 ics_getting_history = H_GETTING_MOVES;
3542 started = STARTED_MOVES;
3544 if (oldi > next_out) {
3545 SendToPlayer(&buf[next_out], oldi - next_out);
3548 case H_GOT_UNREQ_HEADER:
3549 ics_getting_history = H_GETTING_MOVES;
3550 started = STARTED_MOVES_NOHIDE;
3553 case H_GOT_UNWANTED_HEADER:
3554 ics_getting_history = H_FALSE;
3560 if (looking_at(buf, &i, "% ") ||
3561 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3562 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3563 if(soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3564 soughtPending = FALSE;
3568 if(suppressKibitz) next_out = i;
3569 savingComment = FALSE;
3573 case STARTED_MOVES_NOHIDE:
3574 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3575 parse[parse_pos + i - oldi] = NULLCHAR;
3576 ParseGameHistory(parse);
3578 if (appData.zippyPlay && first.initDone) {
3579 FeedMovesToProgram(&first, forwardMostMove);
3580 if (gameMode == IcsPlayingWhite) {
3581 if (WhiteOnMove(forwardMostMove)) {
3582 if (first.sendTime) {
3583 if (first.useColors) {
3584 SendToProgram("black\n", &first);
3586 SendTimeRemaining(&first, TRUE);
3588 if (first.useColors) {
3589 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3591 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3592 first.maybeThinking = TRUE;
3594 if (first.usePlayother) {
3595 if (first.sendTime) {
3596 SendTimeRemaining(&first, TRUE);
3598 SendToProgram("playother\n", &first);
3604 } else if (gameMode == IcsPlayingBlack) {
3605 if (!WhiteOnMove(forwardMostMove)) {
3606 if (first.sendTime) {
3607 if (first.useColors) {
3608 SendToProgram("white\n", &first);
3610 SendTimeRemaining(&first, FALSE);
3612 if (first.useColors) {
3613 SendToProgram("black\n", &first);
3615 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3616 first.maybeThinking = TRUE;
3618 if (first.usePlayother) {
3619 if (first.sendTime) {
3620 SendTimeRemaining(&first, FALSE);
3622 SendToProgram("playother\n", &first);
3631 if (gameMode == IcsObserving && ics_gamenum == -1) {
3632 /* Moves came from oldmoves or moves command
3633 while we weren't doing anything else.
3635 currentMove = forwardMostMove;
3636 ClearHighlights();/*!!could figure this out*/
3637 flipView = appData.flipView;
3638 DrawPosition(TRUE, boards[currentMove]);
3639 DisplayBothClocks();
3640 snprintf(str, MSG_SIZ, "%s %s %s",
3641 gameInfo.white, _("vs."), gameInfo.black);
3645 /* Moves were history of an active game */
3646 if (gameInfo.resultDetails != NULL) {
3647 free(gameInfo.resultDetails);
3648 gameInfo.resultDetails = NULL;
3651 HistorySet(parseList, backwardMostMove,
3652 forwardMostMove, currentMove-1);
3653 DisplayMove(currentMove - 1);
3654 if (started == STARTED_MOVES) next_out = i;
3655 started = STARTED_NONE;
3656 ics_getting_history = H_FALSE;
3659 case STARTED_OBSERVE:
3660 started = STARTED_NONE;
3661 SendToICS(ics_prefix);
3662 SendToICS("refresh\n");
3668 if(bookHit) { // [HGM] book: simulate book reply
3669 static char bookMove[MSG_SIZ]; // a bit generous?
3671 programStats.nodes = programStats.depth = programStats.time =
3672 programStats.score = programStats.got_only_move = 0;
3673 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3675 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3676 strcat(bookMove, bookHit);
3677 HandleMachineMove(bookMove, &first);
3682 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3683 started == STARTED_HOLDINGS ||
3684 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3685 /* Accumulate characters in move list or board */
3686 parse[parse_pos++] = buf[i];
3689 /* Start of game messages. Mostly we detect start of game
3690 when the first board image arrives. On some versions
3691 of the ICS, though, we need to do a "refresh" after starting
3692 to observe in order to get the current board right away. */
3693 if (looking_at(buf, &i, "Adding game * to observation list")) {
3694 started = STARTED_OBSERVE;
3698 /* Handle auto-observe */
3699 if (appData.autoObserve &&
3700 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3701 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3703 /* Choose the player that was highlighted, if any. */
3704 if (star_match[0][0] == '\033' ||
3705 star_match[1][0] != '\033') {
3706 player = star_match[0];
3708 player = star_match[2];
3710 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3711 ics_prefix, StripHighlightAndTitle(player));
3714 /* Save ratings from notify string */
3715 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3716 player1Rating = string_to_rating(star_match[1]);
3717 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3718 player2Rating = string_to_rating(star_match[3]);
3720 if (appData.debugMode)
3722 "Ratings from 'Game notification:' %s %d, %s %d\n",
3723 player1Name, player1Rating,
3724 player2Name, player2Rating);
3729 /* Deal with automatic examine mode after a game,
3730 and with IcsObserving -> IcsExamining transition */
3731 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3732 looking_at(buf, &i, "has made you an examiner of game *")) {
3734 int gamenum = atoi(star_match[0]);
3735 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3736 gamenum == ics_gamenum) {
3737 /* We were already playing or observing this game;
3738 no need to refetch history */
3739 gameMode = IcsExamining;
3741 pauseExamForwardMostMove = forwardMostMove;
3742 } else if (currentMove < forwardMostMove) {
3743 ForwardInner(forwardMostMove);
3746 /* I don't think this case really can happen */
3747 SendToICS(ics_prefix);
3748 SendToICS("refresh\n");
3753 /* Error messages */
3754 // if (ics_user_moved) {
3755 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3756 if (looking_at(buf, &i, "Illegal move") ||
3757 looking_at(buf, &i, "Not a legal move") ||
3758 looking_at(buf, &i, "Your king is in check") ||
3759 looking_at(buf, &i, "It isn't your turn") ||
3760 looking_at(buf, &i, "It is not your move")) {
3762 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3763 currentMove = forwardMostMove-1;
3764 DisplayMove(currentMove - 1); /* before DMError */
3765 DrawPosition(FALSE, boards[currentMove]);
3766 SwitchClocks(forwardMostMove-1); // [HGM] race
3767 DisplayBothClocks();
3769 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3775 if (looking_at(buf, &i, "still have time") ||
3776 looking_at(buf, &i, "not out of time") ||
3777 looking_at(buf, &i, "either player is out of time") ||
3778 looking_at(buf, &i, "has timeseal; checking")) {
3779 /* We must have called his flag a little too soon */
3780 whiteFlag = blackFlag = FALSE;
3784 if (looking_at(buf, &i, "added * seconds to") ||
3785 looking_at(buf, &i, "seconds were added to")) {
3786 /* Update the clocks */
3787 SendToICS(ics_prefix);
3788 SendToICS("refresh\n");
3792 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3793 ics_clock_paused = TRUE;
3798 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3799 ics_clock_paused = FALSE;
3804 /* Grab player ratings from the Creating: message.
3805 Note we have to check for the special case when
3806 the ICS inserts things like [white] or [black]. */
3807 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3808 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3810 0 player 1 name (not necessarily white)
3812 2 empty, white, or black (IGNORED)
3813 3 player 2 name (not necessarily black)
3816 The names/ratings are sorted out when the game
3817 actually starts (below).
3819 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3820 player1Rating = string_to_rating(star_match[1]);
3821 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3822 player2Rating = string_to_rating(star_match[4]);
3824 if (appData.debugMode)
3826 "Ratings from 'Creating:' %s %d, %s %d\n",
3827 player1Name, player1Rating,
3828 player2Name, player2Rating);
3833 /* Improved generic start/end-of-game messages */
3834 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3835 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3836 /* If tkind == 0: */
3837 /* star_match[0] is the game number */
3838 /* [1] is the white player's name */
3839 /* [2] is the black player's name */
3840 /* For end-of-game: */
3841 /* [3] is the reason for the game end */
3842 /* [4] is a PGN end game-token, preceded by " " */
3843 /* For start-of-game: */
3844 /* [3] begins with "Creating" or "Continuing" */
3845 /* [4] is " *" or empty (don't care). */
3846 int gamenum = atoi(star_match[0]);
3847 char *whitename, *blackname, *why, *endtoken;
3848 ChessMove endtype = EndOfFile;
3851 whitename = star_match[1];
3852 blackname = star_match[2];
3853 why = star_match[3];
3854 endtoken = star_match[4];
3856 whitename = star_match[1];
3857 blackname = star_match[3];
3858 why = star_match[5];
3859 endtoken = star_match[6];