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[] = {
742 InitEngine (ChessProgramState *cps, int n)
743 { // [HGM] all engine initialiation put in a function that does one engine
747 cps->which = engineNames[n];
748 cps->maybeThinking = FALSE;
752 cps->sendDrawOffers = 1;
754 cps->program = appData.chessProgram[n];
755 cps->host = appData.host[n];
756 cps->dir = appData.directory[n];
757 cps->initString = appData.engInitString[n];
758 cps->computerString = appData.computerString[n];
759 cps->useSigint = TRUE;
760 cps->useSigterm = TRUE;
761 cps->reuse = appData.reuse[n];
762 cps->nps = appData.NPS[n]; // [HGM] nps: copy nodes per second
763 cps->useSetboard = FALSE;
765 cps->usePing = FALSE;
768 cps->usePlayother = FALSE;
769 cps->useColors = TRUE;
770 cps->useUsermove = FALSE;
771 cps->sendICS = FALSE;
772 cps->sendName = appData.icsActive;
773 cps->sdKludge = FALSE;
774 cps->stKludge = FALSE;
775 TidyProgramName(cps->program, cps->host, cps->tidy);
777 safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
778 cps->analysisSupport = 2; /* detect */
779 cps->analyzing = FALSE;
780 cps->initDone = FALSE;
782 /* New features added by Tord: */
783 cps->useFEN960 = FALSE;
784 cps->useOOCastle = TRUE;
785 /* End of new features added by Tord. */
786 cps->fenOverride = appData.fenOverride[n];
788 /* [HGM] time odds: set factor for each machine */
789 cps->timeOdds = appData.timeOdds[n];
791 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
792 cps->accumulateTC = appData.accumulateTC[n];
793 cps->maxNrOfSessions = 1;
798 cps->supportsNPS = UNKNOWN;
799 cps->memSize = FALSE;
800 cps->maxCores = FALSE;
801 cps->egtFormats[0] = NULLCHAR;
804 cps->optionSettings = appData.engOptions[n];
806 cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
807 cps->isUCI = appData.isUCI[n]; /* [AS] */
808 cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
810 if (appData.protocolVersion[n] > PROTOVER
811 || appData.protocolVersion[n] < 1)
816 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
817 appData.protocolVersion[n]);
818 if( (len >= MSG_SIZ) && appData.debugMode )
819 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
821 DisplayFatalError(buf, 0, 2);
825 cps->protocolVersion = appData.protocolVersion[n];
828 InitEngineUCI( installDir, cps ); // [HGM] moved here from winboard.c, to make available in xboard
829 ParseFeatures(appData.featureDefaults, cps);
832 ChessProgramState *savCps;
838 if(WaitForEngine(savCps, LoadEngine)) return;
839 CommonEngineInit(); // recalculate time odds
840 if(gameInfo.variant != StringToVariant(appData.variant)) {
841 // we changed variant when loading the engine; this forces us to reset
842 Reset(TRUE, savCps != &first);
843 EditGameEvent(); // for consistency with other path, as Reset changes mode
845 InitChessProgram(savCps, FALSE);
846 SendToProgram("force\n", savCps);
847 DisplayMessage("", "");
848 if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
849 for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
855 ReplaceEngine (ChessProgramState *cps, int n)
859 appData.noChessProgram = FALSE;
860 appData.clockMode = TRUE;
863 if(n) return; // only startup first engine immediately; second can wait
864 savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
868 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
869 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
871 static char resetOptions[] =
872 "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
873 "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
874 "-firstOptions \"\" -firstNPS -1 -fn \"\"";
877 FloatToFront(char **list, char *engineLine)
879 char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
881 if(appData.recentEngines <= 0) return;
882 TidyProgramName(engineLine, "localhost", tidy+1);
883 tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
884 strncpy(buf+1, *list, MSG_SIZ-50);
885 if(p = strstr(buf, tidy)) { // tidy name appears in list
886 q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
887 while(*p++ = *++q); // squeeze out
889 strcat(tidy, buf+1); // put list behind tidy name
890 p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
891 if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
892 ASSIGN(*list, tidy+1);
896 Load (ChessProgramState *cps, int i)
898 char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
899 if(engineLine && engineLine[0]) { // an engine was selected from the combo box
900 snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
901 SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
902 ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
903 appData.firstProtocolVersion = PROTOVER;
904 ParseArgsFromString(buf);
906 ReplaceEngine(cps, i);
907 FloatToFront(&appData.recentEngineList, engineLine);
911 while(q = strchr(p, SLASH)) p = q+1;
912 if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
913 if(engineDir[0] != NULLCHAR)
914 appData.directory[i] = engineDir;
915 else if(p != engineName) { // derive directory from engine path, when not given
917 appData.directory[i] = strdup(engineName);
919 } else appData.directory[i] = ".";
921 if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
922 snprintf(command, MSG_SIZ, "%s %s", p, params);
925 appData.chessProgram[i] = strdup(p);
926 appData.isUCI[i] = isUCI;
927 appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
928 appData.hasOwnBookUCI[i] = hasBook;
929 if(!nickName[0]) useNick = FALSE;
930 if(useNick) ASSIGN(appData.pgnName[i], nickName);
934 q = firstChessProgramNames;
935 if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
936 quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
937 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
938 quote, p, quote, appData.directory[i],
939 useNick ? " -fn \"" : "",
940 useNick ? nickName : "",
942 v1 ? " -firstProtocolVersion 1" : "",
943 hasBook ? "" : " -fNoOwnBookUCI",
944 isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
945 storeVariant ? " -variant " : "",
946 storeVariant ? VariantName(gameInfo.variant) : "");
947 firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
948 snprintf(firstChessProgramNames, len, "%s%s", q, buf);
950 FloatToFront(&appData.recentEngineList, buf);
952 ReplaceEngine(cps, i);
958 int matched, min, sec;
960 * Parse timeControl resource
962 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
963 appData.movesPerSession)) {
965 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
966 DisplayFatalError(buf, 0, 2);
970 * Parse searchTime resource
972 if (*appData.searchTime != NULLCHAR) {
973 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
975 searchTime = min * 60;
976 } else if (matched == 2) {
977 searchTime = min * 60 + sec;
980 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
981 DisplayFatalError(buf, 0, 2);
990 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
991 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
993 GetTimeMark(&programStartTime);
994 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
995 appData.seedBase = random() + (random()<<15);
996 pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
999 programStats.ok_to_send = 1;
1000 programStats.seen_stat = 0;
1003 * Initialize game list
1009 * Internet chess server status
1011 if (appData.icsActive) {
1012 appData.matchMode = FALSE;
1013 appData.matchGames = 0;
1015 appData.noChessProgram = !appData.zippyPlay;
1017 appData.zippyPlay = FALSE;
1018 appData.zippyTalk = FALSE;
1019 appData.noChessProgram = TRUE;
1021 if (*appData.icsHelper != NULLCHAR) {
1022 appData.useTelnet = TRUE;
1023 appData.telnetProgram = appData.icsHelper;
1026 appData.zippyTalk = appData.zippyPlay = FALSE;
1029 /* [AS] Initialize pv info list [HGM] and game state */
1033 for( i=0; i<=framePtr; i++ ) {
1034 pvInfoList[i].depth = -1;
1035 boards[i][EP_STATUS] = EP_NONE;
1036 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1042 /* [AS] Adjudication threshold */
1043 adjudicateLossThreshold = appData.adjudicateLossThreshold;
1045 InitEngine(&first, 0);
1046 InitEngine(&second, 1);
1049 pairing.which = "pairing"; // pairing engine
1050 pairing.pr = NoProc;
1052 pairing.program = appData.pairingEngine;
1053 pairing.host = "localhost";
1056 if (appData.icsActive) {
1057 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
1058 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1059 appData.clockMode = FALSE;
1060 first.sendTime = second.sendTime = 0;
1064 /* Override some settings from environment variables, for backward
1065 compatibility. Unfortunately it's not feasible to have the env
1066 vars just set defaults, at least in xboard. Ugh.
1068 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1073 if (!appData.icsActive) {
1077 /* Check for variants that are supported only in ICS mode,
1078 or not at all. Some that are accepted here nevertheless
1079 have bugs; see comments below.
1081 VariantClass variant = StringToVariant(appData.variant);
1083 case VariantBughouse: /* need four players and two boards */
1084 case VariantKriegspiel: /* need to hide pieces and move details */
1085 /* case VariantFischeRandom: (Fabien: moved below) */
1086 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1087 if( (len >= MSG_SIZ) && appData.debugMode )
1088 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1090 DisplayFatalError(buf, 0, 2);
1093 case VariantUnknown:
1094 case VariantLoadable:
1104 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1105 if( (len >= MSG_SIZ) && appData.debugMode )
1106 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1108 DisplayFatalError(buf, 0, 2);
1111 case VariantXiangqi: /* [HGM] repetition rules not implemented */
1112 case VariantFairy: /* [HGM] TestLegality definitely off! */
1113 case VariantGothic: /* [HGM] should work */
1114 case VariantCapablanca: /* [HGM] should work */
1115 case VariantCourier: /* [HGM] initial forced moves not implemented */
1116 case VariantShogi: /* [HGM] could still mate with pawn drop */
1117 case VariantKnightmate: /* [HGM] should work */
1118 case VariantCylinder: /* [HGM] untested */
1119 case VariantFalcon: /* [HGM] untested */
1120 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1121 offboard interposition not understood */
1122 case VariantNormal: /* definitely works! */
1123 case VariantWildCastle: /* pieces not automatically shuffled */
1124 case VariantNoCastle: /* pieces not automatically shuffled */
1125 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1126 case VariantLosers: /* should work except for win condition,
1127 and doesn't know captures are mandatory */
1128 case VariantSuicide: /* should work except for win condition,
1129 and doesn't know captures are mandatory */
1130 case VariantGiveaway: /* should work except for win condition,
1131 and doesn't know captures are mandatory */
1132 case VariantTwoKings: /* should work */
1133 case VariantAtomic: /* should work except for win condition */
1134 case Variant3Check: /* should work except for win condition */
1135 case VariantShatranj: /* should work except for all win conditions */
1136 case VariantMakruk: /* should work except for draw countdown */
1137 case VariantBerolina: /* might work if TestLegality is off */
1138 case VariantCapaRandom: /* should work */
1139 case VariantJanus: /* should work */
1140 case VariantSuper: /* experimental */
1141 case VariantGreat: /* experimental, requires legality testing to be off */
1142 case VariantSChess: /* S-Chess, should work */
1143 case VariantGrand: /* should work */
1144 case VariantSpartan: /* should work */
1152 NextIntegerFromString (char ** str, long * value)
1157 while( *s == ' ' || *s == '\t' ) {
1163 if( *s >= '0' && *s <= '9' ) {
1164 while( *s >= '0' && *s <= '9' ) {
1165 *value = *value * 10 + (*s - '0');
1178 NextTimeControlFromString (char ** str, long * value)
1181 int result = NextIntegerFromString( str, &temp );
1184 *value = temp * 60; /* Minutes */
1185 if( **str == ':' ) {
1187 result = NextIntegerFromString( str, &temp );
1188 *value += temp; /* Seconds */
1196 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1197 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1198 int result = -1, type = 0; long temp, temp2;
1200 if(**str != ':') return -1; // old params remain in force!
1202 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1203 if( NextIntegerFromString( str, &temp ) ) return -1;
1204 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1207 /* time only: incremental or sudden-death time control */
1208 if(**str == '+') { /* increment follows; read it */
1210 if(**str == '!') type = *(*str)++; // Bronstein TC
1211 if(result = NextIntegerFromString( str, &temp2)) return -1;
1212 *inc = temp2 * 1000;
1213 if(**str == '.') { // read fraction of increment
1214 char *start = ++(*str);
1215 if(result = NextIntegerFromString( str, &temp2)) return -1;
1217 while(start++ < *str) temp2 /= 10;
1221 *moves = 0; *tc = temp * 1000; *incType = type;
1225 (*str)++; /* classical time control */
1226 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1238 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1239 { /* [HGM] get time to add from the multi-session time-control string */
1240 int incType, moves=1; /* kludge to force reading of first session */
1241 long time, increment;
1244 if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1245 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1247 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1248 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1249 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1250 if(movenr == -1) return time; /* last move before new session */
1251 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1252 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1253 if(!moves) return increment; /* current session is incremental */
1254 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1255 } while(movenr >= -1); /* try again for next session */
1257 return 0; // no new time quota on this move
1261 ParseTimeControl (char *tc, float ti, int mps)
1265 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1268 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1269 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1270 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1274 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1276 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1279 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1281 snprintf(buf, MSG_SIZ, ":%s", mytc);
1283 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1285 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1290 /* Parse second time control */
1293 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1301 timeControl_2 = tc2 * 1000;
1311 timeControl = tc1 * 1000;
1314 timeIncrement = ti * 1000; /* convert to ms */
1315 movesPerSession = 0;
1318 movesPerSession = mps;
1326 if (appData.debugMode) {
1327 fprintf(debugFP, "%s\n", programVersion);
1329 ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1331 set_cont_sequence(appData.wrapContSeq);
1332 if (appData.matchGames > 0) {
1333 appData.matchMode = TRUE;
1334 } else if (appData.matchMode) {
1335 appData.matchGames = 1;
1337 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1338 appData.matchGames = appData.sameColorGames;
1339 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1340 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1341 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1344 if (appData.noChessProgram || first.protocolVersion == 1) {
1347 /* kludge: allow timeout for initial "feature" commands */
1349 DisplayMessage("", _("Starting chess program"));
1350 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1355 CalculateIndex (int index, int gameNr)
1356 { // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1358 if(index > 0) return index; // fixed nmber
1359 if(index == 0) return 1;
1360 res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1361 if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1366 LoadGameOrPosition (int gameNr)
1367 { // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1368 if (*appData.loadGameFile != NULLCHAR) {
1369 if (!LoadGameFromFile(appData.loadGameFile,
1370 CalculateIndex(appData.loadGameIndex, gameNr),
1371 appData.loadGameFile, FALSE)) {
1372 DisplayFatalError(_("Bad game file"), 0, 1);
1375 } else if (*appData.loadPositionFile != NULLCHAR) {
1376 if (!LoadPositionFromFile(appData.loadPositionFile,
1377 CalculateIndex(appData.loadPositionIndex, gameNr),
1378 appData.loadPositionFile)) {
1379 DisplayFatalError(_("Bad position file"), 0, 1);
1387 ReserveGame (int gameNr, char resChar)
1389 FILE *tf = fopen(appData.tourneyFile, "r+");
1390 char *p, *q, c, buf[MSG_SIZ];
1391 if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1392 safeStrCpy(buf, lastMsg, MSG_SIZ);
1393 DisplayMessage(_("Pick new game"), "");
1394 flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1395 ParseArgsFromFile(tf);
1396 p = q = appData.results;
1397 if(appData.debugMode) {
1398 char *r = appData.participants;
1399 fprintf(debugFP, "results = '%s'\n", p);
1400 while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1401 fprintf(debugFP, "\n");
1403 while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1405 q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1406 safeStrCpy(q, p, strlen(p) + 2);
1407 if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1408 if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1409 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1410 if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1413 fseek(tf, -(strlen(p)+4), SEEK_END);
1415 if(c != '"') // depending on DOS or Unix line endings we can be one off
1416 fseek(tf, -(strlen(p)+2), SEEK_END);
1417 else fseek(tf, -(strlen(p)+3), SEEK_END);
1418 fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1419 DisplayMessage(buf, "");
1420 free(p); appData.results = q;
1421 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1422 (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1423 int round = appData.defaultMatchGames * appData.tourneyType;
1424 if(gameNr < 0 || appData.tourneyType < 1 || // gauntlet engine can always stay loaded as first engine
1425 appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1426 UnloadEngine(&first); // next game belongs to other pairing;
1427 UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1429 if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d, procs=(%x,%x)\n", nextGame, gameNr, first.pr, second.pr);
1433 MatchEvent (int mode)
1434 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1436 if(matchMode) { // already in match mode: switch it off
1438 if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1441 // if(gameMode != BeginningOfGame) {
1442 // DisplayError(_("You can only start a match from the initial position."), 0);
1446 if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1447 /* Set up machine vs. machine match */
1449 NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1450 if(appData.tourneyFile[0]) {
1452 if(nextGame > appData.matchGames) {
1454 if(strchr(appData.results, '*') == NULL) {
1456 appData.tourneyCycles++;
1457 if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1459 NextTourneyGame(-1, &dummy);
1461 if(nextGame <= appData.matchGames) {
1462 DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1464 ScheduleDelayedEvent(NextMatchGame, 10000);
1469 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1470 DisplayError(buf, 0);
1471 appData.tourneyFile[0] = 0;
1475 if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically
1476 DisplayFatalError(_("Can't have a match with no chess programs"),
1481 matchGame = roundNr = 1;
1482 first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1487 InitBackEnd3 P((void))
1489 GameMode initialMode;
1493 InitChessProgram(&first, startedFromSetupPosition);
1495 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1496 free(programVersion);
1497 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1498 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1499 FloatToFront(&appData.recentEngineList, appData.firstChessProgram);
1502 if (appData.icsActive) {
1504 /* [DM] Make a console window if needed [HGM] merged ifs */
1510 if (*appData.icsCommPort != NULLCHAR)
1511 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1512 appData.icsCommPort);
1514 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1515 appData.icsHost, appData.icsPort);
1517 if( (len >= MSG_SIZ) && appData.debugMode )
1518 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1520 DisplayFatalError(buf, err, 1);
1525 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1527 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1528 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1529 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1530 } else if (appData.noChessProgram) {
1536 if (*appData.cmailGameName != NULLCHAR) {
1538 OpenLoopback(&cmailPR);
1540 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1544 DisplayMessage("", "");
1545 if (StrCaseCmp(appData.initialMode, "") == 0) {
1546 initialMode = BeginningOfGame;
1547 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1548 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1549 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1550 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1553 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1554 initialMode = TwoMachinesPlay;
1555 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1556 initialMode = AnalyzeFile;
1557 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1558 initialMode = AnalyzeMode;
1559 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1560 initialMode = MachinePlaysWhite;
1561 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1562 initialMode = MachinePlaysBlack;
1563 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1564 initialMode = EditGame;
1565 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1566 initialMode = EditPosition;
1567 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1568 initialMode = Training;
1570 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1571 if( (len >= MSG_SIZ) && appData.debugMode )
1572 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1574 DisplayFatalError(buf, 0, 2);
1578 if (appData.matchMode) {
1579 if(appData.tourneyFile[0]) { // start tourney from command line
1581 if(f = fopen(appData.tourneyFile, "r")) {
1582 ParseArgsFromFile(f); // make sure tourney parmeters re known
1584 appData.clockMode = TRUE;
1586 } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1589 } else if (*appData.cmailGameName != NULLCHAR) {
1590 /* Set up cmail mode */
1591 ReloadCmailMsgEvent(TRUE);
1593 /* Set up other modes */
1594 if (initialMode == AnalyzeFile) {
1595 if (*appData.loadGameFile == NULLCHAR) {
1596 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1600 if (*appData.loadGameFile != NULLCHAR) {
1601 (void) LoadGameFromFile(appData.loadGameFile,
1602 appData.loadGameIndex,
1603 appData.loadGameFile, TRUE);
1604 } else if (*appData.loadPositionFile != NULLCHAR) {
1605 (void) LoadPositionFromFile(appData.loadPositionFile,
1606 appData.loadPositionIndex,
1607 appData.loadPositionFile);
1608 /* [HGM] try to make self-starting even after FEN load */
1609 /* to allow automatic setup of fairy variants with wtm */
1610 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1611 gameMode = BeginningOfGame;
1612 setboardSpoiledMachineBlack = 1;
1614 /* [HGM] loadPos: make that every new game uses the setup */
1615 /* from file as long as we do not switch variant */
1616 if(!blackPlaysFirst) {
1617 startedFromPositionFile = TRUE;
1618 CopyBoard(filePosition, boards[0]);
1621 if (initialMode == AnalyzeMode) {
1622 if (appData.noChessProgram) {
1623 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1626 if (appData.icsActive) {
1627 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1631 } else if (initialMode == AnalyzeFile) {
1632 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1633 ShowThinkingEvent();
1635 AnalysisPeriodicEvent(1);
1636 } else if (initialMode == MachinePlaysWhite) {
1637 if (appData.noChessProgram) {
1638 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1642 if (appData.icsActive) {
1643 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1647 MachineWhiteEvent();
1648 } else if (initialMode == MachinePlaysBlack) {
1649 if (appData.noChessProgram) {
1650 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1654 if (appData.icsActive) {
1655 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1659 MachineBlackEvent();
1660 } else if (initialMode == TwoMachinesPlay) {
1661 if (appData.noChessProgram) {
1662 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1666 if (appData.icsActive) {
1667 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1672 } else if (initialMode == EditGame) {
1674 } else if (initialMode == EditPosition) {
1675 EditPositionEvent();
1676 } else if (initialMode == Training) {
1677 if (*appData.loadGameFile == NULLCHAR) {
1678 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1687 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1689 DisplayBook(current+1);
1691 MoveHistorySet( movelist, first, last, current, pvInfoList );
1693 EvalGraphSet( first, last, current, pvInfoList );
1695 MakeEngineOutputTitle();
1699 * Establish will establish a contact to a remote host.port.
1700 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1701 * used to talk to the host.
1702 * Returns 0 if okay, error code if not.
1709 if (*appData.icsCommPort != NULLCHAR) {
1710 /* Talk to the host through a serial comm port */
1711 return OpenCommPort(appData.icsCommPort, &icsPR);
1713 } else if (*appData.gateway != NULLCHAR) {
1714 if (*appData.remoteShell == NULLCHAR) {
1715 /* Use the rcmd protocol to run telnet program on a gateway host */
1716 snprintf(buf, sizeof(buf), "%s %s %s",
1717 appData.telnetProgram, appData.icsHost, appData.icsPort);
1718 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1721 /* Use the rsh program to run telnet program on a gateway host */
1722 if (*appData.remoteUser == NULLCHAR) {
1723 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1724 appData.gateway, appData.telnetProgram,
1725 appData.icsHost, appData.icsPort);
1727 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1728 appData.remoteShell, appData.gateway,
1729 appData.remoteUser, appData.telnetProgram,
1730 appData.icsHost, appData.icsPort);
1732 return StartChildProcess(buf, "", &icsPR);
1735 } else if (appData.useTelnet) {
1736 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1739 /* TCP socket interface differs somewhat between
1740 Unix and NT; handle details in the front end.
1742 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1747 EscapeExpand (char *p, char *q)
1748 { // [HGM] initstring: routine to shape up string arguments
1749 while(*p++ = *q++) if(p[-1] == '\\')
1751 case 'n': p[-1] = '\n'; break;
1752 case 'r': p[-1] = '\r'; break;
1753 case 't': p[-1] = '\t'; break;
1754 case '\\': p[-1] = '\\'; break;
1755 case 0: *p = 0; return;
1756 default: p[-1] = q[-1]; break;
1761 show_bytes (FILE *fp, char *buf, int count)
1764 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1765 fprintf(fp, "\\%03o", *buf & 0xff);
1774 /* Returns an errno value */
1776 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1778 char buf[8192], *p, *q, *buflim;
1779 int left, newcount, outcount;
1781 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1782 *appData.gateway != NULLCHAR) {
1783 if (appData.debugMode) {
1784 fprintf(debugFP, ">ICS: ");
1785 show_bytes(debugFP, message, count);
1786 fprintf(debugFP, "\n");
1788 return OutputToProcess(pr, message, count, outError);
1791 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1798 if (appData.debugMode) {
1799 fprintf(debugFP, ">ICS: ");
1800 show_bytes(debugFP, buf, newcount);
1801 fprintf(debugFP, "\n");
1803 outcount = OutputToProcess(pr, buf, newcount, outError);
1804 if (outcount < newcount) return -1; /* to be sure */
1811 } else if (((unsigned char) *p) == TN_IAC) {
1812 *q++ = (char) TN_IAC;
1819 if (appData.debugMode) {
1820 fprintf(debugFP, ">ICS: ");
1821 show_bytes(debugFP, buf, newcount);
1822 fprintf(debugFP, "\n");
1824 outcount = OutputToProcess(pr, buf, newcount, outError);
1825 if (outcount < newcount) return -1; /* to be sure */
1830 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1832 int outError, outCount;
1833 static int gotEof = 0;
1835 /* Pass data read from player on to ICS */
1838 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1839 if (outCount < count) {
1840 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1842 } else if (count < 0) {
1843 RemoveInputSource(isr);
1844 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1845 } else if (gotEof++ > 0) {
1846 RemoveInputSource(isr);
1847 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1853 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1854 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1855 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1856 SendToICS("date\n");
1857 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1860 /* added routine for printf style output to ics */
1862 ics_printf (char *format, ...)
1864 char buffer[MSG_SIZ];
1867 va_start(args, format);
1868 vsnprintf(buffer, sizeof(buffer), format, args);
1869 buffer[sizeof(buffer)-1] = '\0';
1877 int count, outCount, outError;
1879 if (icsPR == NoProc) return;
1882 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1883 if (outCount < count) {
1884 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1888 /* This is used for sending logon scripts to the ICS. Sending
1889 without a delay causes problems when using timestamp on ICC
1890 (at least on my machine). */
1892 SendToICSDelayed (char *s, long msdelay)
1894 int count, outCount, outError;
1896 if (icsPR == NoProc) return;
1899 if (appData.debugMode) {
1900 fprintf(debugFP, ">ICS: ");
1901 show_bytes(debugFP, s, count);
1902 fprintf(debugFP, "\n");
1904 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1906 if (outCount < count) {
1907 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1912 /* Remove all highlighting escape sequences in s
1913 Also deletes any suffix starting with '('
1916 StripHighlightAndTitle (char *s)
1918 static char retbuf[MSG_SIZ];
1921 while (*s != NULLCHAR) {
1922 while (*s == '\033') {
1923 while (*s != NULLCHAR && !isalpha(*s)) s++;
1924 if (*s != NULLCHAR) s++;
1926 while (*s != NULLCHAR && *s != '\033') {
1927 if (*s == '(' || *s == '[') {
1938 /* Remove all highlighting escape sequences in s */
1940 StripHighlight (char *s)
1942 static char retbuf[MSG_SIZ];
1945 while (*s != NULLCHAR) {
1946 while (*s == '\033') {
1947 while (*s != NULLCHAR && !isalpha(*s)) s++;
1948 if (*s != NULLCHAR) s++;
1950 while (*s != NULLCHAR && *s != '\033') {
1958 char *variantNames[] = VARIANT_NAMES;
1960 VariantName (VariantClass v)
1962 return variantNames[v];
1966 /* Identify a variant from the strings the chess servers use or the
1967 PGN Variant tag names we use. */
1969 StringToVariant (char *e)
1973 VariantClass v = VariantNormal;
1974 int i, found = FALSE;
1980 /* [HGM] skip over optional board-size prefixes */
1981 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1982 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1983 while( *e++ != '_');
1986 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1990 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1991 if (StrCaseStr(e, variantNames[i])) {
1992 v = (VariantClass) i;
1999 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2000 || StrCaseStr(e, "wild/fr")
2001 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2002 v = VariantFischeRandom;
2003 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2004 (i = 1, p = StrCaseStr(e, "w"))) {
2006 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2013 case 0: /* FICS only, actually */
2015 /* Castling legal even if K starts on d-file */
2016 v = VariantWildCastle;
2021 /* Castling illegal even if K & R happen to start in
2022 normal positions. */
2023 v = VariantNoCastle;
2036 /* Castling legal iff K & R start in normal positions */
2042 /* Special wilds for position setup; unclear what to do here */
2043 v = VariantLoadable;
2046 /* Bizarre ICC game */
2047 v = VariantTwoKings;
2050 v = VariantKriegspiel;
2056 v = VariantFischeRandom;
2059 v = VariantCrazyhouse;
2062 v = VariantBughouse;
2068 /* Not quite the same as FICS suicide! */
2069 v = VariantGiveaway;
2075 v = VariantShatranj;
2078 /* Temporary names for future ICC types. The name *will* change in
2079 the next xboard/WinBoard release after ICC defines it. */
2117 v = VariantCapablanca;
2120 v = VariantKnightmate;
2126 v = VariantCylinder;
2132 v = VariantCapaRandom;
2135 v = VariantBerolina;
2147 /* Found "wild" or "w" in the string but no number;
2148 must assume it's normal chess. */
2152 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2153 if( (len >= MSG_SIZ) && appData.debugMode )
2154 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2156 DisplayError(buf, 0);
2162 if (appData.debugMode) {
2163 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2164 e, wnum, VariantName(v));
2169 static int leftover_start = 0, leftover_len = 0;
2170 char star_match[STAR_MATCH_N][MSG_SIZ];
2172 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2173 advance *index beyond it, and set leftover_start to the new value of
2174 *index; else return FALSE. If pattern contains the character '*', it
2175 matches any sequence of characters not containing '\r', '\n', or the
2176 character following the '*' (if any), and the matched sequence(s) are
2177 copied into star_match.
2180 looking_at ( char *buf, int *index, char *pattern)
2182 char *bufp = &buf[*index], *patternp = pattern;
2184 char *matchp = star_match[0];
2187 if (*patternp == NULLCHAR) {
2188 *index = leftover_start = bufp - buf;
2192 if (*bufp == NULLCHAR) return FALSE;
2193 if (*patternp == '*') {
2194 if (*bufp == *(patternp + 1)) {
2196 matchp = star_match[++star_count];
2200 } else if (*bufp == '\n' || *bufp == '\r') {
2202 if (*patternp == NULLCHAR)
2207 *matchp++ = *bufp++;
2211 if (*patternp != *bufp) return FALSE;
2218 SendToPlayer (char *data, int length)
2220 int error, outCount;
2221 outCount = OutputToProcess(NoProc, data, length, &error);
2222 if (outCount < length) {
2223 DisplayFatalError(_("Error writing to display"), error, 1);
2228 PackHolding (char packed[], char *holding)
2238 switch (runlength) {
2249 sprintf(q, "%d", runlength);
2261 /* Telnet protocol requests from the front end */
2263 TelnetRequest (unsigned char ddww, unsigned char option)
2265 unsigned char msg[3];
2266 int outCount, outError;
2268 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2270 if (appData.debugMode) {
2271 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2287 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2296 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2299 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2304 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2306 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2313 if (!appData.icsActive) return;
2314 TelnetRequest(TN_DO, TN_ECHO);
2320 if (!appData.icsActive) return;
2321 TelnetRequest(TN_DONT, TN_ECHO);
2325 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2327 /* put the holdings sent to us by the server on the board holdings area */
2328 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2332 if(gameInfo.holdingsWidth < 2) return;
2333 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2334 return; // prevent overwriting by pre-board holdings
2336 if( (int)lowestPiece >= BlackPawn ) {
2339 holdingsStartRow = BOARD_HEIGHT-1;
2342 holdingsColumn = BOARD_WIDTH-1;
2343 countsColumn = BOARD_WIDTH-2;
2344 holdingsStartRow = 0;
2348 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2349 board[i][holdingsColumn] = EmptySquare;
2350 board[i][countsColumn] = (ChessSquare) 0;
2352 while( (p=*holdings++) != NULLCHAR ) {
2353 piece = CharToPiece( ToUpper(p) );
2354 if(piece == EmptySquare) continue;
2355 /*j = (int) piece - (int) WhitePawn;*/
2356 j = PieceToNumber(piece);
2357 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2358 if(j < 0) continue; /* should not happen */
2359 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2360 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2361 board[holdingsStartRow+j*direction][countsColumn]++;
2367 VariantSwitch (Board board, VariantClass newVariant)
2369 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2370 static Board oldBoard;
2372 startedFromPositionFile = FALSE;
2373 if(gameInfo.variant == newVariant) return;
2375 /* [HGM] This routine is called each time an assignment is made to
2376 * gameInfo.variant during a game, to make sure the board sizes
2377 * are set to match the new variant. If that means adding or deleting
2378 * holdings, we shift the playing board accordingly
2379 * This kludge is needed because in ICS observe mode, we get boards
2380 * of an ongoing game without knowing the variant, and learn about the
2381 * latter only later. This can be because of the move list we requested,
2382 * in which case the game history is refilled from the beginning anyway,
2383 * but also when receiving holdings of a crazyhouse game. In the latter
2384 * case we want to add those holdings to the already received position.
2388 if (appData.debugMode) {
2389 fprintf(debugFP, "Switch board from %s to %s\n",
2390 VariantName(gameInfo.variant), VariantName(newVariant));
2391 setbuf(debugFP, NULL);
2393 shuffleOpenings = 0; /* [HGM] shuffle */
2394 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2398 newWidth = 9; newHeight = 9;
2399 gameInfo.holdingsSize = 7;
2400 case VariantBughouse:
2401 case VariantCrazyhouse:
2402 newHoldingsWidth = 2; break;
2406 newHoldingsWidth = 2;
2407 gameInfo.holdingsSize = 8;
2410 case VariantCapablanca:
2411 case VariantCapaRandom:
2414 newHoldingsWidth = gameInfo.holdingsSize = 0;
2417 if(newWidth != gameInfo.boardWidth ||
2418 newHeight != gameInfo.boardHeight ||
2419 newHoldingsWidth != gameInfo.holdingsWidth ) {
2421 /* shift position to new playing area, if needed */
2422 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2423 for(i=0; i<BOARD_HEIGHT; i++)
2424 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2425 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2427 for(i=0; i<newHeight; i++) {
2428 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2429 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2431 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2432 for(i=0; i<BOARD_HEIGHT; i++)
2433 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2434 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2437 gameInfo.boardWidth = newWidth;
2438 gameInfo.boardHeight = newHeight;
2439 gameInfo.holdingsWidth = newHoldingsWidth;
2440 gameInfo.variant = newVariant;
2441 InitDrawingSizes(-2, 0);
2442 } else gameInfo.variant = newVariant;
2443 CopyBoard(oldBoard, board); // remember correctly formatted board
2444 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2445 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2448 static int loggedOn = FALSE;
2450 /*-- Game start info cache: --*/
2452 char gs_kind[MSG_SIZ];
2453 static char player1Name[128] = "";
2454 static char player2Name[128] = "";
2455 static char cont_seq[] = "\n\\ ";
2456 static int player1Rating = -1;
2457 static int player2Rating = -1;
2458 /*----------------------------*/
2460 ColorClass curColor = ColorNormal;
2461 int suppressKibitz = 0;
2464 Boolean soughtPending = FALSE;
2465 Boolean seekGraphUp;
2466 #define MAX_SEEK_ADS 200
2468 char *seekAdList[MAX_SEEK_ADS];
2469 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2470 float tcList[MAX_SEEK_ADS];
2471 char colorList[MAX_SEEK_ADS];
2472 int nrOfSeekAds = 0;
2473 int minRating = 1010, maxRating = 2800;
2474 int hMargin = 10, vMargin = 20, h, w;
2475 extern int squareSize, lineGap;
2480 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2481 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2482 if(r < minRating+100 && r >=0 ) r = minRating+100;
2483 if(r > maxRating) r = maxRating;
2484 if(tc < 1.) tc = 1.;
2485 if(tc > 95.) tc = 95.;
2486 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2487 y = ((double)r - minRating)/(maxRating - minRating)
2488 * (h-vMargin-squareSize/8-1) + vMargin;
2489 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2490 if(strstr(seekAdList[i], " u ")) color = 1;
2491 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2492 !strstr(seekAdList[i], "bullet") &&
2493 !strstr(seekAdList[i], "blitz") &&
2494 !strstr(seekAdList[i], "standard") ) color = 2;
2495 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2496 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2500 AddAd (char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2502 char buf[MSG_SIZ], *ext = "";
2503 VariantClass v = StringToVariant(type);
2504 if(strstr(type, "wild")) {
2505 ext = type + 4; // append wild number
2506 if(v == VariantFischeRandom) type = "chess960"; else
2507 if(v == VariantLoadable) type = "setup"; else
2508 type = VariantName(v);
2510 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2511 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2512 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2513 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2514 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2515 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2516 seekNrList[nrOfSeekAds] = nr;
2517 zList[nrOfSeekAds] = 0;
2518 seekAdList[nrOfSeekAds++] = StrSave(buf);
2519 if(plot) PlotSeekAd(nrOfSeekAds-1);
2524 EraseSeekDot (int i)
2526 int x = xList[i], y = yList[i], d=squareSize/4, k;
2527 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2528 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2529 // now replot every dot that overlapped
2530 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2531 int xx = xList[k], yy = yList[k];
2532 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2533 DrawSeekDot(xx, yy, colorList[k]);
2538 RemoveSeekAd (int nr)
2541 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2543 if(seekAdList[i]) free(seekAdList[i]);
2544 seekAdList[i] = seekAdList[--nrOfSeekAds];
2545 seekNrList[i] = seekNrList[nrOfSeekAds];
2546 ratingList[i] = ratingList[nrOfSeekAds];
2547 colorList[i] = colorList[nrOfSeekAds];
2548 tcList[i] = tcList[nrOfSeekAds];
2549 xList[i] = xList[nrOfSeekAds];
2550 yList[i] = yList[nrOfSeekAds];
2551 zList[i] = zList[nrOfSeekAds];
2552 seekAdList[nrOfSeekAds] = NULL;
2558 MatchSoughtLine (char *line)
2560 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2561 int nr, base, inc, u=0; char dummy;
2563 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2564 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2566 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2567 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2568 // match: compact and save the line
2569 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2579 if(!seekGraphUp) return FALSE;
2580 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2581 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2583 DrawSeekBackground(0, 0, w, h);
2584 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2585 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2586 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2587 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2589 DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2592 snprintf(buf, MSG_SIZ, "%d", i);
2593 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2596 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2597 for(i=1; i<100; i+=(i<10?1:5)) {
2598 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2599 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2600 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2602 snprintf(buf, MSG_SIZ, "%d", i);
2603 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2606 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2611 SeekGraphClick (ClickType click, int x, int y, int moving)
2613 static int lastDown = 0, displayed = 0, lastSecond;
2614 if(y < 0) return FALSE;
2615 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2616 if(click == Release || moving) return FALSE;
2618 soughtPending = TRUE;
2619 SendToICS(ics_prefix);
2620 SendToICS("sought\n"); // should this be "sought all"?
2621 } else { // issue challenge based on clicked ad
2622 int dist = 10000; int i, closest = 0, second = 0;
2623 for(i=0; i<nrOfSeekAds; i++) {
2624 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2625 if(d < dist) { dist = d; closest = i; }
2626 second += (d - zList[i] < 120); // count in-range ads
2627 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2631 second = (second > 1);
2632 if(displayed != closest || second != lastSecond) {
2633 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2634 lastSecond = second; displayed = closest;
2636 if(click == Press) {
2637 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2640 } // on press 'hit', only show info
2641 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2642 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2643 SendToICS(ics_prefix);
2645 return TRUE; // let incoming board of started game pop down the graph
2646 } else if(click == Release) { // release 'miss' is ignored
2647 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2648 if(moving == 2) { // right up-click
2649 nrOfSeekAds = 0; // refresh graph
2650 soughtPending = TRUE;
2651 SendToICS(ics_prefix);
2652 SendToICS("sought\n"); // should this be "sought all"?
2655 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2656 // press miss or release hit 'pop down' seek graph
2657 seekGraphUp = FALSE;
2658 DrawPosition(TRUE, NULL);
2664 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2666 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2667 #define STARTED_NONE 0
2668 #define STARTED_MOVES 1
2669 #define STARTED_BOARD 2
2670 #define STARTED_OBSERVE 3
2671 #define STARTED_HOLDINGS 4
2672 #define STARTED_CHATTER 5
2673 #define STARTED_COMMENT 6
2674 #define STARTED_MOVES_NOHIDE 7
2676 static int started = STARTED_NONE;
2677 static char parse[20000];
2678 static int parse_pos = 0;
2679 static char buf[BUF_SIZE + 1];
2680 static int firstTime = TRUE, intfSet = FALSE;
2681 static ColorClass prevColor = ColorNormal;
2682 static int savingComment = FALSE;
2683 static int cmatch = 0; // continuation sequence match
2690 int backup; /* [DM] For zippy color lines */
2692 char talker[MSG_SIZ]; // [HGM] chat
2695 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2697 if (appData.debugMode) {
2699 fprintf(debugFP, "<ICS: ");
2700 show_bytes(debugFP, data, count);
2701 fprintf(debugFP, "\n");
2705 if (appData.debugMode) { int f = forwardMostMove;
2706 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2707 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2708 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2711 /* If last read ended with a partial line that we couldn't parse,
2712 prepend it to the new read and try again. */
2713 if (leftover_len > 0) {
2714 for (i=0; i<leftover_len; i++)
2715 buf[i] = buf[leftover_start + i];
2718 /* copy new characters into the buffer */
2719 bp = buf + leftover_len;
2720 buf_len=leftover_len;
2721 for (i=0; i<count; i++)
2724 if (data[i] == '\r')
2727 // join lines split by ICS?
2728 if (!appData.noJoin)
2731 Joining just consists of finding matches against the
2732 continuation sequence, and discarding that sequence
2733 if found instead of copying it. So, until a match
2734 fails, there's nothing to do since it might be the
2735 complete sequence, and thus, something we don't want
2738 if (data[i] == cont_seq[cmatch])
2741 if (cmatch == strlen(cont_seq))
2743 cmatch = 0; // complete match. just reset the counter
2746 it's possible for the ICS to not include the space
2747 at the end of the last word, making our [correct]
2748 join operation fuse two separate words. the server
2749 does this when the space occurs at the width setting.
2751 if (!buf_len || buf[buf_len-1] != ' ')
2762 match failed, so we have to copy what matched before
2763 falling through and copying this character. In reality,
2764 this will only ever be just the newline character, but
2765 it doesn't hurt to be precise.
2767 strncpy(bp, cont_seq, cmatch);
2779 buf[buf_len] = NULLCHAR;
2780 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2785 while (i < buf_len) {
2786 /* Deal with part of the TELNET option negotiation
2787 protocol. We refuse to do anything beyond the
2788 defaults, except that we allow the WILL ECHO option,
2789 which ICS uses to turn off password echoing when we are
2790 directly connected to it. We reject this option
2791 if localLineEditing mode is on (always on in xboard)
2792 and we are talking to port 23, which might be a real
2793 telnet server that will try to keep WILL ECHO on permanently.
2795 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2796 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2797 unsigned char option;
2799 switch ((unsigned char) buf[++i]) {
2801 if (appData.debugMode)
2802 fprintf(debugFP, "\n<WILL ");
2803 switch (option = (unsigned char) buf[++i]) {
2805 if (appData.debugMode)
2806 fprintf(debugFP, "ECHO ");
2807 /* Reply only if this is a change, according
2808 to the protocol rules. */
2809 if (remoteEchoOption) break;
2810 if (appData.localLineEditing &&
2811 atoi(appData.icsPort) == TN_PORT) {
2812 TelnetRequest(TN_DONT, TN_ECHO);
2815 TelnetRequest(TN_DO, TN_ECHO);
2816 remoteEchoOption = TRUE;
2820 if (appData.debugMode)
2821 fprintf(debugFP, "%d ", option);
2822 /* Whatever this is, we don't want it. */
2823 TelnetRequest(TN_DONT, option);
2828 if (appData.debugMode)
2829 fprintf(debugFP, "\n<WONT ");
2830 switch (option = (unsigned char) buf[++i]) {
2832 if (appData.debugMode)
2833 fprintf(debugFP, "ECHO ");
2834 /* Reply only if this is a change, according
2835 to the protocol rules. */
2836 if (!remoteEchoOption) break;
2838 TelnetRequest(TN_DONT, TN_ECHO);
2839 remoteEchoOption = FALSE;
2842 if (appData.debugMode)
2843 fprintf(debugFP, "%d ", (unsigned char) option);
2844 /* Whatever this is, it must already be turned
2845 off, because we never agree to turn on
2846 anything non-default, so according to the
2847 protocol rules, we don't reply. */
2852 if (appData.debugMode)
2853 fprintf(debugFP, "\n<DO ");
2854 switch (option = (unsigned char) buf[++i]) {
2856 /* Whatever this is, we refuse to do it. */
2857 if (appData.debugMode)
2858 fprintf(debugFP, "%d ", option);
2859 TelnetRequest(TN_WONT, option);
2864 if (appData.debugMode)
2865 fprintf(debugFP, "\n<DONT ");
2866 switch (option = (unsigned char) buf[++i]) {
2868 if (appData.debugMode)
2869 fprintf(debugFP, "%d ", option);
2870 /* Whatever this is, we are already not doing
2871 it, because we never agree to do anything
2872 non-default, so according to the protocol
2873 rules, we don't reply. */
2878 if (appData.debugMode)
2879 fprintf(debugFP, "\n<IAC ");
2880 /* Doubled IAC; pass it through */
2884 if (appData.debugMode)
2885 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2886 /* Drop all other telnet commands on the floor */
2889 if (oldi > next_out)
2890 SendToPlayer(&buf[next_out], oldi - next_out);
2896 /* OK, this at least will *usually* work */
2897 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2901 if (loggedOn && !intfSet) {
2902 if (ics_type == ICS_ICC) {
2903 snprintf(str, MSG_SIZ,
2904 "/set-quietly interface %s\n/set-quietly style 12\n",
2906 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2907 strcat(str, "/set-2 51 1\n/set seek 1\n");
2908 } else if (ics_type == ICS_CHESSNET) {
2909 snprintf(str, MSG_SIZ, "/style 12\n");
2911 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2912 strcat(str, programVersion);
2913 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2914 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2915 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2917 strcat(str, "$iset nohighlight 1\n");
2919 strcat(str, "$iset lock 1\n$style 12\n");
2922 NotifyFrontendLogin();
2926 if (started == STARTED_COMMENT) {
2927 /* Accumulate characters in comment */
2928 parse[parse_pos++] = buf[i];
2929 if (buf[i] == '\n') {
2930 parse[parse_pos] = NULLCHAR;
2931 if(chattingPartner>=0) {
2933 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2934 OutputChatMessage(chattingPartner, mess);
2935 chattingPartner = -1;
2936 next_out = i+1; // [HGM] suppress printing in ICS window
2938 if(!suppressKibitz) // [HGM] kibitz
2939 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2940 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2941 int nrDigit = 0, nrAlph = 0, j;
2942 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2943 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2944 parse[parse_pos] = NULLCHAR;
2945 // try to be smart: if it does not look like search info, it should go to
2946 // ICS interaction window after all, not to engine-output window.
2947 for(j=0; j<parse_pos; j++) { // count letters and digits
2948 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2949 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2950 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2952 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2953 int depth=0; float score;
2954 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2955 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2956 pvInfoList[forwardMostMove-1].depth = depth;
2957 pvInfoList[forwardMostMove-1].score = 100*score;
2959 OutputKibitz(suppressKibitz, parse);
2962 if(gameMode == IcsObserving) // restore original ICS messages
2963 snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
2965 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2966 SendToPlayer(tmp, strlen(tmp));
2968 next_out = i+1; // [HGM] suppress printing in ICS window
2970 started = STARTED_NONE;
2972 /* Don't match patterns against characters in comment */
2977 if (started == STARTED_CHATTER) {
2978 if (buf[i] != '\n') {
2979 /* Don't match patterns against characters in chatter */
2983 started = STARTED_NONE;
2984 if(suppressKibitz) next_out = i+1;
2987 /* Kludge to deal with rcmd protocol */
2988 if (firstTime && looking_at(buf, &i, "\001*")) {
2989 DisplayFatalError(&buf[1], 0, 1);
2995 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2998 if (appData.debugMode)
2999 fprintf(debugFP, "ics_type %d\n", ics_type);
3002 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3003 ics_type = ICS_FICS;
3005 if (appData.debugMode)
3006 fprintf(debugFP, "ics_type %d\n", ics_type);
3009 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3010 ics_type = ICS_CHESSNET;
3012 if (appData.debugMode)
3013 fprintf(debugFP, "ics_type %d\n", ics_type);
3018 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3019 looking_at(buf, &i, "Logging you in as \"*\"") ||
3020 looking_at(buf, &i, "will be \"*\""))) {
3021 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3025 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3027 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3028 DisplayIcsInteractionTitle(buf);
3029 have_set_title = TRUE;
3032 /* skip finger notes */
3033 if (started == STARTED_NONE &&
3034 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3035 (buf[i] == '1' && buf[i+1] == '0')) &&
3036 buf[i+2] == ':' && buf[i+3] == ' ') {
3037 started = STARTED_CHATTER;
3043 // [HGM] seekgraph: recognize sought lines and end-of-sought message
3044 if(appData.seekGraph) {
3045 if(soughtPending && MatchSoughtLine(buf+i)) {
3046 i = strstr(buf+i, "rated") - buf;
3047 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3048 next_out = leftover_start = i;
3049 started = STARTED_CHATTER;
3050 suppressKibitz = TRUE;
3053 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3054 && looking_at(buf, &i, "* ads displayed")) {
3055 soughtPending = FALSE;
3060 if(appData.autoRefresh) {
3061 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3062 int s = (ics_type == ICS_ICC); // ICC format differs
3064 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3065 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3066 looking_at(buf, &i, "*% "); // eat prompt
3067 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3068 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3069 next_out = i; // suppress
3072 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3073 char *p = star_match[0];
3075 if(seekGraphUp) RemoveSeekAd(atoi(p));
3076 while(*p && *p++ != ' '); // next
3078 looking_at(buf, &i, "*% "); // eat prompt
3079 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3086 /* skip formula vars */
3087 if (started == STARTED_NONE &&
3088 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3089 started = STARTED_CHATTER;
3094 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3095 if (appData.autoKibitz && started == STARTED_NONE &&
3096 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3097 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3098 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3099 looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3100 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3101 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3102 suppressKibitz = TRUE;
3103 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3105 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3106 && (gameMode == IcsPlayingWhite)) ||
3107 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3108 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3109 started = STARTED_CHATTER; // own kibitz we simply discard
3111 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3112 parse_pos = 0; parse[0] = NULLCHAR;
3113 savingComment = TRUE;
3114 suppressKibitz = gameMode != IcsObserving ? 2 :
3115 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3119 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3120 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3121 && atoi(star_match[0])) {
3122 // suppress the acknowledgements of our own autoKibitz
3124 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3125 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3126 SendToPlayer(star_match[0], strlen(star_match[0]));
3127 if(looking_at(buf, &i, "*% ")) // eat prompt
3128 suppressKibitz = FALSE;
3132 } // [HGM] kibitz: end of patch
3134 if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3136 // [HGM] chat: intercept tells by users for which we have an open chat window
3138 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3139 looking_at(buf, &i, "* whispers:") ||
3140 looking_at(buf, &i, "* kibitzes:") ||
3141 looking_at(buf, &i, "* shouts:") ||
3142 looking_at(buf, &i, "* c-shouts:") ||
3143 looking_at(buf, &i, "--> * ") ||
3144 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3145 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3146 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3147 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3149 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3150 chattingPartner = -1;
3152 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3153 for(p=0; p<MAX_CHAT; p++) {
3154 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3155 talker[0] = '['; strcat(talker, "] ");
3156 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3157 chattingPartner = p; break;
3160 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3161 for(p=0; p<MAX_CHAT; p++) {
3162 if(!strcmp("kibitzes", chatPartner[p])) {
3163 talker[0] = '['; strcat(talker, "] ");
3164 chattingPartner = p; break;
3167 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3168 for(p=0; p<MAX_CHAT; p++) {
3169 if(!strcmp("whispers", chatPartner[p])) {
3170 talker[0] = '['; strcat(talker, "] ");
3171 chattingPartner = p; break;
3174 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3175 if(buf[i-8] == '-' && buf[i-3] == 't')
3176 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3177 if(!strcmp("c-shouts", chatPartner[p])) {
3178 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3179 chattingPartner = p; break;
3182 if(chattingPartner < 0)
3183 for(p=0; p<MAX_CHAT; p++) {
3184 if(!strcmp("shouts", chatPartner[p])) {
3185 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3186 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3187 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3188 chattingPartner = p; break;
3192 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3193 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3194 talker[0] = 0; Colorize(ColorTell, FALSE);
3195 chattingPartner = p; break;
3197 if(chattingPartner<0) i = oldi; else {
3198 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3199 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3200 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3201 started = STARTED_COMMENT;
3202 parse_pos = 0; parse[0] = NULLCHAR;
3203 savingComment = 3 + chattingPartner; // counts as TRUE
3204 suppressKibitz = TRUE;
3207 } // [HGM] chat: end of patch
3210 if (appData.zippyTalk || appData.zippyPlay) {
3211 /* [DM] Backup address for color zippy lines */
3213 if (loggedOn == TRUE)
3214 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3215 (appData.zippyPlay && ZippyMatch(buf, &backup)));
3217 } // [DM] 'else { ' deleted
3219 /* Regular tells and says */
3220 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3221 looking_at(buf, &i, "* (your partner) tells you: ") ||
3222 looking_at(buf, &i, "* says: ") ||
3223 /* Don't color "message" or "messages" output */
3224 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3225 looking_at(buf, &i, "*. * at *:*: ") ||
3226 looking_at(buf, &i, "--* (*:*): ") ||
3227 /* Message notifications (same color as tells) */
3228 looking_at(buf, &i, "* has left a message ") ||
3229 looking_at(buf, &i, "* just sent you a message:\n") ||
3230 /* Whispers and kibitzes */
3231 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3232 looking_at(buf, &i, "* kibitzes: ") ||
3234 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3236 if (tkind == 1 && strchr(star_match[0], ':')) {
3237 /* Avoid "tells you:" spoofs in channels */
3240 if (star_match[0][0] == NULLCHAR ||
3241 strchr(star_match[0], ' ') ||
3242 (tkind == 3 && strchr(star_match[1], ' '))) {
3243 /* Reject bogus matches */
3246 if (appData.colorize) {
3247 if (oldi > next_out) {
3248 SendToPlayer(&buf[next_out], oldi - next_out);
3253 Colorize(ColorTell, FALSE);
3254 curColor = ColorTell;
3257 Colorize(ColorKibitz, FALSE);
3258 curColor = ColorKibitz;
3261 p = strrchr(star_match[1], '(');
3268 Colorize(ColorChannel1, FALSE);
3269 curColor = ColorChannel1;
3271 Colorize(ColorChannel, FALSE);
3272 curColor = ColorChannel;
3276 curColor = ColorNormal;
3280 if (started == STARTED_NONE && appData.autoComment &&
3281 (gameMode == IcsObserving ||
3282 gameMode == IcsPlayingWhite ||
3283 gameMode == IcsPlayingBlack)) {
3284 parse_pos = i - oldi;
3285 memcpy(parse, &buf[oldi], parse_pos);
3286 parse[parse_pos] = NULLCHAR;
3287 started = STARTED_COMMENT;
3288 savingComment = TRUE;
3290 started = STARTED_CHATTER;
3291 savingComment = FALSE;
3298 if (looking_at(buf, &i, "* s-shouts: ") ||
3299 looking_at(buf, &i, "* c-shouts: ")) {
3300 if (appData.colorize) {
3301 if (oldi > next_out) {
3302 SendToPlayer(&buf[next_out], oldi - next_out);
3305 Colorize(ColorSShout, FALSE);
3306 curColor = ColorSShout;
3309 started = STARTED_CHATTER;
3313 if (looking_at(buf, &i, "--->")) {
3318 if (looking_at(buf, &i, "* shouts: ") ||
3319 looking_at(buf, &i, "--> ")) {
3320 if (appData.colorize) {
3321 if (oldi > next_out) {
3322 SendToPlayer(&buf[next_out], oldi - next_out);
3325 Colorize(ColorShout, FALSE);
3326 curColor = ColorShout;
3329 started = STARTED_CHATTER;
3333 if (looking_at( buf, &i, "Challenge:")) {
3334 if (appData.colorize) {
3335 if (oldi > next_out) {
3336 SendToPlayer(&buf[next_out], oldi - next_out);
3339 Colorize(ColorChallenge, FALSE);
3340 curColor = ColorChallenge;
3346 if (looking_at(buf, &i, "* offers you") ||
3347 looking_at(buf, &i, "* offers to be") ||
3348 looking_at(buf, &i, "* would like to") ||
3349 looking_at(buf, &i, "* requests to") ||
3350 looking_at(buf, &i, "Your opponent offers") ||
3351 looking_at(buf, &i, "Your opponent requests")) {
3353 if (appData.colorize) {
3354 if (oldi > next_out) {
3355 SendToPlayer(&buf[next_out], oldi - next_out);
3358 Colorize(ColorRequest, FALSE);
3359 curColor = ColorRequest;
3364 if (looking_at(buf, &i, "* (*) seeking")) {
3365 if (appData.colorize) {
3366 if (oldi > next_out) {
3367 SendToPlayer(&buf[next_out], oldi - next_out);
3370 Colorize(ColorSeek, FALSE);
3371 curColor = ColorSeek;
3376 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3378 if (looking_at(buf, &i, "\\ ")) {
3379 if (prevColor != ColorNormal) {
3380 if (oldi > next_out) {
3381 SendToPlayer(&buf[next_out], oldi - next_out);
3384 Colorize(prevColor, TRUE);
3385 curColor = prevColor;
3387 if (savingComment) {
3388 parse_pos = i - oldi;
3389 memcpy(parse, &buf[oldi], parse_pos);
3390 parse[parse_pos] = NULLCHAR;
3391 started = STARTED_COMMENT;
3392 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3393 chattingPartner = savingComment - 3; // kludge to remember the box
3395 started = STARTED_CHATTER;
3400 if (looking_at(buf, &i, "Black Strength :") ||
3401 looking_at(buf, &i, "<<< style 10 board >>>") ||
3402 looking_at(buf, &i, "<10>") ||
3403 looking_at(buf, &i, "#@#")) {
3404 /* Wrong board style */
3406 SendToICS(ics_prefix);
3407 SendToICS("set style 12\n");
3408 SendToICS(ics_prefix);
3409 SendToICS("refresh\n");
3413 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3415 have_sent_ICS_logon = 1;
3419 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3420 (looking_at(buf, &i, "\n<12> ") ||
3421 looking_at(buf, &i, "<12> "))) {
3423 if (oldi > next_out) {
3424 SendToPlayer(&buf[next_out], oldi - next_out);
3427 started = STARTED_BOARD;
3432 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3433 looking_at(buf, &i, "<b1> ")) {
3434 if (oldi > next_out) {
3435 SendToPlayer(&buf[next_out], oldi - next_out);
3438 started = STARTED_HOLDINGS;
3443 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3445 /* Header for a move list -- first line */
3447 switch (ics_getting_history) {
3451 case BeginningOfGame:
3452 /* User typed "moves" or "oldmoves" while we
3453 were idle. Pretend we asked for these
3454 moves and soak them up so user can step
3455 through them and/or save them.
3458 gameMode = IcsObserving;
3461 ics_getting_history = H_GOT_UNREQ_HEADER;
3463 case EditGame: /*?*/
3464 case EditPosition: /*?*/
3465 /* Should above feature work in these modes too? */
3466 /* For now it doesn't */
3467 ics_getting_history = H_GOT_UNWANTED_HEADER;
3470 ics_getting_history = H_GOT_UNWANTED_HEADER;
3475 /* Is this the right one? */
3476 if (gameInfo.white && gameInfo.black &&
3477 strcmp(gameInfo.white, star_match[0]) == 0 &&
3478 strcmp(gameInfo.black, star_match[2]) == 0) {
3480 ics_getting_history = H_GOT_REQ_HEADER;
3483 case H_GOT_REQ_HEADER:
3484 case H_GOT_UNREQ_HEADER:
3485 case H_GOT_UNWANTED_HEADER:
3486 case H_GETTING_MOVES:
3487 /* Should not happen */
3488 DisplayError(_("Error gathering move list: two headers"), 0);
3489 ics_getting_history = H_FALSE;
3493 /* Save player ratings into gameInfo if needed */
3494 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3495 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3496 (gameInfo.whiteRating == -1 ||
3497 gameInfo.blackRating == -1)) {
3499 gameInfo.whiteRating = string_to_rating(star_match[1]);
3500 gameInfo.blackRating = string_to_rating(star_match[3]);
3501 if (appData.debugMode)
3502 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3503 gameInfo.whiteRating, gameInfo.blackRating);
3508 if (looking_at(buf, &i,
3509 "* * match, initial time: * minute*, increment: * second")) {
3510 /* Header for a move list -- second line */
3511 /* Initial board will follow if this is a wild game */
3512 if (gameInfo.event != NULL) free(gameInfo.event);
3513 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3514 gameInfo.event = StrSave(str);
3515 /* [HGM] we switched variant. Translate boards if needed. */
3516 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3520 if (looking_at(buf, &i, "Move ")) {
3521 /* Beginning of a move list */
3522 switch (ics_getting_history) {
3524 /* Normally should not happen */
3525 /* Maybe user hit reset while we were parsing */
3528 /* Happens if we are ignoring a move list that is not
3529 * the one we just requested. Common if the user
3530 * tries to observe two games without turning off
3533 case H_GETTING_MOVES:
3534 /* Should not happen */
3535 DisplayError(_("Error gathering move list: nested"), 0);
3536 ics_getting_history = H_FALSE;
3538 case H_GOT_REQ_HEADER:
3539 ics_getting_history = H_GETTING_MOVES;
3540 started = STARTED_MOVES;
3542 if (oldi > next_out) {
3543 SendToPlayer(&buf[next_out], oldi - next_out);
3546 case H_GOT_UNREQ_HEADER:
3547 ics_getting_history = H_GETTING_MOVES;
3548 started = STARTED_MOVES_NOHIDE;
3551 case H_GOT_UNWANTED_HEADER:
3552 ics_getting_history = H_FALSE;
3558 if (looking_at(buf, &i, "% ") ||
3559 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3560 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3561 if(soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3562 soughtPending = FALSE;
3566 if(suppressKibitz) next_out = i;
3567 savingComment = FALSE;
3571 case STARTED_MOVES_NOHIDE:
3572 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3573 parse[parse_pos + i - oldi] = NULLCHAR;
3574 ParseGameHistory(parse);
3576 if (appData.zippyPlay && first.initDone) {
3577 FeedMovesToProgram(&first, forwardMostMove);
3578 if (gameMode == IcsPlayingWhite) {
3579 if (WhiteOnMove(forwardMostMove)) {
3580 if (first.sendTime) {
3581 if (first.useColors) {
3582 SendToProgram("black\n", &first);
3584 SendTimeRemaining(&first, TRUE);
3586 if (first.useColors) {
3587 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3589 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3590 first.maybeThinking = TRUE;
3592 if (first.usePlayother) {
3593 if (first.sendTime) {
3594 SendTimeRemaining(&first, TRUE);
3596 SendToProgram("playother\n", &first);
3602 } else if (gameMode == IcsPlayingBlack) {
3603 if (!WhiteOnMove(forwardMostMove)) {
3604 if (first.sendTime) {
3605 if (first.useColors) {
3606 SendToProgram("white\n", &first);
3608 SendTimeRemaining(&first, FALSE);
3610 if (first.useColors) {
3611 SendToProgram("black\n", &first);
3613 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3614 first.maybeThinking = TRUE;
3616 if (first.usePlayother) {
3617 if (first.sendTime) {
3618 SendTimeRemaining(&first, FALSE);
3620 SendToProgram("playother\n", &first);
3629 if (gameMode == IcsObserving && ics_gamenum == -1) {
3630 /* Moves came from oldmoves or moves command
3631 while we weren't doing anything else.
3633 currentMove = forwardMostMove;
3634 ClearHighlights();/*!!could figure this out*/
3635 flipView = appData.flipView;
3636 DrawPosition(TRUE, boards[currentMove]);
3637 DisplayBothClocks();
3638 snprintf(str, MSG_SIZ, "%s %s %s",
3639 gameInfo.white, _("vs."), gameInfo.black);
3643 /* Moves were history of an active game */
3644 if (gameInfo.resultDetails != NULL) {
3645 free(gameInfo.resultDetails);
3646 gameInfo.resultDetails = NULL;
3649 HistorySet(parseList, backwardMostMove,
3650 forwardMostMove, currentMove-1);
3651 DisplayMove(currentMove - 1);
3652 if (started == STARTED_MOVES) next_out = i;
3653 started = STARTED_NONE;
3654 ics_getting_history = H_FALSE;
3657 case STARTED_OBSERVE:
3658 started = STARTED_NONE;
3659 SendToICS(ics_prefix);
3660 SendToICS("refresh\n");
3666 if(bookHit) { // [HGM] book: simulate book reply
3667 static char bookMove[MSG_SIZ]; // a bit generous?
3669 programStats.nodes = programStats.depth = programStats.time =
3670 programStats.score = programStats.got_only_move = 0;
3671 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3673 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3674 strcat(bookMove, bookHit);
3675 HandleMachineMove(bookMove, &first);
3680 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3681 started == STARTED_HOLDINGS ||
3682 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3683 /* Accumulate characters in move list or board */
3684 parse[parse_pos++] = buf[i];
3687 /* Start of game messages. Mostly we detect start of game
3688 when the first board image arrives. On some versions
3689 of the ICS, though, we need to do a "refresh" after starting
3690 to observe in order to get the current board right away. */
3691 if (looking_at(buf, &i, "Adding game * to observation list")) {
3692 started = STARTED_OBSERVE;
3696 /* Handle auto-observe */
3697 if (appData.autoObserve &&
3698 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3699 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3701 /* Choose the player that was highlighted, if any. */
3702 if (star_match[0][0] == '\033' ||
3703 star_match[1][0] != '\033') {
3704 player = star_match[0];
3706 player = star_match[2];
3708 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3709 ics_prefix, StripHighlightAndTitle(player));
3712 /* Save ratings from notify string */
3713 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3714 player1Rating = string_to_rating(star_match[1]);
3715 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3716 player2Rating = string_to_rating(star_match[3]);
3718 if (appData.debugMode)
3720 "Ratings from 'Game notification:' %s %d, %s %d\n",
3721 player1Name, player1Rating,
3722 player2Name, player2Rating);
3727 /* Deal with automatic examine mode after a game,
3728 and with IcsObserving -> IcsExamining transition */
3729 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3730 looking_at(buf, &i, "has made you an examiner of game *")) {
3732 int gamenum = atoi(star_match[0]);
3733 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3734 gamenum == ics_gamenum) {
3735 /* We were already playing or observing this game;
3736 no need to refetch history */
3737 gameMode = IcsExamining;
3739 pauseExamForwardMostMove = forwardMostMove;
3740 } else if (currentMove < forwardMostMove) {
3741 ForwardInner(forwardMostMove);
3744 /* I don't think this case really can happen */
3745 SendToICS(ics_prefix);
3746 SendToICS("refresh\n");
3751 /* Error messages */
3752 // if (ics_user_moved) {
3753 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3754 if (looking_at(buf, &i, "Illegal move") ||
3755 looking_at(buf, &i, "Not a legal move") ||
3756 looking_at(buf, &i, "Your king is in check") ||
3757 looking_at(buf, &i, "It isn't your turn") ||
3758 looking_at(buf, &i, "It is not your move")) {
3760 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3761 currentMove = forwardMostMove-1;
3762 DisplayMove(currentMove - 1); /* before DMError */
3763 DrawPosition(FALSE, boards[currentMove]);
3764 SwitchClocks(forwardMostMove-1); // [HGM] race
3765 DisplayBothClocks();
3767 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3773 if (looking_at(buf, &i, "still have time") ||
3774 looking_at(buf, &i, "not out of time") ||
3775 looking_at(buf, &i, "either player is out of time") ||
3776 looking_at(buf, &i, "has timeseal; checking")) {
3777 /* We must have called his flag a little too soon */
3778 whiteFlag = blackFlag = FALSE;
3782 if (looking_at(buf, &i, "added * seconds to") ||
3783 looking_at(buf, &i, "seconds were added to")) {
3784 /* Update the clocks */
3785 SendToICS(ics_prefix);
3786 SendToICS("refresh\n");
3790 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3791 ics_clock_paused = TRUE;
3796 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3797 ics_clock_paused = FALSE;
3802 /* Grab player ratings from the Creating: message.
3803 Note we have to check for the special case when
3804 the ICS inserts things like [white] or [black]. */
3805 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3806 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3808 0 player 1 name (not necessarily white)
3810 2 empty, white, or black (IGNORED)
3811 3 player 2 name (not necessarily black)
3814 The names/ratings are sorted out when the game
3815 actually starts (below).
3817 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3818 player1Rating = string_to_rating(star_match[1]);
3819 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3820 player2Rating = string_to_rating(star_match[4]);
3822 if (appData.debugMode)
3824 "Ratings from 'Creating:' %s %d, %s %d\n",
3825 player1Name, player1Rating,
3826 player2Name, player2Rating);
3831 /* Improved generic start/end-of-game messages */
3832 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3833 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3834 /* If tkind == 0: */
3835 /* star_match[0] is the game number */
3836 /* [1] is the white player's name */
3837 /* [2] is the black player's name */
3838 /* For end-of-game: */
3839 /* [3] is the reason for the game end */
3840 /* [4] is a PGN end game-token, preceded by " " */
3841 /* For start-of-game: */
3842 /* [3] begins with "Creating" or "Continuing" */
3843 /* [4] is " *" or empty (don't care). */
3844 int gamenum = atoi(star_match[0]);
3845 char *whitename, *blackname, *why, *endtoken;
3846 ChessMove endtype = EndOfFile;
3849 whitename = star_match[1];
3850 blackname = star_match[2];
3851 why = star_match[3];
3852 endtoken = star_match[4];
3854 whitename = star_match[1];
3855 blackname = star_match[3];
3856 why = star_match[5];
3857 endtoken = star_match[6];
3860 /* Game start messages */
3861 if (strncmp(why, "Creating ", 9) == 0 ||
3862 strncmp(why, "Continuing ", 11) == 0) {