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
1246 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1247 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1248 if(movenr == -1) return time; /* last move before new session */
1249 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1250 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1251 if(!moves) return increment; /* current session is incremental */
1252 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1253 } while(movenr >= -1); /* try again for next session */
1255 return 0; // no new time quota on this move
1259 ParseTimeControl (char *tc, float ti, int mps)
1263 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1266 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1267 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1268 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1272 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1274 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1277 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1279 snprintf(buf, MSG_SIZ, ":%s", mytc);
1281 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1283 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1288 /* Parse second time control */
1291 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1299 timeControl_2 = tc2 * 1000;
1309 timeControl = tc1 * 1000;
1312 timeIncrement = ti * 1000; /* convert to ms */
1313 movesPerSession = 0;
1316 movesPerSession = mps;
1324 if (appData.debugMode) {
1325 fprintf(debugFP, "%s\n", programVersion);
1327 ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1329 set_cont_sequence(appData.wrapContSeq);
1330 if (appData.matchGames > 0) {
1331 appData.matchMode = TRUE;
1332 } else if (appData.matchMode) {
1333 appData.matchGames = 1;
1335 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1336 appData.matchGames = appData.sameColorGames;
1337 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1338 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1339 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1342 if (appData.noChessProgram || first.protocolVersion == 1) {
1345 /* kludge: allow timeout for initial "feature" commands */
1347 DisplayMessage("", _("Starting chess program"));
1348 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1353 CalculateIndex (int index, int gameNr)
1354 { // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1356 if(index > 0) return index; // fixed nmber
1357 if(index == 0) return 1;
1358 res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1359 if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1364 LoadGameOrPosition (int gameNr)
1365 { // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1366 if (*appData.loadGameFile != NULLCHAR) {
1367 if (!LoadGameFromFile(appData.loadGameFile,
1368 CalculateIndex(appData.loadGameIndex, gameNr),
1369 appData.loadGameFile, FALSE)) {
1370 DisplayFatalError(_("Bad game file"), 0, 1);
1373 } else if (*appData.loadPositionFile != NULLCHAR) {
1374 if (!LoadPositionFromFile(appData.loadPositionFile,
1375 CalculateIndex(appData.loadPositionIndex, gameNr),
1376 appData.loadPositionFile)) {
1377 DisplayFatalError(_("Bad position file"), 0, 1);
1385 ReserveGame (int gameNr, char resChar)
1387 FILE *tf = fopen(appData.tourneyFile, "r+");
1388 char *p, *q, c, buf[MSG_SIZ];
1389 if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1390 safeStrCpy(buf, lastMsg, MSG_SIZ);
1391 DisplayMessage(_("Pick new game"), "");
1392 flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1393 ParseArgsFromFile(tf);
1394 p = q = appData.results;
1395 if(appData.debugMode) {
1396 char *r = appData.participants;
1397 fprintf(debugFP, "results = '%s'\n", p);
1398 while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1399 fprintf(debugFP, "\n");
1401 while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1403 q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1404 safeStrCpy(q, p, strlen(p) + 2);
1405 if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1406 if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1407 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1408 if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1411 fseek(tf, -(strlen(p)+4), SEEK_END);
1413 if(c != '"') // depending on DOS or Unix line endings we can be one off
1414 fseek(tf, -(strlen(p)+2), SEEK_END);
1415 else fseek(tf, -(strlen(p)+3), SEEK_END);
1416 fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1417 DisplayMessage(buf, "");
1418 free(p); appData.results = q;
1419 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1420 (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1421 int round = appData.defaultMatchGames * appData.tourneyType;
1422 if(gameNr < 0 || appData.tourneyType < 1 || // gauntlet engine can always stay loaded as first engine
1423 appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1424 UnloadEngine(&first); // next game belongs to other pairing;
1425 UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1427 if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1431 MatchEvent (int mode)
1432 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1434 if(matchMode) { // already in match mode: switch it off
1436 if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1439 // if(gameMode != BeginningOfGame) {
1440 // DisplayError(_("You can only start a match from the initial position."), 0);
1444 if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1445 /* Set up machine vs. machine match */
1447 NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1448 if(appData.tourneyFile[0]) {
1450 if(nextGame > appData.matchGames) {
1452 if(strchr(appData.results, '*') == NULL) {
1454 appData.tourneyCycles++;
1455 if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1457 NextTourneyGame(-1, &dummy);
1459 if(nextGame <= appData.matchGames) {
1460 DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1462 ScheduleDelayedEvent(NextMatchGame, 10000);
1467 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1468 DisplayError(buf, 0);
1469 appData.tourneyFile[0] = 0;
1473 if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically
1474 DisplayFatalError(_("Can't have a match with no chess programs"),
1479 matchGame = roundNr = 1;
1480 first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1485 InitBackEnd3 P((void))
1487 GameMode initialMode;
1491 InitChessProgram(&first, startedFromSetupPosition);
1493 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1494 free(programVersion);
1495 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1496 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1497 FloatToFront(&appData.recentEngineList, appData.firstChessProgram);
1500 if (appData.icsActive) {
1502 /* [DM] Make a console window if needed [HGM] merged ifs */
1508 if (*appData.icsCommPort != NULLCHAR)
1509 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1510 appData.icsCommPort);
1512 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1513 appData.icsHost, appData.icsPort);
1515 if( (len >= MSG_SIZ) && appData.debugMode )
1516 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1518 DisplayFatalError(buf, err, 1);
1523 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1525 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1526 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1527 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1528 } else if (appData.noChessProgram) {
1534 if (*appData.cmailGameName != NULLCHAR) {
1536 OpenLoopback(&cmailPR);
1538 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1542 DisplayMessage("", "");
1543 if (StrCaseCmp(appData.initialMode, "") == 0) {
1544 initialMode = BeginningOfGame;
1545 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1546 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1547 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1548 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1551 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1552 initialMode = TwoMachinesPlay;
1553 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1554 initialMode = AnalyzeFile;
1555 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1556 initialMode = AnalyzeMode;
1557 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1558 initialMode = MachinePlaysWhite;
1559 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1560 initialMode = MachinePlaysBlack;
1561 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1562 initialMode = EditGame;
1563 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1564 initialMode = EditPosition;
1565 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1566 initialMode = Training;
1568 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1569 if( (len >= MSG_SIZ) && appData.debugMode )
1570 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1572 DisplayFatalError(buf, 0, 2);
1576 if (appData.matchMode) {
1577 if(appData.tourneyFile[0]) { // start tourney from command line
1579 if(f = fopen(appData.tourneyFile, "r")) {
1580 ParseArgsFromFile(f); // make sure tourney parmeters re known
1582 appData.clockMode = TRUE;
1584 } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1587 } else if (*appData.cmailGameName != NULLCHAR) {
1588 /* Set up cmail mode */
1589 ReloadCmailMsgEvent(TRUE);
1591 /* Set up other modes */
1592 if (initialMode == AnalyzeFile) {
1593 if (*appData.loadGameFile == NULLCHAR) {
1594 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1598 if (*appData.loadGameFile != NULLCHAR) {
1599 (void) LoadGameFromFile(appData.loadGameFile,
1600 appData.loadGameIndex,
1601 appData.loadGameFile, TRUE);
1602 } else if (*appData.loadPositionFile != NULLCHAR) {
1603 (void) LoadPositionFromFile(appData.loadPositionFile,
1604 appData.loadPositionIndex,
1605 appData.loadPositionFile);
1606 /* [HGM] try to make self-starting even after FEN load */
1607 /* to allow automatic setup of fairy variants with wtm */
1608 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1609 gameMode = BeginningOfGame;
1610 setboardSpoiledMachineBlack = 1;
1612 /* [HGM] loadPos: make that every new game uses the setup */
1613 /* from file as long as we do not switch variant */
1614 if(!blackPlaysFirst) {
1615 startedFromPositionFile = TRUE;
1616 CopyBoard(filePosition, boards[0]);
1619 if (initialMode == AnalyzeMode) {
1620 if (appData.noChessProgram) {
1621 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1624 if (appData.icsActive) {
1625 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1629 } else if (initialMode == AnalyzeFile) {
1630 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1631 ShowThinkingEvent();
1633 AnalysisPeriodicEvent(1);
1634 } else if (initialMode == MachinePlaysWhite) {
1635 if (appData.noChessProgram) {
1636 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1640 if (appData.icsActive) {
1641 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1645 MachineWhiteEvent();
1646 } else if (initialMode == MachinePlaysBlack) {
1647 if (appData.noChessProgram) {
1648 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1652 if (appData.icsActive) {
1653 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1657 MachineBlackEvent();
1658 } else if (initialMode == TwoMachinesPlay) {
1659 if (appData.noChessProgram) {
1660 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1664 if (appData.icsActive) {
1665 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1670 } else if (initialMode == EditGame) {
1672 } else if (initialMode == EditPosition) {
1673 EditPositionEvent();
1674 } else if (initialMode == Training) {
1675 if (*appData.loadGameFile == NULLCHAR) {
1676 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1685 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1687 DisplayBook(current+1);
1689 MoveHistorySet( movelist, first, last, current, pvInfoList );
1691 EvalGraphSet( first, last, current, pvInfoList );
1693 MakeEngineOutputTitle();
1697 * Establish will establish a contact to a remote host.port.
1698 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1699 * used to talk to the host.
1700 * Returns 0 if okay, error code if not.
1707 if (*appData.icsCommPort != NULLCHAR) {
1708 /* Talk to the host through a serial comm port */
1709 return OpenCommPort(appData.icsCommPort, &icsPR);
1711 } else if (*appData.gateway != NULLCHAR) {
1712 if (*appData.remoteShell == NULLCHAR) {
1713 /* Use the rcmd protocol to run telnet program on a gateway host */
1714 snprintf(buf, sizeof(buf), "%s %s %s",
1715 appData.telnetProgram, appData.icsHost, appData.icsPort);
1716 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1719 /* Use the rsh program to run telnet program on a gateway host */
1720 if (*appData.remoteUser == NULLCHAR) {
1721 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1722 appData.gateway, appData.telnetProgram,
1723 appData.icsHost, appData.icsPort);
1725 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1726 appData.remoteShell, appData.gateway,
1727 appData.remoteUser, appData.telnetProgram,
1728 appData.icsHost, appData.icsPort);
1730 return StartChildProcess(buf, "", &icsPR);
1733 } else if (appData.useTelnet) {
1734 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1737 /* TCP socket interface differs somewhat between
1738 Unix and NT; handle details in the front end.
1740 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1745 EscapeExpand (char *p, char *q)
1746 { // [HGM] initstring: routine to shape up string arguments
1747 while(*p++ = *q++) if(p[-1] == '\\')
1749 case 'n': p[-1] = '\n'; break;
1750 case 'r': p[-1] = '\r'; break;
1751 case 't': p[-1] = '\t'; break;
1752 case '\\': p[-1] = '\\'; break;
1753 case 0: *p = 0; return;
1754 default: p[-1] = q[-1]; break;
1759 show_bytes (FILE *fp, char *buf, int count)
1762 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1763 fprintf(fp, "\\%03o", *buf & 0xff);
1772 /* Returns an errno value */
1774 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1776 char buf[8192], *p, *q, *buflim;
1777 int left, newcount, outcount;
1779 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1780 *appData.gateway != NULLCHAR) {
1781 if (appData.debugMode) {
1782 fprintf(debugFP, ">ICS: ");
1783 show_bytes(debugFP, message, count);
1784 fprintf(debugFP, "\n");
1786 return OutputToProcess(pr, message, count, outError);
1789 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1796 if (appData.debugMode) {
1797 fprintf(debugFP, ">ICS: ");
1798 show_bytes(debugFP, buf, newcount);
1799 fprintf(debugFP, "\n");
1801 outcount = OutputToProcess(pr, buf, newcount, outError);
1802 if (outcount < newcount) return -1; /* to be sure */
1809 } else if (((unsigned char) *p) == TN_IAC) {
1810 *q++ = (char) TN_IAC;
1817 if (appData.debugMode) {
1818 fprintf(debugFP, ">ICS: ");
1819 show_bytes(debugFP, buf, newcount);
1820 fprintf(debugFP, "\n");
1822 outcount = OutputToProcess(pr, buf, newcount, outError);
1823 if (outcount < newcount) return -1; /* to be sure */
1828 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1830 int outError, outCount;
1831 static int gotEof = 0;
1833 /* Pass data read from player on to ICS */
1836 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1837 if (outCount < count) {
1838 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1840 } else if (count < 0) {
1841 RemoveInputSource(isr);
1842 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1843 } else if (gotEof++ > 0) {
1844 RemoveInputSource(isr);
1845 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1851 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1852 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1853 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1854 SendToICS("date\n");
1855 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1858 /* added routine for printf style output to ics */
1860 ics_printf (char *format, ...)
1862 char buffer[MSG_SIZ];
1865 va_start(args, format);
1866 vsnprintf(buffer, sizeof(buffer), format, args);
1867 buffer[sizeof(buffer)-1] = '\0';
1875 int count, outCount, outError;
1877 if (icsPR == NoProc) return;
1880 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1881 if (outCount < count) {
1882 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1886 /* This is used for sending logon scripts to the ICS. Sending
1887 without a delay causes problems when using timestamp on ICC
1888 (at least on my machine). */
1890 SendToICSDelayed (char *s, long msdelay)
1892 int count, outCount, outError;
1894 if (icsPR == NoProc) return;
1897 if (appData.debugMode) {
1898 fprintf(debugFP, ">ICS: ");
1899 show_bytes(debugFP, s, count);
1900 fprintf(debugFP, "\n");
1902 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1904 if (outCount < count) {
1905 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1910 /* Remove all highlighting escape sequences in s
1911 Also deletes any suffix starting with '('
1914 StripHighlightAndTitle (char *s)
1916 static char retbuf[MSG_SIZ];
1919 while (*s != NULLCHAR) {
1920 while (*s == '\033') {
1921 while (*s != NULLCHAR && !isalpha(*s)) s++;
1922 if (*s != NULLCHAR) s++;
1924 while (*s != NULLCHAR && *s != '\033') {
1925 if (*s == '(' || *s == '[') {
1936 /* Remove all highlighting escape sequences in s */
1938 StripHighlight (char *s)
1940 static char retbuf[MSG_SIZ];
1943 while (*s != NULLCHAR) {
1944 while (*s == '\033') {
1945 while (*s != NULLCHAR && !isalpha(*s)) s++;
1946 if (*s != NULLCHAR) s++;
1948 while (*s != NULLCHAR && *s != '\033') {
1956 char *variantNames[] = VARIANT_NAMES;
1958 VariantName (VariantClass v)
1960 return variantNames[v];
1964 /* Identify a variant from the strings the chess servers use or the
1965 PGN Variant tag names we use. */
1967 StringToVariant (char *e)
1971 VariantClass v = VariantNormal;
1972 int i, found = FALSE;
1978 /* [HGM] skip over optional board-size prefixes */
1979 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1980 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1981 while( *e++ != '_');
1984 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1988 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1989 if (StrCaseStr(e, variantNames[i])) {
1990 v = (VariantClass) i;
1997 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1998 || StrCaseStr(e, "wild/fr")
1999 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2000 v = VariantFischeRandom;
2001 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2002 (i = 1, p = StrCaseStr(e, "w"))) {
2004 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2011 case 0: /* FICS only, actually */
2013 /* Castling legal even if K starts on d-file */
2014 v = VariantWildCastle;
2019 /* Castling illegal even if K & R happen to start in
2020 normal positions. */
2021 v = VariantNoCastle;
2034 /* Castling legal iff K & R start in normal positions */
2040 /* Special wilds for position setup; unclear what to do here */
2041 v = VariantLoadable;
2044 /* Bizarre ICC game */
2045 v = VariantTwoKings;
2048 v = VariantKriegspiel;
2054 v = VariantFischeRandom;
2057 v = VariantCrazyhouse;
2060 v = VariantBughouse;
2066 /* Not quite the same as FICS suicide! */
2067 v = VariantGiveaway;
2073 v = VariantShatranj;
2076 /* Temporary names for future ICC types. The name *will* change in
2077 the next xboard/WinBoard release after ICC defines it. */
2115 v = VariantCapablanca;
2118 v = VariantKnightmate;
2124 v = VariantCylinder;
2130 v = VariantCapaRandom;
2133 v = VariantBerolina;
2145 /* Found "wild" or "w" in the string but no number;
2146 must assume it's normal chess. */
2150 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2151 if( (len >= MSG_SIZ) && appData.debugMode )
2152 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2154 DisplayError(buf, 0);
2160 if (appData.debugMode) {
2161 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2162 e, wnum, VariantName(v));
2167 static int leftover_start = 0, leftover_len = 0;
2168 char star_match[STAR_MATCH_N][MSG_SIZ];
2170 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2171 advance *index beyond it, and set leftover_start to the new value of
2172 *index; else return FALSE. If pattern contains the character '*', it
2173 matches any sequence of characters not containing '\r', '\n', or the
2174 character following the '*' (if any), and the matched sequence(s) are
2175 copied into star_match.
2178 looking_at ( char *buf, int *index, char *pattern)
2180 char *bufp = &buf[*index], *patternp = pattern;
2182 char *matchp = star_match[0];
2185 if (*patternp == NULLCHAR) {
2186 *index = leftover_start = bufp - buf;
2190 if (*bufp == NULLCHAR) return FALSE;
2191 if (*patternp == '*') {
2192 if (*bufp == *(patternp + 1)) {
2194 matchp = star_match[++star_count];
2198 } else if (*bufp == '\n' || *bufp == '\r') {
2200 if (*patternp == NULLCHAR)
2205 *matchp++ = *bufp++;
2209 if (*patternp != *bufp) return FALSE;
2216 SendToPlayer (char *data, int length)
2218 int error, outCount;
2219 outCount = OutputToProcess(NoProc, data, length, &error);
2220 if (outCount < length) {
2221 DisplayFatalError(_("Error writing to display"), error, 1);
2226 PackHolding (char packed[], char *holding)
2236 switch (runlength) {
2247 sprintf(q, "%d", runlength);
2259 /* Telnet protocol requests from the front end */
2261 TelnetRequest (unsigned char ddww, unsigned char option)
2263 unsigned char msg[3];
2264 int outCount, outError;
2266 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2268 if (appData.debugMode) {
2269 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2285 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2294 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2297 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2302 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2304 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2311 if (!appData.icsActive) return;
2312 TelnetRequest(TN_DO, TN_ECHO);
2318 if (!appData.icsActive) return;
2319 TelnetRequest(TN_DONT, TN_ECHO);
2323 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2325 /* put the holdings sent to us by the server on the board holdings area */
2326 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2330 if(gameInfo.holdingsWidth < 2) return;
2331 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2332 return; // prevent overwriting by pre-board holdings
2334 if( (int)lowestPiece >= BlackPawn ) {
2337 holdingsStartRow = BOARD_HEIGHT-1;
2340 holdingsColumn = BOARD_WIDTH-1;
2341 countsColumn = BOARD_WIDTH-2;
2342 holdingsStartRow = 0;
2346 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2347 board[i][holdingsColumn] = EmptySquare;
2348 board[i][countsColumn] = (ChessSquare) 0;
2350 while( (p=*holdings++) != NULLCHAR ) {
2351 piece = CharToPiece( ToUpper(p) );
2352 if(piece == EmptySquare) continue;
2353 /*j = (int) piece - (int) WhitePawn;*/
2354 j = PieceToNumber(piece);
2355 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2356 if(j < 0) continue; /* should not happen */
2357 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2358 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2359 board[holdingsStartRow+j*direction][countsColumn]++;
2365 VariantSwitch (Board board, VariantClass newVariant)
2367 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2368 static Board oldBoard;
2370 startedFromPositionFile = FALSE;
2371 if(gameInfo.variant == newVariant) return;
2373 /* [HGM] This routine is called each time an assignment is made to
2374 * gameInfo.variant during a game, to make sure the board sizes
2375 * are set to match the new variant. If that means adding or deleting
2376 * holdings, we shift the playing board accordingly
2377 * This kludge is needed because in ICS observe mode, we get boards
2378 * of an ongoing game without knowing the variant, and learn about the
2379 * latter only later. This can be because of the move list we requested,
2380 * in which case the game history is refilled from the beginning anyway,
2381 * but also when receiving holdings of a crazyhouse game. In the latter
2382 * case we want to add those holdings to the already received position.
2386 if (appData.debugMode) {
2387 fprintf(debugFP, "Switch board from %s to %s\n",
2388 VariantName(gameInfo.variant), VariantName(newVariant));
2389 setbuf(debugFP, NULL);
2391 shuffleOpenings = 0; /* [HGM] shuffle */
2392 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2396 newWidth = 9; newHeight = 9;
2397 gameInfo.holdingsSize = 7;
2398 case VariantBughouse:
2399 case VariantCrazyhouse:
2400 newHoldingsWidth = 2; break;
2404 newHoldingsWidth = 2;
2405 gameInfo.holdingsSize = 8;
2408 case VariantCapablanca:
2409 case VariantCapaRandom:
2412 newHoldingsWidth = gameInfo.holdingsSize = 0;
2415 if(newWidth != gameInfo.boardWidth ||
2416 newHeight != gameInfo.boardHeight ||
2417 newHoldingsWidth != gameInfo.holdingsWidth ) {
2419 /* shift position to new playing area, if needed */
2420 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2421 for(i=0; i<BOARD_HEIGHT; i++)
2422 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2423 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2425 for(i=0; i<newHeight; i++) {
2426 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2427 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2429 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2430 for(i=0; i<BOARD_HEIGHT; i++)
2431 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2432 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2435 gameInfo.boardWidth = newWidth;
2436 gameInfo.boardHeight = newHeight;
2437 gameInfo.holdingsWidth = newHoldingsWidth;
2438 gameInfo.variant = newVariant;
2439 InitDrawingSizes(-2, 0);
2440 } else gameInfo.variant = newVariant;
2441 CopyBoard(oldBoard, board); // remember correctly formatted board
2442 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2443 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2446 static int loggedOn = FALSE;
2448 /*-- Game start info cache: --*/
2450 char gs_kind[MSG_SIZ];
2451 static char player1Name[128] = "";
2452 static char player2Name[128] = "";
2453 static char cont_seq[] = "\n\\ ";
2454 static int player1Rating = -1;
2455 static int player2Rating = -1;
2456 /*----------------------------*/
2458 ColorClass curColor = ColorNormal;
2459 int suppressKibitz = 0;
2462 Boolean soughtPending = FALSE;
2463 Boolean seekGraphUp;
2464 #define MAX_SEEK_ADS 200
2466 char *seekAdList[MAX_SEEK_ADS];
2467 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2468 float tcList[MAX_SEEK_ADS];
2469 char colorList[MAX_SEEK_ADS];
2470 int nrOfSeekAds = 0;
2471 int minRating = 1010, maxRating = 2800;
2472 int hMargin = 10, vMargin = 20, h, w;
2473 extern int squareSize, lineGap;
2478 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2479 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2480 if(r < minRating+100 && r >=0 ) r = minRating+100;
2481 if(r > maxRating) r = maxRating;
2482 if(tc < 1.) tc = 1.;
2483 if(tc > 95.) tc = 95.;
2484 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2485 y = ((double)r - minRating)/(maxRating - minRating)
2486 * (h-vMargin-squareSize/8-1) + vMargin;
2487 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2488 if(strstr(seekAdList[i], " u ")) color = 1;
2489 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2490 !strstr(seekAdList[i], "bullet") &&
2491 !strstr(seekAdList[i], "blitz") &&
2492 !strstr(seekAdList[i], "standard") ) color = 2;
2493 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2494 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2498 AddAd (char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2500 char buf[MSG_SIZ], *ext = "";
2501 VariantClass v = StringToVariant(type);
2502 if(strstr(type, "wild")) {
2503 ext = type + 4; // append wild number
2504 if(v == VariantFischeRandom) type = "chess960"; else
2505 if(v == VariantLoadable) type = "setup"; else
2506 type = VariantName(v);
2508 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2509 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2510 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2511 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2512 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2513 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2514 seekNrList[nrOfSeekAds] = nr;
2515 zList[nrOfSeekAds] = 0;
2516 seekAdList[nrOfSeekAds++] = StrSave(buf);
2517 if(plot) PlotSeekAd(nrOfSeekAds-1);
2522 EraseSeekDot (int i)
2524 int x = xList[i], y = yList[i], d=squareSize/4, k;
2525 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2526 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2527 // now replot every dot that overlapped
2528 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2529 int xx = xList[k], yy = yList[k];
2530 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2531 DrawSeekDot(xx, yy, colorList[k]);
2536 RemoveSeekAd (int nr)
2539 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2541 if(seekAdList[i]) free(seekAdList[i]);
2542 seekAdList[i] = seekAdList[--nrOfSeekAds];
2543 seekNrList[i] = seekNrList[nrOfSeekAds];
2544 ratingList[i] = ratingList[nrOfSeekAds];
2545 colorList[i] = colorList[nrOfSeekAds];
2546 tcList[i] = tcList[nrOfSeekAds];
2547 xList[i] = xList[nrOfSeekAds];
2548 yList[i] = yList[nrOfSeekAds];
2549 zList[i] = zList[nrOfSeekAds];
2550 seekAdList[nrOfSeekAds] = NULL;
2556 MatchSoughtLine (char *line)
2558 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2559 int nr, base, inc, u=0; char dummy;
2561 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2562 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2564 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2565 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2566 // match: compact and save the line
2567 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2577 if(!seekGraphUp) return FALSE;
2578 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2579 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2581 DrawSeekBackground(0, 0, w, h);
2582 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2583 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2584 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2585 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2587 DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2590 snprintf(buf, MSG_SIZ, "%d", i);
2591 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2594 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2595 for(i=1; i<100; i+=(i<10?1:5)) {
2596 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2597 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2598 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2600 snprintf(buf, MSG_SIZ, "%d", i);
2601 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2604 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2609 SeekGraphClick (ClickType click, int x, int y, int moving)
2611 static int lastDown = 0, displayed = 0, lastSecond;
2612 if(y < 0) return FALSE;
2613 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2614 if(click == Release || moving) return FALSE;
2616 soughtPending = TRUE;
2617 SendToICS(ics_prefix);
2618 SendToICS("sought\n"); // should this be "sought all"?
2619 } else { // issue challenge based on clicked ad
2620 int dist = 10000; int i, closest = 0, second = 0;
2621 for(i=0; i<nrOfSeekAds; i++) {
2622 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2623 if(d < dist) { dist = d; closest = i; }
2624 second += (d - zList[i] < 120); // count in-range ads
2625 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2629 second = (second > 1);
2630 if(displayed != closest || second != lastSecond) {
2631 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2632 lastSecond = second; displayed = closest;
2634 if(click == Press) {
2635 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2638 } // on press 'hit', only show info
2639 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2640 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2641 SendToICS(ics_prefix);
2643 return TRUE; // let incoming board of started game pop down the graph
2644 } else if(click == Release) { // release 'miss' is ignored
2645 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2646 if(moving == 2) { // right up-click
2647 nrOfSeekAds = 0; // refresh graph
2648 soughtPending = TRUE;
2649 SendToICS(ics_prefix);
2650 SendToICS("sought\n"); // should this be "sought all"?
2653 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2654 // press miss or release hit 'pop down' seek graph
2655 seekGraphUp = FALSE;
2656 DrawPosition(TRUE, NULL);
2662 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2664 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2665 #define STARTED_NONE 0
2666 #define STARTED_MOVES 1
2667 #define STARTED_BOARD 2
2668 #define STARTED_OBSERVE 3
2669 #define STARTED_HOLDINGS 4
2670 #define STARTED_CHATTER 5
2671 #define STARTED_COMMENT 6
2672 #define STARTED_MOVES_NOHIDE 7
2674 static int started = STARTED_NONE;
2675 static char parse[20000];
2676 static int parse_pos = 0;
2677 static char buf[BUF_SIZE + 1];
2678 static int firstTime = TRUE, intfSet = FALSE;
2679 static ColorClass prevColor = ColorNormal;
2680 static int savingComment = FALSE;
2681 static int cmatch = 0; // continuation sequence match
2688 int backup; /* [DM] For zippy color lines */
2690 char talker[MSG_SIZ]; // [HGM] chat
2693 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2695 if (appData.debugMode) {
2697 fprintf(debugFP, "<ICS: ");
2698 show_bytes(debugFP, data, count);
2699 fprintf(debugFP, "\n");
2703 if (appData.debugMode) { int f = forwardMostMove;
2704 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2705 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2706 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2709 /* If last read ended with a partial line that we couldn't parse,
2710 prepend it to the new read and try again. */
2711 if (leftover_len > 0) {
2712 for (i=0; i<leftover_len; i++)
2713 buf[i] = buf[leftover_start + i];
2716 /* copy new characters into the buffer */
2717 bp = buf + leftover_len;
2718 buf_len=leftover_len;
2719 for (i=0; i<count; i++)
2722 if (data[i] == '\r')
2725 // join lines split by ICS?
2726 if (!appData.noJoin)
2729 Joining just consists of finding matches against the
2730 continuation sequence, and discarding that sequence
2731 if found instead of copying it. So, until a match
2732 fails, there's nothing to do since it might be the
2733 complete sequence, and thus, something we don't want
2736 if (data[i] == cont_seq[cmatch])
2739 if (cmatch == strlen(cont_seq))
2741 cmatch = 0; // complete match. just reset the counter
2744 it's possible for the ICS to not include the space
2745 at the end of the last word, making our [correct]
2746 join operation fuse two separate words. the server
2747 does this when the space occurs at the width setting.
2749 if (!buf_len || buf[buf_len-1] != ' ')
2760 match failed, so we have to copy what matched before
2761 falling through and copying this character. In reality,
2762 this will only ever be just the newline character, but
2763 it doesn't hurt to be precise.
2765 strncpy(bp, cont_seq, cmatch);
2777 buf[buf_len] = NULLCHAR;
2778 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2783 while (i < buf_len) {
2784 /* Deal with part of the TELNET option negotiation
2785 protocol. We refuse to do anything beyond the
2786 defaults, except that we allow the WILL ECHO option,
2787 which ICS uses to turn off password echoing when we are
2788 directly connected to it. We reject this option
2789 if localLineEditing mode is on (always on in xboard)
2790 and we are talking to port 23, which might be a real
2791 telnet server that will try to keep WILL ECHO on permanently.
2793 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2794 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2795 unsigned char option;
2797 switch ((unsigned char) buf[++i]) {
2799 if (appData.debugMode)
2800 fprintf(debugFP, "\n<WILL ");
2801 switch (option = (unsigned char) buf[++i]) {
2803 if (appData.debugMode)
2804 fprintf(debugFP, "ECHO ");
2805 /* Reply only if this is a change, according
2806 to the protocol rules. */
2807 if (remoteEchoOption) break;
2808 if (appData.localLineEditing &&
2809 atoi(appData.icsPort) == TN_PORT) {
2810 TelnetRequest(TN_DONT, TN_ECHO);
2813 TelnetRequest(TN_DO, TN_ECHO);
2814 remoteEchoOption = TRUE;
2818 if (appData.debugMode)
2819 fprintf(debugFP, "%d ", option);
2820 /* Whatever this is, we don't want it. */
2821 TelnetRequest(TN_DONT, option);
2826 if (appData.debugMode)
2827 fprintf(debugFP, "\n<WONT ");
2828 switch (option = (unsigned char) buf[++i]) {
2830 if (appData.debugMode)
2831 fprintf(debugFP, "ECHO ");
2832 /* Reply only if this is a change, according
2833 to the protocol rules. */
2834 if (!remoteEchoOption) break;
2836 TelnetRequest(TN_DONT, TN_ECHO);
2837 remoteEchoOption = FALSE;
2840 if (appData.debugMode)
2841 fprintf(debugFP, "%d ", (unsigned char) option);
2842 /* Whatever this is, it must already be turned
2843 off, because we never agree to turn on
2844 anything non-default, so according to the
2845 protocol rules, we don't reply. */
2850 if (appData.debugMode)
2851 fprintf(debugFP, "\n<DO ");
2852 switch (option = (unsigned char) buf[++i]) {
2854 /* Whatever this is, we refuse to do it. */
2855 if (appData.debugMode)
2856 fprintf(debugFP, "%d ", option);
2857 TelnetRequest(TN_WONT, option);
2862 if (appData.debugMode)
2863 fprintf(debugFP, "\n<DONT ");
2864 switch (option = (unsigned char) buf[++i]) {
2866 if (appData.debugMode)
2867 fprintf(debugFP, "%d ", option);
2868 /* Whatever this is, we are already not doing
2869 it, because we never agree to do anything
2870 non-default, so according to the protocol
2871 rules, we don't reply. */
2876 if (appData.debugMode)
2877 fprintf(debugFP, "\n<IAC ");
2878 /* Doubled IAC; pass it through */
2882 if (appData.debugMode)
2883 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2884 /* Drop all other telnet commands on the floor */
2887 if (oldi > next_out)
2888 SendToPlayer(&buf[next_out], oldi - next_out);
2894 /* OK, this at least will *usually* work */
2895 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2899 if (loggedOn && !intfSet) {
2900 if (ics_type == ICS_ICC) {
2901 snprintf(str, MSG_SIZ,
2902 "/set-quietly interface %s\n/set-quietly style 12\n",
2904 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2905 strcat(str, "/set-2 51 1\n/set seek 1\n");
2906 } else if (ics_type == ICS_CHESSNET) {
2907 snprintf(str, MSG_SIZ, "/style 12\n");
2909 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2910 strcat(str, programVersion);
2911 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2912 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2913 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2915 strcat(str, "$iset nohighlight 1\n");
2917 strcat(str, "$iset lock 1\n$style 12\n");
2920 NotifyFrontendLogin();
2924 if (started == STARTED_COMMENT) {
2925 /* Accumulate characters in comment */
2926 parse[parse_pos++] = buf[i];
2927 if (buf[i] == '\n') {
2928 parse[parse_pos] = NULLCHAR;
2929 if(chattingPartner>=0) {
2931 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2932 OutputChatMessage(chattingPartner, mess);
2933 chattingPartner = -1;
2934 next_out = i+1; // [HGM] suppress printing in ICS window
2936 if(!suppressKibitz) // [HGM] kibitz
2937 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2938 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2939 int nrDigit = 0, nrAlph = 0, j;
2940 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2941 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2942 parse[parse_pos] = NULLCHAR;
2943 // try to be smart: if it does not look like search info, it should go to
2944 // ICS interaction window after all, not to engine-output window.
2945 for(j=0; j<parse_pos; j++) { // count letters and digits
2946 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2947 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2948 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2950 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2951 int depth=0; float score;
2952 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2953 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2954 pvInfoList[forwardMostMove-1].depth = depth;
2955 pvInfoList[forwardMostMove-1].score = 100*score;
2957 OutputKibitz(suppressKibitz, parse);
2960 if(gameMode == IcsObserving) // restore original ICS messages
2961 snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
2963 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2964 SendToPlayer(tmp, strlen(tmp));
2966 next_out = i+1; // [HGM] suppress printing in ICS window
2968 started = STARTED_NONE;
2970 /* Don't match patterns against characters in comment */
2975 if (started == STARTED_CHATTER) {
2976 if (buf[i] != '\n') {
2977 /* Don't match patterns against characters in chatter */
2981 started = STARTED_NONE;
2982 if(suppressKibitz) next_out = i+1;
2985 /* Kludge to deal with rcmd protocol */
2986 if (firstTime && looking_at(buf, &i, "\001*")) {
2987 DisplayFatalError(&buf[1], 0, 1);
2993 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2996 if (appData.debugMode)
2997 fprintf(debugFP, "ics_type %d\n", ics_type);
3000 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3001 ics_type = ICS_FICS;
3003 if (appData.debugMode)
3004 fprintf(debugFP, "ics_type %d\n", ics_type);
3007 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3008 ics_type = ICS_CHESSNET;
3010 if (appData.debugMode)
3011 fprintf(debugFP, "ics_type %d\n", ics_type);
3016 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3017 looking_at(buf, &i, "Logging you in as \"*\"") ||
3018 looking_at(buf, &i, "will be \"*\""))) {
3019 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3023 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3025 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3026 DisplayIcsInteractionTitle(buf);
3027 have_set_title = TRUE;
3030 /* skip finger notes */
3031 if (started == STARTED_NONE &&
3032 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3033 (buf[i] == '1' && buf[i+1] == '0')) &&
3034 buf[i+2] == ':' && buf[i+3] == ' ') {
3035 started = STARTED_CHATTER;
3041 // [HGM] seekgraph: recognize sought lines and end-of-sought message
3042 if(appData.seekGraph) {
3043 if(soughtPending && MatchSoughtLine(buf+i)) {
3044 i = strstr(buf+i, "rated") - buf;
3045 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3046 next_out = leftover_start = i;
3047 started = STARTED_CHATTER;
3048 suppressKibitz = TRUE;
3051 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3052 && looking_at(buf, &i, "* ads displayed")) {
3053 soughtPending = FALSE;
3058 if(appData.autoRefresh) {
3059 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3060 int s = (ics_type == ICS_ICC); // ICC format differs
3062 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3063 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3064 looking_at(buf, &i, "*% "); // eat prompt
3065 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3066 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3067 next_out = i; // suppress
3070 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3071 char *p = star_match[0];
3073 if(seekGraphUp) RemoveSeekAd(atoi(p));
3074 while(*p && *p++ != ' '); // next
3076 looking_at(buf, &i, "*% "); // eat prompt
3077 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3084 /* skip formula vars */
3085 if (started == STARTED_NONE &&
3086 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3087 started = STARTED_CHATTER;
3092 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3093 if (appData.autoKibitz && started == STARTED_NONE &&
3094 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3095 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3096 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3097 looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3098 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3099 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3100 suppressKibitz = TRUE;
3101 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3103 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3104 && (gameMode == IcsPlayingWhite)) ||
3105 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3106 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3107 started = STARTED_CHATTER; // own kibitz we simply discard
3109 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3110 parse_pos = 0; parse[0] = NULLCHAR;
3111 savingComment = TRUE;
3112 suppressKibitz = gameMode != IcsObserving ? 2 :
3113 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3117 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3118 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3119 && atoi(star_match[0])) {
3120 // suppress the acknowledgements of our own autoKibitz
3122 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3123 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3124 SendToPlayer(star_match[0], strlen(star_match[0]));
3125 if(looking_at(buf, &i, "*% ")) // eat prompt
3126 suppressKibitz = FALSE;
3130 } // [HGM] kibitz: end of patch
3132 if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3134 // [HGM] chat: intercept tells by users for which we have an open chat window
3136 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3137 looking_at(buf, &i, "* whispers:") ||
3138 looking_at(buf, &i, "* kibitzes:") ||
3139 looking_at(buf, &i, "* shouts:") ||
3140 looking_at(buf, &i, "* c-shouts:") ||
3141 looking_at(buf, &i, "--> * ") ||
3142 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3143 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3144 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3145 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3147 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3148 chattingPartner = -1;
3150 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3151 for(p=0; p<MAX_CHAT; p++) {
3152 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3153 talker[0] = '['; strcat(talker, "] ");
3154 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3155 chattingPartner = p; break;
3158 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3159 for(p=0; p<MAX_CHAT; p++) {
3160 if(!strcmp("kibitzes", chatPartner[p])) {
3161 talker[0] = '['; strcat(talker, "] ");
3162 chattingPartner = p; break;
3165 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3166 for(p=0; p<MAX_CHAT; p++) {
3167 if(!strcmp("whispers", chatPartner[p])) {
3168 talker[0] = '['; strcat(talker, "] ");
3169 chattingPartner = p; break;
3172 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3173 if(buf[i-8] == '-' && buf[i-3] == 't')
3174 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3175 if(!strcmp("c-shouts", chatPartner[p])) {
3176 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3177 chattingPartner = p; break;
3180 if(chattingPartner < 0)
3181 for(p=0; p<MAX_CHAT; p++) {
3182 if(!strcmp("shouts", chatPartner[p])) {
3183 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3184 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3185 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3186 chattingPartner = p; break;
3190 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3191 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3192 talker[0] = 0; Colorize(ColorTell, FALSE);
3193 chattingPartner = p; break;
3195 if(chattingPartner<0) i = oldi; else {
3196 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3197 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3198 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3199 started = STARTED_COMMENT;
3200 parse_pos = 0; parse[0] = NULLCHAR;
3201 savingComment = 3 + chattingPartner; // counts as TRUE
3202 suppressKibitz = TRUE;
3205 } // [HGM] chat: end of patch
3208 if (appData.zippyTalk || appData.zippyPlay) {
3209 /* [DM] Backup address for color zippy lines */
3211 if (loggedOn == TRUE)
3212 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3213 (appData.zippyPlay && ZippyMatch(buf, &backup)));
3215 } // [DM] 'else { ' deleted
3217 /* Regular tells and says */
3218 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3219 looking_at(buf, &i, "* (your partner) tells you: ") ||
3220 looking_at(buf, &i, "* says: ") ||
3221 /* Don't color "message" or "messages" output */
3222 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3223 looking_at(buf, &i, "*. * at *:*: ") ||
3224 looking_at(buf, &i, "--* (*:*): ") ||
3225 /* Message notifications (same color as tells) */
3226 looking_at(buf, &i, "* has left a message ") ||
3227 looking_at(buf, &i, "* just sent you a message:\n") ||
3228 /* Whispers and kibitzes */
3229 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3230 looking_at(buf, &i, "* kibitzes: ") ||
3232 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3234 if (tkind == 1 && strchr(star_match[0], ':')) {
3235 /* Avoid "tells you:" spoofs in channels */
3238 if (star_match[0][0] == NULLCHAR ||
3239 strchr(star_match[0], ' ') ||
3240 (tkind == 3 && strchr(star_match[1], ' '))) {
3241 /* Reject bogus matches */
3244 if (appData.colorize) {
3245 if (oldi > next_out) {
3246 SendToPlayer(&buf[next_out], oldi - next_out);
3251 Colorize(ColorTell, FALSE);
3252 curColor = ColorTell;
3255 Colorize(ColorKibitz, FALSE);
3256 curColor = ColorKibitz;
3259 p = strrchr(star_match[1], '(');
3266 Colorize(ColorChannel1, FALSE);
3267 curColor = ColorChannel1;
3269 Colorize(ColorChannel, FALSE);
3270 curColor = ColorChannel;
3274 curColor = ColorNormal;
3278 if (started == STARTED_NONE && appData.autoComment &&
3279 (gameMode == IcsObserving ||
3280 gameMode == IcsPlayingWhite ||
3281 gameMode == IcsPlayingBlack)) {
3282 parse_pos = i - oldi;
3283 memcpy(parse, &buf[oldi], parse_pos);
3284 parse[parse_pos] = NULLCHAR;
3285 started = STARTED_COMMENT;
3286 savingComment = TRUE;
3288 started = STARTED_CHATTER;
3289 savingComment = FALSE;
3296 if (looking_at(buf, &i, "* s-shouts: ") ||
3297 looking_at(buf, &i, "* c-shouts: ")) {
3298 if (appData.colorize) {
3299 if (oldi > next_out) {
3300 SendToPlayer(&buf[next_out], oldi - next_out);
3303 Colorize(ColorSShout, FALSE);
3304 curColor = ColorSShout;
3307 started = STARTED_CHATTER;
3311 if (looking_at(buf, &i, "--->")) {
3316 if (looking_at(buf, &i, "* shouts: ") ||
3317 looking_at(buf, &i, "--> ")) {
3318 if (appData.colorize) {
3319 if (oldi > next_out) {
3320 SendToPlayer(&buf[next_out], oldi - next_out);
3323 Colorize(ColorShout, FALSE);
3324 curColor = ColorShout;
3327 started = STARTED_CHATTER;
3331 if (looking_at( buf, &i, "Challenge:")) {
3332 if (appData.colorize) {
3333 if (oldi > next_out) {
3334 SendToPlayer(&buf[next_out], oldi - next_out);
3337 Colorize(ColorChallenge, FALSE);
3338 curColor = ColorChallenge;
3344 if (looking_at(buf, &i, "* offers you") ||
3345 looking_at(buf, &i, "* offers to be") ||
3346 looking_at(buf, &i, "* would like to") ||
3347 looking_at(buf, &i, "* requests to") ||
3348 looking_at(buf, &i, "Your opponent offers") ||
3349 looking_at(buf, &i, "Your opponent requests")) {
3351 if (appData.colorize) {
3352 if (oldi > next_out) {
3353 SendToPlayer(&buf[next_out], oldi - next_out);
3356 Colorize(ColorRequest, FALSE);
3357 curColor = ColorRequest;
3362 if (looking_at(buf, &i, "* (*) seeking")) {
3363 if (appData.colorize) {
3364 if (oldi > next_out) {
3365 SendToPlayer(&buf[next_out], oldi - next_out);
3368 Colorize(ColorSeek, FALSE);
3369 curColor = ColorSeek;
3374 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3376 if (looking_at(buf, &i, "\\ ")) {
3377 if (prevColor != ColorNormal) {
3378 if (oldi > next_out) {
3379 SendToPlayer(&buf[next_out], oldi - next_out);
3382 Colorize(prevColor, TRUE);
3383 curColor = prevColor;
3385 if (savingComment) {
3386 parse_pos = i - oldi;
3387 memcpy(parse, &buf[oldi], parse_pos);
3388 parse[parse_pos] = NULLCHAR;
3389 started = STARTED_COMMENT;
3390 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3391 chattingPartner = savingComment - 3; // kludge to remember the box
3393 started = STARTED_CHATTER;
3398 if (looking_at(buf, &i, "Black Strength :") ||
3399 looking_at(buf, &i, "<<< style 10 board >>>") ||
3400 looking_at(buf, &i, "<10>") ||
3401 looking_at(buf, &i, "#@#")) {
3402 /* Wrong board style */
3404 SendToICS(ics_prefix);
3405 SendToICS("set style 12\n");
3406 SendToICS(ics_prefix);
3407 SendToICS("refresh\n");
3411 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3413 have_sent_ICS_logon = 1;
3417 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3418 (looking_at(buf, &i, "\n<12> ") ||
3419 looking_at(buf, &i, "<12> "))) {
3421 if (oldi > next_out) {
3422 SendToPlayer(&buf[next_out], oldi - next_out);
3425 started = STARTED_BOARD;
3430 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3431 looking_at(buf, &i, "<b1> ")) {
3432 if (oldi > next_out) {
3433 SendToPlayer(&buf[next_out], oldi - next_out);
3436 started = STARTED_HOLDINGS;
3441 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3443 /* Header for a move list -- first line */
3445 switch (ics_getting_history) {
3449 case BeginningOfGame:
3450 /* User typed "moves" or "oldmoves" while we
3451 were idle. Pretend we asked for these
3452 moves and soak them up so user can step
3453 through them and/or save them.
3456 gameMode = IcsObserving;
3459 ics_getting_history = H_GOT_UNREQ_HEADER;
3461 case EditGame: /*?*/
3462 case EditPosition: /*?*/
3463 /* Should above feature work in these modes too? */
3464 /* For now it doesn't */
3465 ics_getting_history = H_GOT_UNWANTED_HEADER;
3468 ics_getting_history = H_GOT_UNWANTED_HEADER;
3473 /* Is this the right one? */
3474 if (gameInfo.white && gameInfo.black &&
3475 strcmp(gameInfo.white, star_match[0]) == 0 &&
3476 strcmp(gameInfo.black, star_match[2]) == 0) {
3478 ics_getting_history = H_GOT_REQ_HEADER;
3481 case H_GOT_REQ_HEADER:
3482 case H_GOT_UNREQ_HEADER:
3483 case H_GOT_UNWANTED_HEADER:
3484 case H_GETTING_MOVES:
3485 /* Should not happen */
3486 DisplayError(_("Error gathering move list: two headers"), 0);
3487 ics_getting_history = H_FALSE;
3491 /* Save player ratings into gameInfo if needed */
3492 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3493 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3494 (gameInfo.whiteRating == -1 ||
3495 gameInfo.blackRating == -1)) {
3497 gameInfo.whiteRating = string_to_rating(star_match[1]);
3498 gameInfo.blackRating = string_to_rating(star_match[3]);
3499 if (appData.debugMode)
3500 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3501 gameInfo.whiteRating, gameInfo.blackRating);
3506 if (looking_at(buf, &i,
3507 "* * match, initial time: * minute*, increment: * second")) {
3508 /* Header for a move list -- second line */
3509 /* Initial board will follow if this is a wild game */
3510 if (gameInfo.event != NULL) free(gameInfo.event);
3511 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3512 gameInfo.event = StrSave(str);
3513 /* [HGM] we switched variant. Translate boards if needed. */
3514 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3518 if (looking_at(buf, &i, "Move ")) {
3519 /* Beginning of a move list */
3520 switch (ics_getting_history) {
3522 /* Normally should not happen */
3523 /* Maybe user hit reset while we were parsing */
3526 /* Happens if we are ignoring a move list that is not
3527 * the one we just requested. Common if the user
3528 * tries to observe two games without turning off
3531 case H_GETTING_MOVES:
3532 /* Should not happen */
3533 DisplayError(_("Error gathering move list: nested"), 0);
3534 ics_getting_history = H_FALSE;
3536 case H_GOT_REQ_HEADER:
3537 ics_getting_history = H_GETTING_MOVES;
3538 started = STARTED_MOVES;
3540 if (oldi > next_out) {
3541 SendToPlayer(&buf[next_out], oldi - next_out);
3544 case H_GOT_UNREQ_HEADER:
3545 ics_getting_history = H_GETTING_MOVES;
3546 started = STARTED_MOVES_NOHIDE;
3549 case H_GOT_UNWANTED_HEADER:
3550 ics_getting_history = H_FALSE;
3556 if (looking_at(buf, &i, "% ") ||
3557 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3558 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3559 if(soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3560 soughtPending = FALSE;
3564 if(suppressKibitz) next_out = i;
3565 savingComment = FALSE;
3569 case STARTED_MOVES_NOHIDE:
3570 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3571 parse[parse_pos + i - oldi] = NULLCHAR;
3572 ParseGameHistory(parse);
3574 if (appData.zippyPlay && first.initDone) {
3575 FeedMovesToProgram(&first, forwardMostMove);
3576 if (gameMode == IcsPlayingWhite) {
3577 if (WhiteOnMove(forwardMostMove)) {
3578 if (first.sendTime) {
3579 if (first.useColors) {
3580 SendToProgram("black\n", &first);
3582 SendTimeRemaining(&first, TRUE);
3584 if (first.useColors) {
3585 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3587 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3588 first.maybeThinking = TRUE;
3590 if (first.usePlayother) {
3591 if (first.sendTime) {
3592 SendTimeRemaining(&first, TRUE);
3594 SendToProgram("playother\n", &first);
3600 } else if (gameMode == IcsPlayingBlack) {
3601 if (!WhiteOnMove(forwardMostMove)) {
3602 if (first.sendTime) {
3603 if (first.useColors) {
3604 SendToProgram("white\n", &first);
3606 SendTimeRemaining(&first, FALSE);
3608 if (first.useColors) {
3609 SendToProgram("black\n", &first);
3611 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3612 first.maybeThinking = TRUE;
3614 if (first.usePlayother) {
3615 if (first.sendTime) {
3616 SendTimeRemaining(&first, FALSE);
3618 SendToProgram("playother\n", &first);
3627 if (gameMode == IcsObserving && ics_gamenum == -1) {
3628 /* Moves came from oldmoves or moves command
3629 while we weren't doing anything else.
3631 currentMove = forwardMostMove;
3632 ClearHighlights();/*!!could figure this out*/
3633 flipView = appData.flipView;
3634 DrawPosition(TRUE, boards[currentMove]);
3635 DisplayBothClocks();
3636 snprintf(str, MSG_SIZ, "%s %s %s",
3637 gameInfo.white, _("vs."), gameInfo.black);
3641 /* Moves were history of an active game */
3642 if (gameInfo.resultDetails != NULL) {
3643 free(gameInfo.resultDetails);
3644 gameInfo.resultDetails = NULL;
3647 HistorySet(parseList, backwardMostMove,
3648 forwardMostMove, currentMove-1);
3649 DisplayMove(currentMove - 1);
3650 if (started == STARTED_MOVES) next_out = i;
3651 started = STARTED_NONE;
3652 ics_getting_history = H_FALSE;
3655 case STARTED_OBSERVE:
3656 started = STARTED_NONE;
3657 SendToICS(ics_prefix);
3658 SendToICS("refresh\n");
3664 if(bookHit) { // [HGM] book: simulate book reply
3665 static char bookMove[MSG_SIZ]; // a bit generous?
3667 programStats.nodes = programStats.depth = programStats.time =
3668 programStats.score = programStats.got_only_move = 0;
3669 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3671 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3672 strcat(bookMove, bookHit);
3673 HandleMachineMove(bookMove, &first);
3678 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3679 started == STARTED_HOLDINGS ||
3680 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3681 /* Accumulate characters in move list or board */
3682 parse[parse_pos++] = buf[i];
3685 /* Start of game messages. Mostly we detect start of game
3686 when the first board image arrives. On some versions
3687 of the ICS, though, we need to do a "refresh" after starting
3688 to observe in order to get the current board right away. */
3689 if (looking_at(buf, &i, "Adding game * to observation list")) {
3690 started = STARTED_OBSERVE;
3694 /* Handle auto-observe */
3695 if (appData.autoObserve &&
3696 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3697 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3699 /* Choose the player that was highlighted, if any. */
3700 if (star_match[0][0] == '\033' ||
3701 star_match[1][0] != '\033') {
3702 player = star_match[0];
3704 player = star_match[2];
3706 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3707 ics_prefix, StripHighlightAndTitle(player));
3710 /* Save ratings from notify string */
3711 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3712 player1Rating = string_to_rating(star_match[1]);
3713 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3714 player2Rating = string_to_rating(star_match[3]);
3716 if (appData.debugMode)
3718 "Ratings from 'Game notification:' %s %d, %s %d\n",
3719 player1Name, player1Rating,
3720 player2Name, player2Rating);
3725 /* Deal with automatic examine mode after a game,
3726 and with IcsObserving -> IcsExamining transition */
3727 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3728 looking_at(buf, &i, "has made you an examiner of game *")) {
3730 int gamenum = atoi(star_match[0]);
3731 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3732 gamenum == ics_gamenum) {
3733 /* We were already playing or observing this game;
3734 no need to refetch history */
3735 gameMode = IcsExamining;
3737 pauseExamForwardMostMove = forwardMostMove;
3738 } else if (currentMove < forwardMostMove) {
3739 ForwardInner(forwardMostMove);
3742 /* I don't think this case really can happen */
3743 SendToICS(ics_prefix);
3744 SendToICS("refresh\n");
3749 /* Error messages */
3750 // if (ics_user_moved) {
3751 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3752 if (looking_at(buf, &i, "Illegal move") ||
3753 looking_at(buf, &i, "Not a legal move") ||
3754 looking_at(buf, &i, "Your king is in check") ||
3755 looking_at(buf, &i, "It isn't your turn") ||
3756 looking_at(buf, &i, "It is not your move")) {
3758 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3759 currentMove = forwardMostMove-1;
3760 DisplayMove(currentMove - 1); /* before DMError */
3761 DrawPosition(FALSE, boards[currentMove]);
3762 SwitchClocks(forwardMostMove-1); // [HGM] race
3763 DisplayBothClocks();
3765 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3771 if (looking_at(buf, &i, "still have time") ||
3772 looking_at(buf, &i, "not out of time") ||
3773 looking_at(buf, &i, "either player is out of time") ||
3774 looking_at(buf, &i, "has timeseal; checking")) {
3775 /* We must have called his flag a little too soon */
3776 whiteFlag = blackFlag = FALSE;
3780 if (looking_at(buf, &i, "added * seconds to") ||
3781 looking_at(buf, &i, "seconds were added to")) {
3782 /* Update the clocks */
3783 SendToICS(ics_prefix);
3784 SendToICS("refresh\n");
3788 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3789 ics_clock_paused = TRUE;
3794 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3795 ics_clock_paused = FALSE;
3800 /* Grab player ratings from the Creating: message.
3801 Note we have to check for the special case when
3802 the ICS inserts things like [white] or [black]. */
3803 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3804 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3806 0 player 1 name (not necessarily white)
3808 2 empty, white, or black (IGNORED)
3809 3 player 2 name (not necessarily black)
3812 The names/ratings are sorted out when the game
3813 actually starts (below).
3815 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3816 player1Rating = string_to_rating(star_match[1]);
3817 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3818 player2Rating = string_to_rating(star_match[4]);
3820 if (appData.debugMode)
3822 "Ratings from 'Creating:' %s %d, %s %d\n",
3823 player1Name, player1Rating,
3824 player2Name, player2Rating);
3829 /* Improved generic start/end-of-game messages */
3830 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3831 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3832 /* If tkind == 0: */
3833 /* star_match[0] is the game number */
3834 /* [1] is the white player's name */
3835 /* [2] is the black player's name */
3836 /* For end-of-game: */
3837 /* [3] is the reason for the game end */
3838 /* [4] is a PGN end game-token, preceded by " " */
3839 /* For start-of-game: */
3840 /* [3] begins with "Creating" or "Continuing" */
3841 /* [4] is " *" or empty (don't care). */
3842 int gamenum = atoi(star_match[0]);
3843 char *whitename, *blackname, *why, *endtoken;
3844 ChessMove endtype = EndOfFile;
3847 whitename = star_match[1];
3848 blackname = star_match[2];
3849 why = star_match[3];
3850 endtoken = star_match[4];
3852 whitename = star_match[1];
3853 blackname = star_match[3];
3854 why = star_match[5];
3855 endtoken = star_match[6];
3858 /* Game start messages */
3859 if (strncmp(why, "Creating ", 9) == 0 ||
3860 strncmp(why, "Continuing ", 11) == 0) {
3861 gs_gamenum = gamenum;
3862 safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));