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"
132 #include "evalgraph.h"
136 # define _(s) gettext (s)
137 # define N_(s) gettext_noop (s)
138 # define T_(s) gettext(s)
151 int establish P((void));
152 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
153 char *buf, int count, int error));
154 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
155 char *buf, int count, int error));
156 void SendToICS P((char *s));
157 void SendToICSDelayed P((char *s, long msdelay));
158 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
159 void HandleMachineMove P((char *message, ChessProgramState *cps));
160 int AutoPlayOneMove P((void));
161 int LoadGameOneMove P((ChessMove readAhead));
162 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
163 int LoadPositionFromFile P((char *filename, int n, char *title));
164 int SavePositionToFile P((char *filename));
165 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
166 void ShowMove P((int fromX, int fromY, int toX, int toY));
167 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
168 /*char*/int promoChar));
169 void BackwardInner P((int target));
170 void ForwardInner P((int target));
171 int Adjudicate P((ChessProgramState *cps));
172 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
173 void EditPositionDone P((Boolean fakeRights));
174 void PrintOpponents P((FILE *fp));
175 void PrintPosition P((FILE *fp, int move));
176 void StartChessProgram P((ChessProgramState *cps));
177 void SendToProgram P((char *message, ChessProgramState *cps));
178 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
179 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
180 char *buf, int count, int error));
181 void SendTimeControl P((ChessProgramState *cps,
182 int mps, long tc, int inc, int sd, int st));
183 char *TimeControlTagValue P((void));
184 void Attention P((ChessProgramState *cps));
185 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
186 int ResurrectChessProgram P((void));
187 void DisplayComment P((int moveNumber, char *text));
188 void DisplayMove P((int moveNumber));
190 void ParseGameHistory P((char *game));
191 void ParseBoard12 P((char *string));
192 void KeepAlive P((void));
193 void StartClocks P((void));
194 void SwitchClocks P((int nr));
195 void StopClocks P((void));
196 void ResetClocks P((void));
197 char *PGNDate P((void));
198 void SetGameInfo P((void));
199 int RegisterMove P((void));
200 void MakeRegisteredMove P((void));
201 void TruncateGame P((void));
202 int looking_at P((char *, int *, char *));
203 void CopyPlayerNameIntoFileName P((char **, char *));
204 char *SavePart P((char *));
205 int SaveGameOldStyle P((FILE *));
206 int SaveGamePGN P((FILE *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
220 void NextMatchGame P((void));
221 int NextTourneyGame P((int nr, int *swap));
222 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
223 FILE *WriteTourneyFile P((char *results, FILE *f));
224 void DisplayTwoMachinesTitle P(());
225 static void ExcludeClick P((int index));
226 void ToggleSecond P((void));
229 extern void ConsoleCreate();
232 ChessProgramState *WhitePlayer();
233 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
234 int VerifyDisplayMode P(());
236 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
237 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
238 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
239 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
240 void ics_update_width P((int new_width));
241 extern char installDir[MSG_SIZ];
242 VariantClass startVariant; /* [HGM] nicks: initial variant */
245 extern int tinyLayout, smallLayout;
246 ChessProgramStats programStats;
247 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
249 static int exiting = 0; /* [HGM] moved to top */
250 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
251 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
252 Board partnerBoard; /* [HGM] bughouse: for peeking at partner game */
253 int partnerHighlight[2];
254 Boolean partnerBoardValid = 0;
255 char partnerStatus[MSG_SIZ];
257 Boolean originalFlip;
258 Boolean twoBoards = 0;
259 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
260 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
261 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
262 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
263 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
264 int opponentKibitzes;
265 int lastSavedGame; /* [HGM] save: ID of game */
266 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
267 extern int chatCount;
269 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
270 char lastMsg[MSG_SIZ];
271 ChessSquare pieceSweep = EmptySquare;
272 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
273 int promoDefaultAltered;
274 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
276 /* States for ics_getting_history */
278 #define H_REQUESTED 1
279 #define H_GOT_REQ_HEADER 2
280 #define H_GOT_UNREQ_HEADER 3
281 #define H_GETTING_MOVES 4
282 #define H_GOT_UNWANTED_HEADER 5
284 /* whosays values for GameEnds */
293 /* Maximum number of games in a cmail message */
294 #define CMAIL_MAX_GAMES 20
296 /* Different types of move when calling RegisterMove */
298 #define CMAIL_RESIGN 1
300 #define CMAIL_ACCEPT 3
302 /* Different types of result to remember for each game */
303 #define CMAIL_NOT_RESULT 0
304 #define CMAIL_OLD_RESULT 1
305 #define CMAIL_NEW_RESULT 2
307 /* Telnet protocol constants */
318 safeStrCpy (char *dst, const char *src, size_t count)
321 assert( dst != NULL );
322 assert( src != NULL );
325 for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
326 if( i == count && dst[count-1] != NULLCHAR)
328 dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
329 if(appData.debugMode)
330 fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
336 /* Some compiler can't cast u64 to double
337 * This function do the job for us:
339 * We use the highest bit for cast, this only
340 * works if the highest bit is not
341 * in use (This should not happen)
343 * We used this for all compiler
346 u64ToDouble (u64 value)
349 u64 tmp = value & u64Const(0x7fffffffffffffff);
350 r = (double)(s64)tmp;
351 if (value & u64Const(0x8000000000000000))
352 r += 9.2233720368547758080e18; /* 2^63 */
356 /* Fake up flags for now, as we aren't keeping track of castling
357 availability yet. [HGM] Change of logic: the flag now only
358 indicates the type of castlings allowed by the rule of the game.
359 The actual rights themselves are maintained in the array
360 castlingRights, as part of the game history, and are not probed
366 int flags = F_ALL_CASTLE_OK;
367 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
368 switch (gameInfo.variant) {
370 flags &= ~F_ALL_CASTLE_OK;
371 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
372 flags |= F_IGNORE_CHECK;
374 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
377 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
379 case VariantKriegspiel:
380 flags |= F_KRIEGSPIEL_CAPTURE;
382 case VariantCapaRandom:
383 case VariantFischeRandom:
384 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
385 case VariantNoCastle:
386 case VariantShatranj:
390 flags &= ~F_ALL_CASTLE_OK;
398 FILE *gameFileFP, *debugFP, *serverFP;
399 char *currentDebugFile; // [HGM] debug split: to remember name
402 [AS] Note: sometimes, the sscanf() function is used to parse the input
403 into a fixed-size buffer. Because of this, we must be prepared to
404 receive strings as long as the size of the input buffer, which is currently
405 set to 4K for Windows and 8K for the rest.
406 So, we must either allocate sufficiently large buffers here, or
407 reduce the size of the input buffer in the input reading part.
410 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
411 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
412 char thinkOutput1[MSG_SIZ*10];
414 ChessProgramState first, second, pairing;
416 /* premove variables */
419 int premoveFromX = 0;
420 int premoveFromY = 0;
421 int premovePromoChar = 0;
423 Boolean alarmSounded;
424 /* end premove variables */
426 char *ics_prefix = "$";
427 enum ICS_TYPE ics_type = ICS_GENERIC;
429 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
430 int pauseExamForwardMostMove = 0;
431 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
432 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
433 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
434 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
435 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
436 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
437 int whiteFlag = FALSE, blackFlag = FALSE;
438 int userOfferedDraw = FALSE;
439 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
440 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
441 int cmailMoveType[CMAIL_MAX_GAMES];
442 long ics_clock_paused = 0;
443 ProcRef icsPR = NoProc, cmailPR = NoProc;
444 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
445 GameMode gameMode = BeginningOfGame;
446 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
447 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
448 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
449 int hiddenThinkOutputState = 0; /* [AS] */
450 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
451 int adjudicateLossPlies = 6;
452 char white_holding[64], black_holding[64];
453 TimeMark lastNodeCountTime;
454 long lastNodeCount=0;
455 int shiftKey, controlKey; // [HGM] set by mouse handler
457 int have_sent_ICS_logon = 0;
459 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
460 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
461 Boolean adjustedClock;
462 long timeControl_2; /* [AS] Allow separate time controls */
463 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
464 long timeRemaining[2][MAX_MOVES];
465 int matchGame = 0, nextGame = 0, roundNr = 0;
466 Boolean waitingForGame = FALSE;
467 TimeMark programStartTime, pauseStart;
468 char ics_handle[MSG_SIZ];
469 int have_set_title = 0;
471 /* animateTraining preserves the state of appData.animate
472 * when Training mode is activated. This allows the
473 * response to be animated when appData.animate == TRUE and
474 * appData.animateDragging == TRUE.
476 Boolean animateTraining;
482 Board boards[MAX_MOVES];
483 /* [HGM] Following 7 needed for accurate legality tests: */
484 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
485 signed char initialRights[BOARD_FILES];
486 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
487 int initialRulePlies, FENrulePlies;
488 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
490 Boolean shuffleOpenings;
491 int mute; // mute all sounds
493 // [HGM] vari: next 12 to save and restore variations
494 #define MAX_VARIATIONS 10
495 int framePtr = MAX_MOVES-1; // points to free stack entry
497 int savedFirst[MAX_VARIATIONS];
498 int savedLast[MAX_VARIATIONS];
499 int savedFramePtr[MAX_VARIATIONS];
500 char *savedDetails[MAX_VARIATIONS];
501 ChessMove savedResult[MAX_VARIATIONS];
503 void PushTail P((int firstMove, int lastMove));
504 Boolean PopTail P((Boolean annotate));
505 void PushInner P((int firstMove, int lastMove));
506 void PopInner P((Boolean annotate));
507 void CleanupTail P((void));
509 ChessSquare FIDEArray[2][BOARD_FILES] = {
510 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
511 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
512 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
513 BlackKing, BlackBishop, BlackKnight, BlackRook }
516 ChessSquare twoKingsArray[2][BOARD_FILES] = {
517 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
518 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
519 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
520 BlackKing, BlackKing, BlackKnight, BlackRook }
523 ChessSquare KnightmateArray[2][BOARD_FILES] = {
524 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
525 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
526 { BlackRook, BlackMan, BlackBishop, BlackQueen,
527 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
530 ChessSquare SpartanArray[2][BOARD_FILES] = {
531 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
532 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
533 { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
534 BlackDragon, BlackKing, BlackAngel, BlackAlfil }
537 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
538 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
539 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
540 { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
541 BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
544 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
545 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
546 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
547 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
548 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
551 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
552 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
553 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
554 { BlackRook, BlackKnight, BlackMan, BlackFerz,
555 BlackKing, BlackMan, BlackKnight, BlackRook }
559 #if (BOARD_FILES>=10)
560 ChessSquare ShogiArray[2][BOARD_FILES] = {
561 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
562 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
563 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
564 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
567 ChessSquare XiangqiArray[2][BOARD_FILES] = {
568 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
569 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
570 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
571 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
574 ChessSquare CapablancaArray[2][BOARD_FILES] = {
575 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
576 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
577 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
578 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
581 ChessSquare GreatArray[2][BOARD_FILES] = {
582 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
583 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
584 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
585 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
588 ChessSquare JanusArray[2][BOARD_FILES] = {
589 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
590 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
591 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
592 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
595 ChessSquare GrandArray[2][BOARD_FILES] = {
596 { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
597 WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
598 { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
599 BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
603 ChessSquare GothicArray[2][BOARD_FILES] = {
604 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
605 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
606 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
607 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
610 #define GothicArray CapablancaArray
614 ChessSquare FalconArray[2][BOARD_FILES] = {
615 { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
616 WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
617 { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
618 BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
621 #define FalconArray CapablancaArray
624 #else // !(BOARD_FILES>=10)
625 #define XiangqiPosition FIDEArray
626 #define CapablancaArray FIDEArray
627 #define GothicArray FIDEArray
628 #define GreatArray FIDEArray
629 #endif // !(BOARD_FILES>=10)
631 #if (BOARD_FILES>=12)
632 ChessSquare CourierArray[2][BOARD_FILES] = {
633 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
634 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
635 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
636 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
638 #else // !(BOARD_FILES>=12)
639 #define CourierArray CapablancaArray
640 #endif // !(BOARD_FILES>=12)
643 Board initialPosition;
646 /* Convert str to a rating. Checks for special cases of "----",
648 "++++", etc. Also strips ()'s */
650 string_to_rating (char *str)
652 while(*str && !isdigit(*str)) ++str;
654 return 0; /* One of the special "no rating" cases */
662 /* Init programStats */
663 programStats.movelist[0] = 0;
664 programStats.depth = 0;
665 programStats.nr_moves = 0;
666 programStats.moves_left = 0;
667 programStats.nodes = 0;
668 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
669 programStats.score = 0;
670 programStats.got_only_move = 0;
671 programStats.got_fail = 0;
672 programStats.line_is_book = 0;
677 { // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
678 if (appData.firstPlaysBlack) {
679 first.twoMachinesColor = "black\n";
680 second.twoMachinesColor = "white\n";
682 first.twoMachinesColor = "white\n";
683 second.twoMachinesColor = "black\n";
686 first.other = &second;
687 second.other = &first;
690 if(appData.timeOddsMode) {
691 norm = appData.timeOdds[0];
692 if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
694 first.timeOdds = appData.timeOdds[0]/norm;
695 second.timeOdds = appData.timeOdds[1]/norm;
698 if(programVersion) free(programVersion);
699 if (appData.noChessProgram) {
700 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
701 sprintf(programVersion, "%s", PACKAGE_STRING);
703 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
704 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
705 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
710 UnloadEngine (ChessProgramState *cps)
712 /* Kill off first chess program */
713 if (cps->isr != NULL)
714 RemoveInputSource(cps->isr);
717 if (cps->pr != NoProc) {
719 DoSleep( appData.delayBeforeQuit );
720 SendToProgram("quit\n", cps);
721 DoSleep( appData.delayAfterQuit );
722 DestroyChildProcess(cps->pr, cps->useSigterm);
725 if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
729 ClearOptions (ChessProgramState *cps)
732 cps->nrOptions = cps->comboCnt = 0;
733 for(i=0; i<MAX_OPTIONS; i++) {
734 cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
735 cps->option[i].textValue = 0;
739 char *engineNames[] = {
740 /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
741 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
743 /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
744 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
749 InitEngine (ChessProgramState *cps, int n)
750 { // [HGM] all engine initialiation put in a function that does one engine
754 cps->which = engineNames[n];
755 cps->maybeThinking = FALSE;
759 cps->sendDrawOffers = 1;
761 cps->program = appData.chessProgram[n];
762 cps->host = appData.host[n];
763 cps->dir = appData.directory[n];
764 cps->initString = appData.engInitString[n];
765 cps->computerString = appData.computerString[n];
766 cps->useSigint = TRUE;
767 cps->useSigterm = TRUE;
768 cps->reuse = appData.reuse[n];
769 cps->nps = appData.NPS[n]; // [HGM] nps: copy nodes per second
770 cps->useSetboard = FALSE;
772 cps->usePing = FALSE;
775 cps->usePlayother = FALSE;
776 cps->useColors = TRUE;
777 cps->useUsermove = FALSE;
778 cps->sendICS = FALSE;
779 cps->sendName = appData.icsActive;
780 cps->sdKludge = FALSE;
781 cps->stKludge = FALSE;
782 TidyProgramName(cps->program, cps->host, cps->tidy);
784 safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
785 cps->analysisSupport = 2; /* detect */
786 cps->analyzing = FALSE;
787 cps->initDone = FALSE;
789 /* New features added by Tord: */
790 cps->useFEN960 = FALSE;
791 cps->useOOCastle = TRUE;
792 /* End of new features added by Tord. */
793 cps->fenOverride = appData.fenOverride[n];
795 /* [HGM] time odds: set factor for each machine */
796 cps->timeOdds = appData.timeOdds[n];
798 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
799 cps->accumulateTC = appData.accumulateTC[n];
800 cps->maxNrOfSessions = 1;
805 cps->supportsNPS = UNKNOWN;
806 cps->memSize = FALSE;
807 cps->maxCores = FALSE;
808 cps->egtFormats[0] = NULLCHAR;
811 cps->optionSettings = appData.engOptions[n];
813 cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
814 cps->isUCI = appData.isUCI[n]; /* [AS] */
815 cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
817 if (appData.protocolVersion[n] > PROTOVER
818 || appData.protocolVersion[n] < 1)
823 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
824 appData.protocolVersion[n]);
825 if( (len >= MSG_SIZ) && appData.debugMode )
826 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
828 DisplayFatalError(buf, 0, 2);
832 cps->protocolVersion = appData.protocolVersion[n];
835 InitEngineUCI( installDir, cps ); // [HGM] moved here from winboard.c, to make available in xboard
836 ParseFeatures(appData.featureDefaults, cps);
839 ChessProgramState *savCps;
845 if(WaitForEngine(savCps, LoadEngine)) return;
846 CommonEngineInit(); // recalculate time odds
847 if(gameInfo.variant != StringToVariant(appData.variant)) {
848 // we changed variant when loading the engine; this forces us to reset
849 Reset(TRUE, savCps != &first);
850 EditGameEvent(); // for consistency with other path, as Reset changes mode
852 InitChessProgram(savCps, FALSE);
853 SendToProgram("force\n", savCps);
854 DisplayMessage("", "");
855 if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
856 for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
862 ReplaceEngine (ChessProgramState *cps, int n)
866 appData.noChessProgram = FALSE;
867 appData.clockMode = TRUE;
870 if(n) return; // only startup first engine immediately; second can wait
871 savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
875 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
876 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
878 static char resetOptions[] =
879 "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
880 "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
881 "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
882 "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
885 FloatToFront(char **list, char *engineLine)
887 char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
889 if(appData.recentEngines <= 0) return;
890 TidyProgramName(engineLine, "localhost", tidy+1);
891 tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
892 strncpy(buf+1, *list, MSG_SIZ-50);
893 if(p = strstr(buf, tidy)) { // tidy name appears in list
894 q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
895 while(*p++ = *++q); // squeeze out
897 strcat(tidy, buf+1); // put list behind tidy name
898 p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
899 if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
900 ASSIGN(*list, tidy+1);
903 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
906 Load (ChessProgramState *cps, int i)
908 char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
909 if(engineLine && engineLine[0]) { // an engine was selected from the combo box
910 snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
911 SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
912 ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
913 FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
914 appData.firstProtocolVersion = PROTOVER;
915 ParseArgsFromString(buf);
917 ReplaceEngine(cps, i);
918 FloatToFront(&appData.recentEngineList, engineLine);
922 while(q = strchr(p, SLASH)) p = q+1;
923 if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
924 if(engineDir[0] != NULLCHAR) {
925 ASSIGN(appData.directory[i], engineDir); p = engineName;
926 } else if(p != engineName) { // derive directory from engine path, when not given
928 ASSIGN(appData.directory[i], engineName);
930 if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
931 } else { ASSIGN(appData.directory[i], "."); }
933 if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
934 snprintf(command, MSG_SIZ, "%s %s", p, params);
937 ASSIGN(appData.chessProgram[i], p);
938 appData.isUCI[i] = isUCI;
939 appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
940 appData.hasOwnBookUCI[i] = hasBook;
941 if(!nickName[0]) useNick = FALSE;
942 if(useNick) ASSIGN(appData.pgnName[i], nickName);
946 q = firstChessProgramNames;
947 if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
948 quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
949 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
950 quote, p, quote, appData.directory[i],
951 useNick ? " -fn \"" : "",
952 useNick ? nickName : "",
954 v1 ? " -firstProtocolVersion 1" : "",
955 hasBook ? "" : " -fNoOwnBookUCI",
956 isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
957 storeVariant ? " -variant " : "",
958 storeVariant ? VariantName(gameInfo.variant) : "");
959 if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
960 firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
961 if(insert != q) insert[-1] = NULLCHAR;
962 snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
964 FloatToFront(&appData.recentEngineList, buf);
966 ReplaceEngine(cps, i);
972 int matched, min, sec;
974 * Parse timeControl resource
976 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
977 appData.movesPerSession)) {
979 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
980 DisplayFatalError(buf, 0, 2);
984 * Parse searchTime resource
986 if (*appData.searchTime != NULLCHAR) {
987 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
989 searchTime = min * 60;
990 } else if (matched == 2) {
991 searchTime = min * 60 + sec;
994 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
995 DisplayFatalError(buf, 0, 2);
1004 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1005 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1007 GetTimeMark(&programStartTime);
1008 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1009 appData.seedBase = random() + (random()<<15);
1010 pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1012 ClearProgramStats();
1013 programStats.ok_to_send = 1;
1014 programStats.seen_stat = 0;
1017 * Initialize game list
1023 * Internet chess server status
1025 if (appData.icsActive) {
1026 appData.matchMode = FALSE;
1027 appData.matchGames = 0;
1029 appData.noChessProgram = !appData.zippyPlay;
1031 appData.zippyPlay = FALSE;
1032 appData.zippyTalk = FALSE;
1033 appData.noChessProgram = TRUE;
1035 if (*appData.icsHelper != NULLCHAR) {
1036 appData.useTelnet = TRUE;
1037 appData.telnetProgram = appData.icsHelper;
1040 appData.zippyTalk = appData.zippyPlay = FALSE;
1043 /* [AS] Initialize pv info list [HGM] and game state */
1047 for( i=0; i<=framePtr; i++ ) {
1048 pvInfoList[i].depth = -1;
1049 boards[i][EP_STATUS] = EP_NONE;
1050 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1056 /* [AS] Adjudication threshold */
1057 adjudicateLossThreshold = appData.adjudicateLossThreshold;
1059 InitEngine(&first, 0);
1060 InitEngine(&second, 1);
1063 pairing.which = "pairing"; // pairing engine
1064 pairing.pr = NoProc;
1066 pairing.program = appData.pairingEngine;
1067 pairing.host = "localhost";
1070 if (appData.icsActive) {
1071 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
1072 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1073 appData.clockMode = FALSE;
1074 first.sendTime = second.sendTime = 0;
1078 /* Override some settings from environment variables, for backward
1079 compatibility. Unfortunately it's not feasible to have the env
1080 vars just set defaults, at least in xboard. Ugh.
1082 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1087 if (!appData.icsActive) {
1091 /* Check for variants that are supported only in ICS mode,
1092 or not at all. Some that are accepted here nevertheless
1093 have bugs; see comments below.
1095 VariantClass variant = StringToVariant(appData.variant);
1097 case VariantBughouse: /* need four players and two boards */
1098 case VariantKriegspiel: /* need to hide pieces and move details */
1099 /* case VariantFischeRandom: (Fabien: moved below) */
1100 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1101 if( (len >= MSG_SIZ) && appData.debugMode )
1102 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1104 DisplayFatalError(buf, 0, 2);
1107 case VariantUnknown:
1108 case VariantLoadable:
1118 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1119 if( (len >= MSG_SIZ) && appData.debugMode )
1120 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1122 DisplayFatalError(buf, 0, 2);
1125 case VariantXiangqi: /* [HGM] repetition rules not implemented */
1126 case VariantFairy: /* [HGM] TestLegality definitely off! */
1127 case VariantGothic: /* [HGM] should work */
1128 case VariantCapablanca: /* [HGM] should work */
1129 case VariantCourier: /* [HGM] initial forced moves not implemented */
1130 case VariantShogi: /* [HGM] could still mate with pawn drop */
1131 case VariantKnightmate: /* [HGM] should work */
1132 case VariantCylinder: /* [HGM] untested */
1133 case VariantFalcon: /* [HGM] untested */
1134 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1135 offboard interposition not understood */
1136 case VariantNormal: /* definitely works! */
1137 case VariantWildCastle: /* pieces not automatically shuffled */
1138 case VariantNoCastle: /* pieces not automatically shuffled */
1139 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1140 case VariantLosers: /* should work except for win condition,
1141 and doesn't know captures are mandatory */
1142 case VariantSuicide: /* should work except for win condition,
1143 and doesn't know captures are mandatory */
1144 case VariantGiveaway: /* should work except for win condition,
1145 and doesn't know captures are mandatory */
1146 case VariantTwoKings: /* should work */
1147 case VariantAtomic: /* should work except for win condition */
1148 case Variant3Check: /* should work except for win condition */
1149 case VariantShatranj: /* should work except for all win conditions */
1150 case VariantMakruk: /* should work except for draw countdown */
1151 case VariantBerolina: /* might work if TestLegality is off */
1152 case VariantCapaRandom: /* should work */
1153 case VariantJanus: /* should work */
1154 case VariantSuper: /* experimental */
1155 case VariantGreat: /* experimental, requires legality testing to be off */
1156 case VariantSChess: /* S-Chess, should work */
1157 case VariantGrand: /* should work */
1158 case VariantSpartan: /* should work */
1166 NextIntegerFromString (char ** str, long * value)
1171 while( *s == ' ' || *s == '\t' ) {
1177 if( *s >= '0' && *s <= '9' ) {
1178 while( *s >= '0' && *s <= '9' ) {
1179 *value = *value * 10 + (*s - '0');
1192 NextTimeControlFromString (char ** str, long * value)
1195 int result = NextIntegerFromString( str, &temp );
1198 *value = temp * 60; /* Minutes */
1199 if( **str == ':' ) {
1201 result = NextIntegerFromString( str, &temp );
1202 *value += temp; /* Seconds */
1210 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1211 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1212 int result = -1, type = 0; long temp, temp2;
1214 if(**str != ':') return -1; // old params remain in force!
1216 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1217 if( NextIntegerFromString( str, &temp ) ) return -1;
1218 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1221 /* time only: incremental or sudden-death time control */
1222 if(**str == '+') { /* increment follows; read it */
1224 if(**str == '!') type = *(*str)++; // Bronstein TC
1225 if(result = NextIntegerFromString( str, &temp2)) return -1;
1226 *inc = temp2 * 1000;
1227 if(**str == '.') { // read fraction of increment
1228 char *start = ++(*str);
1229 if(result = NextIntegerFromString( str, &temp2)) return -1;
1231 while(start++ < *str) temp2 /= 10;
1235 *moves = 0; *tc = temp * 1000; *incType = type;
1239 (*str)++; /* classical time control */
1240 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1252 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1253 { /* [HGM] get time to add from the multi-session time-control string */
1254 int incType, moves=1; /* kludge to force reading of first session */
1255 long time, increment;
1258 if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1260 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1261 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1262 if(movenr == -1) return time; /* last move before new session */
1263 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1264 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1265 if(!moves) return increment; /* current session is incremental */
1266 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1267 } while(movenr >= -1); /* try again for next session */
1269 return 0; // no new time quota on this move
1273 ParseTimeControl (char *tc, float ti, int mps)
1277 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1280 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1281 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1282 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1286 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1288 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1291 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1293 snprintf(buf, MSG_SIZ, ":%s", mytc);
1295 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1297 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1302 /* Parse second time control */
1305 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1313 timeControl_2 = tc2 * 1000;
1323 timeControl = tc1 * 1000;
1326 timeIncrement = ti * 1000; /* convert to ms */
1327 movesPerSession = 0;
1330 movesPerSession = mps;
1338 if (appData.debugMode) {
1339 fprintf(debugFP, "%s\n", programVersion);
1341 ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1343 set_cont_sequence(appData.wrapContSeq);
1344 if (appData.matchGames > 0) {
1345 appData.matchMode = TRUE;
1346 } else if (appData.matchMode) {
1347 appData.matchGames = 1;
1349 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1350 appData.matchGames = appData.sameColorGames;
1351 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1352 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1353 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1356 if (appData.noChessProgram || first.protocolVersion == 1) {
1359 /* kludge: allow timeout for initial "feature" commands */
1361 DisplayMessage("", _("Starting chess program"));
1362 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1367 CalculateIndex (int index, int gameNr)
1368 { // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1370 if(index > 0) return index; // fixed nmber
1371 if(index == 0) return 1;
1372 res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1373 if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1378 LoadGameOrPosition (int gameNr)
1379 { // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1380 if (*appData.loadGameFile != NULLCHAR) {
1381 if (!LoadGameFromFile(appData.loadGameFile,
1382 CalculateIndex(appData.loadGameIndex, gameNr),
1383 appData.loadGameFile, FALSE)) {
1384 DisplayFatalError(_("Bad game file"), 0, 1);
1387 } else if (*appData.loadPositionFile != NULLCHAR) {
1388 if (!LoadPositionFromFile(appData.loadPositionFile,
1389 CalculateIndex(appData.loadPositionIndex, gameNr),
1390 appData.loadPositionFile)) {
1391 DisplayFatalError(_("Bad position file"), 0, 1);
1399 ReserveGame (int gameNr, char resChar)
1401 FILE *tf = fopen(appData.tourneyFile, "r+");
1402 char *p, *q, c, buf[MSG_SIZ];
1403 if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1404 safeStrCpy(buf, lastMsg, MSG_SIZ);
1405 DisplayMessage(_("Pick new game"), "");
1406 flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1407 ParseArgsFromFile(tf);
1408 p = q = appData.results;
1409 if(appData.debugMode) {
1410 char *r = appData.participants;
1411 fprintf(debugFP, "results = '%s'\n", p);
1412 while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1413 fprintf(debugFP, "\n");
1415 while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1417 q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1418 safeStrCpy(q, p, strlen(p) + 2);
1419 if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1420 if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1421 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1422 if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1425 fseek(tf, -(strlen(p)+4), SEEK_END);
1427 if(c != '"') // depending on DOS or Unix line endings we can be one off
1428 fseek(tf, -(strlen(p)+2), SEEK_END);
1429 else fseek(tf, -(strlen(p)+3), SEEK_END);
1430 fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1431 DisplayMessage(buf, "");
1432 free(p); appData.results = q;
1433 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1434 (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1435 int round = appData.defaultMatchGames * appData.tourneyType;
1436 if(gameNr < 0 || appData.tourneyType < 1 || // gauntlet engine can always stay loaded as first engine
1437 appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1438 UnloadEngine(&first); // next game belongs to other pairing;
1439 UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1441 if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1445 MatchEvent (int mode)
1446 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1448 if(matchMode) { // already in match mode: switch it off
1450 if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1453 // if(gameMode != BeginningOfGame) {
1454 // DisplayError(_("You can only start a match from the initial position."), 0);
1458 if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1459 /* Set up machine vs. machine match */
1461 NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1462 if(appData.tourneyFile[0]) {
1464 if(nextGame > appData.matchGames) {
1466 if(strchr(appData.results, '*') == NULL) {
1468 appData.tourneyCycles++;
1469 if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1471 NextTourneyGame(-1, &dummy);
1473 if(nextGame <= appData.matchGames) {
1474 DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1476 ScheduleDelayedEvent(NextMatchGame, 10000);
1481 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1482 DisplayError(buf, 0);
1483 appData.tourneyFile[0] = 0;
1487 if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically
1488 DisplayFatalError(_("Can't have a match with no chess programs"),
1493 matchGame = roundNr = 1;
1494 first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1498 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1501 InitBackEnd3 P((void))
1503 GameMode initialMode;
1507 InitChessProgram(&first, startedFromSetupPosition);
1509 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1510 free(programVersion);
1511 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1512 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1513 FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1516 if (appData.icsActive) {
1518 /* [DM] Make a console window if needed [HGM] merged ifs */
1524 if (*appData.icsCommPort != NULLCHAR)
1525 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1526 appData.icsCommPort);
1528 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1529 appData.icsHost, appData.icsPort);
1531 if( (len >= MSG_SIZ) && appData.debugMode )
1532 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1534 DisplayFatalError(buf, err, 1);
1539 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1541 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1542 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1543 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1544 } else if (appData.noChessProgram) {
1550 if (*appData.cmailGameName != NULLCHAR) {
1552 OpenLoopback(&cmailPR);
1554 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1558 DisplayMessage("", "");
1559 if (StrCaseCmp(appData.initialMode, "") == 0) {
1560 initialMode = BeginningOfGame;
1561 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1562 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1563 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1564 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1567 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1568 initialMode = TwoMachinesPlay;
1569 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1570 initialMode = AnalyzeFile;
1571 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1572 initialMode = AnalyzeMode;
1573 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1574 initialMode = MachinePlaysWhite;
1575 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1576 initialMode = MachinePlaysBlack;
1577 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1578 initialMode = EditGame;
1579 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1580 initialMode = EditPosition;
1581 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1582 initialMode = Training;
1584 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1585 if( (len >= MSG_SIZ) && appData.debugMode )
1586 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1588 DisplayFatalError(buf, 0, 2);
1592 if (appData.matchMode) {
1593 if(appData.tourneyFile[0]) { // start tourney from command line
1595 if(f = fopen(appData.tourneyFile, "r")) {
1596 ParseArgsFromFile(f); // make sure tourney parmeters re known
1598 appData.clockMode = TRUE;
1600 } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1603 } else if (*appData.cmailGameName != NULLCHAR) {
1604 /* Set up cmail mode */
1605 ReloadCmailMsgEvent(TRUE);
1607 /* Set up other modes */
1608 if (initialMode == AnalyzeFile) {
1609 if (*appData.loadGameFile == NULLCHAR) {
1610 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1614 if (*appData.loadGameFile != NULLCHAR) {
1615 (void) LoadGameFromFile(appData.loadGameFile,
1616 appData.loadGameIndex,
1617 appData.loadGameFile, TRUE);
1618 } else if (*appData.loadPositionFile != NULLCHAR) {
1619 (void) LoadPositionFromFile(appData.loadPositionFile,
1620 appData.loadPositionIndex,
1621 appData.loadPositionFile);
1622 /* [HGM] try to make self-starting even after FEN load */
1623 /* to allow automatic setup of fairy variants with wtm */
1624 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1625 gameMode = BeginningOfGame;
1626 setboardSpoiledMachineBlack = 1;
1628 /* [HGM] loadPos: make that every new game uses the setup */
1629 /* from file as long as we do not switch variant */
1630 if(!blackPlaysFirst) {
1631 startedFromPositionFile = TRUE;
1632 CopyBoard(filePosition, boards[0]);
1635 if (initialMode == AnalyzeMode) {
1636 if (appData.noChessProgram) {
1637 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1640 if (appData.icsActive) {
1641 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1645 } else if (initialMode == AnalyzeFile) {
1646 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1647 ShowThinkingEvent();
1649 AnalysisPeriodicEvent(1);
1650 } else if (initialMode == MachinePlaysWhite) {
1651 if (appData.noChessProgram) {
1652 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1656 if (appData.icsActive) {
1657 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1661 MachineWhiteEvent();
1662 } else if (initialMode == MachinePlaysBlack) {
1663 if (appData.noChessProgram) {
1664 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1668 if (appData.icsActive) {
1669 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1673 MachineBlackEvent();
1674 } else if (initialMode == TwoMachinesPlay) {
1675 if (appData.noChessProgram) {
1676 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1680 if (appData.icsActive) {
1681 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1686 } else if (initialMode == EditGame) {
1688 } else if (initialMode == EditPosition) {
1689 EditPositionEvent();
1690 } else if (initialMode == Training) {
1691 if (*appData.loadGameFile == NULLCHAR) {
1692 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1701 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1703 DisplayBook(current+1);
1705 MoveHistorySet( movelist, first, last, current, pvInfoList );
1707 EvalGraphSet( first, last, current, pvInfoList );
1709 MakeEngineOutputTitle();
1713 * Establish will establish a contact to a remote host.port.
1714 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1715 * used to talk to the host.
1716 * Returns 0 if okay, error code if not.
1723 if (*appData.icsCommPort != NULLCHAR) {
1724 /* Talk to the host through a serial comm port */
1725 return OpenCommPort(appData.icsCommPort, &icsPR);
1727 } else if (*appData.gateway != NULLCHAR) {
1728 if (*appData.remoteShell == NULLCHAR) {
1729 /* Use the rcmd protocol to run telnet program on a gateway host */
1730 snprintf(buf, sizeof(buf), "%s %s %s",
1731 appData.telnetProgram, appData.icsHost, appData.icsPort);
1732 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1735 /* Use the rsh program to run telnet program on a gateway host */
1736 if (*appData.remoteUser == NULLCHAR) {
1737 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1738 appData.gateway, appData.telnetProgram,
1739 appData.icsHost, appData.icsPort);
1741 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1742 appData.remoteShell, appData.gateway,
1743 appData.remoteUser, appData.telnetProgram,
1744 appData.icsHost, appData.icsPort);
1746 return StartChildProcess(buf, "", &icsPR);
1749 } else if (appData.useTelnet) {
1750 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1753 /* TCP socket interface differs somewhat between
1754 Unix and NT; handle details in the front end.
1756 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1761 EscapeExpand (char *p, char *q)
1762 { // [HGM] initstring: routine to shape up string arguments
1763 while(*p++ = *q++) if(p[-1] == '\\')
1765 case 'n': p[-1] = '\n'; break;
1766 case 'r': p[-1] = '\r'; break;
1767 case 't': p[-1] = '\t'; break;
1768 case '\\': p[-1] = '\\'; break;
1769 case 0: *p = 0; return;
1770 default: p[-1] = q[-1]; break;
1775 show_bytes (FILE *fp, char *buf, int count)
1778 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1779 fprintf(fp, "\\%03o", *buf & 0xff);
1788 /* Returns an errno value */
1790 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1792 char buf[8192], *p, *q, *buflim;
1793 int left, newcount, outcount;
1795 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1796 *appData.gateway != NULLCHAR) {
1797 if (appData.debugMode) {
1798 fprintf(debugFP, ">ICS: ");
1799 show_bytes(debugFP, message, count);
1800 fprintf(debugFP, "\n");
1802 return OutputToProcess(pr, message, count, outError);
1805 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1812 if (appData.debugMode) {
1813 fprintf(debugFP, ">ICS: ");
1814 show_bytes(debugFP, buf, newcount);
1815 fprintf(debugFP, "\n");
1817 outcount = OutputToProcess(pr, buf, newcount, outError);
1818 if (outcount < newcount) return -1; /* to be sure */
1825 } else if (((unsigned char) *p) == TN_IAC) {
1826 *q++ = (char) TN_IAC;
1833 if (appData.debugMode) {
1834 fprintf(debugFP, ">ICS: ");
1835 show_bytes(debugFP, buf, newcount);
1836 fprintf(debugFP, "\n");
1838 outcount = OutputToProcess(pr, buf, newcount, outError);
1839 if (outcount < newcount) return -1; /* to be sure */
1844 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1846 int outError, outCount;
1847 static int gotEof = 0;
1849 /* Pass data read from player on to ICS */
1852 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1853 if (outCount < count) {
1854 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1856 } else if (count < 0) {
1857 RemoveInputSource(isr);
1858 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1859 } else if (gotEof++ > 0) {
1860 RemoveInputSource(isr);
1861 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1867 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1868 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1869 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1870 SendToICS("date\n");
1871 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1874 /* added routine for printf style output to ics */
1876 ics_printf (char *format, ...)
1878 char buffer[MSG_SIZ];
1881 va_start(args, format);
1882 vsnprintf(buffer, sizeof(buffer), format, args);
1883 buffer[sizeof(buffer)-1] = '\0';
1891 int count, outCount, outError;
1893 if (icsPR == NoProc) return;
1896 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1897 if (outCount < count) {
1898 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1902 /* This is used for sending logon scripts to the ICS. Sending
1903 without a delay causes problems when using timestamp on ICC
1904 (at least on my machine). */
1906 SendToICSDelayed (char *s, long msdelay)
1908 int count, outCount, outError;
1910 if (icsPR == NoProc) return;
1913 if (appData.debugMode) {
1914 fprintf(debugFP, ">ICS: ");
1915 show_bytes(debugFP, s, count);
1916 fprintf(debugFP, "\n");
1918 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1920 if (outCount < count) {
1921 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1926 /* Remove all highlighting escape sequences in s
1927 Also deletes any suffix starting with '('
1930 StripHighlightAndTitle (char *s)
1932 static char retbuf[MSG_SIZ];
1935 while (*s != NULLCHAR) {
1936 while (*s == '\033') {
1937 while (*s != NULLCHAR && !isalpha(*s)) s++;
1938 if (*s != NULLCHAR) s++;
1940 while (*s != NULLCHAR && *s != '\033') {
1941 if (*s == '(' || *s == '[') {
1952 /* Remove all highlighting escape sequences in s */
1954 StripHighlight (char *s)
1956 static char retbuf[MSG_SIZ];
1959 while (*s != NULLCHAR) {
1960 while (*s == '\033') {
1961 while (*s != NULLCHAR && !isalpha(*s)) s++;
1962 if (*s != NULLCHAR) s++;
1964 while (*s != NULLCHAR && *s != '\033') {
1972 char *variantNames[] = VARIANT_NAMES;
1974 VariantName (VariantClass v)
1976 return variantNames[v];
1980 /* Identify a variant from the strings the chess servers use or the
1981 PGN Variant tag names we use. */
1983 StringToVariant (char *e)
1987 VariantClass v = VariantNormal;
1988 int i, found = FALSE;
1994 /* [HGM] skip over optional board-size prefixes */
1995 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1996 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1997 while( *e++ != '_');
2000 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2004 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2005 if (StrCaseStr(e, variantNames[i])) {
2006 v = (VariantClass) i;
2013 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2014 || StrCaseStr(e, "wild/fr")
2015 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2016 v = VariantFischeRandom;
2017 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2018 (i = 1, p = StrCaseStr(e, "w"))) {
2020 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2027 case 0: /* FICS only, actually */
2029 /* Castling legal even if K starts on d-file */
2030 v = VariantWildCastle;
2035 /* Castling illegal even if K & R happen to start in
2036 normal positions. */
2037 v = VariantNoCastle;
2050 /* Castling legal iff K & R start in normal positions */
2056 /* Special wilds for position setup; unclear what to do here */
2057 v = VariantLoadable;
2060 /* Bizarre ICC game */
2061 v = VariantTwoKings;
2064 v = VariantKriegspiel;
2070 v = VariantFischeRandom;
2073 v = VariantCrazyhouse;
2076 v = VariantBughouse;
2082 /* Not quite the same as FICS suicide! */
2083 v = VariantGiveaway;
2089 v = VariantShatranj;
2092 /* Temporary names for future ICC types. The name *will* change in
2093 the next xboard/WinBoard release after ICC defines it. */
2131 v = VariantCapablanca;
2134 v = VariantKnightmate;
2140 v = VariantCylinder;
2146 v = VariantCapaRandom;
2149 v = VariantBerolina;
2161 /* Found "wild" or "w" in the string but no number;
2162 must assume it's normal chess. */
2166 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2167 if( (len >= MSG_SIZ) && appData.debugMode )
2168 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2170 DisplayError(buf, 0);
2176 if (appData.debugMode) {
2177 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2178 e, wnum, VariantName(v));
2183 static int leftover_start = 0, leftover_len = 0;
2184 char star_match[STAR_MATCH_N][MSG_SIZ];
2186 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2187 advance *index beyond it, and set leftover_start to the new value of
2188 *index; else return FALSE. If pattern contains the character '*', it
2189 matches any sequence of characters not containing '\r', '\n', or the
2190 character following the '*' (if any), and the matched sequence(s) are
2191 copied into star_match.
2194 looking_at ( char *buf, int *index, char *pattern)
2196 char *bufp = &buf[*index], *patternp = pattern;
2198 char *matchp = star_match[0];
2201 if (*patternp == NULLCHAR) {
2202 *index = leftover_start = bufp - buf;
2206 if (*bufp == NULLCHAR) return FALSE;
2207 if (*patternp == '*') {
2208 if (*bufp == *(patternp + 1)) {
2210 matchp = star_match[++star_count];
2214 } else if (*bufp == '\n' || *bufp == '\r') {
2216 if (*patternp == NULLCHAR)
2221 *matchp++ = *bufp++;
2225 if (*patternp != *bufp) return FALSE;
2232 SendToPlayer (char *data, int length)
2234 int error, outCount;
2235 outCount = OutputToProcess(NoProc, data, length, &error);
2236 if (outCount < length) {
2237 DisplayFatalError(_("Error writing to display"), error, 1);
2242 PackHolding (char packed[], char *holding)
2252 switch (runlength) {
2263 sprintf(q, "%d", runlength);
2275 /* Telnet protocol requests from the front end */
2277 TelnetRequest (unsigned char ddww, unsigned char option)
2279 unsigned char msg[3];
2280 int outCount, outError;
2282 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2284 if (appData.debugMode) {
2285 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2301 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2310 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2313 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2318 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2320 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2327 if (!appData.icsActive) return;
2328 TelnetRequest(TN_DO, TN_ECHO);
2334 if (!appData.icsActive) return;
2335 TelnetRequest(TN_DONT, TN_ECHO);
2339 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2341 /* put the holdings sent to us by the server on the board holdings area */
2342 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2346 if(gameInfo.holdingsWidth < 2) return;
2347 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2348 return; // prevent overwriting by pre-board holdings
2350 if( (int)lowestPiece >= BlackPawn ) {
2353 holdingsStartRow = BOARD_HEIGHT-1;
2356 holdingsColumn = BOARD_WIDTH-1;
2357 countsColumn = BOARD_WIDTH-2;
2358 holdingsStartRow = 0;
2362 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2363 board[i][holdingsColumn] = EmptySquare;
2364 board[i][countsColumn] = (ChessSquare) 0;
2366 while( (p=*holdings++) != NULLCHAR ) {
2367 piece = CharToPiece( ToUpper(p) );
2368 if(piece == EmptySquare) continue;
2369 /*j = (int) piece - (int) WhitePawn;*/
2370 j = PieceToNumber(piece);
2371 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2372 if(j < 0) continue; /* should not happen */
2373 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2374 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2375 board[holdingsStartRow+j*direction][countsColumn]++;
2381 VariantSwitch (Board board, VariantClass newVariant)
2383 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2384 static Board oldBoard;
2386 startedFromPositionFile = FALSE;
2387 if(gameInfo.variant == newVariant) return;
2389 /* [HGM] This routine is called each time an assignment is made to
2390 * gameInfo.variant during a game, to make sure the board sizes
2391 * are set to match the new variant. If that means adding or deleting
2392 * holdings, we shift the playing board accordingly
2393 * This kludge is needed because in ICS observe mode, we get boards
2394 * of an ongoing game without knowing the variant, and learn about the
2395 * latter only later. This can be because of the move list we requested,
2396 * in which case the game history is refilled from the beginning anyway,
2397 * but also when receiving holdings of a crazyhouse game. In the latter
2398 * case we want to add those holdings to the already received position.
2402 if (appData.debugMode) {
2403 fprintf(debugFP, "Switch board from %s to %s\n",
2404 VariantName(gameInfo.variant), VariantName(newVariant));
2405 setbuf(debugFP, NULL);
2407 shuffleOpenings = 0; /* [HGM] shuffle */
2408 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2412 newWidth = 9; newHeight = 9;
2413 gameInfo.holdingsSize = 7;
2414 case VariantBughouse:
2415 case VariantCrazyhouse:
2416 newHoldingsWidth = 2; break;
2420 newHoldingsWidth = 2;
2421 gameInfo.holdingsSize = 8;
2424 case VariantCapablanca:
2425 case VariantCapaRandom:
2428 newHoldingsWidth = gameInfo.holdingsSize = 0;
2431 if(newWidth != gameInfo.boardWidth ||
2432 newHeight != gameInfo.boardHeight ||
2433 newHoldingsWidth != gameInfo.holdingsWidth ) {
2435 /* shift position to new playing area, if needed */
2436 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2437 for(i=0; i<BOARD_HEIGHT; i++)
2438 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2439 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2441 for(i=0; i<newHeight; i++) {
2442 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2443 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2445 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2446 for(i=0; i<BOARD_HEIGHT; i++)
2447 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2448 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2451 board[HOLDINGS_SET] = 0;
2452 gameInfo.boardWidth = newWidth;
2453 gameInfo.boardHeight = newHeight;
2454 gameInfo.holdingsWidth = newHoldingsWidth;
2455 gameInfo.variant = newVariant;
2456 InitDrawingSizes(-2, 0);
2457 } else gameInfo.variant = newVariant;
2458 CopyBoard(oldBoard, board); // remember correctly formatted board
2459 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2460 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2463 static int loggedOn = FALSE;
2465 /*-- Game start info cache: --*/
2467 char gs_kind[MSG_SIZ];
2468 static char player1Name[128] = "";
2469 static char player2Name[128] = "";
2470 static char cont_seq[] = "\n\\ ";
2471 static int player1Rating = -1;
2472 static int player2Rating = -1;
2473 /*----------------------------*/
2475 ColorClass curColor = ColorNormal;
2476 int suppressKibitz = 0;
2479 Boolean soughtPending = FALSE;
2480 Boolean seekGraphUp;
2481 #define MAX_SEEK_ADS 200
2483 char *seekAdList[MAX_SEEK_ADS];
2484 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2485 float tcList[MAX_SEEK_ADS];
2486 char colorList[MAX_SEEK_ADS];
2487 int nrOfSeekAds = 0;
2488 int minRating = 1010, maxRating = 2800;
2489 int hMargin = 10, vMargin = 20, h, w;
2490 extern int squareSize, lineGap;
2495 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2496 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2497 if(r < minRating+100 && r >=0 ) r = minRating+100;
2498 if(r > maxRating) r = maxRating;
2499 if(tc < 1.f) tc = 1.f;
2500 if(tc > 95.f) tc = 95.f;
2501 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2502 y = ((double)r - minRating)/(maxRating - minRating)
2503 * (h-vMargin-squareSize/8-1) + vMargin;
2504 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2505 if(strstr(seekAdList[i], " u ")) color = 1;
2506 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2507 !strstr(seekAdList[i], "bullet") &&
2508 !strstr(seekAdList[i], "blitz") &&
2509 !strstr(seekAdList[i], "standard") ) color = 2;
2510 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2511 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2515 PlotSingleSeekAd (int i)
2521 AddAd (char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2523 char buf[MSG_SIZ], *ext = "";
2524 VariantClass v = StringToVariant(type);
2525 if(strstr(type, "wild")) {
2526 ext = type + 4; // append wild number
2527 if(v == VariantFischeRandom) type = "chess960"; else
2528 if(v == VariantLoadable) type = "setup"; else
2529 type = VariantName(v);
2531 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2532 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2533 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2534 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2535 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2536 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2537 seekNrList[nrOfSeekAds] = nr;
2538 zList[nrOfSeekAds] = 0;
2539 seekAdList[nrOfSeekAds++] = StrSave(buf);
2540 if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2545 EraseSeekDot (int i)
2547 int x = xList[i], y = yList[i], d=squareSize/4, k;
2548 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2549 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2550 // now replot every dot that overlapped
2551 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2552 int xx = xList[k], yy = yList[k];
2553 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2554 DrawSeekDot(xx, yy, colorList[k]);
2559 RemoveSeekAd (int nr)
2562 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2564 if(seekAdList[i]) free(seekAdList[i]);
2565 seekAdList[i] = seekAdList[--nrOfSeekAds];
2566 seekNrList[i] = seekNrList[nrOfSeekAds];
2567 ratingList[i] = ratingList[nrOfSeekAds];
2568 colorList[i] = colorList[nrOfSeekAds];
2569 tcList[i] = tcList[nrOfSeekAds];
2570 xList[i] = xList[nrOfSeekAds];
2571 yList[i] = yList[nrOfSeekAds];
2572 zList[i] = zList[nrOfSeekAds];
2573 seekAdList[nrOfSeekAds] = NULL;
2579 MatchSoughtLine (char *line)
2581 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2582 int nr, base, inc, u=0; char dummy;
2584 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2585 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2587 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2588 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2589 // match: compact and save the line
2590 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2600 if(!seekGraphUp) return FALSE;
2601 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2602 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2604 DrawSeekBackground(0, 0, w, h);
2605 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2606 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2607 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2608 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2610 DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2613 snprintf(buf, MSG_SIZ, "%d", i);
2614 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2617 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2618 for(i=1; i<100; i+=(i<10?1:5)) {
2619 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2620 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2621 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2623 snprintf(buf, MSG_SIZ, "%d", i);
2624 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2627 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2632 SeekGraphClick (ClickType click, int x, int y, int moving)
2634 static int lastDown = 0, displayed = 0, lastSecond;
2635 if(y < 0) return FALSE;
2636 if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2637 (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2638 if(!seekGraphUp) return FALSE;
2639 seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2640 DrawPosition(TRUE, NULL);
2643 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2644 if(click == Release || moving) return FALSE;
2646 soughtPending = TRUE;
2647 SendToICS(ics_prefix);
2648 SendToICS("sought\n"); // should this be "sought all"?
2649 } else { // issue challenge based on clicked ad
2650 int dist = 10000; int i, closest = 0, second = 0;
2651 for(i=0; i<nrOfSeekAds; i++) {
2652 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2653 if(d < dist) { dist = d; closest = i; }
2654 second += (d - zList[i] < 120); // count in-range ads
2655 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2659 second = (second > 1);
2660 if(displayed != closest || second != lastSecond) {
2661 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2662 lastSecond = second; displayed = closest;
2664 if(click == Press) {
2665 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2668 } // on press 'hit', only show info
2669 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2670 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2671 SendToICS(ics_prefix);
2673 return TRUE; // let incoming board of started game pop down the graph
2674 } else if(click == Release) { // release 'miss' is ignored
2675 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2676 if(moving == 2) { // right up-click
2677 nrOfSeekAds = 0; // refresh graph
2678 soughtPending = TRUE;
2679 SendToICS(ics_prefix);
2680 SendToICS("sought\n"); // should this be "sought all"?
2683 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2684 // press miss or release hit 'pop down' seek graph
2685 seekGraphUp = FALSE;
2686 DrawPosition(TRUE, NULL);
2692 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2694 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2695 #define STARTED_NONE 0
2696 #define STARTED_MOVES 1
2697 #define STARTED_BOARD 2
2698 #define STARTED_OBSERVE 3
2699 #define STARTED_HOLDINGS 4
2700 #define STARTED_CHATTER 5
2701 #define STARTED_COMMENT 6
2702 #define STARTED_MOVES_NOHIDE 7
2704 static int started = STARTED_NONE;
2705 static char parse[20000];
2706 static int parse_pos = 0;
2707 static char buf[BUF_SIZE + 1];
2708 static int firstTime = TRUE, intfSet = FALSE;
2709 static ColorClass prevColor = ColorNormal;
2710 static int savingComment = FALSE;
2711 static int cmatch = 0; // continuation sequence match
2718 int backup; /* [DM] For zippy color lines */
2720 char talker[MSG_SIZ]; // [HGM] chat
2723 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2725 if (appData.debugMode) {
2727 fprintf(debugFP, "<ICS: ");
2728 show_bytes(debugFP, data, count);
2729 fprintf(debugFP, "\n");
2733 if (appData.debugMode) { int f = forwardMostMove;
2734 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2735 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2736 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2739 /* If last read ended with a partial line that we couldn't parse,
2740 prepend it to the new read and try again. */
2741 if (leftover_len > 0) {
2742 for (i=0; i<leftover_len; i++)
2743 buf[i] = buf[leftover_start + i];
2746 /* copy new characters into the buffer */
2747 bp = buf + leftover_len;
2748 buf_len=leftover_len;
2749 for (i=0; i<count; i++)
2752 if (data[i] == '\r')
2755 // join lines split by ICS?
2756 if (!appData.noJoin)
2759 Joining just consists of finding matches against the
2760 continuation sequence, and discarding that sequence
2761 if found instead of copying it. So, until a match
2762 fails, there's nothing to do since it might be the
2763 complete sequence, and thus, something we don't want
2766 if (data[i] == cont_seq[cmatch])
2769 if (cmatch == strlen(cont_seq))
2771 cmatch = 0; // complete match. just reset the counter
2774 it's possible for the ICS to not include the space
2775 at the end of the last word, making our [correct]
2776 join operation fuse two separate words. the server
2777 does this when the space occurs at the width setting.
2779 if (!buf_len || buf[buf_len-1] != ' ')
2790 match failed, so we have to copy what matched before
2791 falling through and copying this character. In reality,
2792 this will only ever be just the newline character, but
2793 it doesn't hurt to be precise.
2795 strncpy(bp, cont_seq, cmatch);
2807 buf[buf_len] = NULLCHAR;
2808 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2813 while (i < buf_len) {
2814 /* Deal with part of the TELNET option negotiation
2815 protocol. We refuse to do anything beyond the
2816 defaults, except that we allow the WILL ECHO option,
2817 which ICS uses to turn off password echoing when we are
2818 directly connected to it. We reject this option
2819 if localLineEditing mode is on (always on in xboard)
2820 and we are talking to port 23, which might be a real
2821 telnet server that will try to keep WILL ECHO on permanently.
2823 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2824 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2825 unsigned char option;
2827 switch ((unsigned char) buf[++i]) {
2829 if (appData.debugMode)
2830 fprintf(debugFP, "\n<WILL ");
2831 switch (option = (unsigned char) buf[++i]) {
2833 if (appData.debugMode)
2834 fprintf(debugFP, "ECHO ");
2835 /* Reply only if this is a change, according
2836 to the protocol rules. */
2837 if (remoteEchoOption) break;
2838 if (appData.localLineEditing &&
2839 atoi(appData.icsPort) == TN_PORT) {
2840 TelnetRequest(TN_DONT, TN_ECHO);
2843 TelnetRequest(TN_DO, TN_ECHO);
2844 remoteEchoOption = TRUE;
2848 if (appData.debugMode)
2849 fprintf(debugFP, "%d ", option);
2850 /* Whatever this is, we don't want it. */
2851 TelnetRequest(TN_DONT, option);
2856 if (appData.debugMode)
2857 fprintf(debugFP, "\n<WONT ");
2858 switch (option = (unsigned char) buf[++i]) {
2860 if (appData.debugMode)
2861 fprintf(debugFP, "ECHO ");
2862 /* Reply only if this is a change, according
2863 to the protocol rules. */
2864 if (!remoteEchoOption) break;
2866 TelnetRequest(TN_DONT, TN_ECHO);
2867 remoteEchoOption = FALSE;
2870 if (appData.debugMode)
2871 fprintf(debugFP, "%d ", (unsigned char) option);
2872 /* Whatever this is, it must already be turned
2873 off, because we never agree to turn on
2874 anything non-default, so according to the
2875 protocol rules, we don't reply. */
2880 if (appData.debugMode)
2881 fprintf(debugFP, "\n<DO ");
2882 switch (option = (unsigned char) buf[++i]) {
2884 /* Whatever this is, we refuse to do it. */
2885 if (appData.debugMode)
2886 fprintf(debugFP, "%d ", option);
2887 TelnetRequest(TN_WONT, option);
2892 if (appData.debugMode)
2893 fprintf(debugFP, "\n<DONT ");
2894 switch (option = (unsigned char) buf[++i]) {
2896 if (appData.debugMode)
2897 fprintf(debugFP, "%d ", option);
2898 /* Whatever this is, we are already not doing
2899 it, because we never agree to do anything
2900 non-default, so according to the protocol
2901 rules, we don't reply. */
2906 if (appData.debugMode)
2907 fprintf(debugFP, "\n<IAC ");
2908 /* Doubled IAC; pass it through */
2912 if (appData.debugMode)
2913 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2914 /* Drop all other telnet commands on the floor */
2917 if (oldi > next_out)
2918 SendToPlayer(&buf[next_out], oldi - next_out);
2924 /* OK, this at least will *usually* work */
2925 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2929 if (loggedOn && !intfSet) {
2930 if (ics_type == ICS_ICC) {
2931 snprintf(str, MSG_SIZ,
2932 "/set-quietly interface %s\n/set-quietly style 12\n",
2934 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2935 strcat(str, "/set-2 51 1\n/set seek 1\n");
2936 } else if (ics_type == ICS_CHESSNET) {
2937 snprintf(str, MSG_SIZ, "/style 12\n");
2939 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2940 strcat(str, programVersion);
2941 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2942 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2943 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2945 strcat(str, "$iset nohighlight 1\n");
2947 strcat(str, "$iset lock 1\n$style 12\n");
2950 NotifyFrontendLogin();
2954 if (started == STARTED_COMMENT) {
2955 /* Accumulate characters in comment */
2956 parse[parse_pos++] = buf[i];
2957 if (buf[i] == '\n') {
2958 parse[parse_pos] = NULLCHAR;
2959 if(chattingPartner>=0) {
2961 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2962 OutputChatMessage(chattingPartner, mess);
2963 chattingPartner = -1;
2964 next_out = i+1; // [HGM] suppress printing in ICS window
2966 if(!suppressKibitz) // [HGM] kibitz
2967 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2968 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2969 int nrDigit = 0, nrAlph = 0, j;
2970 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2971 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2972 parse[parse_pos] = NULLCHAR;
2973 // try to be smart: if it does not look like search info, it should go to
2974 // ICS interaction window after all, not to engine-output window.
2975 for(j=0; j<parse_pos; j++) { // count letters and digits
2976 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2977 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2978 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2980 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2981 int depth=0; float score;
2982 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2983 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2984 pvInfoList[forwardMostMove-1].depth = depth;
2985 pvInfoList[forwardMostMove-1].score = 100*score;
2987 OutputKibitz(suppressKibitz, parse);
2990 if(gameMode == IcsObserving) // restore original ICS messages
2991 snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
2993 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2994 SendToPlayer(tmp, strlen(tmp));
2996 next_out = i+1; // [HGM] suppress printing in ICS window
2998 started = STARTED_NONE;
3000 /* Don't match patterns against characters in comment */
3005 if (started == STARTED_CHATTER) {
3006 if (buf[i] != '\n') {
3007 /* Don't match patterns against characters in chatter */
3011 started = STARTED_NONE;
3012 if(suppressKibitz) next_out = i+1;
3015 /* Kludge to deal with rcmd protocol */
3016 if (firstTime && looking_at(buf, &i, "\001*")) {
3017 DisplayFatalError(&buf[1], 0, 1);
3023 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3026 if (appData.debugMode)
3027 fprintf(debugFP, "ics_type %d\n", ics_type);
3030 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3031 ics_type = ICS_FICS;
3033 if (appData.debugMode)
3034 fprintf(debugFP, "ics_type %d\n", ics_type);
3037 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3038 ics_type = ICS_CHESSNET;
3040 if (appData.debugMode)
3041 fprintf(debugFP, "ics_type %d\n", ics_type);
3046 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3047 looking_at(buf, &i, "Logging you in as \"*\"") ||
3048 looking_at(buf, &i, "will be \"*\""))) {
3049 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3053 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3055 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3056 DisplayIcsInteractionTitle(buf);
3057 have_set_title = TRUE;
3060 /* skip finger notes */
3061 if (started == STARTED_NONE &&
3062 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3063 (buf[i] == '1' && buf[i+1] == '0')) &&
3064 buf[i+2] == ':' && buf[i+3] == ' ') {
3065 started = STARTED_CHATTER;
3071 // [HGM] seekgraph: recognize sought lines and end-of-sought message
3072 if(appData.seekGraph) {
3073 if(soughtPending && MatchSoughtLine(buf+i)) {
3074 i = strstr(buf+i, "rated") - buf;
3075 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3076 next_out = leftover_start = i;
3077 started = STARTED_CHATTER;
3078 suppressKibitz = TRUE;
3081 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3082 && looking_at(buf, &i, "* ads displayed")) {
3083 soughtPending = FALSE;
3088 if(appData.autoRefresh) {
3089 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3090 int s = (ics_type == ICS_ICC); // ICC format differs
3092 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3093 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3094 looking_at(buf, &i, "*% "); // eat prompt
3095 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3096 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3097 next_out = i; // suppress
3100 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3101 char *p = star_match[0];
3103 if(seekGraphUp) RemoveSeekAd(atoi(p));
3104 while(*p && *p++ != ' '); // next
3106 looking_at(buf, &i, "*% "); // eat prompt
3107 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3114 /* skip formula vars */
3115 if (started == STARTED_NONE &&
3116 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3117 started = STARTED_CHATTER;
3122 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3123 if (appData.autoKibitz && started == STARTED_NONE &&
3124 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3125 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3126 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3127 looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3128 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3129 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3130 suppressKibitz = TRUE;
3131 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3133 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3134 && (gameMode == IcsPlayingWhite)) ||
3135 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3136 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3137 started = STARTED_CHATTER; // own kibitz we simply discard
3139 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3140 parse_pos = 0; parse[0] = NULLCHAR;
3141 savingComment = TRUE;
3142 suppressKibitz = gameMode != IcsObserving ? 2 :
3143 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3147 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3148 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3149 && atoi(star_match[0])) {
3150 // suppress the acknowledgements of our own autoKibitz
3152 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3153 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3154 SendToPlayer(star_match[0], strlen(star_match[0]));
3155 if(looking_at(buf, &i, "*% ")) // eat prompt
3156 suppressKibitz = FALSE;
3160 } // [HGM] kibitz: end of patch
3162 if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3164 // [HGM] chat: intercept tells by users for which we have an open chat window
3166 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3167 looking_at(buf, &i, "* whispers:") ||
3168 looking_at(buf, &i, "* kibitzes:") ||
3169 looking_at(buf, &i, "* shouts:") ||
3170 looking_at(buf, &i, "* c-shouts:") ||
3171 looking_at(buf, &i, "--> * ") ||
3172 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3173 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3174 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3175 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3177 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3178 chattingPartner = -1;
3180 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3181 for(p=0; p<MAX_CHAT; p++) {
3182 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3183 talker[0] = '['; strcat(talker, "] ");
3184 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3185 chattingPartner = p; break;
3188 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3189 for(p=0; p<MAX_CHAT; p++) {
3190 if(!strcmp("kibitzes", chatPartner[p])) {
3191 talker[0] = '['; strcat(talker, "] ");
3192 chattingPartner = p; break;
3195 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3196 for(p=0; p<MAX_CHAT; p++) {
3197 if(!strcmp("whispers", chatPartner[p])) {
3198 talker[0] = '['; strcat(talker, "] ");
3199 chattingPartner = p; break;
3202 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3203 if(buf[i-8] == '-' && buf[i-3] == 't')
3204 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3205 if(!strcmp("c-shouts", chatPartner[p])) {
3206 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3207 chattingPartner = p; break;
3210 if(chattingPartner < 0)
3211 for(p=0; p<MAX_CHAT; p++) {
3212 if(!strcmp("shouts", chatPartner[p])) {
3213 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3214 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3215 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3216 chattingPartner = p; break;
3220 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3221 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3222 talker[0] = 0; Colorize(ColorTell, FALSE);
3223 chattingPartner = p; break;
3225 if(chattingPartner<0) i = oldi; else {
3226 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3227 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3228 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3229 started = STARTED_COMMENT;
3230 parse_pos = 0; parse[0] = NULLCHAR;
3231 savingComment = 3 + chattingPartner; // counts as TRUE
3232 suppressKibitz = TRUE;
3235 } // [HGM] chat: end of patch
3238 if (appData.zippyTalk || appData.zippyPlay) {
3239 /* [DM] Backup address for color zippy lines */
3241 if (loggedOn == TRUE)
3242 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3243 (appData.zippyPlay && ZippyMatch(buf, &backup)));
3245 } // [DM] 'else { ' deleted
3247 /* Regular tells and says */
3248 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3249 looking_at(buf, &i, "* (your partner) tells you: ") ||
3250 looking_at(buf, &i, "* says: ") ||
3251 /* Don't color "message" or "messages" output */
3252 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3253 looking_at(buf, &i, "*. * at *:*: ") ||
3254 looking_at(buf, &i, "--* (*:*): ") ||
3255 /* Message notifications (same color as tells) */
3256 looking_at(buf, &i, "* has left a message ") ||
3257 looking_at(buf, &i, "* just sent you a message:\n") ||
3258 /* Whispers and kibitzes */
3259 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3260 looking_at(buf, &i, "* kibitzes: ") ||
3262 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3264 if (tkind == 1 && strchr(star_match[0], ':')) {
3265 /* Avoid "tells you:" spoofs in channels */
3268 if (star_match[0][0] == NULLCHAR ||
3269 strchr(star_match[0], ' ') ||
3270 (tkind == 3 && strchr(star_match[1], ' '))) {
3271 /* Reject bogus matches */
3274 if (appData.colorize) {
3275 if (oldi > next_out) {
3276 SendToPlayer(&buf[next_out], oldi - next_out);
3281 Colorize(ColorTell, FALSE);
3282 curColor = ColorTell;
3285 Colorize(ColorKibitz, FALSE);
3286 curColor = ColorKibitz;
3289 p = strrchr(star_match[1], '(');
3296 Colorize(ColorChannel1, FALSE);
3297 curColor = ColorChannel1;
3299 Colorize(ColorChannel, FALSE);
3300 curColor = ColorChannel;
3304 curColor = ColorNormal;
3308 if (started == STARTED_NONE && appData.autoComment &&
3309 (gameMode == IcsObserving ||
3310 gameMode == IcsPlayingWhite ||
3311 gameMode == IcsPlayingBlack)) {
3312 parse_pos = i - oldi;
3313 memcpy(parse, &buf[oldi], parse_pos);
3314 parse[parse_pos] = NULLCHAR;
3315 started = STARTED_COMMENT;
3316 savingComment = TRUE;
3318 started = STARTED_CHATTER;
3319 savingComment = FALSE;
3326 if (looking_at(buf, &i, "* s-shouts: ") ||
3327 looking_at(buf, &i, "* c-shouts: ")) {
3328 if (appData.colorize) {
3329 if (oldi > next_out) {
3330 SendToPlayer(&buf[next_out], oldi - next_out);
3333 Colorize(ColorSShout, FALSE);
3334 curColor = ColorSShout;
3337 started = STARTED_CHATTER;
3341 if (looking_at(buf, &i, "--->")) {
3346 if (looking_at(buf, &i, "* shouts: ") ||
3347 looking_at(buf, &i, "--> ")) {
3348 if (appData.colorize) {
3349 if (oldi > next_out) {
3350 SendToPlayer(&buf[next_out], oldi - next_out);
3353 Colorize(ColorShout, FALSE);
3354 curColor = ColorShout;
3357 started = STARTED_CHATTER;
3361 if (looking_at( buf, &i, "Challenge:")) {
3362 if (appData.colorize) {
3363 if (oldi > next_out) {
3364 SendToPlayer(&buf[next_out], oldi - next_out);
3367 Colorize(ColorChallenge, FALSE);
3368 curColor = ColorChallenge;
3374 if (looking_at(buf, &i, "* offers you") ||
3375 looking_at(buf, &i, "* offers to be") ||
3376 looking_at(buf, &i, "* would like to") ||
3377 looking_at(buf, &i, "* requests to") ||
3378 looking_at(buf, &i, "Your opponent offers") ||
3379 looking_at(buf, &i, "Your opponent requests")) {
3381 if (appData.colorize) {
3382 if (oldi > next_out) {
3383 SendToPlayer(&buf[next_out], oldi - next_out);
3386 Colorize(ColorRequest, FALSE);
3387 curColor = ColorRequest;
3392 if (looking_at(buf, &i, "* (*) seeking")) {
3393 if (appData.colorize) {
3394 if (oldi > next_out) {
3395 SendToPlayer(&buf[next_out], oldi - next_out);
3398 Colorize(ColorSeek, FALSE);
3399 curColor = ColorSeek;
3404 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3406 if (looking_at(buf, &i, "\\ ")) {
3407 if (prevColor != ColorNormal) {
3408 if (oldi > next_out) {
3409 SendToPlayer(&buf[next_out], oldi - next_out);
3412 Colorize(prevColor, TRUE);
3413 curColor = prevColor;
3415 if (savingComment) {
3416 parse_pos = i - oldi;
3417 memcpy(parse, &buf[oldi], parse_pos);
3418 parse[parse_pos] = NULLCHAR;
3419 started = STARTED_COMMENT;
3420 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3421 chattingPartner = savingComment - 3; // kludge to remember the box
3423 started = STARTED_CHATTER;
3428 if (looking_at(buf, &i, "Black Strength :") ||
3429 looking_at(buf, &i, "<<< style 10 board >>>") ||
3430 looking_at(buf, &i, "<10>") ||
3431 looking_at(buf, &i, "#@#")) {
3432 /* Wrong board style */
3434 SendToICS(ics_prefix);
3435 SendToICS("set style 12\n");
3436 SendToICS(ics_prefix);
3437 SendToICS("refresh\n");
3441 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3443 have_sent_ICS_logon = 1;
3447 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3448 (looking_at(buf, &i, "\n<12> ") ||
3449 looking_at(buf, &i, "<12> "))) {
3451 if (oldi > next_out) {
3452 SendToPlayer(&buf[next_out], oldi - next_out);
3455 started = STARTED_BOARD;
3460 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3461 looking_at(buf, &i, "<b1> ")) {
3462 if (oldi > next_out) {
3463 SendToPlayer(&buf[next_out], oldi - next_out);
3466 started = STARTED_HOLDINGS;
3471 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3473 /* Header for a move list -- first line */
3475 switch (ics_getting_history) {
3479 case BeginningOfGame:
3480 /* User typed "moves" or "oldmoves" while we
3481 were idle. Pretend we asked for these
3482 moves and soak them up so user can step
3483 through them and/or save them.
3486 gameMode = IcsObserving;
3489 ics_getting_history = H_GOT_UNREQ_HEADER;
3491 case EditGame: /*?*/
3492 case EditPosition: /*?*/
3493 /* Should above feature work in these modes too? */
3494 /* For now it doesn't */
3495 ics_getting_history = H_GOT_UNWANTED_HEADER;
3498 ics_getting_history = H_GOT_UNWANTED_HEADER;
3503 /* Is this the right one? */
3504 if (gameInfo.white && gameInfo.black &&
3505 strcmp(gameInfo.white, star_match[0]) == 0 &&
3506 strcmp(gameInfo.black, star_match[2]) == 0) {
3508 ics_getting_history = H_GOT_REQ_HEADER;
3511 case H_GOT_REQ_HEADER:
3512 case H_GOT_UNREQ_HEADER:
3513 case H_GOT_UNWANTED_HEADER:
3514 case H_GETTING_MOVES:
3515 /* Should not happen */
3516 DisplayError(_("Error gathering move list: two headers"), 0);
3517 ics_getting_history = H_FALSE;
3521 /* Save player ratings into gameInfo if needed */
3522 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3523 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3524 (gameInfo.whiteRating == -1 ||
3525 gameInfo.blackRating == -1)) {
3527 gameInfo.whiteRating = string_to_rating(star_match[1]);
3528 gameInfo.blackRating = string_to_rating(star_match[3]);
3529 if (appData.debugMode)
3530 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3531 gameInfo.whiteRating, gameInfo.blackRating);
3536 if (looking_at(buf, &i,
3537 "* * match, initial time: * minute*, increment: * second")) {
3538 /* Header for a move list -- second line */
3539 /* Initial board will follow if this is a wild game */
3540 if (gameInfo.event != NULL) free(gameInfo.event);
3541 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3542 gameInfo.event = StrSave(str);
3543 /* [HGM] we switched variant. Translate boards if needed. */
3544 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3548 if (looking_at(buf, &i, "Move ")) {
3549 /* Beginning of a move list */
3550 switch (ics_getting_history) {
3552 /* Normally should not happen */
3553 /* Maybe user hit reset while we were parsing */
3556 /* Happens if we are ignoring a move list that is not
3557 * the one we just requested. Common if the user
3558 * tries to observe two games without turning off
3561 case H_GETTING_MOVES:
3562 /* Should not happen */
3563 DisplayError(_("Error gathering move list: nested"), 0);
3564 ics_getting_history = H_FALSE;
3566 case H_GOT_REQ_HEADER:
3567 ics_getting_history = H_GETTING_MOVES;
3568 started = STARTED_MOVES;
3570 if (oldi > next_out) {
3571 SendToPlayer(&buf[next_out], oldi - next_out);
3574 case H_GOT_UNREQ_HEADER:
3575 ics_getting_history = H_GETTING_MOVES;
3576 started = STARTED_MOVES_NOHIDE;
3579 case H_GOT_UNWANTED_HEADER:
3580 ics_getting_history = H_FALSE;
3586 if (looking_at(buf, &i, "% ") ||
3587 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3588 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3589 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3590 soughtPending = FALSE;
3594 if(suppressKibitz) next_out = i;
3595 savingComment = FALSE;
3599 case STARTED_MOVES_NOHIDE:
3600 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3601 parse[parse_pos + i - oldi] = NULLCHAR;
3602 ParseGameHistory(parse);
3604 if (appData.zippyPlay && first.initDone) {
3605 FeedMovesToProgram(&first, forwardMostMove);
3606 if (gameMode == IcsPlayingWhite) {
3607 if (WhiteOnMove(forwardMostMove)) {
3608 if (first.sendTime) {
3609 if (first.useColors) {
3610 SendToProgram("black\n", &first);
3612 SendTimeRemaining(&first, TRUE);
3614 if (first.useColors) {
3615 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3617 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3618 first.maybeThinking = TRUE;
3620 if (first.usePlayother) {
3621 if (first.sendTime) {
3622 SendTimeRemaining(&first, TRUE);
3624 SendToProgram("playother\n", &first);
3630 } else if (gameMode == IcsPlayingBlack) {
3631 if (!WhiteOnMove(forwardMostMove)) {
3632 if (first.sendTime) {
3633 if (first.useColors) {
3634 SendToProgram("white\n", &first);
3636 SendTimeRemaining(&first, FALSE);
3638 if (first.useColors) {
3639 SendToProgram("black\n", &first);
3641 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3642 first.maybeThinking = TRUE;
3644 if (first.usePlayother) {
3645 if (first.sendTime) {
3646 SendTimeRemaining(&first, FALSE);
3648 SendToProgram("playother\n", &first);
3657 if (gameMode == IcsObserving && ics_gamenum == -1) {
3658 /* Moves came from oldmoves or moves command
3659 while we weren't doing anything else.
3661 currentMove = forwardMostMove;
3662 ClearHighlights();/*!!could figure this out*/
3663 flipView = appData.flipView;
3664 DrawPosition(TRUE, boards[currentMove]);
3665 DisplayBothClocks();
3666 snprintf(str, MSG_SIZ, "%s %s %s",
3667 gameInfo.white, _("vs."), gameInfo.black);
3671 /* Moves were history of an active game */
3672 if (gameInfo.resultDetails != NULL) {
3673 free(gameInfo.resultDetails);
3674 gameInfo.resultDetails = NULL;
3677 HistorySet(parseList, backwardMostMove,
3678 forwardMostMove, currentMove-1);
3679 DisplayMove(currentMove - 1);
3680 if (started == STARTED_MOVES) next_out = i;
3681 started = STARTED_NONE;
3682 ics_getting_history = H_FALSE;
3685 case STARTED_OBSERVE:
3686 started = STARTED_NONE;
3687 SendToICS(ics_prefix);
3688 SendToICS("refresh\n");
3694 if(bookHit) { // [HGM] book: simulate book reply
3695 static char bookMove[MSG_SIZ]; // a bit generous?
3697 programStats.nodes = programStats.depth = programStats.time =
3698 programStats.score = programStats.got_only_move = 0;
3699 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3701 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3702 strcat(bookMove, bookHit);
3703 HandleMachineMove(bookMove, &first);
3708 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3709 started == STARTED_HOLDINGS ||
3710 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3711 /* Accumulate characters in move list or board */
3712 parse[parse_pos++] = buf[i];
3715 /* Start of game messages. Mostly we detect start of game
3716 when the first board image arrives. On some versions
3717 of the ICS, though, we need to do a "refresh" after starting
3718 to observe in order to get the current board right away. */
3719 if (looking_at(buf, &i, "Adding game * to observation list")) {
3720 started = STARTED_OBSERVE;
3724 /* Handle auto-observe */
3725 if (appData.autoObserve &&
3726 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3727 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3729 /* Choose the player that was highlighted, if any. */
3730 if (star_match[0][0] == '\033' ||
3731 star_match[1][0] != '\033') {
3732 player = star_match[0];
3734 player = star_match[2];
3736 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3737 ics_prefix, StripHighlightAndTitle(player));
3740 /* Save ratings from notify string */
3741 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3742 player1Rating = string_to_rating(star_match[1]);
3743 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3744 player2Rating = string_to_rating(star_match[3]);
3746 if (appData.debugMode)
3748 "Ratings from 'Game notification:' %s %d, %s %d\n",
3749 player1Name, player1Rating,
3750 player2Name, player2Rating);
3755 /* Deal with automatic examine mode after a game,
3756 and with IcsObserving -> IcsExamining transition */
3757 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3758 looking_at(buf, &i, "has made you an examiner of game *")) {
3760 int gamenum = atoi(star_match[0]);
3761 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3762 gamenum == ics_gamenum) {
3763 /* We were already playing or observing this game;
3764 no need to refetch history */
3765 gameMode = IcsExamining;
3767 pauseExamForwardMostMove = forwardMostMove;
3768 } else if (currentMove < forwardMostMove) {
3769 ForwardInner(forwardMostMove);
3772 /* I don't think this case really can happen */
3773 SendToICS(ics_prefix);
3774 SendToICS("refresh\n");
3779 /* Error messages */
3780 // if (ics_user_moved) {
3781 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3782 if (looking_at(buf, &i, "Illegal move") ||
3783 looking_at(buf, &i, "Not a legal move") ||
3784 looking_at(buf, &i, "Your king is in check") ||
3785 looking_at(buf, &i, "It isn't your turn") ||
3786 looking_at(buf, &i, "It is not your move")) {
3788 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3789 currentMove = forwardMostMove-1;
3790 DisplayMove(currentMove - 1); /* before DMError */
3791 DrawPosition(FALSE, boards[currentMove]);
3792 SwitchClocks(forwardMostMove-1); // [HGM] race
3793 DisplayBothClocks();
3795 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3801 if (looking_at(buf, &i, "still have time") ||
3802 looking_at(buf, &i, "not out of time") ||
3803 looking_at(buf, &i, "either player is out of time") ||
3804 looking_at(buf, &i, "has timeseal; checking")) {
3805 /* We must have called his flag a little too soon */
3806 whiteFlag = blackFlag = FALSE;
3810 if (looking_at(buf, &i, "added * seconds to") ||
3811 looking_at(buf, &i, "seconds were added to")) {
3812 /* Update the clocks */
3813 SendToICS(ics_prefix);
3814 SendToICS("refresh\n");
3818 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3819 ics_clock_paused = TRUE;
3824 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3825 ics_clock_paused = FALSE;
3830 /* Grab player ratings from the Creating: message.
3831 Note we have to check for the special case when
3832 the ICS inserts things like [white] or [black]. */
3833 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3834 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3836 0 player 1 name (not necessarily white)
3838 2 empty, white, or black (IGNORED)
3839 3 player 2 name (not necessarily black)
3842 The names/ratings are sorted out when the game
3843 actually starts (below).
3845 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3846 player1Rating = string_to_rating(star_match[1]);
3847 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3848 player2Rating = string_to_rating(star_match[4]);
3850 if (appData.debugMode)
3852 "Ratings from 'Creating:' %s %d, %s %d\n",
3853 player1Name, player1Rating,
3854 player2Name, player2Rating);
3859 /* Improved generic start/end-of-game messages */
3860 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3861 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3862 /* If tkind == 0: */
3863 /* star_match[0] is the game number */
3864 /* [1] is the white player's name */
3865 /* [2] is the black player's name */
3866 /* For end-of-game: */