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 SendToICS P((char *s));
156 void SendToICSDelayed P((char *s, long msdelay));
157 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
158 void HandleMachineMove P((char *message, ChessProgramState *cps));
159 int AutoPlayOneMove P((void));
160 int LoadGameOneMove P((ChessMove readAhead));
161 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
162 int LoadPositionFromFile P((char *filename, int n, char *title));
163 int SavePositionToFile P((char *filename));
164 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
165 void ShowMove P((int fromX, int fromY, int toX, int toY));
166 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
167 /*char*/int promoChar));
168 void BackwardInner P((int target));
169 void ForwardInner P((int target));
170 int Adjudicate P((ChessProgramState *cps));
171 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
172 void EditPositionDone P((Boolean fakeRights));
173 void PrintOpponents P((FILE *fp));
174 void PrintPosition P((FILE *fp, int move));
175 void StartChessProgram P((ChessProgramState *cps));
176 void SendToProgram P((char *message, ChessProgramState *cps));
177 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
178 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
179 char *buf, int count, int error));
180 void SendTimeControl P((ChessProgramState *cps,
181 int mps, long tc, int inc, int sd, int st));
182 char *TimeControlTagValue P((void));
183 void Attention P((ChessProgramState *cps));
184 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
185 int ResurrectChessProgram P((void));
186 void DisplayComment P((int moveNumber, char *text));
187 void DisplayMove P((int moveNumber));
189 void ParseGameHistory P((char *game));
190 void ParseBoard12 P((char *string));
191 void KeepAlive P((void));
192 void StartClocks P((void));
193 void SwitchClocks P((int nr));
194 void StopClocks P((void));
195 void ResetClocks P((void));
196 char *PGNDate P((void));
197 void SetGameInfo P((void));
198 int RegisterMove P((void));
199 void MakeRegisteredMove P((void));
200 void TruncateGame P((void));
201 int looking_at P((char *, int *, char *));
202 void CopyPlayerNameIntoFileName P((char **, char *));
203 char *SavePart P((char *));
204 int SaveGameOldStyle P((FILE *));
205 int SaveGamePGN P((FILE *));
206 int CheckFlags P((void));
207 long NextTickLength P((long));
208 void CheckTimeControl P((void));
209 void show_bytes P((FILE *, char *, int));
210 int string_to_rating P((char *str));
211 void ParseFeatures P((char* args, ChessProgramState *cps));
212 void InitBackEnd3 P((void));
213 void FeatureDone P((ChessProgramState* cps, int val));
214 void InitChessProgram P((ChessProgramState *cps, int setup));
215 void OutputKibitz(int window, char *text);
216 int PerpetualChase(int first, int last);
217 int EngineOutputIsUp();
218 void InitDrawingSizes(int x, int y);
219 void NextMatchGame P((void));
220 int NextTourneyGame P((int nr, int *swap));
221 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
222 FILE *WriteTourneyFile P((char *results, FILE *f));
223 void DisplayTwoMachinesTitle P(());
224 static void ExcludeClick P((int index));
227 extern void ConsoleCreate();
230 ChessProgramState *WhitePlayer();
231 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
232 int VerifyDisplayMode P(());
234 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
235 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
236 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
237 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
238 void ics_update_width P((int new_width));
239 extern char installDir[MSG_SIZ];
240 VariantClass startVariant; /* [HGM] nicks: initial variant */
243 extern int tinyLayout, smallLayout;
244 ChessProgramStats programStats;
245 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
247 static int exiting = 0; /* [HGM] moved to top */
248 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
249 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
250 Board partnerBoard; /* [HGM] bughouse: for peeking at partner game */
251 int partnerHighlight[2];
252 Boolean partnerBoardValid = 0;
253 char partnerStatus[MSG_SIZ];
255 Boolean originalFlip;
256 Boolean twoBoards = 0;
257 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
258 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
259 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
260 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
261 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
262 int opponentKibitzes;
263 int lastSavedGame; /* [HGM] save: ID of game */
264 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
265 extern int chatCount;
267 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
268 char lastMsg[MSG_SIZ];
269 ChessSquare pieceSweep = EmptySquare;
270 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
271 int promoDefaultAltered;
273 /* States for ics_getting_history */
275 #define H_REQUESTED 1
276 #define H_GOT_REQ_HEADER 2
277 #define H_GOT_UNREQ_HEADER 3
278 #define H_GETTING_MOVES 4
279 #define H_GOT_UNWANTED_HEADER 5
281 /* whosays values for GameEnds */
290 /* Maximum number of games in a cmail message */
291 #define CMAIL_MAX_GAMES 20
293 /* Different types of move when calling RegisterMove */
295 #define CMAIL_RESIGN 1
297 #define CMAIL_ACCEPT 3
299 /* Different types of result to remember for each game */
300 #define CMAIL_NOT_RESULT 0
301 #define CMAIL_OLD_RESULT 1
302 #define CMAIL_NEW_RESULT 2
304 /* Telnet protocol constants */
315 safeStrCpy (char *dst, const char *src, size_t count)
318 assert( dst != NULL );
319 assert( src != NULL );
322 for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
323 if( i == count && dst[count-1] != NULLCHAR)
325 dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
326 if(appData.debugMode)
327 fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
333 /* Some compiler can't cast u64 to double
334 * This function do the job for us:
336 * We use the highest bit for cast, this only
337 * works if the highest bit is not
338 * in use (This should not happen)
340 * We used this for all compiler
343 u64ToDouble (u64 value)
346 u64 tmp = value & u64Const(0x7fffffffffffffff);
347 r = (double)(s64)tmp;
348 if (value & u64Const(0x8000000000000000))
349 r += 9.2233720368547758080e18; /* 2^63 */
353 /* Fake up flags for now, as we aren't keeping track of castling
354 availability yet. [HGM] Change of logic: the flag now only
355 indicates the type of castlings allowed by the rule of the game.
356 The actual rights themselves are maintained in the array
357 castlingRights, as part of the game history, and are not probed
363 int flags = F_ALL_CASTLE_OK;
364 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
365 switch (gameInfo.variant) {
367 flags &= ~F_ALL_CASTLE_OK;
368 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
369 flags |= F_IGNORE_CHECK;
371 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
374 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
376 case VariantKriegspiel:
377 flags |= F_KRIEGSPIEL_CAPTURE;
379 case VariantCapaRandom:
380 case VariantFischeRandom:
381 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
382 case VariantNoCastle:
383 case VariantShatranj:
387 flags &= ~F_ALL_CASTLE_OK;
395 FILE *gameFileFP, *debugFP, *serverFP;
396 char *currentDebugFile; // [HGM] debug split: to remember name
399 [AS] Note: sometimes, the sscanf() function is used to parse the input
400 into a fixed-size buffer. Because of this, we must be prepared to
401 receive strings as long as the size of the input buffer, which is currently
402 set to 4K for Windows and 8K for the rest.
403 So, we must either allocate sufficiently large buffers here, or
404 reduce the size of the input buffer in the input reading part.
407 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
408 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
409 char thinkOutput1[MSG_SIZ*10];
411 ChessProgramState first, second, pairing;
413 /* premove variables */
416 int premoveFromX = 0;
417 int premoveFromY = 0;
418 int premovePromoChar = 0;
420 Boolean alarmSounded;
421 /* end premove variables */
423 char *ics_prefix = "$";
424 int ics_type = ICS_GENERIC;
426 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
427 int pauseExamForwardMostMove = 0;
428 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
429 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
430 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
431 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
432 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
433 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
434 int whiteFlag = FALSE, blackFlag = FALSE;
435 int userOfferedDraw = FALSE;
436 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
437 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
438 int cmailMoveType[CMAIL_MAX_GAMES];
439 long ics_clock_paused = 0;
440 ProcRef icsPR = NoProc, cmailPR = NoProc;
441 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
442 GameMode gameMode = BeginningOfGame;
443 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
444 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
445 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
446 int hiddenThinkOutputState = 0; /* [AS] */
447 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
448 int adjudicateLossPlies = 6;
449 char white_holding[64], black_holding[64];
450 TimeMark lastNodeCountTime;
451 long lastNodeCount=0;
452 int shiftKey, controlKey; // [HGM] set by mouse handler
454 int have_sent_ICS_logon = 0;
456 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
457 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
458 Boolean adjustedClock;
459 long timeControl_2; /* [AS] Allow separate time controls */
460 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
461 long timeRemaining[2][MAX_MOVES];
462 int matchGame = 0, nextGame = 0, roundNr = 0;
463 Boolean waitingForGame = FALSE;
464 TimeMark programStartTime, pauseStart;
465 char ics_handle[MSG_SIZ];
466 int have_set_title = 0;
468 /* animateTraining preserves the state of appData.animate
469 * when Training mode is activated. This allows the
470 * response to be animated when appData.animate == TRUE and
471 * appData.animateDragging == TRUE.
473 Boolean animateTraining;
479 Board boards[MAX_MOVES];
480 /* [HGM] Following 7 needed for accurate legality tests: */
481 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
482 signed char initialRights[BOARD_FILES];
483 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
484 int initialRulePlies, FENrulePlies;
485 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
487 Boolean shuffleOpenings;
488 int mute; // mute all sounds
490 // [HGM] vari: next 12 to save and restore variations
491 #define MAX_VARIATIONS 10
492 int framePtr = MAX_MOVES-1; // points to free stack entry
494 int savedFirst[MAX_VARIATIONS];
495 int savedLast[MAX_VARIATIONS];
496 int savedFramePtr[MAX_VARIATIONS];
497 char *savedDetails[MAX_VARIATIONS];
498 ChessMove savedResult[MAX_VARIATIONS];
500 void PushTail P((int firstMove, int lastMove));
501 Boolean PopTail P((Boolean annotate));
502 void PushInner P((int firstMove, int lastMove));
503 void PopInner P((Boolean annotate));
504 void CleanupTail P((void));
506 ChessSquare FIDEArray[2][BOARD_FILES] = {
507 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
508 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
509 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
510 BlackKing, BlackBishop, BlackKnight, BlackRook }
513 ChessSquare twoKingsArray[2][BOARD_FILES] = {
514 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
515 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
516 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
517 BlackKing, BlackKing, BlackKnight, BlackRook }
520 ChessSquare KnightmateArray[2][BOARD_FILES] = {
521 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
522 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
523 { BlackRook, BlackMan, BlackBishop, BlackQueen,
524 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
527 ChessSquare SpartanArray[2][BOARD_FILES] = {
528 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
529 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
530 { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
531 BlackDragon, BlackKing, BlackAngel, BlackAlfil }
534 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
535 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
536 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
537 { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
538 BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
541 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
542 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
543 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
544 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
545 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
548 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
549 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
550 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
551 { BlackRook, BlackKnight, BlackMan, BlackFerz,
552 BlackKing, BlackMan, BlackKnight, BlackRook }
556 #if (BOARD_FILES>=10)
557 ChessSquare ShogiArray[2][BOARD_FILES] = {
558 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
559 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
560 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
561 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
564 ChessSquare XiangqiArray[2][BOARD_FILES] = {
565 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
566 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
567 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
568 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
571 ChessSquare CapablancaArray[2][BOARD_FILES] = {
572 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
573 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
574 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
575 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
578 ChessSquare GreatArray[2][BOARD_FILES] = {
579 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
580 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
581 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
582 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
585 ChessSquare JanusArray[2][BOARD_FILES] = {
586 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
587 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
588 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
589 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
592 ChessSquare GrandArray[2][BOARD_FILES] = {
593 { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
594 WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
595 { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
596 BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
600 ChessSquare GothicArray[2][BOARD_FILES] = {
601 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
602 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
603 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
604 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
607 #define GothicArray CapablancaArray
611 ChessSquare FalconArray[2][BOARD_FILES] = {
612 { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
613 WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
614 { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
615 BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
618 #define FalconArray CapablancaArray
621 #else // !(BOARD_FILES>=10)
622 #define XiangqiPosition FIDEArray
623 #define CapablancaArray FIDEArray
624 #define GothicArray FIDEArray
625 #define GreatArray FIDEArray
626 #endif // !(BOARD_FILES>=10)
628 #if (BOARD_FILES>=12)
629 ChessSquare CourierArray[2][BOARD_FILES] = {
630 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
631 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
632 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
633 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
635 #else // !(BOARD_FILES>=12)
636 #define CourierArray CapablancaArray
637 #endif // !(BOARD_FILES>=12)
640 Board initialPosition;
643 /* Convert str to a rating. Checks for special cases of "----",
645 "++++", etc. Also strips ()'s */
647 string_to_rating (char *str)
649 while(*str && !isdigit(*str)) ++str;
651 return 0; /* One of the special "no rating" cases */
659 /* Init programStats */
660 programStats.movelist[0] = 0;
661 programStats.depth = 0;
662 programStats.nr_moves = 0;
663 programStats.moves_left = 0;
664 programStats.nodes = 0;
665 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
666 programStats.score = 0;
667 programStats.got_only_move = 0;
668 programStats.got_fail = 0;
669 programStats.line_is_book = 0;
674 { // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
675 if (appData.firstPlaysBlack) {
676 first.twoMachinesColor = "black\n";
677 second.twoMachinesColor = "white\n";
679 first.twoMachinesColor = "white\n";
680 second.twoMachinesColor = "black\n";
683 first.other = &second;
684 second.other = &first;
687 if(appData.timeOddsMode) {
688 norm = appData.timeOdds[0];
689 if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
691 first.timeOdds = appData.timeOdds[0]/norm;
692 second.timeOdds = appData.timeOdds[1]/norm;
695 if(programVersion) free(programVersion);
696 if (appData.noChessProgram) {
697 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
698 sprintf(programVersion, "%s", PACKAGE_STRING);
700 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
701 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
702 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
707 UnloadEngine (ChessProgramState *cps)
709 /* Kill off first chess program */
710 if (cps->isr != NULL)
711 RemoveInputSource(cps->isr);
714 if (cps->pr != NoProc) {
716 DoSleep( appData.delayBeforeQuit );
717 SendToProgram("quit\n", cps);
718 DoSleep( appData.delayAfterQuit );
719 DestroyChildProcess(cps->pr, cps->useSigterm);
722 if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
726 ClearOptions (ChessProgramState *cps)
729 cps->nrOptions = cps->comboCnt = 0;
730 for(i=0; i<MAX_OPTIONS; i++) {
731 cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
732 cps->option[i].textValue = 0;
736 char *engineNames[] = {
737 /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
738 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
740 /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
741 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
746 InitEngine (ChessProgramState *cps, int n)
747 { // [HGM] all engine initialiation put in a function that does one engine
751 cps->which = engineNames[n];
752 cps->maybeThinking = FALSE;
756 cps->sendDrawOffers = 1;
758 cps->program = appData.chessProgram[n];
759 cps->host = appData.host[n];
760 cps->dir = appData.directory[n];
761 cps->initString = appData.engInitString[n];
762 cps->computerString = appData.computerString[n];
763 cps->useSigint = TRUE;
764 cps->useSigterm = TRUE;
765 cps->reuse = appData.reuse[n];
766 cps->nps = appData.NPS[n]; // [HGM] nps: copy nodes per second
767 cps->useSetboard = FALSE;
769 cps->usePing = FALSE;
772 cps->usePlayother = FALSE;
773 cps->useColors = TRUE;
774 cps->useUsermove = FALSE;
775 cps->sendICS = FALSE;
776 cps->sendName = appData.icsActive;
777 cps->sdKludge = FALSE;
778 cps->stKludge = FALSE;
779 TidyProgramName(cps->program, cps->host, cps->tidy);
781 safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
782 cps->analysisSupport = 2; /* detect */
783 cps->analyzing = FALSE;
784 cps->initDone = FALSE;
786 /* New features added by Tord: */
787 cps->useFEN960 = FALSE;
788 cps->useOOCastle = TRUE;
789 /* End of new features added by Tord. */
790 cps->fenOverride = appData.fenOverride[n];
792 /* [HGM] time odds: set factor for each machine */
793 cps->timeOdds = appData.timeOdds[n];
795 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
796 cps->accumulateTC = appData.accumulateTC[n];
797 cps->maxNrOfSessions = 1;
802 cps->supportsNPS = UNKNOWN;
803 cps->memSize = FALSE;
804 cps->maxCores = FALSE;
805 cps->egtFormats[0] = NULLCHAR;
808 cps->optionSettings = appData.engOptions[n];
810 cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
811 cps->isUCI = appData.isUCI[n]; /* [AS] */
812 cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
814 if (appData.protocolVersion[n] > PROTOVER
815 || appData.protocolVersion[n] < 1)
820 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
821 appData.protocolVersion[n]);
822 if( (len >= MSG_SIZ) && appData.debugMode )
823 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
825 DisplayFatalError(buf, 0, 2);
829 cps->protocolVersion = appData.protocolVersion[n];
832 InitEngineUCI( installDir, cps ); // [HGM] moved here from winboard.c, to make available in xboard
833 ParseFeatures(appData.featureDefaults, cps);
836 ChessProgramState *savCps;
842 if(WaitForEngine(savCps, LoadEngine)) return;
843 CommonEngineInit(); // recalculate time odds
844 if(gameInfo.variant != StringToVariant(appData.variant)) {
845 // we changed variant when loading the engine; this forces us to reset
846 Reset(TRUE, savCps != &first);
847 EditGameEvent(); // for consistency with other path, as Reset changes mode
849 InitChessProgram(savCps, FALSE);
850 SendToProgram("force\n", savCps);
851 DisplayMessage("", "");
852 if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
853 for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
859 ReplaceEngine (ChessProgramState *cps, int n)
863 appData.noChessProgram = FALSE;
864 appData.clockMode = TRUE;
867 if(n) return; // only startup first engine immediately; second can wait
868 savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
872 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
873 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
875 static char resetOptions[] =
876 "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
877 "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
878 "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
879 "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
882 FloatToFront(char **list, char *engineLine)
884 char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
886 if(appData.recentEngines <= 0) return;
887 TidyProgramName(engineLine, "localhost", tidy+1);
888 tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
889 strncpy(buf+1, *list, MSG_SIZ-50);
890 if(p = strstr(buf, tidy)) { // tidy name appears in list
891 q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
892 while(*p++ = *++q); // squeeze out
894 strcat(tidy, buf+1); // put list behind tidy name
895 p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
896 if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
897 ASSIGN(*list, tidy+1);
900 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
903 Load (ChessProgramState *cps, int i)
905 char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
906 if(engineLine && engineLine[0]) { // an engine was selected from the combo box
907 snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
908 SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
909 ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
910 FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
911 appData.firstProtocolVersion = PROTOVER;
912 ParseArgsFromString(buf);
914 ReplaceEngine(cps, i);
915 FloatToFront(&appData.recentEngineList, engineLine);
919 while(q = strchr(p, SLASH)) p = q+1;
920 if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
921 if(engineDir[0] != NULLCHAR) {
922 ASSIGN(appData.directory[i], engineDir); p = engineName;
923 } else if(p != engineName) { // derive directory from engine path, when not given
925 ASSIGN(appData.directory[i], engineName);
927 if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
928 } else { ASSIGN(appData.directory[i], "."); }
930 if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
931 snprintf(command, MSG_SIZ, "%s %s", p, params);
934 ASSIGN(appData.chessProgram[i], p);
935 appData.isUCI[i] = isUCI;
936 appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
937 appData.hasOwnBookUCI[i] = hasBook;
938 if(!nickName[0]) useNick = FALSE;
939 if(useNick) ASSIGN(appData.pgnName[i], nickName);
943 q = firstChessProgramNames;
944 if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
945 quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
946 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
947 quote, p, quote, appData.directory[i],
948 useNick ? " -fn \"" : "",
949 useNick ? nickName : "",
951 v1 ? " -firstProtocolVersion 1" : "",
952 hasBook ? "" : " -fNoOwnBookUCI",
953 isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
954 storeVariant ? " -variant " : "",
955 storeVariant ? VariantName(gameInfo.variant) : "");
956 if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
957 firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
958 if(insert != q) insert[-1] = NULLCHAR;
959 snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
961 FloatToFront(&appData.recentEngineList, buf);
963 ReplaceEngine(cps, i);
969 int matched, min, sec;
971 * Parse timeControl resource
973 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
974 appData.movesPerSession)) {
976 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
977 DisplayFatalError(buf, 0, 2);
981 * Parse searchTime resource
983 if (*appData.searchTime != NULLCHAR) {
984 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
986 searchTime = min * 60;
987 } else if (matched == 2) {
988 searchTime = min * 60 + sec;
991 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
992 DisplayFatalError(buf, 0, 2);
1001 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1002 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1004 GetTimeMark(&programStartTime);
1005 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1006 appData.seedBase = random() + (random()<<15);
1007 pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1009 ClearProgramStats();
1010 programStats.ok_to_send = 1;
1011 programStats.seen_stat = 0;
1014 * Initialize game list
1020 * Internet chess server status
1022 if (appData.icsActive) {
1023 appData.matchMode = FALSE;
1024 appData.matchGames = 0;
1026 appData.noChessProgram = !appData.zippyPlay;
1028 appData.zippyPlay = FALSE;
1029 appData.zippyTalk = FALSE;
1030 appData.noChessProgram = TRUE;
1032 if (*appData.icsHelper != NULLCHAR) {
1033 appData.useTelnet = TRUE;
1034 appData.telnetProgram = appData.icsHelper;
1037 appData.zippyTalk = appData.zippyPlay = FALSE;
1040 /* [AS] Initialize pv info list [HGM] and game state */
1044 for( i=0; i<=framePtr; i++ ) {
1045 pvInfoList[i].depth = -1;
1046 boards[i][EP_STATUS] = EP_NONE;
1047 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1053 /* [AS] Adjudication threshold */
1054 adjudicateLossThreshold = appData.adjudicateLossThreshold;
1056 InitEngine(&first, 0);
1057 InitEngine(&second, 1);
1060 pairing.which = "pairing"; // pairing engine
1061 pairing.pr = NoProc;
1063 pairing.program = appData.pairingEngine;
1064 pairing.host = "localhost";
1067 if (appData.icsActive) {
1068 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
1069 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1070 appData.clockMode = FALSE;
1071 first.sendTime = second.sendTime = 0;
1075 /* Override some settings from environment variables, for backward
1076 compatibility. Unfortunately it's not feasible to have the env
1077 vars just set defaults, at least in xboard. Ugh.
1079 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1084 if (!appData.icsActive) {
1088 /* Check for variants that are supported only in ICS mode,
1089 or not at all. Some that are accepted here nevertheless
1090 have bugs; see comments below.
1092 VariantClass variant = StringToVariant(appData.variant);
1094 case VariantBughouse: /* need four players and two boards */
1095 case VariantKriegspiel: /* need to hide pieces and move details */
1096 /* case VariantFischeRandom: (Fabien: moved below) */
1097 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1098 if( (len >= MSG_SIZ) && appData.debugMode )
1099 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1101 DisplayFatalError(buf, 0, 2);
1104 case VariantUnknown:
1105 case VariantLoadable:
1115 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1116 if( (len >= MSG_SIZ) && appData.debugMode )
1117 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1119 DisplayFatalError(buf, 0, 2);
1122 case VariantXiangqi: /* [HGM] repetition rules not implemented */
1123 case VariantFairy: /* [HGM] TestLegality definitely off! */
1124 case VariantGothic: /* [HGM] should work */
1125 case VariantCapablanca: /* [HGM] should work */
1126 case VariantCourier: /* [HGM] initial forced moves not implemented */
1127 case VariantShogi: /* [HGM] could still mate with pawn drop */
1128 case VariantKnightmate: /* [HGM] should work */
1129 case VariantCylinder: /* [HGM] untested */
1130 case VariantFalcon: /* [HGM] untested */
1131 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1132 offboard interposition not understood */
1133 case VariantNormal: /* definitely works! */
1134 case VariantWildCastle: /* pieces not automatically shuffled */
1135 case VariantNoCastle: /* pieces not automatically shuffled */
1136 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1137 case VariantLosers: /* should work except for win condition,
1138 and doesn't know captures are mandatory */
1139 case VariantSuicide: /* should work except for win condition,
1140 and doesn't know captures are mandatory */
1141 case VariantGiveaway: /* should work except for win condition,
1142 and doesn't know captures are mandatory */
1143 case VariantTwoKings: /* should work */
1144 case VariantAtomic: /* should work except for win condition */
1145 case Variant3Check: /* should work except for win condition */
1146 case VariantShatranj: /* should work except for all win conditions */
1147 case VariantMakruk: /* should work except for draw countdown */
1148 case VariantBerolina: /* might work if TestLegality is off */
1149 case VariantCapaRandom: /* should work */
1150 case VariantJanus: /* should work */
1151 case VariantSuper: /* experimental */
1152 case VariantGreat: /* experimental, requires legality testing to be off */
1153 case VariantSChess: /* S-Chess, should work */
1154 case VariantGrand: /* should work */
1155 case VariantSpartan: /* should work */
1163 NextIntegerFromString (char ** str, long * value)
1168 while( *s == ' ' || *s == '\t' ) {
1174 if( *s >= '0' && *s <= '9' ) {
1175 while( *s >= '0' && *s <= '9' ) {
1176 *value = *value * 10 + (*s - '0');
1189 NextTimeControlFromString (char ** str, long * value)
1192 int result = NextIntegerFromString( str, &temp );
1195 *value = temp * 60; /* Minutes */
1196 if( **str == ':' ) {
1198 result = NextIntegerFromString( str, &temp );
1199 *value += temp; /* Seconds */
1207 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1208 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1209 int result = -1, type = 0; long temp, temp2;
1211 if(**str != ':') return -1; // old params remain in force!
1213 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1214 if( NextIntegerFromString( str, &temp ) ) return -1;
1215 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1218 /* time only: incremental or sudden-death time control */
1219 if(**str == '+') { /* increment follows; read it */
1221 if(**str == '!') type = *(*str)++; // Bronstein TC
1222 if(result = NextIntegerFromString( str, &temp2)) return -1;
1223 *inc = temp2 * 1000;
1224 if(**str == '.') { // read fraction of increment
1225 char *start = ++(*str);
1226 if(result = NextIntegerFromString( str, &temp2)) return -1;
1228 while(start++ < *str) temp2 /= 10;
1232 *moves = 0; *tc = temp * 1000; *incType = type;
1236 (*str)++; /* classical time control */
1237 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1249 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1250 { /* [HGM] get time to add from the multi-session time-control string */
1251 int incType, moves=1; /* kludge to force reading of first session */
1252 long time, increment;
1255 if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1257 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1258 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1259 if(movenr == -1) return time; /* last move before new session */
1260 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1261 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1262 if(!moves) return increment; /* current session is incremental */
1263 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1264 } while(movenr >= -1); /* try again for next session */
1266 return 0; // no new time quota on this move
1270 ParseTimeControl (char *tc, float ti, int mps)
1274 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1277 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1278 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1279 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1283 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1285 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1288 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1290 snprintf(buf, MSG_SIZ, ":%s", mytc);
1292 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1294 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1299 /* Parse second time control */
1302 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1310 timeControl_2 = tc2 * 1000;
1320 timeControl = tc1 * 1000;
1323 timeIncrement = ti * 1000; /* convert to ms */
1324 movesPerSession = 0;
1327 movesPerSession = mps;
1335 if (appData.debugMode) {
1336 fprintf(debugFP, "%s\n", programVersion);
1338 ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1340 set_cont_sequence(appData.wrapContSeq);
1341 if (appData.matchGames > 0) {
1342 appData.matchMode = TRUE;
1343 } else if (appData.matchMode) {
1344 appData.matchGames = 1;
1346 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1347 appData.matchGames = appData.sameColorGames;
1348 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1349 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1350 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1353 if (appData.noChessProgram || first.protocolVersion == 1) {
1356 /* kludge: allow timeout for initial "feature" commands */
1358 DisplayMessage("", _("Starting chess program"));
1359 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1364 CalculateIndex (int index, int gameNr)
1365 { // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1367 if(index > 0) return index; // fixed nmber
1368 if(index == 0) return 1;
1369 res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1370 if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1375 LoadGameOrPosition (int gameNr)
1376 { // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1377 if (*appData.loadGameFile != NULLCHAR) {
1378 if (!LoadGameFromFile(appData.loadGameFile,
1379 CalculateIndex(appData.loadGameIndex, gameNr),
1380 appData.loadGameFile, FALSE)) {
1381 DisplayFatalError(_("Bad game file"), 0, 1);
1384 } else if (*appData.loadPositionFile != NULLCHAR) {
1385 if (!LoadPositionFromFile(appData.loadPositionFile,
1386 CalculateIndex(appData.loadPositionIndex, gameNr),
1387 appData.loadPositionFile)) {
1388 DisplayFatalError(_("Bad position file"), 0, 1);
1396 ReserveGame (int gameNr, char resChar)
1398 FILE *tf = fopen(appData.tourneyFile, "r+");
1399 char *p, *q, c, buf[MSG_SIZ];
1400 if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1401 safeStrCpy(buf, lastMsg, MSG_SIZ);
1402 DisplayMessage(_("Pick new game"), "");
1403 flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1404 ParseArgsFromFile(tf);
1405 p = q = appData.results;
1406 if(appData.debugMode) {
1407 char *r = appData.participants;
1408 fprintf(debugFP, "results = '%s'\n", p);
1409 while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1410 fprintf(debugFP, "\n");
1412 while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1414 q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1415 safeStrCpy(q, p, strlen(p) + 2);
1416 if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1417 if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1418 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1419 if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1422 fseek(tf, -(strlen(p)+4), SEEK_END);
1424 if(c != '"') // depending on DOS or Unix line endings we can be one off
1425 fseek(tf, -(strlen(p)+2), SEEK_END);
1426 else fseek(tf, -(strlen(p)+3), SEEK_END);
1427 fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1428 DisplayMessage(buf, "");
1429 free(p); appData.results = q;
1430 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1431 (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1432 int round = appData.defaultMatchGames * appData.tourneyType;
1433 if(gameNr < 0 || appData.tourneyType < 1 || // gauntlet engine can always stay loaded as first engine
1434 appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1435 UnloadEngine(&first); // next game belongs to other pairing;
1436 UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1438 if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1442 MatchEvent (int mode)
1443 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1445 if(matchMode) { // already in match mode: switch it off
1447 if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1450 // if(gameMode != BeginningOfGame) {
1451 // DisplayError(_("You can only start a match from the initial position."), 0);
1455 if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1456 /* Set up machine vs. machine match */
1458 NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1459 if(appData.tourneyFile[0]) {
1461 if(nextGame > appData.matchGames) {
1463 if(strchr(appData.results, '*') == NULL) {
1465 appData.tourneyCycles++;
1466 if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1468 NextTourneyGame(-1, &dummy);
1470 if(nextGame <= appData.matchGames) {
1471 DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1473 ScheduleDelayedEvent(NextMatchGame, 10000);
1478 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1479 DisplayError(buf, 0);
1480 appData.tourneyFile[0] = 0;
1484 if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically
1485 DisplayFatalError(_("Can't have a match with no chess programs"),
1490 matchGame = roundNr = 1;
1491 first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1495 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1498 InitBackEnd3 P((void))
1500 GameMode initialMode;
1504 InitChessProgram(&first, startedFromSetupPosition);
1506 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1507 free(programVersion);
1508 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1509 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1510 FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1513 if (appData.icsActive) {
1515 /* [DM] Make a console window if needed [HGM] merged ifs */
1521 if (*appData.icsCommPort != NULLCHAR)
1522 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1523 appData.icsCommPort);
1525 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1526 appData.icsHost, appData.icsPort);
1528 if( (len >= MSG_SIZ) && appData.debugMode )
1529 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1531 DisplayFatalError(buf, err, 1);
1536 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1538 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1539 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1540 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1541 } else if (appData.noChessProgram) {
1547 if (*appData.cmailGameName != NULLCHAR) {
1549 OpenLoopback(&cmailPR);
1551 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1555 DisplayMessage("", "");
1556 if (StrCaseCmp(appData.initialMode, "") == 0) {
1557 initialMode = BeginningOfGame;
1558 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1559 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1560 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1561 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1564 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1565 initialMode = TwoMachinesPlay;
1566 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1567 initialMode = AnalyzeFile;
1568 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1569 initialMode = AnalyzeMode;
1570 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1571 initialMode = MachinePlaysWhite;
1572 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1573 initialMode = MachinePlaysBlack;
1574 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1575 initialMode = EditGame;
1576 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1577 initialMode = EditPosition;
1578 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1579 initialMode = Training;
1581 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1582 if( (len >= MSG_SIZ) && appData.debugMode )
1583 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1585 DisplayFatalError(buf, 0, 2);
1589 if (appData.matchMode) {
1590 if(appData.tourneyFile[0]) { // start tourney from command line
1592 if(f = fopen(appData.tourneyFile, "r")) {
1593 ParseArgsFromFile(f); // make sure tourney parmeters re known
1595 appData.clockMode = TRUE;
1597 } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1600 } else if (*appData.cmailGameName != NULLCHAR) {
1601 /* Set up cmail mode */
1602 ReloadCmailMsgEvent(TRUE);
1604 /* Set up other modes */
1605 if (initialMode == AnalyzeFile) {
1606 if (*appData.loadGameFile == NULLCHAR) {
1607 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1611 if (*appData.loadGameFile != NULLCHAR) {
1612 (void) LoadGameFromFile(appData.loadGameFile,
1613 appData.loadGameIndex,
1614 appData.loadGameFile, TRUE);
1615 } else if (*appData.loadPositionFile != NULLCHAR) {
1616 (void) LoadPositionFromFile(appData.loadPositionFile,
1617 appData.loadPositionIndex,
1618 appData.loadPositionFile);
1619 /* [HGM] try to make self-starting even after FEN load */
1620 /* to allow automatic setup of fairy variants with wtm */
1621 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1622 gameMode = BeginningOfGame;
1623 setboardSpoiledMachineBlack = 1;
1625 /* [HGM] loadPos: make that every new game uses the setup */
1626 /* from file as long as we do not switch variant */
1627 if(!blackPlaysFirst) {
1628 startedFromPositionFile = TRUE;
1629 CopyBoard(filePosition, boards[0]);
1632 if (initialMode == AnalyzeMode) {
1633 if (appData.noChessProgram) {
1634 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1637 if (appData.icsActive) {
1638 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1642 } else if (initialMode == AnalyzeFile) {
1643 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1644 ShowThinkingEvent();
1646 AnalysisPeriodicEvent(1);
1647 } else if (initialMode == MachinePlaysWhite) {
1648 if (appData.noChessProgram) {
1649 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1653 if (appData.icsActive) {
1654 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1658 MachineWhiteEvent();
1659 } else if (initialMode == MachinePlaysBlack) {
1660 if (appData.noChessProgram) {
1661 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1665 if (appData.icsActive) {
1666 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1670 MachineBlackEvent();
1671 } else if (initialMode == TwoMachinesPlay) {
1672 if (appData.noChessProgram) {
1673 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1677 if (appData.icsActive) {
1678 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1683 } else if (initialMode == EditGame) {
1685 } else if (initialMode == EditPosition) {
1686 EditPositionEvent();
1687 } else if (initialMode == Training) {
1688 if (*appData.loadGameFile == NULLCHAR) {
1689 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1698 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1700 DisplayBook(current+1);
1702 MoveHistorySet( movelist, first, last, current, pvInfoList );
1704 EvalGraphSet( first, last, current, pvInfoList );
1706 MakeEngineOutputTitle();
1710 * Establish will establish a contact to a remote host.port.
1711 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1712 * used to talk to the host.
1713 * Returns 0 if okay, error code if not.
1720 if (*appData.icsCommPort != NULLCHAR) {
1721 /* Talk to the host through a serial comm port */
1722 return OpenCommPort(appData.icsCommPort, &icsPR);
1724 } else if (*appData.gateway != NULLCHAR) {
1725 if (*appData.remoteShell == NULLCHAR) {
1726 /* Use the rcmd protocol to run telnet program on a gateway host */
1727 snprintf(buf, sizeof(buf), "%s %s %s",
1728 appData.telnetProgram, appData.icsHost, appData.icsPort);
1729 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1732 /* Use the rsh program to run telnet program on a gateway host */
1733 if (*appData.remoteUser == NULLCHAR) {
1734 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1735 appData.gateway, appData.telnetProgram,
1736 appData.icsHost, appData.icsPort);
1738 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1739 appData.remoteShell, appData.gateway,
1740 appData.remoteUser, appData.telnetProgram,
1741 appData.icsHost, appData.icsPort);
1743 return StartChildProcess(buf, "", &icsPR);
1746 } else if (appData.useTelnet) {
1747 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1750 /* TCP socket interface differs somewhat between
1751 Unix and NT; handle details in the front end.
1753 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1758 EscapeExpand (char *p, char *q)
1759 { // [HGM] initstring: routine to shape up string arguments
1760 while(*p++ = *q++) if(p[-1] == '\\')
1762 case 'n': p[-1] = '\n'; break;
1763 case 'r': p[-1] = '\r'; break;
1764 case 't': p[-1] = '\t'; break;
1765 case '\\': p[-1] = '\\'; break;
1766 case 0: *p = 0; return;
1767 default: p[-1] = q[-1]; break;
1772 show_bytes (FILE *fp, char *buf, int count)
1775 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1776 fprintf(fp, "\\%03o", *buf & 0xff);
1785 /* Returns an errno value */
1787 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1789 char buf[8192], *p, *q, *buflim;
1790 int left, newcount, outcount;
1792 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1793 *appData.gateway != NULLCHAR) {
1794 if (appData.debugMode) {
1795 fprintf(debugFP, ">ICS: ");
1796 show_bytes(debugFP, message, count);
1797 fprintf(debugFP, "\n");
1799 return OutputToProcess(pr, message, count, outError);
1802 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1809 if (appData.debugMode) {
1810 fprintf(debugFP, ">ICS: ");
1811 show_bytes(debugFP, buf, newcount);
1812 fprintf(debugFP, "\n");
1814 outcount = OutputToProcess(pr, buf, newcount, outError);
1815 if (outcount < newcount) return -1; /* to be sure */
1822 } else if (((unsigned char) *p) == TN_IAC) {
1823 *q++ = (char) TN_IAC;
1830 if (appData.debugMode) {
1831 fprintf(debugFP, ">ICS: ");
1832 show_bytes(debugFP, buf, newcount);
1833 fprintf(debugFP, "\n");
1835 outcount = OutputToProcess(pr, buf, newcount, outError);
1836 if (outcount < newcount) return -1; /* to be sure */
1841 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1843 int outError, outCount;
1844 static int gotEof = 0;
1846 /* Pass data read from player on to ICS */
1849 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1850 if (outCount < count) {
1851 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1853 } else if (count < 0) {
1854 RemoveInputSource(isr);
1855 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1856 } else if (gotEof++ > 0) {
1857 RemoveInputSource(isr);
1858 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1864 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1865 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1866 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1867 SendToICS("date\n");
1868 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1871 /* added routine for printf style output to ics */
1873 ics_printf (char *format, ...)
1875 char buffer[MSG_SIZ];
1878 va_start(args, format);
1879 vsnprintf(buffer, sizeof(buffer), format, args);
1880 buffer[sizeof(buffer)-1] = '\0';
1888 int count, outCount, outError;
1890 if (icsPR == NoProc) return;
1893 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1894 if (outCount < count) {
1895 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1899 /* This is used for sending logon scripts to the ICS. Sending
1900 without a delay causes problems when using timestamp on ICC
1901 (at least on my machine). */
1903 SendToICSDelayed (char *s, long msdelay)
1905 int count, outCount, outError;
1907 if (icsPR == NoProc) return;
1910 if (appData.debugMode) {
1911 fprintf(debugFP, ">ICS: ");
1912 show_bytes(debugFP, s, count);
1913 fprintf(debugFP, "\n");
1915 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1917 if (outCount < count) {
1918 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1923 /* Remove all highlighting escape sequences in s
1924 Also deletes any suffix starting with '('
1927 StripHighlightAndTitle (char *s)
1929 static char retbuf[MSG_SIZ];
1932 while (*s != NULLCHAR) {
1933 while (*s == '\033') {
1934 while (*s != NULLCHAR && !isalpha(*s)) s++;
1935 if (*s != NULLCHAR) s++;
1937 while (*s != NULLCHAR && *s != '\033') {
1938 if (*s == '(' || *s == '[') {
1949 /* Remove all highlighting escape sequences in s */
1951 StripHighlight (char *s)
1953 static char retbuf[MSG_SIZ];
1956 while (*s != NULLCHAR) {
1957 while (*s == '\033') {
1958 while (*s != NULLCHAR && !isalpha(*s)) s++;
1959 if (*s != NULLCHAR) s++;
1961 while (*s != NULLCHAR && *s != '\033') {
1969 char *variantNames[] = VARIANT_NAMES;
1971 VariantName (VariantClass v)
1973 return variantNames[v];
1977 /* Identify a variant from the strings the chess servers use or the
1978 PGN Variant tag names we use. */
1980 StringToVariant (char *e)
1984 VariantClass v = VariantNormal;
1985 int i, found = FALSE;
1991 /* [HGM] skip over optional board-size prefixes */
1992 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1993 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1994 while( *e++ != '_');
1997 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2001 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2002 if (StrCaseStr(e, variantNames[i])) {
2003 v = (VariantClass) i;
2010 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2011 || StrCaseStr(e, "wild/fr")
2012 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2013 v = VariantFischeRandom;
2014 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2015 (i = 1, p = StrCaseStr(e, "w"))) {
2017 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2024 case 0: /* FICS only, actually */
2026 /* Castling legal even if K starts on d-file */
2027 v = VariantWildCastle;
2032 /* Castling illegal even if K & R happen to start in
2033 normal positions. */
2034 v = VariantNoCastle;
2047 /* Castling legal iff K & R start in normal positions */
2053 /* Special wilds for position setup; unclear what to do here */
2054 v = VariantLoadable;
2057 /* Bizarre ICC game */
2058 v = VariantTwoKings;
2061 v = VariantKriegspiel;
2067 v = VariantFischeRandom;
2070 v = VariantCrazyhouse;
2073 v = VariantBughouse;
2079 /* Not quite the same as FICS suicide! */
2080 v = VariantGiveaway;
2086 v = VariantShatranj;
2089 /* Temporary names for future ICC types. The name *will* change in
2090 the next xboard/WinBoard release after ICC defines it. */
2128 v = VariantCapablanca;
2131 v = VariantKnightmate;
2137 v = VariantCylinder;
2143 v = VariantCapaRandom;
2146 v = VariantBerolina;
2158 /* Found "wild" or "w" in the string but no number;
2159 must assume it's normal chess. */
2163 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2164 if( (len >= MSG_SIZ) && appData.debugMode )
2165 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2167 DisplayError(buf, 0);
2173 if (appData.debugMode) {
2174 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2175 e, wnum, VariantName(v));
2180 static int leftover_start = 0, leftover_len = 0;
2181 char star_match[STAR_MATCH_N][MSG_SIZ];
2183 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2184 advance *index beyond it, and set leftover_start to the new value of
2185 *index; else return FALSE. If pattern contains the character '*', it
2186 matches any sequence of characters not containing '\r', '\n', or the
2187 character following the '*' (if any), and the matched sequence(s) are
2188 copied into star_match.
2191 looking_at ( char *buf, int *index, char *pattern)
2193 char *bufp = &buf[*index], *patternp = pattern;
2195 char *matchp = star_match[0];
2198 if (*patternp == NULLCHAR) {
2199 *index = leftover_start = bufp - buf;
2203 if (*bufp == NULLCHAR) return FALSE;
2204 if (*patternp == '*') {
2205 if (*bufp == *(patternp + 1)) {
2207 matchp = star_match[++star_count];
2211 } else if (*bufp == '\n' || *bufp == '\r') {
2213 if (*patternp == NULLCHAR)
2218 *matchp++ = *bufp++;
2222 if (*patternp != *bufp) return FALSE;
2229 SendToPlayer (char *data, int length)
2231 int error, outCount;
2232 outCount = OutputToProcess(NoProc, data, length, &error);
2233 if (outCount < length) {
2234 DisplayFatalError(_("Error writing to display"), error, 1);
2239 PackHolding (char packed[], char *holding)
2249 switch (runlength) {
2260 sprintf(q, "%d", runlength);
2272 /* Telnet protocol requests from the front end */
2274 TelnetRequest (unsigned char ddww, unsigned char option)
2276 unsigned char msg[3];
2277 int outCount, outError;
2279 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2281 if (appData.debugMode) {
2282 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2298 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2307 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2310 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2315 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2317 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2324 if (!appData.icsActive) return;
2325 TelnetRequest(TN_DO, TN_ECHO);
2331 if (!appData.icsActive) return;
2332 TelnetRequest(TN_DONT, TN_ECHO);
2336 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2338 /* put the holdings sent to us by the server on the board holdings area */
2339 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2343 if(gameInfo.holdingsWidth < 2) return;
2344 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2345 return; // prevent overwriting by pre-board holdings
2347 if( (int)lowestPiece >= BlackPawn ) {
2350 holdingsStartRow = BOARD_HEIGHT-1;
2353 holdingsColumn = BOARD_WIDTH-1;
2354 countsColumn = BOARD_WIDTH-2;
2355 holdingsStartRow = 0;
2359 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2360 board[i][holdingsColumn] = EmptySquare;
2361 board[i][countsColumn] = (ChessSquare) 0;
2363 while( (p=*holdings++) != NULLCHAR ) {
2364 piece = CharToPiece( ToUpper(p) );
2365 if(piece == EmptySquare) continue;
2366 /*j = (int) piece - (int) WhitePawn;*/
2367 j = PieceToNumber(piece);
2368 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2369 if(j < 0) continue; /* should not happen */
2370 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2371 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2372 board[holdingsStartRow+j*direction][countsColumn]++;
2378 VariantSwitch (Board board, VariantClass newVariant)
2380 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2381 static Board oldBoard;
2383 startedFromPositionFile = FALSE;
2384 if(gameInfo.variant == newVariant) return;
2386 /* [HGM] This routine is called each time an assignment is made to
2387 * gameInfo.variant during a game, to make sure the board sizes
2388 * are set to match the new variant. If that means adding or deleting
2389 * holdings, we shift the playing board accordingly
2390 * This kludge is needed because in ICS observe mode, we get boards
2391 * of an ongoing game without knowing the variant, and learn about the
2392 * latter only later. This can be because of the move list we requested,
2393 * in which case the game history is refilled from the beginning anyway,
2394 * but also when receiving holdings of a crazyhouse game. In the latter
2395 * case we want to add those holdings to the already received position.
2399 if (appData.debugMode) {
2400 fprintf(debugFP, "Switch board from %s to %s\n",
2401 VariantName(gameInfo.variant), VariantName(newVariant));
2402 setbuf(debugFP, NULL);
2404 shuffleOpenings = 0; /* [HGM] shuffle */
2405 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2409 newWidth = 9; newHeight = 9;
2410 gameInfo.holdingsSize = 7;
2411 case VariantBughouse:
2412 case VariantCrazyhouse:
2413 newHoldingsWidth = 2; break;
2417 newHoldingsWidth = 2;
2418 gameInfo.holdingsSize = 8;
2421 case VariantCapablanca:
2422 case VariantCapaRandom:
2425 newHoldingsWidth = gameInfo.holdingsSize = 0;
2428 if(newWidth != gameInfo.boardWidth ||
2429 newHeight != gameInfo.boardHeight ||
2430 newHoldingsWidth != gameInfo.holdingsWidth ) {
2432 /* shift position to new playing area, if needed */
2433 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2434 for(i=0; i<BOARD_HEIGHT; i++)
2435 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2436 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2438 for(i=0; i<newHeight; i++) {
2439 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2440 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2442 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2443 for(i=0; i<BOARD_HEIGHT; i++)
2444 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2445 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2448 board[HOLDINGS_SET] = 0;
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.f) tc = 1.f;
2497 if(tc > 95.f) tc = 95.f;
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 (don't care). */
3863 int gamenum = atoi(star_match[0]);
3864 char *whitename, *blackname, *why, *endtoken;
3865 ChessMove endtype = EndOfFile;
3868 whitename = star_match[1];
3869 blackname = star_match[2];
3870 why = star_match[3];
3871 endtoken = star_match[4];
3873 whitename = star_match[1];
3874 blackname = star_match[3];
3875 why = star_match[5];
3876 endtoken = star_match[6];