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;
1850 /* Pass data read from player on to ICS */
1853 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1854 if (outCount < count) {
1855 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1857 if(have_sent_ICS_logon == 2) {
1858 if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1859 fprintf(ini, "%s", message);
1860 have_sent_ICS_logon = 3;
1862 have_sent_ICS_logon = 1;
1863 } else if(have_sent_ICS_logon == 3) {
1864 fprintf(ini, "%s", message);
1866 have_sent_ICS_logon = 1;
1868 } else if (count < 0) {
1869 RemoveInputSource(isr);
1870 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1871 } else if (gotEof++ > 0) {
1872 RemoveInputSource(isr);
1873 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1879 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1880 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1881 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1882 SendToICS("date\n");
1883 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1886 /* added routine for printf style output to ics */
1888 ics_printf (char *format, ...)
1890 char buffer[MSG_SIZ];
1893 va_start(args, format);
1894 vsnprintf(buffer, sizeof(buffer), format, args);
1895 buffer[sizeof(buffer)-1] = '\0';
1903 int count, outCount, outError;
1905 if (icsPR == NoProc) return;
1908 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1909 if (outCount < count) {
1910 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1914 /* This is used for sending logon scripts to the ICS. Sending
1915 without a delay causes problems when using timestamp on ICC
1916 (at least on my machine). */
1918 SendToICSDelayed (char *s, long msdelay)
1920 int count, outCount, outError;
1922 if (icsPR == NoProc) return;
1925 if (appData.debugMode) {
1926 fprintf(debugFP, ">ICS: ");
1927 show_bytes(debugFP, s, count);
1928 fprintf(debugFP, "\n");
1930 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1932 if (outCount < count) {
1933 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1938 /* Remove all highlighting escape sequences in s
1939 Also deletes any suffix starting with '('
1942 StripHighlightAndTitle (char *s)
1944 static char retbuf[MSG_SIZ];
1947 while (*s != NULLCHAR) {
1948 while (*s == '\033') {
1949 while (*s != NULLCHAR && !isalpha(*s)) s++;
1950 if (*s != NULLCHAR) s++;
1952 while (*s != NULLCHAR && *s != '\033') {
1953 if (*s == '(' || *s == '[') {
1964 /* Remove all highlighting escape sequences in s */
1966 StripHighlight (char *s)
1968 static char retbuf[MSG_SIZ];
1971 while (*s != NULLCHAR) {
1972 while (*s == '\033') {
1973 while (*s != NULLCHAR && !isalpha(*s)) s++;
1974 if (*s != NULLCHAR) s++;
1976 while (*s != NULLCHAR && *s != '\033') {
1984 char *variantNames[] = VARIANT_NAMES;
1986 VariantName (VariantClass v)
1988 return variantNames[v];
1992 /* Identify a variant from the strings the chess servers use or the
1993 PGN Variant tag names we use. */
1995 StringToVariant (char *e)
1999 VariantClass v = VariantNormal;
2000 int i, found = FALSE;
2006 /* [HGM] skip over optional board-size prefixes */
2007 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
2008 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
2009 while( *e++ != '_');
2012 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2016 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2017 if (StrCaseStr(e, variantNames[i])) {
2018 v = (VariantClass) i;
2025 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2026 || StrCaseStr(e, "wild/fr")
2027 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2028 v = VariantFischeRandom;
2029 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2030 (i = 1, p = StrCaseStr(e, "w"))) {
2032 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2039 case 0: /* FICS only, actually */
2041 /* Castling legal even if K starts on d-file */
2042 v = VariantWildCastle;
2047 /* Castling illegal even if K & R happen to start in
2048 normal positions. */
2049 v = VariantNoCastle;
2062 /* Castling legal iff K & R start in normal positions */
2068 /* Special wilds for position setup; unclear what to do here */
2069 v = VariantLoadable;
2072 /* Bizarre ICC game */
2073 v = VariantTwoKings;
2076 v = VariantKriegspiel;
2082 v = VariantFischeRandom;
2085 v = VariantCrazyhouse;
2088 v = VariantBughouse;
2094 /* Not quite the same as FICS suicide! */
2095 v = VariantGiveaway;
2101 v = VariantShatranj;
2104 /* Temporary names for future ICC types. The name *will* change in
2105 the next xboard/WinBoard release after ICC defines it. */
2143 v = VariantCapablanca;
2146 v = VariantKnightmate;
2152 v = VariantCylinder;
2158 v = VariantCapaRandom;
2161 v = VariantBerolina;
2173 /* Found "wild" or "w" in the string but no number;
2174 must assume it's normal chess. */
2178 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2179 if( (len >= MSG_SIZ) && appData.debugMode )
2180 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2182 DisplayError(buf, 0);
2188 if (appData.debugMode) {
2189 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2190 e, wnum, VariantName(v));
2195 static int leftover_start = 0, leftover_len = 0;
2196 char star_match[STAR_MATCH_N][MSG_SIZ];
2198 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2199 advance *index beyond it, and set leftover_start to the new value of
2200 *index; else return FALSE. If pattern contains the character '*', it
2201 matches any sequence of characters not containing '\r', '\n', or the
2202 character following the '*' (if any), and the matched sequence(s) are
2203 copied into star_match.
2206 looking_at ( char *buf, int *index, char *pattern)
2208 char *bufp = &buf[*index], *patternp = pattern;
2210 char *matchp = star_match[0];
2213 if (*patternp == NULLCHAR) {
2214 *index = leftover_start = bufp - buf;
2218 if (*bufp == NULLCHAR) return FALSE;
2219 if (*patternp == '*') {
2220 if (*bufp == *(patternp + 1)) {
2222 matchp = star_match[++star_count];
2226 } else if (*bufp == '\n' || *bufp == '\r') {
2228 if (*patternp == NULLCHAR)
2233 *matchp++ = *bufp++;
2237 if (*patternp != *bufp) return FALSE;
2244 SendToPlayer (char *data, int length)
2246 int error, outCount;
2247 outCount = OutputToProcess(NoProc, data, length, &error);
2248 if (outCount < length) {
2249 DisplayFatalError(_("Error writing to display"), error, 1);
2254 PackHolding (char packed[], char *holding)
2264 switch (runlength) {
2275 sprintf(q, "%d", runlength);
2287 /* Telnet protocol requests from the front end */
2289 TelnetRequest (unsigned char ddww, unsigned char option)
2291 unsigned char msg[3];
2292 int outCount, outError;
2294 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2296 if (appData.debugMode) {
2297 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2313 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2322 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2325 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2330 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2332 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2339 if (!appData.icsActive) return;
2340 TelnetRequest(TN_DO, TN_ECHO);
2346 if (!appData.icsActive) return;
2347 TelnetRequest(TN_DONT, TN_ECHO);
2351 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2353 /* put the holdings sent to us by the server on the board holdings area */
2354 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2358 if(gameInfo.holdingsWidth < 2) return;
2359 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2360 return; // prevent overwriting by pre-board holdings
2362 if( (int)lowestPiece >= BlackPawn ) {
2365 holdingsStartRow = BOARD_HEIGHT-1;
2368 holdingsColumn = BOARD_WIDTH-1;
2369 countsColumn = BOARD_WIDTH-2;
2370 holdingsStartRow = 0;
2374 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2375 board[i][holdingsColumn] = EmptySquare;
2376 board[i][countsColumn] = (ChessSquare) 0;
2378 while( (p=*holdings++) != NULLCHAR ) {
2379 piece = CharToPiece( ToUpper(p) );
2380 if(piece == EmptySquare) continue;
2381 /*j = (int) piece - (int) WhitePawn;*/
2382 j = PieceToNumber(piece);
2383 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2384 if(j < 0) continue; /* should not happen */
2385 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2386 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2387 board[holdingsStartRow+j*direction][countsColumn]++;
2393 VariantSwitch (Board board, VariantClass newVariant)
2395 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2396 static Board oldBoard;
2398 startedFromPositionFile = FALSE;
2399 if(gameInfo.variant == newVariant) return;
2401 /* [HGM] This routine is called each time an assignment is made to
2402 * gameInfo.variant during a game, to make sure the board sizes
2403 * are set to match the new variant. If that means adding or deleting
2404 * holdings, we shift the playing board accordingly
2405 * This kludge is needed because in ICS observe mode, we get boards
2406 * of an ongoing game without knowing the variant, and learn about the
2407 * latter only later. This can be because of the move list we requested,
2408 * in which case the game history is refilled from the beginning anyway,
2409 * but also when receiving holdings of a crazyhouse game. In the latter
2410 * case we want to add those holdings to the already received position.
2414 if (appData.debugMode) {
2415 fprintf(debugFP, "Switch board from %s to %s\n",
2416 VariantName(gameInfo.variant), VariantName(newVariant));
2417 setbuf(debugFP, NULL);
2419 shuffleOpenings = 0; /* [HGM] shuffle */
2420 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2424 newWidth = 9; newHeight = 9;
2425 gameInfo.holdingsSize = 7;
2426 case VariantBughouse:
2427 case VariantCrazyhouse:
2428 newHoldingsWidth = 2; break;
2432 newHoldingsWidth = 2;
2433 gameInfo.holdingsSize = 8;
2436 case VariantCapablanca:
2437 case VariantCapaRandom:
2440 newHoldingsWidth = gameInfo.holdingsSize = 0;
2443 if(newWidth != gameInfo.boardWidth ||
2444 newHeight != gameInfo.boardHeight ||
2445 newHoldingsWidth != gameInfo.holdingsWidth ) {
2447 /* shift position to new playing area, if needed */
2448 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2449 for(i=0; i<BOARD_HEIGHT; i++)
2450 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2451 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2453 for(i=0; i<newHeight; i++) {
2454 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2455 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2457 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2458 for(i=0; i<BOARD_HEIGHT; i++)
2459 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2460 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2463 board[HOLDINGS_SET] = 0;
2464 gameInfo.boardWidth = newWidth;
2465 gameInfo.boardHeight = newHeight;
2466 gameInfo.holdingsWidth = newHoldingsWidth;
2467 gameInfo.variant = newVariant;
2468 InitDrawingSizes(-2, 0);
2469 } else gameInfo.variant = newVariant;
2470 CopyBoard(oldBoard, board); // remember correctly formatted board
2471 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2472 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2475 static int loggedOn = FALSE;
2477 /*-- Game start info cache: --*/
2479 char gs_kind[MSG_SIZ];
2480 static char player1Name[128] = "";
2481 static char player2Name[128] = "";
2482 static char cont_seq[] = "\n\\ ";
2483 static int player1Rating = -1;
2484 static int player2Rating = -1;
2485 /*----------------------------*/
2487 ColorClass curColor = ColorNormal;
2488 int suppressKibitz = 0;
2491 Boolean soughtPending = FALSE;
2492 Boolean seekGraphUp;
2493 #define MAX_SEEK_ADS 200
2495 char *seekAdList[MAX_SEEK_ADS];
2496 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2497 float tcList[MAX_SEEK_ADS];
2498 char colorList[MAX_SEEK_ADS];
2499 int nrOfSeekAds = 0;
2500 int minRating = 1010, maxRating = 2800;
2501 int hMargin = 10, vMargin = 20, h, w;
2502 extern int squareSize, lineGap;
2507 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2508 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2509 if(r < minRating+100 && r >=0 ) r = minRating+100;
2510 if(r > maxRating) r = maxRating;
2511 if(tc < 1.f) tc = 1.f;
2512 if(tc > 95.f) tc = 95.f;
2513 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2514 y = ((double)r - minRating)/(maxRating - minRating)
2515 * (h-vMargin-squareSize/8-1) + vMargin;
2516 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2517 if(strstr(seekAdList[i], " u ")) color = 1;
2518 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2519 !strstr(seekAdList[i], "bullet") &&
2520 !strstr(seekAdList[i], "blitz") &&
2521 !strstr(seekAdList[i], "standard") ) color = 2;
2522 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2523 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2527 PlotSingleSeekAd (int i)
2533 AddAd (char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2535 char buf[MSG_SIZ], *ext = "";
2536 VariantClass v = StringToVariant(type);
2537 if(strstr(type, "wild")) {
2538 ext = type + 4; // append wild number
2539 if(v == VariantFischeRandom) type = "chess960"; else
2540 if(v == VariantLoadable) type = "setup"; else
2541 type = VariantName(v);
2543 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2544 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2545 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2546 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2547 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2548 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2549 seekNrList[nrOfSeekAds] = nr;
2550 zList[nrOfSeekAds] = 0;
2551 seekAdList[nrOfSeekAds++] = StrSave(buf);
2552 if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2557 EraseSeekDot (int i)
2559 int x = xList[i], y = yList[i], d=squareSize/4, k;
2560 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2561 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2562 // now replot every dot that overlapped
2563 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2564 int xx = xList[k], yy = yList[k];
2565 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2566 DrawSeekDot(xx, yy, colorList[k]);
2571 RemoveSeekAd (int nr)
2574 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2576 if(seekAdList[i]) free(seekAdList[i]);
2577 seekAdList[i] = seekAdList[--nrOfSeekAds];
2578 seekNrList[i] = seekNrList[nrOfSeekAds];
2579 ratingList[i] = ratingList[nrOfSeekAds];
2580 colorList[i] = colorList[nrOfSeekAds];
2581 tcList[i] = tcList[nrOfSeekAds];
2582 xList[i] = xList[nrOfSeekAds];
2583 yList[i] = yList[nrOfSeekAds];
2584 zList[i] = zList[nrOfSeekAds];
2585 seekAdList[nrOfSeekAds] = NULL;
2591 MatchSoughtLine (char *line)
2593 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2594 int nr, base, inc, u=0; char dummy;
2596 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2597 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2599 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2600 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2601 // match: compact and save the line
2602 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2612 if(!seekGraphUp) return FALSE;
2613 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2614 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2616 DrawSeekBackground(0, 0, w, h);
2617 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2618 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2619 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2620 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2622 DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2625 snprintf(buf, MSG_SIZ, "%d", i);
2626 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2629 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2630 for(i=1; i<100; i+=(i<10?1:5)) {
2631 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2632 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2633 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2635 snprintf(buf, MSG_SIZ, "%d", i);
2636 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2639 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2644 SeekGraphClick (ClickType click, int x, int y, int moving)
2646 static int lastDown = 0, displayed = 0, lastSecond;
2647 if(y < 0) return FALSE;
2648 if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2649 (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2650 if(!seekGraphUp) return FALSE;
2651 seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2652 DrawPosition(TRUE, NULL);
2655 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2656 if(click == Release || moving) return FALSE;
2658 soughtPending = TRUE;
2659 SendToICS(ics_prefix);
2660 SendToICS("sought\n"); // should this be "sought all"?
2661 } else { // issue challenge based on clicked ad
2662 int dist = 10000; int i, closest = 0, second = 0;
2663 for(i=0; i<nrOfSeekAds; i++) {
2664 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2665 if(d < dist) { dist = d; closest = i; }
2666 second += (d - zList[i] < 120); // count in-range ads
2667 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2671 second = (second > 1);
2672 if(displayed != closest || second != lastSecond) {
2673 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2674 lastSecond = second; displayed = closest;
2676 if(click == Press) {
2677 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2680 } // on press 'hit', only show info
2681 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2682 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2683 SendToICS(ics_prefix);
2685 return TRUE; // let incoming board of started game pop down the graph
2686 } else if(click == Release) { // release 'miss' is ignored
2687 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2688 if(moving == 2) { // right up-click
2689 nrOfSeekAds = 0; // refresh graph
2690 soughtPending = TRUE;
2691 SendToICS(ics_prefix);
2692 SendToICS("sought\n"); // should this be "sought all"?
2695 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2696 // press miss or release hit 'pop down' seek graph
2697 seekGraphUp = FALSE;
2698 DrawPosition(TRUE, NULL);
2704 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2706 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2707 #define STARTED_NONE 0
2708 #define STARTED_MOVES 1
2709 #define STARTED_BOARD 2
2710 #define STARTED_OBSERVE 3
2711 #define STARTED_HOLDINGS 4
2712 #define STARTED_CHATTER 5
2713 #define STARTED_COMMENT 6
2714 #define STARTED_MOVES_NOHIDE 7
2716 static int started = STARTED_NONE;
2717 static char parse[20000];
2718 static int parse_pos = 0;
2719 static char buf[BUF_SIZE + 1];
2720 static int firstTime = TRUE, intfSet = FALSE;
2721 static ColorClass prevColor = ColorNormal;
2722 static int savingComment = FALSE;
2723 static int cmatch = 0; // continuation sequence match
2730 int backup; /* [DM] For zippy color lines */
2732 char talker[MSG_SIZ]; // [HGM] chat
2735 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2737 if (appData.debugMode) {
2739 fprintf(debugFP, "<ICS: ");
2740 show_bytes(debugFP, data, count);
2741 fprintf(debugFP, "\n");
2745 if (appData.debugMode) { int f = forwardMostMove;
2746 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2747 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2748 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2751 /* If last read ended with a partial line that we couldn't parse,
2752 prepend it to the new read and try again. */
2753 if (leftover_len > 0) {
2754 for (i=0; i<leftover_len; i++)
2755 buf[i] = buf[leftover_start + i];
2758 /* copy new characters into the buffer */
2759 bp = buf + leftover_len;
2760 buf_len=leftover_len;
2761 for (i=0; i<count; i++)
2764 if (data[i] == '\r')
2767 // join lines split by ICS?
2768 if (!appData.noJoin)
2771 Joining just consists of finding matches against the
2772 continuation sequence, and discarding that sequence
2773 if found instead of copying it. So, until a match
2774 fails, there's nothing to do since it might be the
2775 complete sequence, and thus, something we don't want
2778 if (data[i] == cont_seq[cmatch])
2781 if (cmatch == strlen(cont_seq))
2783 cmatch = 0; // complete match. just reset the counter
2786 it's possible for the ICS to not include the space
2787 at the end of the last word, making our [correct]
2788 join operation fuse two separate words. the server
2789 does this when the space occurs at the width setting.
2791 if (!buf_len || buf[buf_len-1] != ' ')
2802 match failed, so we have to copy what matched before
2803 falling through and copying this character. In reality,
2804 this will only ever be just the newline character, but
2805 it doesn't hurt to be precise.
2807 strncpy(bp, cont_seq, cmatch);
2819 buf[buf_len] = NULLCHAR;
2820 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2825 while (i < buf_len) {
2826 /* Deal with part of the TELNET option negotiation
2827 protocol. We refuse to do anything beyond the
2828 defaults, except that we allow the WILL ECHO option,
2829 which ICS uses to turn off password echoing when we are
2830 directly connected to it. We reject this option
2831 if localLineEditing mode is on (always on in xboard)
2832 and we are talking to port 23, which might be a real
2833 telnet server that will try to keep WILL ECHO on permanently.
2835 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2836 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2837 unsigned char option;
2839 switch ((unsigned char) buf[++i]) {
2841 if (appData.debugMode)
2842 fprintf(debugFP, "\n<WILL ");
2843 switch (option = (unsigned char) buf[++i]) {
2845 if (appData.debugMode)
2846 fprintf(debugFP, "ECHO ");
2847 /* Reply only if this is a change, according
2848 to the protocol rules. */
2849 if (remoteEchoOption) break;
2850 if (appData.localLineEditing &&
2851 atoi(appData.icsPort) == TN_PORT) {
2852 TelnetRequest(TN_DONT, TN_ECHO);
2855 TelnetRequest(TN_DO, TN_ECHO);
2856 remoteEchoOption = TRUE;
2860 if (appData.debugMode)
2861 fprintf(debugFP, "%d ", option);
2862 /* Whatever this is, we don't want it. */
2863 TelnetRequest(TN_DONT, option);
2868 if (appData.debugMode)
2869 fprintf(debugFP, "\n<WONT ");
2870 switch (option = (unsigned char) buf[++i]) {
2872 if (appData.debugMode)
2873 fprintf(debugFP, "ECHO ");
2874 /* Reply only if this is a change, according
2875 to the protocol rules. */
2876 if (!remoteEchoOption) break;
2878 TelnetRequest(TN_DONT, TN_ECHO);
2879 remoteEchoOption = FALSE;
2882 if (appData.debugMode)
2883 fprintf(debugFP, "%d ", (unsigned char) option);
2884 /* Whatever this is, it must already be turned
2885 off, because we never agree to turn on
2886 anything non-default, so according to the
2887 protocol rules, we don't reply. */
2892 if (appData.debugMode)
2893 fprintf(debugFP, "\n<DO ");
2894 switch (option = (unsigned char) buf[++i]) {
2896 /* Whatever this is, we refuse to do it. */
2897 if (appData.debugMode)
2898 fprintf(debugFP, "%d ", option);
2899 TelnetRequest(TN_WONT, option);
2904 if (appData.debugMode)
2905 fprintf(debugFP, "\n<DONT ");
2906 switch (option = (unsigned char) buf[++i]) {
2908 if (appData.debugMode)
2909 fprintf(debugFP, "%d ", option);
2910 /* Whatever this is, we are already not doing
2911 it, because we never agree to do anything
2912 non-default, so according to the protocol
2913 rules, we don't reply. */
2918 if (appData.debugMode)
2919 fprintf(debugFP, "\n<IAC ");
2920 /* Doubled IAC; pass it through */
2924 if (appData.debugMode)
2925 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2926 /* Drop all other telnet commands on the floor */
2929 if (oldi > next_out)
2930 SendToPlayer(&buf[next_out], oldi - next_out);
2936 /* OK, this at least will *usually* work */
2937 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2941 if (loggedOn && !intfSet) {
2942 if (ics_type == ICS_ICC) {
2943 snprintf(str, MSG_SIZ,
2944 "/set-quietly interface %s\n/set-quietly style 12\n",
2946 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2947 strcat(str, "/set-2 51 1\n/set seek 1\n");
2948 } else if (ics_type == ICS_CHESSNET) {
2949 snprintf(str, MSG_SIZ, "/style 12\n");
2951 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2952 strcat(str, programVersion);
2953 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2954 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2955 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2957 strcat(str, "$iset nohighlight 1\n");
2959 strcat(str, "$iset lock 1\n$style 12\n");
2962 NotifyFrontendLogin();
2966 if (started == STARTED_COMMENT) {
2967 /* Accumulate characters in comment */
2968 parse[parse_pos++] = buf[i];
2969 if (buf[i] == '\n') {
2970 parse[parse_pos] = NULLCHAR;
2971 if(chattingPartner>=0) {
2973 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2974 OutputChatMessage(chattingPartner, mess);
2975 chattingPartner = -1;
2976 next_out = i+1; // [HGM] suppress printing in ICS window
2978 if(!suppressKibitz) // [HGM] kibitz
2979 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2980 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2981 int nrDigit = 0, nrAlph = 0, j;
2982 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2983 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2984 parse[parse_pos] = NULLCHAR;
2985 // try to be smart: if it does not look like search info, it should go to
2986 // ICS interaction window after all, not to engine-output window.
2987 for(j=0; j<parse_pos; j++) { // count letters and digits
2988 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2989 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2990 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2992 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2993 int depth=0; float score;
2994 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2995 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2996 pvInfoList[forwardMostMove-1].depth = depth;
2997 pvInfoList[forwardMostMove-1].score = 100*score;
2999 OutputKibitz(suppressKibitz, parse);
3002 if(gameMode == IcsObserving) // restore original ICS messages
3003 snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3005 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3006 SendToPlayer(tmp, strlen(tmp));
3008 next_out = i+1; // [HGM] suppress printing in ICS window
3010 started = STARTED_NONE;
3012 /* Don't match patterns against characters in comment */
3017 if (started == STARTED_CHATTER) {
3018 if (buf[i] != '\n') {
3019 /* Don't match patterns against characters in chatter */
3023 started = STARTED_NONE;
3024 if(suppressKibitz) next_out = i+1;
3027 /* Kludge to deal with rcmd protocol */
3028 if (firstTime && looking_at(buf, &i, "\001*")) {
3029 DisplayFatalError(&buf[1], 0, 1);
3035 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3038 if (appData.debugMode)
3039 fprintf(debugFP, "ics_type %d\n", ics_type);
3042 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3043 ics_type = ICS_FICS;
3045 if (appData.debugMode)
3046 fprintf(debugFP, "ics_type %d\n", ics_type);
3049 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3050 ics_type = ICS_CHESSNET;
3052 if (appData.debugMode)
3053 fprintf(debugFP, "ics_type %d\n", ics_type);
3058 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3059 looking_at(buf, &i, "Logging you in as \"*\"") ||
3060 looking_at(buf, &i, "will be \"*\""))) {
3061 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3065 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3067 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3068 DisplayIcsInteractionTitle(buf);
3069 have_set_title = TRUE;
3072 /* skip finger notes */
3073 if (started == STARTED_NONE &&
3074 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3075 (buf[i] == '1' && buf[i+1] == '0')) &&
3076 buf[i+2] == ':' && buf[i+3] == ' ') {
3077 started = STARTED_CHATTER;
3083 // [HGM] seekgraph: recognize sought lines and end-of-sought message
3084 if(appData.seekGraph) {
3085 if(soughtPending && MatchSoughtLine(buf+i)) {
3086 i = strstr(buf+i, "rated") - buf;
3087 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3088 next_out = leftover_start = i;
3089 started = STARTED_CHATTER;
3090 suppressKibitz = TRUE;
3093 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3094 && looking_at(buf, &i, "* ads displayed")) {
3095 soughtPending = FALSE;
3100 if(appData.autoRefresh) {
3101 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3102 int s = (ics_type == ICS_ICC); // ICC format differs
3104 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3105 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3106 looking_at(buf, &i, "*% "); // eat prompt
3107 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3108 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3109 next_out = i; // suppress
3112 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3113 char *p = star_match[0];
3115 if(seekGraphUp) RemoveSeekAd(atoi(p));
3116 while(*p && *p++ != ' '); // next
3118 looking_at(buf, &i, "*% "); // eat prompt
3119 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3126 /* skip formula vars */
3127 if (started == STARTED_NONE &&
3128 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3129 started = STARTED_CHATTER;
3134 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3135 if (appData.autoKibitz && started == STARTED_NONE &&
3136 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3137 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3138 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3139 looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3140 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3141 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3142 suppressKibitz = TRUE;
3143 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3145 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3146 && (gameMode == IcsPlayingWhite)) ||
3147 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3148 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3149 started = STARTED_CHATTER; // own kibitz we simply discard
3151 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3152 parse_pos = 0; parse[0] = NULLCHAR;
3153 savingComment = TRUE;
3154 suppressKibitz = gameMode != IcsObserving ? 2 :
3155 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3159 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3160 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3161 && atoi(star_match[0])) {
3162 // suppress the acknowledgements of our own autoKibitz
3164 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3165 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3166 SendToPlayer(star_match[0], strlen(star_match[0]));
3167 if(looking_at(buf, &i, "*% ")) // eat prompt
3168 suppressKibitz = FALSE;
3172 } // [HGM] kibitz: end of patch
3174 if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3176 // [HGM] chat: intercept tells by users for which we have an open chat window
3178 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3179 looking_at(buf, &i, "* whispers:") ||
3180 looking_at(buf, &i, "* kibitzes:") ||
3181 looking_at(buf, &i, "* shouts:") ||
3182 looking_at(buf, &i, "* c-shouts:") ||
3183 looking_at(buf, &i, "--> * ") ||
3184 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3185 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3186 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3187 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3189 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3190 chattingPartner = -1;
3192 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3193 for(p=0; p<MAX_CHAT; p++) {
3194 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3195 talker[0] = '['; strcat(talker, "] ");
3196 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3197 chattingPartner = p; break;
3200 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3201 for(p=0; p<MAX_CHAT; p++) {
3202 if(!strcmp("kibitzes", chatPartner[p])) {
3203 talker[0] = '['; strcat(talker, "] ");
3204 chattingPartner = p; break;
3207 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3208 for(p=0; p<MAX_CHAT; p++) {
3209 if(!strcmp("whispers", chatPartner[p])) {
3210 talker[0] = '['; strcat(talker, "] ");
3211 chattingPartner = p; break;
3214 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3215 if(buf[i-8] == '-' && buf[i-3] == 't')
3216 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3217 if(!strcmp("c-shouts", chatPartner[p])) {
3218 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3219 chattingPartner = p; break;
3222 if(chattingPartner < 0)
3223 for(p=0; p<MAX_CHAT; p++) {
3224 if(!strcmp("shouts", chatPartner[p])) {
3225 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3226 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3227 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3228 chattingPartner = p; break;
3232 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3233 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3234 talker[0] = 0; Colorize(ColorTell, FALSE);
3235 chattingPartner = p; break;
3237 if(chattingPartner<0) i = oldi; else {
3238 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3239 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3240 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3241 started = STARTED_COMMENT;
3242 parse_pos = 0; parse[0] = NULLCHAR;
3243 savingComment = 3 + chattingPartner; // counts as TRUE
3244 suppressKibitz = TRUE;
3247 } // [HGM] chat: end of patch
3250 if (appData.zippyTalk || appData.zippyPlay) {
3251 /* [DM] Backup address for color zippy lines */
3253 if (loggedOn == TRUE)
3254 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3255 (appData.zippyPlay && ZippyMatch(buf, &backup)));
3257 } // [DM] 'else { ' deleted
3259 /* Regular tells and says */
3260 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3261 looking_at(buf, &i, "* (your partner) tells you: ") ||
3262 looking_at(buf, &i, "* says: ") ||
3263 /* Don't color "message" or "messages" output */
3264 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3265 looking_at(buf, &i, "*. * at *:*: ") ||
3266 looking_at(buf, &i, "--* (*:*): ") ||
3267 /* Message notifications (same color as tells) */
3268 looking_at(buf, &i, "* has left a message ") ||
3269 looking_at(buf, &i, "* just sent you a message:\n") ||
3270 /* Whispers and kibitzes */
3271 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3272 looking_at(buf, &i, "* kibitzes: ") ||
3274 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3276 if (tkind == 1 && strchr(star_match[0], ':')) {
3277 /* Avoid "tells you:" spoofs in channels */
3280 if (star_match[0][0] == NULLCHAR ||
3281 strchr(star_match[0], ' ') ||
3282 (tkind == 3 && strchr(star_match[1], ' '))) {
3283 /* Reject bogus matches */
3286 if (appData.colorize) {
3287 if (oldi > next_out) {
3288 SendToPlayer(&buf[next_out], oldi - next_out);
3293 Colorize(ColorTell, FALSE);
3294 curColor = ColorTell;
3297 Colorize(ColorKibitz, FALSE);
3298 curColor = ColorKibitz;
3301 p = strrchr(star_match[1], '(');
3308 Colorize(ColorChannel1, FALSE);
3309 curColor = ColorChannel1;
3311 Colorize(ColorChannel, FALSE);
3312 curColor = ColorChannel;
3316 curColor = ColorNormal;
3320 if (started == STARTED_NONE && appData.autoComment &&
3321 (gameMode == IcsObserving ||
3322 gameMode == IcsPlayingWhite ||
3323 gameMode == IcsPlayingBlack)) {
3324 parse_pos = i - oldi;
3325 memcpy(parse, &buf[oldi], parse_pos);
3326 parse[parse_pos] = NULLCHAR;
3327 started = STARTED_COMMENT;
3328 savingComment = TRUE;
3330 started = STARTED_CHATTER;
3331 savingComment = FALSE;
3338 if (looking_at(buf, &i, "* s-shouts: ") ||
3339 looking_at(buf, &i, "* c-shouts: ")) {
3340 if (appData.colorize) {
3341 if (oldi > next_out) {
3342 SendToPlayer(&buf[next_out], oldi - next_out);
3345 Colorize(ColorSShout, FALSE);
3346 curColor = ColorSShout;
3349 started = STARTED_CHATTER;
3353 if (looking_at(buf, &i, "--->")) {
3358 if (looking_at(buf, &i, "* shouts: ") ||
3359 looking_at(buf, &i, "--> ")) {
3360 if (appData.colorize) {
3361 if (oldi > next_out) {
3362 SendToPlayer(&buf[next_out], oldi - next_out);
3365 Colorize(ColorShout, FALSE);
3366 curColor = ColorShout;
3369 started = STARTED_CHATTER;
3373 if (looking_at( buf, &i, "Challenge:")) {
3374 if (appData.colorize) {
3375 if (oldi > next_out) {
3376 SendToPlayer(&buf[next_out], oldi - next_out);
3379 Colorize(ColorChallenge, FALSE);
3380 curColor = ColorChallenge;
3386 if (looking_at(buf, &i, "* offers you") ||
3387 looking_at(buf, &i, "* offers to be") ||
3388 looking_at(buf, &i, "* would like to") ||
3389 looking_at(buf, &i, "* requests to") ||
3390 looking_at(buf, &i, "Your opponent offers") ||
3391 looking_at(buf, &i, "Your opponent requests")) {
3393 if (appData.colorize) {
3394 if (oldi > next_out) {
3395 SendToPlayer(&buf[next_out], oldi - next_out);
3398 Colorize(ColorRequest, FALSE);
3399 curColor = ColorRequest;
3404 if (looking_at(buf, &i, "* (*) seeking")) {
3405 if (appData.colorize) {
3406 if (oldi > next_out) {
3407 SendToPlayer(&buf[next_out], oldi - next_out);
3410 Colorize(ColorSeek, FALSE);
3411 curColor = ColorSeek;
3416 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3418 if (looking_at(buf, &i, "\\ ")) {
3419 if (prevColor != ColorNormal) {
3420 if (oldi > next_out) {
3421 SendToPlayer(&buf[next_out], oldi - next_out);
3424 Colorize(prevColor, TRUE);
3425 curColor = prevColor;
3427 if (savingComment) {
3428 parse_pos = i - oldi;
3429 memcpy(parse, &buf[oldi], parse_pos);
3430 parse[parse_pos] = NULLCHAR;
3431 started = STARTED_COMMENT;
3432 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3433 chattingPartner = savingComment - 3; // kludge to remember the box
3435 started = STARTED_CHATTER;
3440 if (looking_at(buf, &i, "Black Strength :") ||
3441 looking_at(buf, &i, "<<< style 10 board >>>") ||
3442 looking_at(buf, &i, "<10>") ||
3443 looking_at(buf, &i, "#@#")) {
3444 /* Wrong board style */
3446 SendToICS(ics_prefix);
3447 SendToICS("set style 12\n");
3448 SendToICS(ics_prefix);
3449 SendToICS("refresh\n");
3453 if (looking_at(buf, &i, "login:")) {
3454 if (!have_sent_ICS_logon) {
3456 have_sent_ICS_logon = 1;
3457 else // no init script was found
3458 have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // flag that we should capture username + password
3459 } else { // we have sent (or created) the InitScript, but apparently the ICS rejected it
3460 have_sent_ICS_logon = (appData.autoCreateLogon ? 2 : 1); // request creation of a new script
3465 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3466 (looking_at(buf, &i, "\n<12> ") ||
3467 looking_at(buf, &i, "<12> "))) {
3469 if (oldi > next_out) {
3470 SendToPlayer(&buf[next_out], oldi - next_out);
3473 started = STARTED_BOARD;
3478 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3479 looking_at(buf, &i, "<b1> ")) {
3480 if (oldi > next_out) {
3481 SendToPlayer(&buf[next_out], oldi - next_out);
3484 started = STARTED_HOLDINGS;
3489 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3491 /* Header for a move list -- first line */
3493 switch (ics_getting_history) {
3497 case BeginningOfGame:
3498 /* User typed "moves" or "oldmoves" while we
3499 were idle. Pretend we asked for these
3500 moves and soak them up so user can step
3501 through them and/or save them.
3504 gameMode = IcsObserving;
3507 ics_getting_history = H_GOT_UNREQ_HEADER;
3509 case EditGame: /*?*/
3510 case EditPosition: /*?*/
3511 /* Should above feature work in these modes too? */
3512 /* For now it doesn't */
3513 ics_getting_history = H_GOT_UNWANTED_HEADER;
3516 ics_getting_history = H_GOT_UNWANTED_HEADER;
3521 /* Is this the right one? */
3522 if (gameInfo.white && gameInfo.black &&
3523 strcmp(gameInfo.white, star_match[0]) == 0 &&
3524 strcmp(gameInfo.black, star_match[2]) == 0) {
3526 ics_getting_history = H_GOT_REQ_HEADER;
3529 case H_GOT_REQ_HEADER:
3530 case H_GOT_UNREQ_HEADER:
3531 case H_GOT_UNWANTED_HEADER:
3532 case H_GETTING_MOVES:
3533 /* Should not happen */
3534 DisplayError(_("Error gathering move list: two headers"), 0);
3535 ics_getting_history = H_FALSE;
3539 /* Save player ratings into gameInfo if needed */
3540 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3541 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3542 (gameInfo.whiteRating == -1 ||
3543 gameInfo.blackRating == -1)) {
3545 gameInfo.whiteRating = string_to_rating(star_match[1]);
3546 gameInfo.blackRating = string_to_rating(star_match[3]);
3547 if (appData.debugMode)
3548 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3549 gameInfo.whiteRating, gameInfo.blackRating);
3554 if (looking_at(buf, &i,
3555 "* * match, initial time: * minute*, increment: * second")) {
3556 /* Header for a move list -- second line */
3557 /* Initial board will follow if this is a wild game */
3558 if (gameInfo.event != NULL) free(gameInfo.event);
3559 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3560 gameInfo.event = StrSave(str);
3561 /* [HGM] we switched variant. Translate boards if needed. */
3562 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3566 if (looking_at(buf, &i, "Move ")) {
3567 /* Beginning of a move list */
3568 switch (ics_getting_history) {
3570 /* Normally should not happen */
3571 /* Maybe user hit reset while we were parsing */
3574 /* Happens if we are ignoring a move list that is not
3575 * the one we just requested. Common if the user
3576 * tries to observe two games without turning off
3579 case H_GETTING_MOVES:
3580 /* Should not happen */
3581 DisplayError(_("Error gathering move list: nested"), 0);
3582 ics_getting_history = H_FALSE;
3584 case H_GOT_REQ_HEADER:
3585 ics_getting_history = H_GETTING_MOVES;
3586 started = STARTED_MOVES;
3588 if (oldi > next_out) {
3589 SendToPlayer(&buf[next_out], oldi - next_out);
3592 case H_GOT_UNREQ_HEADER:
3593 ics_getting_history = H_GETTING_MOVES;
3594 started = STARTED_MOVES_NOHIDE;
3597 case H_GOT_UNWANTED_HEADER:
3598 ics_getting_history = H_FALSE;
3604 if (looking_at(buf, &i, "% ") ||
3605 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3606 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3607 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3608 soughtPending = FALSE;
3612 if(suppressKibitz) next_out = i;
3613 savingComment = FALSE;
3617 case STARTED_MOVES_NOHIDE:
3618 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3619 parse[parse_pos + i - oldi] = NULLCHAR;
3620 ParseGameHistory(parse);
3622 if (appData.zippyPlay && first.initDone) {
3623 FeedMovesToProgram(&first, forwardMostMove);
3624 if (gameMode == IcsPlayingWhite) {
3625 if (WhiteOnMove(forwardMostMove)) {
3626 if (first.sendTime) {
3627 if (first.useColors) {
3628 SendToProgram("black\n", &first);
3630 SendTimeRemaining(&first, TRUE);
3632 if (first.useColors) {
3633 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3635 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3636 first.maybeThinking = TRUE;
3638 if (first.usePlayother) {
3639 if (first.sendTime) {
3640 SendTimeRemaining(&first, TRUE);
3642 SendToProgram("playother\n", &first);
3648 } else if (gameMode == IcsPlayingBlack) {
3649 if (!WhiteOnMove(forwardMostMove)) {
3650 if (first.sendTime) {
3651 if (first.useColors) {
3652 SendToProgram("white\n", &first);
3654 SendTimeRemaining(&first, FALSE);
3656 if (first.useColors) {
3657 SendToProgram("black\n", &first);
3659 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3660 first.maybeThinking = TRUE;
3662 if (first.usePlayother) {
3663 if (first.sendTime) {
3664 SendTimeRemaining(&first, FALSE);
3666 SendToProgram("playother\n", &first);
3675 if (gameMode == IcsObserving && ics_gamenum == -1) {
3676 /* Moves came from oldmoves or moves command
3677 while we weren't doing anything else.
3679 currentMove = forwardMostMove;
3680 ClearHighlights();/*!!could figure this out*/
3681 flipView = appData.flipView;
3682 DrawPosition(TRUE, boards[currentMove]);
3683 DisplayBothClocks();
3684 snprintf(str, MSG_SIZ, "%s %s %s",
3685 gameInfo.white, _("vs."), gameInfo.black);
3689 /* Moves were history of an active game */
3690 if (gameInfo.resultDetails != NULL) {
3691 free(gameInfo.resultDetails);
3692 gameInfo.resultDetails = NULL;
3695 HistorySet(parseList, backwardMostMove,
3696 forwardMostMove, currentMove-1);
3697 DisplayMove(currentMove - 1);
3698 if (started == STARTED_MOVES) next_out = i;
3699 started = STARTED_NONE;
3700 ics_getting_history = H_FALSE;
3703 case STARTED_OBSERVE:
3704 started = STARTED_NONE;
3705 SendToICS(ics_prefix);
3706 SendToICS("refresh\n");
3712 if(bookHit) { // [HGM] book: simulate book reply
3713 static char bookMove[MSG_SIZ]; // a bit generous?
3715 programStats.nodes = programStats.depth = programStats.time =
3716 programStats.score = programStats.got_only_move = 0;
3717 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3719 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3720 strcat(bookMove, bookHit);
3721 HandleMachineMove(bookMove, &first);
3726 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3727 started == STARTED_HOLDINGS ||
3728 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3729 /* Accumulate characters in move list or board */
3730 parse[parse_pos++] = buf[i];
3733 /* Start of game messages. Mostly we detect start of game
3734 when the first board image arrives. On some versions
3735 of the ICS, though, we need to do a "refresh" after starting
3736 to observe in order to get the current board right away. */
3737 if (looking_at(buf, &i, "Adding game * to observation list")) {
3738 started = STARTED_OBSERVE;
3742 /* Handle auto-observe */
3743 if (appData.autoObserve &&
3744 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3745 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3747 /* Choose the player that was highlighted, if any. */
3748 if (star_match[0][0] == '\033' ||
3749 star_match[1][0] != '\033') {
3750 player = star_match[0];
3752 player = star_match[2];
3754 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3755 ics_prefix, StripHighlightAndTitle(player));
3758 /* Save ratings from notify string */
3759 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3760 player1Rating = string_to_rating(star_match[1]);
3761 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3762 player2Rating = string_to_rating(star_match[3]);
3764 if (appData.debugMode)
3766 "Ratings from 'Game notification:' %s %d, %s %d\n",
3767 player1Name, player1Rating,
3768 player2Name, player2Rating);
3773 /* Deal with automatic examine mode after a game,
3774 and with IcsObserving -> IcsExamining transition */
3775 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3776 looking_at(buf, &i, "has made you an examiner of game *")) {
3778 int gamenum = atoi(star_match[0]);
3779 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3780 gamenum == ics_gamenum) {
3781 /* We were already playing or observing this game;
3782 no need to refetch history */
3783 gameMode = IcsExamining;
3785 pauseExamForwardMostMove = forwardMostMove;
3786 } else if (currentMove < forwardMostMove) {
3787 ForwardInner(forwardMostMove);
3790 /* I don't think this case really can happen */
3791 SendToICS(ics_prefix);
3792 SendToICS("refresh\n");
3797 /* Error messages */
3798 // if (ics_user_moved) {
3799 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3800 if (looking_at(buf, &i, "Illegal move") ||
3801 looking_at(buf, &i, "Not a legal move") ||
3802 looking_at(buf, &i, "Your king is in check") ||
3803 looking_at(buf, &i, "It isn't your turn") ||
3804 looking_at(buf, &i, "It is not your move")) {
3806 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3807 currentMove = forwardMostMove-1;
3808 DisplayMove(currentMove - 1); /* before DMError */
3809 DrawPosition(FALSE, boards[currentMove]);
3810 SwitchClocks(forwardMostMove-1); // [HGM] race
3811 DisplayBothClocks();
3813 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3819 if (looking_at(buf, &i, "still have time") ||
3820 looking_at(buf, &i, "not out of time") ||
3821 looking_at(buf, &i, "either player is out of time") ||
3822 looking_at(buf, &i, "has timeseal; checking")) {
3823 /* We must have called his flag a little too soon */
3824 whiteFlag = blackFlag = FALSE;
3828 if (looking_at(buf, &i, "added * seconds to") ||
3829 looking_at(buf, &i, "seconds were added to")) {
3830 /* Update the clocks */
3831 SendToICS(ics_prefix);
3832 SendToICS("refresh\n");
3836 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3837 ics_clock_paused = TRUE;
3842 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3843 ics_clock_paused = FALSE;
3848 /* Grab player ratings from the Creating: message.
3849 Note we have to check for the special case when
3850 the ICS inserts things like [white] or [black]. */
3851 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3852 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3854 0 player 1 name (not necessarily white)
3856 2 empty, white, or black (IGNORED)
3857 3 player 2 name (not necessarily black)
3860 The names/ratings are sorted out when the game
3861 actually starts (below).
3863 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3864 player1Rating = string_to_rating(star_match[1]);
3865 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3866 player2Rating = string_to_rating(star_match[4]);