2 * backend.c -- Common back end for X and Windows NT versions of
4 * Copyright 1991 by Digital Equipment Corporation, Maynard,
7 * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8 * 2007, 2008, 2009, 2010, 2011, 2012 Free Software Foundation, Inc.
10 * Enhancements Copyright 2005 Alessandro Scotti
12 * The following terms apply to Digital Equipment Corporation's copyright
14 * ------------------------------------------------------------------------
17 * Permission to use, copy, modify, and distribute this software and its
18 * documentation for any purpose and without fee is hereby granted,
19 * provided that the above copyright notice appear in all copies and that
20 * both that copyright notice and this permission notice appear in
21 * supporting documentation, and that the name of Digital not be
22 * used in advertising or publicity pertaining to distribution of the
23 * software without specific, written prior permission.
25 * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27 * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
32 * ------------------------------------------------------------------------
34 * The following terms apply to the enhanced version of XBoard
35 * distributed by the Free Software Foundation:
36 * ------------------------------------------------------------------------
38 * GNU XBoard is free software: you can redistribute it and/or modify
39 * it under the terms of the GNU General Public License as published by
40 * the Free Software Foundation, either version 3 of the License, or (at
41 * your option) any later version.
43 * GNU XBoard is distributed in the hope that it will be useful, but
44 * WITHOUT ANY WARRANTY; without even the implied warranty of
45 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46 * General Public License for more details.
48 * You should have received a copy of the GNU General Public License
49 * along with this program. If not, see http://www.gnu.org/licenses/. *
51 *------------------------------------------------------------------------
52 ** See the file ChangeLog for a revision history. */
54 /* [AS] Also useful here for debugging */
58 int flock(int f, int code);
75 #include <sys/types.h>
84 #else /* not STDC_HEADERS */
87 # else /* not HAVE_STRING_H */
89 # endif /* not HAVE_STRING_H */
90 #endif /* not STDC_HEADERS */
93 # include <sys/fcntl.h>
94 #else /* not HAVE_SYS_FCNTL_H */
97 # endif /* HAVE_FCNTL_H */
98 #endif /* not HAVE_SYS_FCNTL_H */
100 #if TIME_WITH_SYS_TIME
101 # include <sys/time.h>
105 # include <sys/time.h>
111 #if defined(_amigados) && !defined(__GNUC__)
116 extern int gettimeofday(struct timeval *, struct timezone *);
124 #include "frontend.h"
131 #include "backendz.h"
135 # define _(s) gettext (s)
136 # define N_(s) gettext_noop (s)
137 # define T_(s) gettext(s)
150 int establish P((void));
151 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
152 char *buf, int count, int error));
153 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
154 char *buf, int count, int error));
155 void ics_printf P((char *format, ...));
156 void SendToICS P((char *s));
157 void SendToICSDelayed P((char *s, long msdelay));
158 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
159 void HandleMachineMove P((char *message, ChessProgramState *cps));
160 int AutoPlayOneMove P((void));
161 int LoadGameOneMove P((ChessMove readAhead));
162 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
163 int LoadPositionFromFile P((char *filename, int n, char *title));
164 int SavePositionToFile P((char *filename));
165 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
166 void ShowMove P((int fromX, int fromY, int toX, int toY));
167 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
168 /*char*/int promoChar));
169 void BackwardInner P((int target));
170 void ForwardInner P((int target));
171 int Adjudicate P((ChessProgramState *cps));
172 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
173 void EditPositionDone P((Boolean fakeRights));
174 void PrintOpponents P((FILE *fp));
175 void PrintPosition P((FILE *fp, int move));
176 void StartChessProgram P((ChessProgramState *cps));
177 void SendToProgram P((char *message, ChessProgramState *cps));
178 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
179 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
180 char *buf, int count, int error));
181 void SendTimeControl P((ChessProgramState *cps,
182 int mps, long tc, int inc, int sd, int st));
183 char *TimeControlTagValue P((void));
184 void Attention P((ChessProgramState *cps));
185 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
186 int ResurrectChessProgram P((void));
187 void DisplayComment P((int moveNumber, char *text));
188 void DisplayMove P((int moveNumber));
190 void ParseGameHistory P((char *game));
191 void ParseBoard12 P((char *string));
192 void KeepAlive P((void));
193 void StartClocks P((void));
194 void SwitchClocks P((int nr));
195 void StopClocks P((void));
196 void ResetClocks P((void));
197 char *PGNDate P((void));
198 void SetGameInfo P((void));
199 int RegisterMove P((void));
200 void MakeRegisteredMove P((void));
201 void TruncateGame P((void));
202 int looking_at P((char *, int *, char *));
203 void CopyPlayerNameIntoFileName P((char **, char *));
204 char *SavePart P((char *));
205 int SaveGameOldStyle P((FILE *));
206 int SaveGamePGN P((FILE *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
220 void NextMatchGame P((void));
221 int NextTourneyGame P((int nr, int *swap));
222 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
223 FILE *WriteTourneyFile P((char *results, FILE *f));
224 void DisplayTwoMachinesTitle P(());
225 static void ExcludeClick P((int index));
228 extern void ConsoleCreate();
231 ChessProgramState *WhitePlayer();
232 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
233 int VerifyDisplayMode P(());
235 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
236 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
237 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
238 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
239 void ics_update_width P((int new_width));
240 extern char installDir[MSG_SIZ];
241 VariantClass startVariant; /* [HGM] nicks: initial variant */
244 extern int tinyLayout, smallLayout;
245 ChessProgramStats programStats;
246 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
248 static int exiting = 0; /* [HGM] moved to top */
249 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
250 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
251 Board partnerBoard; /* [HGM] bughouse: for peeking at partner game */
252 int partnerHighlight[2];
253 Boolean partnerBoardValid = 0;
254 char partnerStatus[MSG_SIZ];
256 Boolean originalFlip;
257 Boolean twoBoards = 0;
258 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
259 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
260 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
261 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
262 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
263 int opponentKibitzes;
264 int lastSavedGame; /* [HGM] save: ID of game */
265 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
266 extern int chatCount;
268 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
269 char lastMsg[MSG_SIZ];
270 ChessSquare pieceSweep = EmptySquare;
271 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
272 int promoDefaultAltered;
274 /* States for ics_getting_history */
276 #define H_REQUESTED 1
277 #define H_GOT_REQ_HEADER 2
278 #define H_GOT_UNREQ_HEADER 3
279 #define H_GETTING_MOVES 4
280 #define H_GOT_UNWANTED_HEADER 5
282 /* whosays values for GameEnds */
291 /* Maximum number of games in a cmail message */
292 #define CMAIL_MAX_GAMES 20
294 /* Different types of move when calling RegisterMove */
296 #define CMAIL_RESIGN 1
298 #define CMAIL_ACCEPT 3
300 /* Different types of result to remember for each game */
301 #define CMAIL_NOT_RESULT 0
302 #define CMAIL_OLD_RESULT 1
303 #define CMAIL_NEW_RESULT 2
305 /* Telnet protocol constants */
316 safeStrCpy (char *dst, const char *src, size_t count)
319 assert( dst != NULL );
320 assert( src != NULL );
323 for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
324 if( i == count && dst[count-1] != NULLCHAR)
326 dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
327 if(appData.debugMode)
328 fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
334 /* Some compiler can't cast u64 to double
335 * This function do the job for us:
337 * We use the highest bit for cast, this only
338 * works if the highest bit is not
339 * in use (This should not happen)
341 * We used this for all compiler
344 u64ToDouble (u64 value)
347 u64 tmp = value & u64Const(0x7fffffffffffffff);
348 r = (double)(s64)tmp;
349 if (value & u64Const(0x8000000000000000))
350 r += 9.2233720368547758080e18; /* 2^63 */
354 /* Fake up flags for now, as we aren't keeping track of castling
355 availability yet. [HGM] Change of logic: the flag now only
356 indicates the type of castlings allowed by the rule of the game.
357 The actual rights themselves are maintained in the array
358 castlingRights, as part of the game history, and are not probed
364 int flags = F_ALL_CASTLE_OK;
365 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
366 switch (gameInfo.variant) {
368 flags &= ~F_ALL_CASTLE_OK;
369 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
370 flags |= F_IGNORE_CHECK;
372 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
375 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
377 case VariantKriegspiel:
378 flags |= F_KRIEGSPIEL_CAPTURE;
380 case VariantCapaRandom:
381 case VariantFischeRandom:
382 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
383 case VariantNoCastle:
384 case VariantShatranj:
388 flags &= ~F_ALL_CASTLE_OK;
396 FILE *gameFileFP, *debugFP, *serverFP;
397 char *currentDebugFile; // [HGM] debug split: to remember name
400 [AS] Note: sometimes, the sscanf() function is used to parse the input
401 into a fixed-size buffer. Because of this, we must be prepared to
402 receive strings as long as the size of the input buffer, which is currently
403 set to 4K for Windows and 8K for the rest.
404 So, we must either allocate sufficiently large buffers here, or
405 reduce the size of the input buffer in the input reading part.
408 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
409 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
410 char thinkOutput1[MSG_SIZ*10];
412 ChessProgramState first, second, pairing;
414 /* premove variables */
417 int premoveFromX = 0;
418 int premoveFromY = 0;
419 int premovePromoChar = 0;
421 Boolean alarmSounded;
422 /* end premove variables */
424 char *ics_prefix = "$";
425 int ics_type = ICS_GENERIC;
427 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
428 int pauseExamForwardMostMove = 0;
429 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
430 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
431 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
432 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
433 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
434 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
435 int whiteFlag = FALSE, blackFlag = FALSE;
436 int userOfferedDraw = FALSE;
437 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
438 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
439 int cmailMoveType[CMAIL_MAX_GAMES];
440 long ics_clock_paused = 0;
441 ProcRef icsPR = NoProc, cmailPR = NoProc;
442 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
443 GameMode gameMode = BeginningOfGame;
444 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
445 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
446 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
447 int hiddenThinkOutputState = 0; /* [AS] */
448 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
449 int adjudicateLossPlies = 6;
450 char white_holding[64], black_holding[64];
451 TimeMark lastNodeCountTime;
452 long lastNodeCount=0;
453 int shiftKey; // [HGM] set by mouse handler
455 int have_sent_ICS_logon = 0;
457 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
458 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
459 Boolean adjustedClock;
460 long timeControl_2; /* [AS] Allow separate time controls */
461 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
462 long timeRemaining[2][MAX_MOVES];
463 int matchGame = 0, nextGame = 0, roundNr = 0;
464 Boolean waitingForGame = FALSE;
465 TimeMark programStartTime, pauseStart;
466 char ics_handle[MSG_SIZ];
467 int have_set_title = 0;
469 /* animateTraining preserves the state of appData.animate
470 * when Training mode is activated. This allows the
471 * response to be animated when appData.animate == TRUE and
472 * appData.animateDragging == TRUE.
474 Boolean animateTraining;
480 Board boards[MAX_MOVES];
481 /* [HGM] Following 7 needed for accurate legality tests: */
482 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
483 signed char initialRights[BOARD_FILES];
484 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
485 int initialRulePlies, FENrulePlies;
486 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
488 Boolean shuffleOpenings;
489 int mute; // mute all sounds
491 // [HGM] vari: next 12 to save and restore variations
492 #define MAX_VARIATIONS 10
493 int framePtr = MAX_MOVES-1; // points to free stack entry
495 int savedFirst[MAX_VARIATIONS];
496 int savedLast[MAX_VARIATIONS];
497 int savedFramePtr[MAX_VARIATIONS];
498 char *savedDetails[MAX_VARIATIONS];
499 ChessMove savedResult[MAX_VARIATIONS];
501 void PushTail P((int firstMove, int lastMove));
502 Boolean PopTail P((Boolean annotate));
503 void PushInner P((int firstMove, int lastMove));
504 void PopInner P((Boolean annotate));
505 void CleanupTail P((void));
507 ChessSquare FIDEArray[2][BOARD_FILES] = {
508 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
509 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
510 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
511 BlackKing, BlackBishop, BlackKnight, BlackRook }
514 ChessSquare twoKingsArray[2][BOARD_FILES] = {
515 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
516 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
517 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
518 BlackKing, BlackKing, BlackKnight, BlackRook }
521 ChessSquare KnightmateArray[2][BOARD_FILES] = {
522 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
523 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
524 { BlackRook, BlackMan, BlackBishop, BlackQueen,
525 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
528 ChessSquare SpartanArray[2][BOARD_FILES] = {
529 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
530 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
531 { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
532 BlackDragon, BlackKing, BlackAngel, BlackAlfil }
535 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
536 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
537 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
538 { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
539 BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
542 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
543 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
544 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
545 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
546 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
549 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
550 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
551 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
552 { BlackRook, BlackKnight, BlackMan, BlackFerz,
553 BlackKing, BlackMan, BlackKnight, BlackRook }
557 #if (BOARD_FILES>=10)
558 ChessSquare ShogiArray[2][BOARD_FILES] = {
559 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
560 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
561 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
562 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
565 ChessSquare XiangqiArray[2][BOARD_FILES] = {
566 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
567 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
568 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
569 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
572 ChessSquare CapablancaArray[2][BOARD_FILES] = {
573 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
574 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
575 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
576 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
579 ChessSquare GreatArray[2][BOARD_FILES] = {
580 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
581 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
582 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
583 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
586 ChessSquare JanusArray[2][BOARD_FILES] = {
587 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
588 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
589 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
590 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
593 ChessSquare GrandArray[2][BOARD_FILES] = {
594 { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
595 WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
596 { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
597 BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
601 ChessSquare GothicArray[2][BOARD_FILES] = {
602 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
603 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
604 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
605 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
608 #define GothicArray CapablancaArray
612 ChessSquare FalconArray[2][BOARD_FILES] = {
613 { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
614 WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
615 { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
616 BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
619 #define FalconArray CapablancaArray
622 #else // !(BOARD_FILES>=10)
623 #define XiangqiPosition FIDEArray
624 #define CapablancaArray FIDEArray
625 #define GothicArray FIDEArray
626 #define GreatArray FIDEArray
627 #endif // !(BOARD_FILES>=10)
629 #if (BOARD_FILES>=12)
630 ChessSquare CourierArray[2][BOARD_FILES] = {
631 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
632 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
633 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
634 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
636 #else // !(BOARD_FILES>=12)
637 #define CourierArray CapablancaArray
638 #endif // !(BOARD_FILES>=12)
641 Board initialPosition;
644 /* Convert str to a rating. Checks for special cases of "----",
646 "++++", etc. Also strips ()'s */
648 string_to_rating (char *str)
650 while(*str && !isdigit(*str)) ++str;
652 return 0; /* One of the special "no rating" cases */
660 /* Init programStats */
661 programStats.movelist[0] = 0;
662 programStats.depth = 0;
663 programStats.nr_moves = 0;
664 programStats.moves_left = 0;
665 programStats.nodes = 0;
666 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
667 programStats.score = 0;
668 programStats.got_only_move = 0;
669 programStats.got_fail = 0;
670 programStats.line_is_book = 0;
675 { // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
676 if (appData.firstPlaysBlack) {
677 first.twoMachinesColor = "black\n";
678 second.twoMachinesColor = "white\n";
680 first.twoMachinesColor = "white\n";
681 second.twoMachinesColor = "black\n";
684 first.other = &second;
685 second.other = &first;
688 if(appData.timeOddsMode) {
689 norm = appData.timeOdds[0];
690 if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
692 first.timeOdds = appData.timeOdds[0]/norm;
693 second.timeOdds = appData.timeOdds[1]/norm;
696 if(programVersion) free(programVersion);
697 if (appData.noChessProgram) {
698 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
699 sprintf(programVersion, "%s", PACKAGE_STRING);
701 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
702 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
703 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
708 UnloadEngine (ChessProgramState *cps)
710 /* Kill off first chess program */
711 if (cps->isr != NULL)
712 RemoveInputSource(cps->isr);
715 if (cps->pr != NoProc) {
717 DoSleep( appData.delayBeforeQuit );
718 SendToProgram("quit\n", cps);
719 DoSleep( appData.delayAfterQuit );
720 DestroyChildProcess(cps->pr, cps->useSigterm);
723 if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
727 ClearOptions (ChessProgramState *cps)
730 cps->nrOptions = cps->comboCnt = 0;
731 for(i=0; i<MAX_OPTIONS; i++) {
732 cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
733 cps->option[i].textValue = 0;
737 char *engineNames[] = {
738 /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
739 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
741 /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
742 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
747 InitEngine (ChessProgramState *cps, int n)
748 { // [HGM] all engine initialiation put in a function that does one engine
752 cps->which = engineNames[n];
753 cps->maybeThinking = FALSE;
757 cps->sendDrawOffers = 1;
759 cps->program = appData.chessProgram[n];
760 cps->host = appData.host[n];
761 cps->dir = appData.directory[n];
762 cps->initString = appData.engInitString[n];
763 cps->computerString = appData.computerString[n];
764 cps->useSigint = TRUE;
765 cps->useSigterm = TRUE;
766 cps->reuse = appData.reuse[n];
767 cps->nps = appData.NPS[n]; // [HGM] nps: copy nodes per second
768 cps->useSetboard = FALSE;
770 cps->usePing = FALSE;
773 cps->usePlayother = FALSE;
774 cps->useColors = TRUE;
775 cps->useUsermove = FALSE;
776 cps->sendICS = FALSE;
777 cps->sendName = appData.icsActive;
778 cps->sdKludge = FALSE;
779 cps->stKludge = FALSE;
780 TidyProgramName(cps->program, cps->host, cps->tidy);
782 safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
783 cps->analysisSupport = 2; /* detect */
784 cps->analyzing = FALSE;
785 cps->initDone = FALSE;
787 /* New features added by Tord: */
788 cps->useFEN960 = FALSE;
789 cps->useOOCastle = TRUE;
790 /* End of new features added by Tord. */
791 cps->fenOverride = appData.fenOverride[n];
793 /* [HGM] time odds: set factor for each machine */
794 cps->timeOdds = appData.timeOdds[n];
796 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
797 cps->accumulateTC = appData.accumulateTC[n];
798 cps->maxNrOfSessions = 1;
803 cps->supportsNPS = UNKNOWN;
804 cps->memSize = FALSE;
805 cps->maxCores = FALSE;
806 cps->egtFormats[0] = NULLCHAR;
809 cps->optionSettings = appData.engOptions[n];
811 cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
812 cps->isUCI = appData.isUCI[n]; /* [AS] */
813 cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
815 if (appData.protocolVersion[n] > PROTOVER
816 || appData.protocolVersion[n] < 1)
821 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
822 appData.protocolVersion[n]);
823 if( (len >= MSG_SIZ) && appData.debugMode )
824 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
826 DisplayFatalError(buf, 0, 2);
830 cps->protocolVersion = appData.protocolVersion[n];
833 InitEngineUCI( installDir, cps ); // [HGM] moved here from winboard.c, to make available in xboard
834 ParseFeatures(appData.featureDefaults, cps);
837 ChessProgramState *savCps;
843 if(WaitForEngine(savCps, LoadEngine)) return;
844 CommonEngineInit(); // recalculate time odds
845 if(gameInfo.variant != StringToVariant(appData.variant)) {
846 // we changed variant when loading the engine; this forces us to reset
847 Reset(TRUE, savCps != &first);
848 EditGameEvent(); // for consistency with other path, as Reset changes mode
850 InitChessProgram(savCps, FALSE);
851 SendToProgram("force\n", savCps);
852 DisplayMessage("", "");
853 if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
854 for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
860 ReplaceEngine (ChessProgramState *cps, int n)
864 appData.noChessProgram = FALSE;
865 appData.clockMode = TRUE;
868 if(n) return; // only startup first engine immediately; second can wait
869 savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
873 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
874 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
876 static char resetOptions[] =
877 "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
878 "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
879 "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
880 "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
883 FloatToFront(char **list, char *engineLine)
885 char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
887 if(appData.recentEngines <= 0) return;
888 TidyProgramName(engineLine, "localhost", tidy+1);
889 tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
890 strncpy(buf+1, *list, MSG_SIZ-50);
891 if(p = strstr(buf, tidy)) { // tidy name appears in list
892 q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
893 while(*p++ = *++q); // squeeze out
895 strcat(tidy, buf+1); // put list behind tidy name
896 p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
897 if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
898 ASSIGN(*list, tidy+1);
901 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
904 Load (ChessProgramState *cps, int i)
906 char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
907 if(engineLine && engineLine[0]) { // an engine was selected from the combo box
908 snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
909 SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
910 ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
911 FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
912 appData.firstProtocolVersion = PROTOVER;
913 ParseArgsFromString(buf);
915 ReplaceEngine(cps, i);
916 FloatToFront(&appData.recentEngineList, engineLine);
920 while(q = strchr(p, SLASH)) p = q+1;
921 if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
922 if(engineDir[0] != NULLCHAR) {
923 ASSIGN(appData.directory[i], engineDir);
924 } else if(p != engineName) { // derive directory from engine path, when not given
926 ASSIGN(appData.directory[i], engineName);
928 if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
929 } else { ASSIGN(appData.directory[i], "."); }
931 if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
932 snprintf(command, MSG_SIZ, "%s %s", p, params);
935 ASSIGN(appData.chessProgram[i], p);
936 appData.isUCI[i] = isUCI;
937 appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
938 appData.hasOwnBookUCI[i] = hasBook;
939 if(!nickName[0]) useNick = FALSE;
940 if(useNick) ASSIGN(appData.pgnName[i], nickName);
944 q = firstChessProgramNames;
945 if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
946 quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
947 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
948 quote, p, quote, appData.directory[i],
949 useNick ? " -fn \"" : "",
950 useNick ? nickName : "",
952 v1 ? " -firstProtocolVersion 1" : "",
953 hasBook ? "" : " -fNoOwnBookUCI",
954 isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
955 storeVariant ? " -variant " : "",
956 storeVariant ? VariantName(gameInfo.variant) : "");
957 if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
958 firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
959 if(insert != q) insert[-1] = NULLCHAR;
960 snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
962 FloatToFront(&appData.recentEngineList, buf);
964 ReplaceEngine(cps, i);
970 int matched, min, sec;
972 * Parse timeControl resource
974 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
975 appData.movesPerSession)) {
977 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
978 DisplayFatalError(buf, 0, 2);
982 * Parse searchTime resource
984 if (*appData.searchTime != NULLCHAR) {
985 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
987 searchTime = min * 60;
988 } else if (matched == 2) {
989 searchTime = min * 60 + sec;
992 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
993 DisplayFatalError(buf, 0, 2);
1002 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1003 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1005 GetTimeMark(&programStartTime);
1006 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1007 appData.seedBase = random() + (random()<<15);
1008 pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1010 ClearProgramStats();
1011 programStats.ok_to_send = 1;
1012 programStats.seen_stat = 0;
1015 * Initialize game list
1021 * Internet chess server status
1023 if (appData.icsActive) {
1024 appData.matchMode = FALSE;
1025 appData.matchGames = 0;
1027 appData.noChessProgram = !appData.zippyPlay;
1029 appData.zippyPlay = FALSE;
1030 appData.zippyTalk = FALSE;
1031 appData.noChessProgram = TRUE;
1033 if (*appData.icsHelper != NULLCHAR) {
1034 appData.useTelnet = TRUE;
1035 appData.telnetProgram = appData.icsHelper;
1038 appData.zippyTalk = appData.zippyPlay = FALSE;
1041 /* [AS] Initialize pv info list [HGM] and game state */
1045 for( i=0; i<=framePtr; i++ ) {
1046 pvInfoList[i].depth = -1;
1047 boards[i][EP_STATUS] = EP_NONE;
1048 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1054 /* [AS] Adjudication threshold */
1055 adjudicateLossThreshold = appData.adjudicateLossThreshold;
1057 InitEngine(&first, 0);
1058 InitEngine(&second, 1);
1061 pairing.which = "pairing"; // pairing engine
1062 pairing.pr = NoProc;
1064 pairing.program = appData.pairingEngine;
1065 pairing.host = "localhost";
1068 if (appData.icsActive) {
1069 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
1070 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1071 appData.clockMode = FALSE;
1072 first.sendTime = second.sendTime = 0;
1076 /* Override some settings from environment variables, for backward
1077 compatibility. Unfortunately it's not feasible to have the env
1078 vars just set defaults, at least in xboard. Ugh.
1080 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1085 if (!appData.icsActive) {
1089 /* Check for variants that are supported only in ICS mode,
1090 or not at all. Some that are accepted here nevertheless
1091 have bugs; see comments below.
1093 VariantClass variant = StringToVariant(appData.variant);
1095 case VariantBughouse: /* need four players and two boards */
1096 case VariantKriegspiel: /* need to hide pieces and move details */
1097 /* case VariantFischeRandom: (Fabien: moved below) */
1098 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1099 if( (len >= MSG_SIZ) && appData.debugMode )
1100 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1102 DisplayFatalError(buf, 0, 2);
1105 case VariantUnknown:
1106 case VariantLoadable:
1116 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1117 if( (len >= MSG_SIZ) && appData.debugMode )
1118 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1120 DisplayFatalError(buf, 0, 2);
1123 case VariantXiangqi: /* [HGM] repetition rules not implemented */
1124 case VariantFairy: /* [HGM] TestLegality definitely off! */
1125 case VariantGothic: /* [HGM] should work */
1126 case VariantCapablanca: /* [HGM] should work */
1127 case VariantCourier: /* [HGM] initial forced moves not implemented */
1128 case VariantShogi: /* [HGM] could still mate with pawn drop */
1129 case VariantKnightmate: /* [HGM] should work */
1130 case VariantCylinder: /* [HGM] untested */
1131 case VariantFalcon: /* [HGM] untested */
1132 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1133 offboard interposition not understood */
1134 case VariantNormal: /* definitely works! */
1135 case VariantWildCastle: /* pieces not automatically shuffled */
1136 case VariantNoCastle: /* pieces not automatically shuffled */
1137 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1138 case VariantLosers: /* should work except for win condition,
1139 and doesn't know captures are mandatory */
1140 case VariantSuicide: /* should work except for win condition,
1141 and doesn't know captures are mandatory */
1142 case VariantGiveaway: /* should work except for win condition,
1143 and doesn't know captures are mandatory */
1144 case VariantTwoKings: /* should work */
1145 case VariantAtomic: /* should work except for win condition */
1146 case Variant3Check: /* should work except for win condition */
1147 case VariantShatranj: /* should work except for all win conditions */
1148 case VariantMakruk: /* should work except for draw countdown */
1149 case VariantBerolina: /* might work if TestLegality is off */
1150 case VariantCapaRandom: /* should work */
1151 case VariantJanus: /* should work */
1152 case VariantSuper: /* experimental */
1153 case VariantGreat: /* experimental, requires legality testing to be off */
1154 case VariantSChess: /* S-Chess, should work */
1155 case VariantGrand: /* should work */
1156 case VariantSpartan: /* should work */
1164 NextIntegerFromString (char ** str, long * value)
1169 while( *s == ' ' || *s == '\t' ) {
1175 if( *s >= '0' && *s <= '9' ) {
1176 while( *s >= '0' && *s <= '9' ) {
1177 *value = *value * 10 + (*s - '0');
1190 NextTimeControlFromString (char ** str, long * value)
1193 int result = NextIntegerFromString( str, &temp );
1196 *value = temp * 60; /* Minutes */
1197 if( **str == ':' ) {
1199 result = NextIntegerFromString( str, &temp );
1200 *value += temp; /* Seconds */
1208 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1209 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1210 int result = -1, type = 0; long temp, temp2;
1212 if(**str != ':') return -1; // old params remain in force!
1214 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1215 if( NextIntegerFromString( str, &temp ) ) return -1;
1216 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1219 /* time only: incremental or sudden-death time control */
1220 if(**str == '+') { /* increment follows; read it */
1222 if(**str == '!') type = *(*str)++; // Bronstein TC
1223 if(result = NextIntegerFromString( str, &temp2)) return -1;
1224 *inc = temp2 * 1000;
1225 if(**str == '.') { // read fraction of increment
1226 char *start = ++(*str);
1227 if(result = NextIntegerFromString( str, &temp2)) return -1;
1229 while(start++ < *str) temp2 /= 10;
1233 *moves = 0; *tc = temp * 1000; *incType = type;
1237 (*str)++; /* classical time control */
1238 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1250 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1251 { /* [HGM] get time to add from the multi-session time-control string */
1252 int incType, moves=1; /* kludge to force reading of first session */
1253 long time, increment;
1256 if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1258 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1259 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1260 if(movenr == -1) return time; /* last move before new session */
1261 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1262 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1263 if(!moves) return increment; /* current session is incremental */
1264 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1265 } while(movenr >= -1); /* try again for next session */
1267 return 0; // no new time quota on this move
1271 ParseTimeControl (char *tc, float ti, int mps)
1275 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1278 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1279 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1280 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1284 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1286 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1289 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1291 snprintf(buf, MSG_SIZ, ":%s", mytc);
1293 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1295 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1300 /* Parse second time control */
1303 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1311 timeControl_2 = tc2 * 1000;
1321 timeControl = tc1 * 1000;
1324 timeIncrement = ti * 1000; /* convert to ms */
1325 movesPerSession = 0;
1328 movesPerSession = mps;
1336 if (appData.debugMode) {
1337 fprintf(debugFP, "%s\n", programVersion);
1339 ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1341 set_cont_sequence(appData.wrapContSeq);
1342 if (appData.matchGames > 0) {
1343 appData.matchMode = TRUE;
1344 } else if (appData.matchMode) {
1345 appData.matchGames = 1;
1347 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1348 appData.matchGames = appData.sameColorGames;
1349 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1350 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1351 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1354 if (appData.noChessProgram || first.protocolVersion == 1) {
1357 /* kludge: allow timeout for initial "feature" commands */
1359 DisplayMessage("", _("Starting chess program"));
1360 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1365 CalculateIndex (int index, int gameNr)
1366 { // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1368 if(index > 0) return index; // fixed nmber
1369 if(index == 0) return 1;
1370 res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1371 if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1376 LoadGameOrPosition (int gameNr)
1377 { // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1378 if (*appData.loadGameFile != NULLCHAR) {
1379 if (!LoadGameFromFile(appData.loadGameFile,
1380 CalculateIndex(appData.loadGameIndex, gameNr),
1381 appData.loadGameFile, FALSE)) {
1382 DisplayFatalError(_("Bad game file"), 0, 1);
1385 } else if (*appData.loadPositionFile != NULLCHAR) {
1386 if (!LoadPositionFromFile(appData.loadPositionFile,
1387 CalculateIndex(appData.loadPositionIndex, gameNr),
1388 appData.loadPositionFile)) {
1389 DisplayFatalError(_("Bad position file"), 0, 1);
1397 ReserveGame (int gameNr, char resChar)
1399 FILE *tf = fopen(appData.tourneyFile, "r+");
1400 char *p, *q, c, buf[MSG_SIZ];
1401 if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1402 safeStrCpy(buf, lastMsg, MSG_SIZ);
1403 DisplayMessage(_("Pick new game"), "");
1404 flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1405 ParseArgsFromFile(tf);
1406 p = q = appData.results;
1407 if(appData.debugMode) {
1408 char *r = appData.participants;
1409 fprintf(debugFP, "results = '%s'\n", p);
1410 while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1411 fprintf(debugFP, "\n");
1413 while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1415 q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1416 safeStrCpy(q, p, strlen(p) + 2);
1417 if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1418 if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1419 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1420 if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1423 fseek(tf, -(strlen(p)+4), SEEK_END);
1425 if(c != '"') // depending on DOS or Unix line endings we can be one off
1426 fseek(tf, -(strlen(p)+2), SEEK_END);
1427 else fseek(tf, -(strlen(p)+3), SEEK_END);
1428 fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1429 DisplayMessage(buf, "");
1430 free(p); appData.results = q;
1431 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1432 (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1433 int round = appData.defaultMatchGames * appData.tourneyType;
1434 if(gameNr < 0 || appData.tourneyType < 1 || // gauntlet engine can always stay loaded as first engine
1435 appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1436 UnloadEngine(&first); // next game belongs to other pairing;
1437 UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1439 if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1443 MatchEvent (int mode)
1444 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1446 if(matchMode) { // already in match mode: switch it off
1448 if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1451 // if(gameMode != BeginningOfGame) {
1452 // DisplayError(_("You can only start a match from the initial position."), 0);
1456 if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1457 /* Set up machine vs. machine match */
1459 NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1460 if(appData.tourneyFile[0]) {
1462 if(nextGame > appData.matchGames) {
1464 if(strchr(appData.results, '*') == NULL) {
1466 appData.tourneyCycles++;
1467 if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1469 NextTourneyGame(-1, &dummy);
1471 if(nextGame <= appData.matchGames) {
1472 DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1474 ScheduleDelayedEvent(NextMatchGame, 10000);
1479 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1480 DisplayError(buf, 0);
1481 appData.tourneyFile[0] = 0;
1485 if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically
1486 DisplayFatalError(_("Can't have a match with no chess programs"),
1491 matchGame = roundNr = 1;
1492 first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1496 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1499 InitBackEnd3 P((void))
1501 GameMode initialMode;
1505 InitChessProgram(&first, startedFromSetupPosition);
1507 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1508 free(programVersion);
1509 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1510 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1511 FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1514 if (appData.icsActive) {
1516 /* [DM] Make a console window if needed [HGM] merged ifs */
1522 if (*appData.icsCommPort != NULLCHAR)
1523 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1524 appData.icsCommPort);
1526 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1527 appData.icsHost, appData.icsPort);
1529 if( (len >= MSG_SIZ) && appData.debugMode )
1530 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1532 DisplayFatalError(buf, err, 1);
1537 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1539 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1540 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1541 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1542 } else if (appData.noChessProgram) {
1548 if (*appData.cmailGameName != NULLCHAR) {
1550 OpenLoopback(&cmailPR);
1552 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1556 DisplayMessage("", "");
1557 if (StrCaseCmp(appData.initialMode, "") == 0) {
1558 initialMode = BeginningOfGame;
1559 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1560 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1561 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1562 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1565 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1566 initialMode = TwoMachinesPlay;
1567 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1568 initialMode = AnalyzeFile;
1569 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1570 initialMode = AnalyzeMode;
1571 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1572 initialMode = MachinePlaysWhite;
1573 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1574 initialMode = MachinePlaysBlack;
1575 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1576 initialMode = EditGame;
1577 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1578 initialMode = EditPosition;
1579 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1580 initialMode = Training;
1582 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1583 if( (len >= MSG_SIZ) && appData.debugMode )
1584 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1586 DisplayFatalError(buf, 0, 2);
1590 if (appData.matchMode) {
1591 if(appData.tourneyFile[0]) { // start tourney from command line
1593 if(f = fopen(appData.tourneyFile, "r")) {
1594 ParseArgsFromFile(f); // make sure tourney parmeters re known
1596 appData.clockMode = TRUE;
1598 } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1601 } else if (*appData.cmailGameName != NULLCHAR) {
1602 /* Set up cmail mode */
1603 ReloadCmailMsgEvent(TRUE);
1605 /* Set up other modes */
1606 if (initialMode == AnalyzeFile) {
1607 if (*appData.loadGameFile == NULLCHAR) {
1608 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1612 if (*appData.loadGameFile != NULLCHAR) {
1613 (void) LoadGameFromFile(appData.loadGameFile,
1614 appData.loadGameIndex,
1615 appData.loadGameFile, TRUE);
1616 } else if (*appData.loadPositionFile != NULLCHAR) {
1617 (void) LoadPositionFromFile(appData.loadPositionFile,
1618 appData.loadPositionIndex,
1619 appData.loadPositionFile);
1620 /* [HGM] try to make self-starting even after FEN load */
1621 /* to allow automatic setup of fairy variants with wtm */
1622 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1623 gameMode = BeginningOfGame;
1624 setboardSpoiledMachineBlack = 1;
1626 /* [HGM] loadPos: make that every new game uses the setup */
1627 /* from file as long as we do not switch variant */
1628 if(!blackPlaysFirst) {
1629 startedFromPositionFile = TRUE;
1630 CopyBoard(filePosition, boards[0]);
1633 if (initialMode == AnalyzeMode) {
1634 if (appData.noChessProgram) {
1635 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1638 if (appData.icsActive) {
1639 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1643 } else if (initialMode == AnalyzeFile) {
1644 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1645 ShowThinkingEvent();
1647 AnalysisPeriodicEvent(1);
1648 } else if (initialMode == MachinePlaysWhite) {
1649 if (appData.noChessProgram) {
1650 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1654 if (appData.icsActive) {
1655 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1659 MachineWhiteEvent();
1660 } else if (initialMode == MachinePlaysBlack) {
1661 if (appData.noChessProgram) {
1662 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1666 if (appData.icsActive) {
1667 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1671 MachineBlackEvent();
1672 } else if (initialMode == TwoMachinesPlay) {
1673 if (appData.noChessProgram) {
1674 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1678 if (appData.icsActive) {
1679 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1684 } else if (initialMode == EditGame) {
1686 } else if (initialMode == EditPosition) {
1687 EditPositionEvent();
1688 } else if (initialMode == Training) {
1689 if (*appData.loadGameFile == NULLCHAR) {
1690 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1699 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1701 DisplayBook(current+1);
1703 MoveHistorySet( movelist, first, last, current, pvInfoList );
1705 EvalGraphSet( first, last, current, pvInfoList );
1707 MakeEngineOutputTitle();
1711 * Establish will establish a contact to a remote host.port.
1712 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1713 * used to talk to the host.
1714 * Returns 0 if okay, error code if not.
1721 if (*appData.icsCommPort != NULLCHAR) {
1722 /* Talk to the host through a serial comm port */
1723 return OpenCommPort(appData.icsCommPort, &icsPR);
1725 } else if (*appData.gateway != NULLCHAR) {
1726 if (*appData.remoteShell == NULLCHAR) {
1727 /* Use the rcmd protocol to run telnet program on a gateway host */
1728 snprintf(buf, sizeof(buf), "%s %s %s",
1729 appData.telnetProgram, appData.icsHost, appData.icsPort);
1730 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1733 /* Use the rsh program to run telnet program on a gateway host */
1734 if (*appData.remoteUser == NULLCHAR) {
1735 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1736 appData.gateway, appData.telnetProgram,
1737 appData.icsHost, appData.icsPort);
1739 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1740 appData.remoteShell, appData.gateway,
1741 appData.remoteUser, appData.telnetProgram,
1742 appData.icsHost, appData.icsPort);
1744 return StartChildProcess(buf, "", &icsPR);
1747 } else if (appData.useTelnet) {
1748 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1751 /* TCP socket interface differs somewhat between
1752 Unix and NT; handle details in the front end.
1754 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1759 EscapeExpand (char *p, char *q)
1760 { // [HGM] initstring: routine to shape up string arguments
1761 while(*p++ = *q++) if(p[-1] == '\\')
1763 case 'n': p[-1] = '\n'; break;
1764 case 'r': p[-1] = '\r'; break;
1765 case 't': p[-1] = '\t'; break;
1766 case '\\': p[-1] = '\\'; break;
1767 case 0: *p = 0; return;
1768 default: p[-1] = q[-1]; break;
1773 show_bytes (FILE *fp, char *buf, int count)
1776 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1777 fprintf(fp, "\\%03o", *buf & 0xff);
1786 /* Returns an errno value */
1788 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1790 char buf[8192], *p, *q, *buflim;
1791 int left, newcount, outcount;
1793 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1794 *appData.gateway != NULLCHAR) {
1795 if (appData.debugMode) {
1796 fprintf(debugFP, ">ICS: ");
1797 show_bytes(debugFP, message, count);
1798 fprintf(debugFP, "\n");
1800 return OutputToProcess(pr, message, count, outError);
1803 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1810 if (appData.debugMode) {
1811 fprintf(debugFP, ">ICS: ");
1812 show_bytes(debugFP, buf, newcount);
1813 fprintf(debugFP, "\n");
1815 outcount = OutputToProcess(pr, buf, newcount, outError);
1816 if (outcount < newcount) return -1; /* to be sure */
1823 } else if (((unsigned char) *p) == TN_IAC) {
1824 *q++ = (char) TN_IAC;
1831 if (appData.debugMode) {
1832 fprintf(debugFP, ">ICS: ");
1833 show_bytes(debugFP, buf, newcount);
1834 fprintf(debugFP, "\n");
1836 outcount = OutputToProcess(pr, buf, newcount, outError);
1837 if (outcount < newcount) return -1; /* to be sure */
1842 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1844 int outError, outCount;
1845 static int gotEof = 0;
1847 /* Pass data read from player on to ICS */
1850 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1851 if (outCount < count) {
1852 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1854 } else if (count < 0) {
1855 RemoveInputSource(isr);
1856 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1857 } else if (gotEof++ > 0) {
1858 RemoveInputSource(isr);
1859 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1865 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1866 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1867 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1868 SendToICS("date\n");
1869 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1872 /* added routine for printf style output to ics */
1874 ics_printf (char *format, ...)
1876 char buffer[MSG_SIZ];
1879 va_start(args, format);
1880 vsnprintf(buffer, sizeof(buffer), format, args);
1881 buffer[sizeof(buffer)-1] = '\0';
1889 int count, outCount, outError;
1891 if (icsPR == NoProc) return;
1894 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1895 if (outCount < count) {
1896 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1900 /* This is used for sending logon scripts to the ICS. Sending
1901 without a delay causes problems when using timestamp on ICC
1902 (at least on my machine). */
1904 SendToICSDelayed (char *s, long msdelay)
1906 int count, outCount, outError;
1908 if (icsPR == NoProc) return;
1911 if (appData.debugMode) {
1912 fprintf(debugFP, ">ICS: ");
1913 show_bytes(debugFP, s, count);
1914 fprintf(debugFP, "\n");
1916 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1918 if (outCount < count) {
1919 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1924 /* Remove all highlighting escape sequences in s
1925 Also deletes any suffix starting with '('
1928 StripHighlightAndTitle (char *s)
1930 static char retbuf[MSG_SIZ];
1933 while (*s != NULLCHAR) {
1934 while (*s == '\033') {
1935 while (*s != NULLCHAR && !isalpha(*s)) s++;
1936 if (*s != NULLCHAR) s++;
1938 while (*s != NULLCHAR && *s != '\033') {
1939 if (*s == '(' || *s == '[') {
1950 /* Remove all highlighting escape sequences in s */
1952 StripHighlight (char *s)
1954 static char retbuf[MSG_SIZ];
1957 while (*s != NULLCHAR) {
1958 while (*s == '\033') {
1959 while (*s != NULLCHAR && !isalpha(*s)) s++;
1960 if (*s != NULLCHAR) s++;
1962 while (*s != NULLCHAR && *s != '\033') {
1970 char *variantNames[] = VARIANT_NAMES;
1972 VariantName (VariantClass v)
1974 return variantNames[v];
1978 /* Identify a variant from the strings the chess servers use or the
1979 PGN Variant tag names we use. */
1981 StringToVariant (char *e)
1985 VariantClass v = VariantNormal;
1986 int i, found = FALSE;
1992 /* [HGM] skip over optional board-size prefixes */
1993 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1994 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1995 while( *e++ != '_');
1998 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2002 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2003 if (StrCaseStr(e, variantNames[i])) {
2004 v = (VariantClass) i;
2011 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2012 || StrCaseStr(e, "wild/fr")
2013 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2014 v = VariantFischeRandom;
2015 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2016 (i = 1, p = StrCaseStr(e, "w"))) {
2018 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2025 case 0: /* FICS only, actually */
2027 /* Castling legal even if K starts on d-file */
2028 v = VariantWildCastle;
2033 /* Castling illegal even if K & R happen to start in
2034 normal positions. */
2035 v = VariantNoCastle;
2048 /* Castling legal iff K & R start in normal positions */
2054 /* Special wilds for position setup; unclear what to do here */
2055 v = VariantLoadable;
2058 /* Bizarre ICC game */
2059 v = VariantTwoKings;
2062 v = VariantKriegspiel;
2068 v = VariantFischeRandom;
2071 v = VariantCrazyhouse;
2074 v = VariantBughouse;
2080 /* Not quite the same as FICS suicide! */
2081 v = VariantGiveaway;
2087 v = VariantShatranj;
2090 /* Temporary names for future ICC types. The name *will* change in
2091 the next xboard/WinBoard release after ICC defines it. */
2129 v = VariantCapablanca;
2132 v = VariantKnightmate;
2138 v = VariantCylinder;
2144 v = VariantCapaRandom;
2147 v = VariantBerolina;
2159 /* Found "wild" or "w" in the string but no number;
2160 must assume it's normal chess. */
2164 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2165 if( (len >= MSG_SIZ) && appData.debugMode )
2166 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2168 DisplayError(buf, 0);
2174 if (appData.debugMode) {
2175 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2176 e, wnum, VariantName(v));
2181 static int leftover_start = 0, leftover_len = 0;
2182 char star_match[STAR_MATCH_N][MSG_SIZ];
2184 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2185 advance *index beyond it, and set leftover_start to the new value of
2186 *index; else return FALSE. If pattern contains the character '*', it
2187 matches any sequence of characters not containing '\r', '\n', or the
2188 character following the '*' (if any), and the matched sequence(s) are
2189 copied into star_match.
2192 looking_at ( char *buf, int *index, char *pattern)
2194 char *bufp = &buf[*index], *patternp = pattern;
2196 char *matchp = star_match[0];
2199 if (*patternp == NULLCHAR) {
2200 *index = leftover_start = bufp - buf;
2204 if (*bufp == NULLCHAR) return FALSE;
2205 if (*patternp == '*') {
2206 if (*bufp == *(patternp + 1)) {
2208 matchp = star_match[++star_count];
2212 } else if (*bufp == '\n' || *bufp == '\r') {
2214 if (*patternp == NULLCHAR)
2219 *matchp++ = *bufp++;
2223 if (*patternp != *bufp) return FALSE;
2230 SendToPlayer (char *data, int length)
2232 int error, outCount;
2233 outCount = OutputToProcess(NoProc, data, length, &error);
2234 if (outCount < length) {
2235 DisplayFatalError(_("Error writing to display"), error, 1);
2240 PackHolding (char packed[], char *holding)
2250 switch (runlength) {
2261 sprintf(q, "%d", runlength);
2273 /* Telnet protocol requests from the front end */
2275 TelnetRequest (unsigned char ddww, unsigned char option)
2277 unsigned char msg[3];
2278 int outCount, outError;
2280 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2282 if (appData.debugMode) {
2283 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2299 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2308 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2311 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2316 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2318 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2325 if (!appData.icsActive) return;
2326 TelnetRequest(TN_DO, TN_ECHO);
2332 if (!appData.icsActive) return;
2333 TelnetRequest(TN_DONT, TN_ECHO);
2337 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2339 /* put the holdings sent to us by the server on the board holdings area */
2340 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2344 if(gameInfo.holdingsWidth < 2) return;
2345 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2346 return; // prevent overwriting by pre-board holdings
2348 if( (int)lowestPiece >= BlackPawn ) {
2351 holdingsStartRow = BOARD_HEIGHT-1;
2354 holdingsColumn = BOARD_WIDTH-1;
2355 countsColumn = BOARD_WIDTH-2;
2356 holdingsStartRow = 0;
2360 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2361 board[i][holdingsColumn] = EmptySquare;
2362 board[i][countsColumn] = (ChessSquare) 0;
2364 while( (p=*holdings++) != NULLCHAR ) {
2365 piece = CharToPiece( ToUpper(p) );
2366 if(piece == EmptySquare) continue;
2367 /*j = (int) piece - (int) WhitePawn;*/
2368 j = PieceToNumber(piece);
2369 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2370 if(j < 0) continue; /* should not happen */
2371 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2372 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2373 board[holdingsStartRow+j*direction][countsColumn]++;
2379 VariantSwitch (Board board, VariantClass newVariant)
2381 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2382 static Board oldBoard;
2384 startedFromPositionFile = FALSE;
2385 if(gameInfo.variant == newVariant) return;
2387 /* [HGM] This routine is called each time an assignment is made to
2388 * gameInfo.variant during a game, to make sure the board sizes
2389 * are set to match the new variant. If that means adding or deleting
2390 * holdings, we shift the playing board accordingly
2391 * This kludge is needed because in ICS observe mode, we get boards
2392 * of an ongoing game without knowing the variant, and learn about the
2393 * latter only later. This can be because of the move list we requested,
2394 * in which case the game history is refilled from the beginning anyway,
2395 * but also when receiving holdings of a crazyhouse game. In the latter
2396 * case we want to add those holdings to the already received position.
2400 if (appData.debugMode) {
2401 fprintf(debugFP, "Switch board from %s to %s\n",
2402 VariantName(gameInfo.variant), VariantName(newVariant));
2403 setbuf(debugFP, NULL);
2405 shuffleOpenings = 0; /* [HGM] shuffle */
2406 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2410 newWidth = 9; newHeight = 9;
2411 gameInfo.holdingsSize = 7;
2412 case VariantBughouse:
2413 case VariantCrazyhouse:
2414 newHoldingsWidth = 2; break;
2418 newHoldingsWidth = 2;
2419 gameInfo.holdingsSize = 8;
2422 case VariantCapablanca:
2423 case VariantCapaRandom:
2426 newHoldingsWidth = gameInfo.holdingsSize = 0;
2429 if(newWidth != gameInfo.boardWidth ||
2430 newHeight != gameInfo.boardHeight ||
2431 newHoldingsWidth != gameInfo.holdingsWidth ) {
2433 /* shift position to new playing area, if needed */
2434 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2435 for(i=0; i<BOARD_HEIGHT; i++)
2436 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2437 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2439 for(i=0; i<newHeight; i++) {
2440 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2441 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2443 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2444 for(i=0; i<BOARD_HEIGHT; i++)
2445 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2446 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2449 gameInfo.boardWidth = newWidth;
2450 gameInfo.boardHeight = newHeight;
2451 gameInfo.holdingsWidth = newHoldingsWidth;
2452 gameInfo.variant = newVariant;
2453 InitDrawingSizes(-2, 0);
2454 } else gameInfo.variant = newVariant;
2455 CopyBoard(oldBoard, board); // remember correctly formatted board
2456 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2457 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2460 static int loggedOn = FALSE;
2462 /*-- Game start info cache: --*/
2464 char gs_kind[MSG_SIZ];
2465 static char player1Name[128] = "";
2466 static char player2Name[128] = "";
2467 static char cont_seq[] = "\n\\ ";
2468 static int player1Rating = -1;
2469 static int player2Rating = -1;
2470 /*----------------------------*/
2472 ColorClass curColor = ColorNormal;
2473 int suppressKibitz = 0;
2476 Boolean soughtPending = FALSE;
2477 Boolean seekGraphUp;
2478 #define MAX_SEEK_ADS 200
2480 char *seekAdList[MAX_SEEK_ADS];
2481 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2482 float tcList[MAX_SEEK_ADS];
2483 char colorList[MAX_SEEK_ADS];
2484 int nrOfSeekAds = 0;
2485 int minRating = 1010, maxRating = 2800;
2486 int hMargin = 10, vMargin = 20, h, w;
2487 extern int squareSize, lineGap;
2492 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2493 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2494 if(r < minRating+100 && r >=0 ) r = minRating+100;
2495 if(r > maxRating) r = maxRating;
2496 if(tc < 1.) tc = 1.;
2497 if(tc > 95.) tc = 95.;
2498 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2499 y = ((double)r - minRating)/(maxRating - minRating)
2500 * (h-vMargin-squareSize/8-1) + vMargin;
2501 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2502 if(strstr(seekAdList[i], " u ")) color = 1;
2503 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2504 !strstr(seekAdList[i], "bullet") &&
2505 !strstr(seekAdList[i], "blitz") &&
2506 !strstr(seekAdList[i], "standard") ) color = 2;
2507 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2508 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2512 AddAd (char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2514 char buf[MSG_SIZ], *ext = "";
2515 VariantClass v = StringToVariant(type);
2516 if(strstr(type, "wild")) {
2517 ext = type + 4; // append wild number
2518 if(v == VariantFischeRandom) type = "chess960"; else
2519 if(v == VariantLoadable) type = "setup"; else
2520 type = VariantName(v);
2522 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2523 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2524 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2525 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2526 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2527 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2528 seekNrList[nrOfSeekAds] = nr;
2529 zList[nrOfSeekAds] = 0;
2530 seekAdList[nrOfSeekAds++] = StrSave(buf);
2531 if(plot) PlotSeekAd(nrOfSeekAds-1);
2536 EraseSeekDot (int i)
2538 int x = xList[i], y = yList[i], d=squareSize/4, k;
2539 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2540 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2541 // now replot every dot that overlapped
2542 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2543 int xx = xList[k], yy = yList[k];
2544 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2545 DrawSeekDot(xx, yy, colorList[k]);
2550 RemoveSeekAd (int nr)
2553 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2555 if(seekAdList[i]) free(seekAdList[i]);
2556 seekAdList[i] = seekAdList[--nrOfSeekAds];
2557 seekNrList[i] = seekNrList[nrOfSeekAds];
2558 ratingList[i] = ratingList[nrOfSeekAds];
2559 colorList[i] = colorList[nrOfSeekAds];
2560 tcList[i] = tcList[nrOfSeekAds];
2561 xList[i] = xList[nrOfSeekAds];
2562 yList[i] = yList[nrOfSeekAds];
2563 zList[i] = zList[nrOfSeekAds];
2564 seekAdList[nrOfSeekAds] = NULL;
2570 MatchSoughtLine (char *line)
2572 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2573 int nr, base, inc, u=0; char dummy;
2575 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2576 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2578 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2579 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2580 // match: compact and save the line
2581 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2591 if(!seekGraphUp) return FALSE;
2592 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2593 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2595 DrawSeekBackground(0, 0, w, h);
2596 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2597 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2598 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2599 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2601 DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2604 snprintf(buf, MSG_SIZ, "%d", i);
2605 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2608 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2609 for(i=1; i<100; i+=(i<10?1:5)) {
2610 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2611 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2612 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2614 snprintf(buf, MSG_SIZ, "%d", i);
2615 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2618 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2623 SeekGraphClick (ClickType click, int x, int y, int moving)
2625 static int lastDown = 0, displayed = 0, lastSecond;
2626 if(y < 0) return FALSE;
2627 if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2628 (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2629 if(!seekGraphUp) return FALSE;
2630 seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2631 DrawPosition(TRUE, NULL);
2634 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2635 if(click == Release || moving) return FALSE;
2637 soughtPending = TRUE;
2638 SendToICS(ics_prefix);
2639 SendToICS("sought\n"); // should this be "sought all"?
2640 } else { // issue challenge based on clicked ad
2641 int dist = 10000; int i, closest = 0, second = 0;
2642 for(i=0; i<nrOfSeekAds; i++) {
2643 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2644 if(d < dist) { dist = d; closest = i; }
2645 second += (d - zList[i] < 120); // count in-range ads
2646 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2650 second = (second > 1);
2651 if(displayed != closest || second != lastSecond) {
2652 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2653 lastSecond = second; displayed = closest;
2655 if(click == Press) {
2656 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2659 } // on press 'hit', only show info
2660 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2661 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2662 SendToICS(ics_prefix);
2664 return TRUE; // let incoming board of started game pop down the graph
2665 } else if(click == Release) { // release 'miss' is ignored
2666 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2667 if(moving == 2) { // right up-click
2668 nrOfSeekAds = 0; // refresh graph
2669 soughtPending = TRUE;
2670 SendToICS(ics_prefix);
2671 SendToICS("sought\n"); // should this be "sought all"?
2674 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2675 // press miss or release hit 'pop down' seek graph
2676 seekGraphUp = FALSE;
2677 DrawPosition(TRUE, NULL);
2683 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2685 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2686 #define STARTED_NONE 0
2687 #define STARTED_MOVES 1
2688 #define STARTED_BOARD 2
2689 #define STARTED_OBSERVE 3
2690 #define STARTED_HOLDINGS 4
2691 #define STARTED_CHATTER 5
2692 #define STARTED_COMMENT 6
2693 #define STARTED_MOVES_NOHIDE 7
2695 static int started = STARTED_NONE;
2696 static char parse[20000];
2697 static int parse_pos = 0;
2698 static char buf[BUF_SIZE + 1];
2699 static int firstTime = TRUE, intfSet = FALSE;
2700 static ColorClass prevColor = ColorNormal;
2701 static int savingComment = FALSE;
2702 static int cmatch = 0; // continuation sequence match
2709 int backup; /* [DM] For zippy color lines */
2711 char talker[MSG_SIZ]; // [HGM] chat
2714 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2716 if (appData.debugMode) {
2718 fprintf(debugFP, "<ICS: ");
2719 show_bytes(debugFP, data, count);
2720 fprintf(debugFP, "\n");
2724 if (appData.debugMode) { int f = forwardMostMove;
2725 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2726 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2727 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2730 /* If last read ended with a partial line that we couldn't parse,
2731 prepend it to the new read and try again. */
2732 if (leftover_len > 0) {
2733 for (i=0; i<leftover_len; i++)
2734 buf[i] = buf[leftover_start + i];
2737 /* copy new characters into the buffer */
2738 bp = buf + leftover_len;
2739 buf_len=leftover_len;
2740 for (i=0; i<count; i++)
2743 if (data[i] == '\r')
2746 // join lines split by ICS?
2747 if (!appData.noJoin)
2750 Joining just consists of finding matches against the
2751 continuation sequence, and discarding that sequence
2752 if found instead of copying it. So, until a match
2753 fails, there's nothing to do since it might be the
2754 complete sequence, and thus, something we don't want
2757 if (data[i] == cont_seq[cmatch])
2760 if (cmatch == strlen(cont_seq))
2762 cmatch = 0; // complete match. just reset the counter
2765 it's possible for the ICS to not include the space
2766 at the end of the last word, making our [correct]
2767 join operation fuse two separate words. the server
2768 does this when the space occurs at the width setting.
2770 if (!buf_len || buf[buf_len-1] != ' ')
2781 match failed, so we have to copy what matched before
2782 falling through and copying this character. In reality,
2783 this will only ever be just the newline character, but
2784 it doesn't hurt to be precise.
2786 strncpy(bp, cont_seq, cmatch);
2798 buf[buf_len] = NULLCHAR;
2799 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2804 while (i < buf_len) {
2805 /* Deal with part of the TELNET option negotiation
2806 protocol. We refuse to do anything beyond the
2807 defaults, except that we allow the WILL ECHO option,
2808 which ICS uses to turn off password echoing when we are
2809 directly connected to it. We reject this option
2810 if localLineEditing mode is on (always on in xboard)
2811 and we are talking to port 23, which might be a real
2812 telnet server that will try to keep WILL ECHO on permanently.
2814 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2815 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2816 unsigned char option;
2818 switch ((unsigned char) buf[++i]) {
2820 if (appData.debugMode)
2821 fprintf(debugFP, "\n<WILL ");
2822 switch (option = (unsigned char) buf[++i]) {
2824 if (appData.debugMode)
2825 fprintf(debugFP, "ECHO ");
2826 /* Reply only if this is a change, according
2827 to the protocol rules. */
2828 if (remoteEchoOption) break;
2829 if (appData.localLineEditing &&
2830 atoi(appData.icsPort) == TN_PORT) {
2831 TelnetRequest(TN_DONT, TN_ECHO);
2834 TelnetRequest(TN_DO, TN_ECHO);
2835 remoteEchoOption = TRUE;
2839 if (appData.debugMode)
2840 fprintf(debugFP, "%d ", option);
2841 /* Whatever this is, we don't want it. */
2842 TelnetRequest(TN_DONT, option);
2847 if (appData.debugMode)
2848 fprintf(debugFP, "\n<WONT ");
2849 switch (option = (unsigned char) buf[++i]) {
2851 if (appData.debugMode)
2852 fprintf(debugFP, "ECHO ");
2853 /* Reply only if this is a change, according
2854 to the protocol rules. */
2855 if (!remoteEchoOption) break;
2857 TelnetRequest(TN_DONT, TN_ECHO);
2858 remoteEchoOption = FALSE;
2861 if (appData.debugMode)
2862 fprintf(debugFP, "%d ", (unsigned char) option);
2863 /* Whatever this is, it must already be turned
2864 off, because we never agree to turn on
2865 anything non-default, so according to the
2866 protocol rules, we don't reply. */
2871 if (appData.debugMode)
2872 fprintf(debugFP, "\n<DO ");
2873 switch (option = (unsigned char) buf[++i]) {
2875 /* Whatever this is, we refuse to do it. */
2876 if (appData.debugMode)
2877 fprintf(debugFP, "%d ", option);
2878 TelnetRequest(TN_WONT, option);
2883 if (appData.debugMode)
2884 fprintf(debugFP, "\n<DONT ");
2885 switch (option = (unsigned char) buf[++i]) {
2887 if (appData.debugMode)
2888 fprintf(debugFP, "%d ", option);
2889 /* Whatever this is, we are already not doing
2890 it, because we never agree to do anything
2891 non-default, so according to the protocol
2892 rules, we don't reply. */
2897 if (appData.debugMode)
2898 fprintf(debugFP, "\n<IAC ");
2899 /* Doubled IAC; pass it through */
2903 if (appData.debugMode)
2904 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2905 /* Drop all other telnet commands on the floor */
2908 if (oldi > next_out)
2909 SendToPlayer(&buf[next_out], oldi - next_out);
2915 /* OK, this at least will *usually* work */
2916 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2920 if (loggedOn && !intfSet) {
2921 if (ics_type == ICS_ICC) {
2922 snprintf(str, MSG_SIZ,
2923 "/set-quietly interface %s\n/set-quietly style 12\n",
2925 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2926 strcat(str, "/set-2 51 1\n/set seek 1\n");
2927 } else if (ics_type == ICS_CHESSNET) {
2928 snprintf(str, MSG_SIZ, "/style 12\n");
2930 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2931 strcat(str, programVersion);
2932 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2933 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2934 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2936 strcat(str, "$iset nohighlight 1\n");
2938 strcat(str, "$iset lock 1\n$style 12\n");
2941 NotifyFrontendLogin();
2945 if (started == STARTED_COMMENT) {
2946 /* Accumulate characters in comment */
2947 parse[parse_pos++] = buf[i];
2948 if (buf[i] == '\n') {
2949 parse[parse_pos] = NULLCHAR;
2950 if(chattingPartner>=0) {
2952 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2953 OutputChatMessage(chattingPartner, mess);
2954 chattingPartner = -1;
2955 next_out = i+1; // [HGM] suppress printing in ICS window
2957 if(!suppressKibitz) // [HGM] kibitz
2958 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2959 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2960 int nrDigit = 0, nrAlph = 0, j;
2961 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2962 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2963 parse[parse_pos] = NULLCHAR;
2964 // try to be smart: if it does not look like search info, it should go to
2965 // ICS interaction window after all, not to engine-output window.
2966 for(j=0; j<parse_pos; j++) { // count letters and digits
2967 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2968 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2969 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2971 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2972 int depth=0; float score;
2973 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2974 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2975 pvInfoList[forwardMostMove-1].depth = depth;
2976 pvInfoList[forwardMostMove-1].score = 100*score;
2978 OutputKibitz(suppressKibitz, parse);
2981 if(gameMode == IcsObserving) // restore original ICS messages
2982 snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
2984 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2985 SendToPlayer(tmp, strlen(tmp));
2987 next_out = i+1; // [HGM] suppress printing in ICS window
2989 started = STARTED_NONE;
2991 /* Don't match patterns against characters in comment */
2996 if (started == STARTED_CHATTER) {
2997 if (buf[i] != '\n') {
2998 /* Don't match patterns against characters in chatter */
3002 started = STARTED_NONE;
3003 if(suppressKibitz) next_out = i+1;
3006 /* Kludge to deal with rcmd protocol */
3007 if (firstTime && looking_at(buf, &i, "\001*")) {
3008 DisplayFatalError(&buf[1], 0, 1);
3014 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3017 if (appData.debugMode)
3018 fprintf(debugFP, "ics_type %d\n", ics_type);
3021 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3022 ics_type = ICS_FICS;
3024 if (appData.debugMode)
3025 fprintf(debugFP, "ics_type %d\n", ics_type);
3028 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3029 ics_type = ICS_CHESSNET;
3031 if (appData.debugMode)
3032 fprintf(debugFP, "ics_type %d\n", ics_type);
3037 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3038 looking_at(buf, &i, "Logging you in as \"*\"") ||
3039 looking_at(buf, &i, "will be \"*\""))) {
3040 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3044 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3046 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3047 DisplayIcsInteractionTitle(buf);
3048 have_set_title = TRUE;
3051 /* skip finger notes */
3052 if (started == STARTED_NONE &&
3053 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3054 (buf[i] == '1' && buf[i+1] == '0')) &&
3055 buf[i+2] == ':' && buf[i+3] == ' ') {
3056 started = STARTED_CHATTER;
3062 // [HGM] seekgraph: recognize sought lines and end-of-sought message
3063 if(appData.seekGraph) {
3064 if(soughtPending && MatchSoughtLine(buf+i)) {
3065 i = strstr(buf+i, "rated") - buf;
3066 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3067 next_out = leftover_start = i;
3068 started = STARTED_CHATTER;
3069 suppressKibitz = TRUE;
3072 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3073 && looking_at(buf, &i, "* ads displayed")) {
3074 soughtPending = FALSE;
3079 if(appData.autoRefresh) {
3080 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3081 int s = (ics_type == ICS_ICC); // ICC format differs
3083 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3084 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3085 looking_at(buf, &i, "*% "); // eat prompt
3086 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3087 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3088 next_out = i; // suppress
3091 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3092 char *p = star_match[0];
3094 if(seekGraphUp) RemoveSeekAd(atoi(p));
3095 while(*p && *p++ != ' '); // next
3097 looking_at(buf, &i, "*% "); // eat prompt
3098 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3105 /* skip formula vars */
3106 if (started == STARTED_NONE &&
3107 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3108 started = STARTED_CHATTER;
3113 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3114 if (appData.autoKibitz && started == STARTED_NONE &&
3115 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3116 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3117 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3118 looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3119 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3120 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3121 suppressKibitz = TRUE;
3122 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3124 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3125 && (gameMode == IcsPlayingWhite)) ||
3126 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3127 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3128 started = STARTED_CHATTER; // own kibitz we simply discard
3130 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3131 parse_pos = 0; parse[0] = NULLCHAR;
3132 savingComment = TRUE;
3133 suppressKibitz = gameMode != IcsObserving ? 2 :
3134 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3138 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3139 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3140 && atoi(star_match[0])) {
3141 // suppress the acknowledgements of our own autoKibitz
3143 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3144 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3145 SendToPlayer(star_match[0], strlen(star_match[0]));
3146 if(looking_at(buf, &i, "*% ")) // eat prompt
3147 suppressKibitz = FALSE;
3151 } // [HGM] kibitz: end of patch
3153 if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3155 // [HGM] chat: intercept tells by users for which we have an open chat window
3157 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3158 looking_at(buf, &i, "* whispers:") ||
3159 looking_at(buf, &i, "* kibitzes:") ||
3160 looking_at(buf, &i, "* shouts:") ||
3161 looking_at(buf, &i, "* c-shouts:") ||
3162 looking_at(buf, &i, "--> * ") ||
3163 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3164 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3165 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3166 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3168 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3169 chattingPartner = -1;
3171 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3172 for(p=0; p<MAX_CHAT; p++) {
3173 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3174 talker[0] = '['; strcat(talker, "] ");
3175 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3176 chattingPartner = p; break;
3179 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3180 for(p=0; p<MAX_CHAT; p++) {
3181 if(!strcmp("kibitzes", chatPartner[p])) {
3182 talker[0] = '['; strcat(talker, "] ");
3183 chattingPartner = p; break;
3186 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3187 for(p=0; p<MAX_CHAT; p++) {
3188 if(!strcmp("whispers", chatPartner[p])) {
3189 talker[0] = '['; strcat(talker, "] ");
3190 chattingPartner = p; break;
3193 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3194 if(buf[i-8] == '-' && buf[i-3] == 't')
3195 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3196 if(!strcmp("c-shouts", chatPartner[p])) {
3197 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3198 chattingPartner = p; break;
3201 if(chattingPartner < 0)
3202 for(p=0; p<MAX_CHAT; p++) {
3203 if(!strcmp("shouts", chatPartner[p])) {
3204 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3205 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3206 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3207 chattingPartner = p; break;
3211 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3212 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3213 talker[0] = 0; Colorize(ColorTell, FALSE);
3214 chattingPartner = p; break;
3216 if(chattingPartner<0) i = oldi; else {
3217 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3218 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3219 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3220 started = STARTED_COMMENT;
3221 parse_pos = 0; parse[0] = NULLCHAR;
3222 savingComment = 3 + chattingPartner; // counts as TRUE
3223 suppressKibitz = TRUE;
3226 } // [HGM] chat: end of patch
3229 if (appData.zippyTalk || appData.zippyPlay) {
3230 /* [DM] Backup address for color zippy lines */
3232 if (loggedOn == TRUE)
3233 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3234 (appData.zippyPlay && ZippyMatch(buf, &backup)));
3236 } // [DM] 'else { ' deleted
3238 /* Regular tells and says */
3239 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3240 looking_at(buf, &i, "* (your partner) tells you: ") ||
3241 looking_at(buf, &i, "* says: ") ||
3242 /* Don't color "message" or "messages" output */
3243 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3244 looking_at(buf, &i, "*. * at *:*: ") ||
3245 looking_at(buf, &i, "--* (*:*): ") ||
3246 /* Message notifications (same color as tells) */
3247 looking_at(buf, &i, "* has left a message ") ||
3248 looking_at(buf, &i, "* just sent you a message:\n") ||
3249 /* Whispers and kibitzes */
3250 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3251 looking_at(buf, &i, "* kibitzes: ") ||
3253 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3255 if (tkind == 1 && strchr(star_match[0], ':')) {
3256 /* Avoid "tells you:" spoofs in channels */
3259 if (star_match[0][0] == NULLCHAR ||
3260 strchr(star_match[0], ' ') ||
3261 (tkind == 3 && strchr(star_match[1], ' '))) {
3262 /* Reject bogus matches */
3265 if (appData.colorize) {
3266 if (oldi > next_out) {
3267 SendToPlayer(&buf[next_out], oldi - next_out);
3272 Colorize(ColorTell, FALSE);
3273 curColor = ColorTell;
3276 Colorize(ColorKibitz, FALSE);
3277 curColor = ColorKibitz;
3280 p = strrchr(star_match[1], '(');
3287 Colorize(ColorChannel1, FALSE);
3288 curColor = ColorChannel1;
3290 Colorize(ColorChannel, FALSE);
3291 curColor = ColorChannel;
3295 curColor = ColorNormal;
3299 if (started == STARTED_NONE && appData.autoComment &&
3300 (gameMode == IcsObserving ||
3301 gameMode == IcsPlayingWhite ||
3302 gameMode == IcsPlayingBlack)) {
3303 parse_pos = i - oldi;
3304 memcpy(parse, &buf[oldi], parse_pos);
3305 parse[parse_pos] = NULLCHAR;
3306 started = STARTED_COMMENT;
3307 savingComment = TRUE;
3309 started = STARTED_CHATTER;
3310 savingComment = FALSE;
3317 if (looking_at(buf, &i, "* s-shouts: ") ||
3318 looking_at(buf, &i, "* c-shouts: ")) {
3319 if (appData.colorize) {
3320 if (oldi > next_out) {
3321 SendToPlayer(&buf[next_out], oldi - next_out);
3324 Colorize(ColorSShout, FALSE);
3325 curColor = ColorSShout;
3328 started = STARTED_CHATTER;
3332 if (looking_at(buf, &i, "--->")) {
3337 if (looking_at(buf, &i, "* shouts: ") ||
3338 looking_at(buf, &i, "--> ")) {
3339 if (appData.colorize) {
3340 if (oldi > next_out) {
3341 SendToPlayer(&buf[next_out], oldi - next_out);
3344 Colorize(ColorShout, FALSE);
3345 curColor = ColorShout;
3348 started = STARTED_CHATTER;
3352 if (looking_at( buf, &i, "Challenge:")) {
3353 if (appData.colorize) {
3354 if (oldi > next_out) {
3355 SendToPlayer(&buf[next_out], oldi - next_out);
3358 Colorize(ColorChallenge, FALSE);
3359 curColor = ColorChallenge;
3365 if (looking_at(buf, &i, "* offers you") ||
3366 looking_at(buf, &i, "* offers to be") ||
3367 looking_at(buf, &i, "* would like to") ||
3368 looking_at(buf, &i, "* requests to") ||
3369 looking_at(buf, &i, "Your opponent offers") ||
3370 looking_at(buf, &i, "Your opponent requests")) {
3372 if (appData.colorize) {
3373 if (oldi > next_out) {
3374 SendToPlayer(&buf[next_out], oldi - next_out);
3377 Colorize(ColorRequest, FALSE);
3378 curColor = ColorRequest;
3383 if (looking_at(buf, &i, "* (*) seeking")) {
3384 if (appData.colorize) {
3385 if (oldi > next_out) {
3386 SendToPlayer(&buf[next_out], oldi - next_out);
3389 Colorize(ColorSeek, FALSE);
3390 curColor = ColorSeek;
3395 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3397 if (looking_at(buf, &i, "\\ ")) {
3398 if (prevColor != ColorNormal) {
3399 if (oldi > next_out) {
3400 SendToPlayer(&buf[next_out], oldi - next_out);
3403 Colorize(prevColor, TRUE);
3404 curColor = prevColor;
3406 if (savingComment) {
3407 parse_pos = i - oldi;
3408 memcpy(parse, &buf[oldi], parse_pos);
3409 parse[parse_pos] = NULLCHAR;
3410 started = STARTED_COMMENT;
3411 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3412 chattingPartner = savingComment - 3; // kludge to remember the box
3414 started = STARTED_CHATTER;
3419 if (looking_at(buf, &i, "Black Strength :") ||
3420 looking_at(buf, &i, "<<< style 10 board >>>") ||
3421 looking_at(buf, &i, "<10>") ||
3422 looking_at(buf, &i, "#@#")) {
3423 /* Wrong board style */
3425 SendToICS(ics_prefix);
3426 SendToICS("set style 12\n");
3427 SendToICS(ics_prefix);
3428 SendToICS("refresh\n");
3432 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3434 have_sent_ICS_logon = 1;
3438 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3439 (looking_at(buf, &i, "\n<12> ") ||
3440 looking_at(buf, &i, "<12> "))) {
3442 if (oldi > next_out) {
3443 SendToPlayer(&buf[next_out], oldi - next_out);
3446 started = STARTED_BOARD;
3451 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3452 looking_at(buf, &i, "<b1> ")) {
3453 if (oldi > next_out) {
3454 SendToPlayer(&buf[next_out], oldi - next_out);
3457 started = STARTED_HOLDINGS;
3462 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3464 /* Header for a move list -- first line */
3466 switch (ics_getting_history) {
3470 case BeginningOfGame:
3471 /* User typed "moves" or "oldmoves" while we
3472 were idle. Pretend we asked for these
3473 moves and soak them up so user can step
3474 through them and/or save them.
3477 gameMode = IcsObserving;
3480 ics_getting_history = H_GOT_UNREQ_HEADER;
3482 case EditGame: /*?*/
3483 case EditPosition: /*?*/
3484 /* Should above feature work in these modes too? */
3485 /* For now it doesn't */
3486 ics_getting_history = H_GOT_UNWANTED_HEADER;
3489 ics_getting_history = H_GOT_UNWANTED_HEADER;
3494 /* Is this the right one? */
3495 if (gameInfo.white && gameInfo.black &&
3496 strcmp(gameInfo.white, star_match[0]) == 0 &&
3497 strcmp(gameInfo.black, star_match[2]) == 0) {
3499 ics_getting_history = H_GOT_REQ_HEADER;
3502 case H_GOT_REQ_HEADER:
3503 case H_GOT_UNREQ_HEADER:
3504 case H_GOT_UNWANTED_HEADER:
3505 case H_GETTING_MOVES:
3506 /* Should not happen */
3507 DisplayError(_("Error gathering move list: two headers"), 0);
3508 ics_getting_history = H_FALSE;
3512 /* Save player ratings into gameInfo if needed */
3513 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3514 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3515 (gameInfo.whiteRating == -1 ||
3516 gameInfo.blackRating == -1)) {
3518 gameInfo.whiteRating = string_to_rating(star_match[1]);
3519 gameInfo.blackRating = string_to_rating(star_match[3]);
3520 if (appData.debugMode)
3521 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3522 gameInfo.whiteRating, gameInfo.blackRating);
3527 if (looking_at(buf, &i,
3528 "* * match, initial time: * minute*, increment: * second")) {
3529 /* Header for a move list -- second line */
3530 /* Initial board will follow if this is a wild game */
3531 if (gameInfo.event != NULL) free(gameInfo.event);
3532 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3533 gameInfo.event = StrSave(str);
3534 /* [HGM] we switched variant. Translate boards if needed. */
3535 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3539 if (looking_at(buf, &i, "Move ")) {
3540 /* Beginning of a move list */
3541 switch (ics_getting_history) {
3543 /* Normally should not happen */
3544 /* Maybe user hit reset while we were parsing */
3547 /* Happens if we are ignoring a move list that is not
3548 * the one we just requested. Common if the user
3549 * tries to observe two games without turning off
3552 case H_GETTING_MOVES:
3553 /* Should not happen */
3554 DisplayError(_("Error gathering move list: nested"), 0);
3555 ics_getting_history = H_FALSE;
3557 case H_GOT_REQ_HEADER:
3558 ics_getting_history = H_GETTING_MOVES;
3559 started = STARTED_MOVES;
3561 if (oldi > next_out) {
3562 SendToPlayer(&buf[next_out], oldi - next_out);
3565 case H_GOT_UNREQ_HEADER:
3566 ics_getting_history = H_GETTING_MOVES;
3567 started = STARTED_MOVES_NOHIDE;
3570 case H_GOT_UNWANTED_HEADER:
3571 ics_getting_history = H_FALSE;
3577 if (looking_at(buf, &i, "% ") ||
3578 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3579 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3580 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3581 soughtPending = FALSE;
3585 if(suppressKibitz) next_out = i;
3586 savingComment = FALSE;
3590 case STARTED_MOVES_NOHIDE:
3591 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3592 parse[parse_pos + i - oldi] = NULLCHAR;
3593 ParseGameHistory(parse);
3595 if (appData.zippyPlay && first.initDone) {
3596 FeedMovesToProgram(&first, forwardMostMove);
3597 if (gameMode == IcsPlayingWhite) {
3598 if (WhiteOnMove(forwardMostMove)) {
3599 if (first.sendTime) {
3600 if (first.useColors) {
3601 SendToProgram("black\n", &first);
3603 SendTimeRemaining(&first, TRUE);
3605 if (first.useColors) {
3606 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3608 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3609 first.maybeThinking = TRUE;
3611 if (first.usePlayother) {
3612 if (first.sendTime) {
3613 SendTimeRemaining(&first, TRUE);
3615 SendToProgram("playother\n", &first);
3621 } else if (gameMode == IcsPlayingBlack) {
3622 if (!WhiteOnMove(forwardMostMove)) {
3623 if (first.sendTime) {
3624 if (first.useColors) {
3625 SendToProgram("white\n", &first);
3627 SendTimeRemaining(&first, FALSE);
3629 if (first.useColors) {
3630 SendToProgram("black\n", &first);
3632 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3633 first.maybeThinking = TRUE;
3635 if (first.usePlayother) {
3636 if (first.sendTime) {
3637 SendTimeRemaining(&first, FALSE);
3639 SendToProgram("playother\n", &first);
3648 if (gameMode == IcsObserving && ics_gamenum == -1) {
3649 /* Moves came from oldmoves or moves command
3650 while we weren't doing anything else.
3652 currentMove = forwardMostMove;
3653 ClearHighlights();/*!!could figure this out*/
3654 flipView = appData.flipView;
3655 DrawPosition(TRUE, boards[currentMove]);
3656 DisplayBothClocks();
3657 snprintf(str, MSG_SIZ, "%s %s %s",
3658 gameInfo.white, _("vs."), gameInfo.black);
3662 /* Moves were history of an active game */
3663 if (gameInfo.resultDetails != NULL) {
3664 free(gameInfo.resultDetails);
3665 gameInfo.resultDetails = NULL;
3668 HistorySet(parseList, backwardMostMove,
3669 forwardMostMove, currentMove-1);
3670 DisplayMove(currentMove - 1);
3671 if (started == STARTED_MOVES) next_out = i;
3672 started = STARTED_NONE;
3673 ics_getting_history = H_FALSE;
3676 case STARTED_OBSERVE:
3677 started = STARTED_NONE;
3678 SendToICS(ics_prefix);
3679 SendToICS("refresh\n");
3685 if(bookHit) { // [HGM] book: simulate book reply
3686 static char bookMove[MSG_SIZ]; // a bit generous?
3688 programStats.nodes = programStats.depth = programStats.time =
3689 programStats.score = programStats.got_only_move = 0;
3690 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3692 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3693 strcat(bookMove, bookHit);
3694 HandleMachineMove(bookMove, &first);
3699 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3700 started == STARTED_HOLDINGS ||
3701 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3702 /* Accumulate characters in move list or board */
3703 parse[parse_pos++] = buf[i];
3706 /* Start of game messages. Mostly we detect start of game
3707 when the first board image arrives. On some versions
3708 of the ICS, though, we need to do a "refresh" after starting
3709 to observe in order to get the current board right away. */
3710 if (looking_at(buf, &i, "Adding game * to observation list")) {
3711 started = STARTED_OBSERVE;
3715 /* Handle auto-observe */
3716 if (appData.autoObserve &&
3717 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3718 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3720 /* Choose the player that was highlighted, if any. */
3721 if (star_match[0][0] == '\033' ||
3722 star_match[1][0] != '\033') {
3723 player = star_match[0];
3725 player = star_match[2];
3727 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3728 ics_prefix, StripHighlightAndTitle(player));
3731 /* Save ratings from notify string */
3732 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3733 player1Rating = string_to_rating(star_match[1]);
3734 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3735 player2Rating = string_to_rating(star_match[3]);
3737 if (appData.debugMode)
3739 "Ratings from 'Game notification:' %s %d, %s %d\n",
3740 player1Name, player1Rating,
3741 player2Name, player2Rating);
3746 /* Deal with automatic examine mode after a game,
3747 and with IcsObserving -> IcsExamining transition */
3748 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3749 looking_at(buf, &i, "has made you an examiner of game *")) {
3751 int gamenum = atoi(star_match[0]);
3752 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3753 gamenum == ics_gamenum) {
3754 /* We were already playing or observing this game;
3755 no need to refetch history */
3756 gameMode = IcsExamining;
3758 pauseExamForwardMostMove = forwardMostMove;
3759 } else if (currentMove < forwardMostMove) {
3760 ForwardInner(forwardMostMove);
3763 /* I don't think this case really can happen */
3764 SendToICS(ics_prefix);
3765 SendToICS("refresh\n");
3770 /* Error messages */
3771 // if (ics_user_moved) {
3772 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3773 if (looking_at(buf, &i, "Illegal move") ||
3774 looking_at(buf, &i, "Not a legal move") ||
3775 looking_at(buf, &i, "Your king is in check") ||
3776 looking_at(buf, &i, "It isn't your turn") ||
3777 looking_at(buf, &i, "It is not your move")) {
3779 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3780 currentMove = forwardMostMove-1;
3781 DisplayMove(currentMove - 1); /* before DMError */
3782 DrawPosition(FALSE, boards[currentMove]);
3783 SwitchClocks(forwardMostMove-1); // [HGM] race
3784 DisplayBothClocks();
3786 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3792 if (looking_at(buf, &i, "still have time") ||
3793 looking_at(buf, &i, "not out of time") ||
3794 looking_at(buf, &i, "either player is out of time") ||
3795 looking_at(buf, &i, "has timeseal; checking")) {
3796 /* We must have called his flag a little too soon */
3797 whiteFlag = blackFlag = FALSE;
3801 if (looking_at(buf, &i, "added * seconds to") ||
3802 looking_at(buf, &i, "seconds were added to")) {
3803 /* Update the clocks */
3804 SendToICS(ics_prefix);
3805 SendToICS("refresh\n");
3809 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3810 ics_clock_paused = TRUE;
3815 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3816 ics_clock_paused = FALSE;
3821 /* Grab player ratings from the Creating: message.
3822 Note we have to check for the special case when
3823 the ICS inserts things like [white] or [black]. */
3824 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3825 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3827 0 player 1 name (not necessarily white)
3829 2 empty, white, or black (IGNORED)
3830 3 player 2 name (not necessarily black)
3833 The names/ratings are sorted out when the game
3834 actually starts (below).
3836 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3837 player1Rating = string_to_rating(star_match[1]);
3838 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3839 player2Rating = string_to_rating(star_match[4]);
3841 if (appData.debugMode)
3843 "Ratings from 'Creating:' %s %d, %s %d\n",
3844 player1Name, player1Rating,
3845 player2Name, player2Rating);
3850 /* Improved generic start/end-of-game messages */
3851 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3852 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3853 /* If tkind == 0: */
3854 /* star_match[0] is the game number */
3855 /* [1] is the white player's name */
3856 /* [2] is the black player's name */
3857 /* For end-of-game: */
3858 /* [3] is the reason for the game end */
3859 /* [4] is a PGN end game-token, preceded by " " */
3860 /* For start-of-game: */
3861 /* [3] begins with "Creating" or "Continuing" */
3862 /* [4] is " *" or empty&