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));
225 void ToggleSecond P((void));
228 extern void ConsoleCreate();
231 ChessProgramState *WhitePlayer();
232 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
233 int VerifyDisplayMode P(());
235 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
236 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
237 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
238 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
239 void ics_update_width P((int new_width));
240 extern char installDir[MSG_SIZ];
241 VariantClass startVariant; /* [HGM] nicks: initial variant */
244 extern int tinyLayout, smallLayout;
245 ChessProgramStats programStats;
246 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
248 static int exiting = 0; /* [HGM] moved to top */
249 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
250 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
251 Board partnerBoard; /* [HGM] bughouse: for peeking at partner game */
252 int partnerHighlight[2];
253 Boolean partnerBoardValid = 0;
254 char partnerStatus[MSG_SIZ];
256 Boolean originalFlip;
257 Boolean twoBoards = 0;
258 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
259 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
260 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
261 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
262 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
263 int opponentKibitzes;
264 int lastSavedGame; /* [HGM] save: ID of game */
265 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
266 extern int chatCount;
268 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
269 char lastMsg[MSG_SIZ];
270 ChessSquare pieceSweep = EmptySquare;
271 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
272 int promoDefaultAltered;
273 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
275 /* States for ics_getting_history */
277 #define H_REQUESTED 1
278 #define H_GOT_REQ_HEADER 2
279 #define H_GOT_UNREQ_HEADER 3
280 #define H_GETTING_MOVES 4
281 #define H_GOT_UNWANTED_HEADER 5
283 /* whosays values for GameEnds */
292 /* Maximum number of games in a cmail message */
293 #define CMAIL_MAX_GAMES 20
295 /* Different types of move when calling RegisterMove */
297 #define CMAIL_RESIGN 1
299 #define CMAIL_ACCEPT 3
301 /* Different types of result to remember for each game */
302 #define CMAIL_NOT_RESULT 0
303 #define CMAIL_OLD_RESULT 1
304 #define CMAIL_NEW_RESULT 2
306 /* Telnet protocol constants */
317 safeStrCpy (char *dst, const char *src, size_t count)
320 assert( dst != NULL );
321 assert( src != NULL );
324 for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
325 if( i == count && dst[count-1] != NULLCHAR)
327 dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
328 if(appData.debugMode)
329 fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
335 /* Some compiler can't cast u64 to double
336 * This function do the job for us:
338 * We use the highest bit for cast, this only
339 * works if the highest bit is not
340 * in use (This should not happen)
342 * We used this for all compiler
345 u64ToDouble (u64 value)
348 u64 tmp = value & u64Const(0x7fffffffffffffff);
349 r = (double)(s64)tmp;
350 if (value & u64Const(0x8000000000000000))
351 r += 9.2233720368547758080e18; /* 2^63 */
355 /* Fake up flags for now, as we aren't keeping track of castling
356 availability yet. [HGM] Change of logic: the flag now only
357 indicates the type of castlings allowed by the rule of the game.
358 The actual rights themselves are maintained in the array
359 castlingRights, as part of the game history, and are not probed
365 int flags = F_ALL_CASTLE_OK;
366 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
367 switch (gameInfo.variant) {
369 flags &= ~F_ALL_CASTLE_OK;
370 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
371 flags |= F_IGNORE_CHECK;
373 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
376 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
378 case VariantKriegspiel:
379 flags |= F_KRIEGSPIEL_CAPTURE;
381 case VariantCapaRandom:
382 case VariantFischeRandom:
383 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
384 case VariantNoCastle:
385 case VariantShatranj:
389 flags &= ~F_ALL_CASTLE_OK;
397 FILE *gameFileFP, *debugFP, *serverFP;
398 char *currentDebugFile; // [HGM] debug split: to remember name
401 [AS] Note: sometimes, the sscanf() function is used to parse the input
402 into a fixed-size buffer. Because of this, we must be prepared to
403 receive strings as long as the size of the input buffer, which is currently
404 set to 4K for Windows and 8K for the rest.
405 So, we must either allocate sufficiently large buffers here, or
406 reduce the size of the input buffer in the input reading part.
409 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
410 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
411 char thinkOutput1[MSG_SIZ*10];
413 ChessProgramState first, second, pairing;
415 /* premove variables */
418 int premoveFromX = 0;
419 int premoveFromY = 0;
420 int premovePromoChar = 0;
422 Boolean alarmSounded;
423 /* end premove variables */
425 char *ics_prefix = "$";
426 enum ICS_TYPE ics_type = ICS_GENERIC;
428 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
429 int pauseExamForwardMostMove = 0;
430 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
431 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
432 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
433 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
434 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
435 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
436 int whiteFlag = FALSE, blackFlag = FALSE;
437 int userOfferedDraw = FALSE;
438 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
439 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
440 int cmailMoveType[CMAIL_MAX_GAMES];
441 long ics_clock_paused = 0;
442 ProcRef icsPR = NoProc, cmailPR = NoProc;
443 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
444 GameMode gameMode = BeginningOfGame;
445 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
446 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
447 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
448 int hiddenThinkOutputState = 0; /* [AS] */
449 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
450 int adjudicateLossPlies = 6;
451 char white_holding[64], black_holding[64];
452 TimeMark lastNodeCountTime;
453 long lastNodeCount=0;
454 int shiftKey, controlKey; // [HGM] set by mouse handler
456 int have_sent_ICS_logon = 0;
458 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
459 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
460 Boolean adjustedClock;
461 long timeControl_2; /* [AS] Allow separate time controls */
462 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [HGM] secondary TC: merge of MPS, TC and inc */
463 long timeRemaining[2][MAX_MOVES];
464 int matchGame = 0, nextGame = 0, roundNr = 0;
465 Boolean waitingForGame = FALSE;
466 TimeMark programStartTime, pauseStart;
467 char ics_handle[MSG_SIZ];
468 int have_set_title = 0;
470 /* animateTraining preserves the state of appData.animate
471 * when Training mode is activated. This allows the
472 * response to be animated when appData.animate == TRUE and
473 * appData.animateDragging == TRUE.
475 Boolean animateTraining;
481 Board boards[MAX_MOVES];
482 /* [HGM] Following 7 needed for accurate legality tests: */
483 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
484 signed char initialRights[BOARD_FILES];
485 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
486 int initialRulePlies, FENrulePlies;
487 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
489 Boolean shuffleOpenings;
490 int mute; // mute all sounds
492 // [HGM] vari: next 12 to save and restore variations
493 #define MAX_VARIATIONS 10
494 int framePtr = MAX_MOVES-1; // points to free stack entry
496 int savedFirst[MAX_VARIATIONS];
497 int savedLast[MAX_VARIATIONS];
498 int savedFramePtr[MAX_VARIATIONS];
499 char *savedDetails[MAX_VARIATIONS];
500 ChessMove savedResult[MAX_VARIATIONS];
502 void PushTail P((int firstMove, int lastMove));
503 Boolean PopTail P((Boolean annotate));
504 void PushInner P((int firstMove, int lastMove));
505 void PopInner P((Boolean annotate));
506 void CleanupTail P((void));
508 ChessSquare FIDEArray[2][BOARD_FILES] = {
509 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
510 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
511 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
512 BlackKing, BlackBishop, BlackKnight, BlackRook }
515 ChessSquare twoKingsArray[2][BOARD_FILES] = {
516 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
517 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
518 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
519 BlackKing, BlackKing, BlackKnight, BlackRook }
522 ChessSquare KnightmateArray[2][BOARD_FILES] = {
523 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
524 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
525 { BlackRook, BlackMan, BlackBishop, BlackQueen,
526 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
529 ChessSquare SpartanArray[2][BOARD_FILES] = {
530 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
531 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
532 { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
533 BlackDragon, BlackKing, BlackAngel, BlackAlfil }
536 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
537 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
538 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
539 { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
540 BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
543 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
544 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
545 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
546 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
547 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
550 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
551 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
552 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
553 { BlackRook, BlackKnight, BlackMan, BlackFerz,
554 BlackKing, BlackMan, BlackKnight, BlackRook }
558 #if (BOARD_FILES>=10)
559 ChessSquare ShogiArray[2][BOARD_FILES] = {
560 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
561 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
562 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
563 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
566 ChessSquare XiangqiArray[2][BOARD_FILES] = {
567 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
568 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
569 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
570 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
573 ChessSquare CapablancaArray[2][BOARD_FILES] = {
574 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
575 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
576 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
577 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
580 ChessSquare GreatArray[2][BOARD_FILES] = {
581 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
582 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
583 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
584 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
587 ChessSquare JanusArray[2][BOARD_FILES] = {
588 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
589 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
590 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
591 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
594 ChessSquare GrandArray[2][BOARD_FILES] = {
595 { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
596 WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
597 { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
598 BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
602 ChessSquare GothicArray[2][BOARD_FILES] = {
603 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
604 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
605 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
606 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
609 #define GothicArray CapablancaArray
613 ChessSquare FalconArray[2][BOARD_FILES] = {
614 { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
615 WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
616 { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
617 BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
620 #define FalconArray CapablancaArray
623 #else // !(BOARD_FILES>=10)
624 #define XiangqiPosition FIDEArray
625 #define CapablancaArray FIDEArray
626 #define GothicArray FIDEArray
627 #define GreatArray FIDEArray
628 #endif // !(BOARD_FILES>=10)
630 #if (BOARD_FILES>=12)
631 ChessSquare CourierArray[2][BOARD_FILES] = {
632 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
633 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
634 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
635 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
637 #else // !(BOARD_FILES>=12)
638 #define CourierArray CapablancaArray
639 #endif // !(BOARD_FILES>=12)
642 Board initialPosition;
645 /* Convert str to a rating. Checks for special cases of "----",
647 "++++", etc. Also strips ()'s */
649 string_to_rating (char *str)
651 while(*str && !isdigit(*str)) ++str;
653 return 0; /* One of the special "no rating" cases */
661 /* Init programStats */
662 programStats.movelist[0] = 0;
663 programStats.depth = 0;
664 programStats.nr_moves = 0;
665 programStats.moves_left = 0;
666 programStats.nodes = 0;
667 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
668 programStats.score = 0;
669 programStats.got_only_move = 0;
670 programStats.got_fail = 0;
671 programStats.line_is_book = 0;
676 { // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
677 if (appData.firstPlaysBlack) {
678 first.twoMachinesColor = "black\n";
679 second.twoMachinesColor = "white\n";
681 first.twoMachinesColor = "white\n";
682 second.twoMachinesColor = "black\n";
685 first.other = &second;
686 second.other = &first;
689 if(appData.timeOddsMode) {
690 norm = appData.timeOdds[0];
691 if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
693 first.timeOdds = appData.timeOdds[0]/norm;
694 second.timeOdds = appData.timeOdds[1]/norm;
697 if(programVersion) free(programVersion);
698 if (appData.noChessProgram) {
699 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
700 sprintf(programVersion, "%s", PACKAGE_STRING);
702 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
703 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
704 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
709 UnloadEngine (ChessProgramState *cps)
711 /* Kill off first chess program */
712 if (cps->isr != NULL)
713 RemoveInputSource(cps->isr);
716 if (cps->pr != NoProc) {
718 DoSleep( appData.delayBeforeQuit );
719 SendToProgram("quit\n", cps);
720 DoSleep( appData.delayAfterQuit );
721 DestroyChildProcess(cps->pr, cps->useSigterm);
724 if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
728 ClearOptions (ChessProgramState *cps)
731 cps->nrOptions = cps->comboCnt = 0;
732 for(i=0; i<MAX_OPTIONS; i++) {
733 cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
734 cps->option[i].textValue = 0;
738 char *engineNames[] = {
739 /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
740 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
742 /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
743 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
748 InitEngine (ChessProgramState *cps, int n)
749 { // [HGM] all engine initialiation put in a function that does one engine
753 cps->which = engineNames[n];
754 cps->maybeThinking = FALSE;
758 cps->sendDrawOffers = 1;
760 cps->program = appData.chessProgram[n];
761 cps->host = appData.host[n];
762 cps->dir = appData.directory[n];
763 cps->initString = appData.engInitString[n];
764 cps->computerString = appData.computerString[n];
765 cps->useSigint = TRUE;
766 cps->useSigterm = TRUE;
767 cps->reuse = appData.reuse[n];
768 cps->nps = appData.NPS[n]; // [HGM] nps: copy nodes per second
769 cps->useSetboard = FALSE;
771 cps->usePing = FALSE;
774 cps->usePlayother = FALSE;
775 cps->useColors = TRUE;
776 cps->useUsermove = FALSE;
777 cps->sendICS = FALSE;
778 cps->sendName = appData.icsActive;
779 cps->sdKludge = FALSE;
780 cps->stKludge = FALSE;
781 TidyProgramName(cps->program, cps->host, cps->tidy);
783 safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
784 cps->analysisSupport = 2; /* detect */
785 cps->analyzing = FALSE;
786 cps->initDone = FALSE;
788 /* New features added by Tord: */
789 cps->useFEN960 = FALSE;
790 cps->useOOCastle = TRUE;
791 /* End of new features added by Tord. */
792 cps->fenOverride = appData.fenOverride[n];
794 /* [HGM] time odds: set factor for each machine */
795 cps->timeOdds = appData.timeOdds[n];
797 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
798 cps->accumulateTC = appData.accumulateTC[n];
799 cps->maxNrOfSessions = 1;
804 cps->supportsNPS = UNKNOWN;
805 cps->memSize = FALSE;
806 cps->maxCores = FALSE;
807 cps->egtFormats[0] = NULLCHAR;
810 cps->optionSettings = appData.engOptions[n];
812 cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
813 cps->isUCI = appData.isUCI[n]; /* [AS] */
814 cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
816 if (appData.protocolVersion[n] > PROTOVER
817 || appData.protocolVersion[n] < 1)
822 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
823 appData.protocolVersion[n]);
824 if( (len >= MSG_SIZ) && appData.debugMode )
825 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
827 DisplayFatalError(buf, 0, 2);
831 cps->protocolVersion = appData.protocolVersion[n];
834 InitEngineUCI( installDir, cps ); // [HGM] moved here from winboard.c, to make available in xboard
835 ParseFeatures(appData.featureDefaults, cps);
838 ChessProgramState *savCps;
844 if(WaitForEngine(savCps, LoadEngine)) return;
845 CommonEngineInit(); // recalculate time odds
846 if(gameInfo.variant != StringToVariant(appData.variant)) {
847 // we changed variant when loading the engine; this forces us to reset
848 Reset(TRUE, savCps != &first);
849 EditGameEvent(); // for consistency with other path, as Reset changes mode
851 InitChessProgram(savCps, FALSE);
852 SendToProgram("force\n", savCps);
853 DisplayMessage("", "");
854 if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
855 for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
861 ReplaceEngine (ChessProgramState *cps, int n)
865 appData.noChessProgram = FALSE;
866 appData.clockMode = TRUE;
869 if(n) return; // only startup first engine immediately; second can wait
870 savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
874 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
875 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
877 static char resetOptions[] =
878 "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
879 "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
880 "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
881 "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
884 FloatToFront(char **list, char *engineLine)
886 char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
888 if(appData.recentEngines <= 0) return;
889 TidyProgramName(engineLine, "localhost", tidy+1);
890 tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
891 strncpy(buf+1, *list, MSG_SIZ-50);
892 if(p = strstr(buf, tidy)) { // tidy name appears in list
893 q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
894 while(*p++ = *++q); // squeeze out
896 strcat(tidy, buf+1); // put list behind tidy name
897 p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
898 if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
899 ASSIGN(*list, tidy+1);
902 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
905 Load (ChessProgramState *cps, int i)
907 char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
908 if(engineLine && engineLine[0]) { // an engine was selected from the combo box
909 snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
910 SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
911 ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
912 FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
913 appData.firstProtocolVersion = PROTOVER;
914 ParseArgsFromString(buf);
916 ReplaceEngine(cps, i);
917 FloatToFront(&appData.recentEngineList, engineLine);
921 while(q = strchr(p, SLASH)) p = q+1;
922 if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
923 if(engineDir[0] != NULLCHAR) {
924 ASSIGN(appData.directory[i], engineDir); p = engineName;
925 } else if(p != engineName) { // derive directory from engine path, when not given
927 ASSIGN(appData.directory[i], engineName);
929 if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
930 } else { ASSIGN(appData.directory[i], "."); }
932 if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
933 snprintf(command, MSG_SIZ, "%s %s", p, params);
936 ASSIGN(appData.chessProgram[i], p);
937 appData.isUCI[i] = isUCI;
938 appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
939 appData.hasOwnBookUCI[i] = hasBook;
940 if(!nickName[0]) useNick = FALSE;
941 if(useNick) ASSIGN(appData.pgnName[i], nickName);
945 q = firstChessProgramNames;
946 if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
947 quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
948 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
949 quote, p, quote, appData.directory[i],
950 useNick ? " -fn \"" : "",
951 useNick ? nickName : "",
953 v1 ? " -firstProtocolVersion 1" : "",
954 hasBook ? "" : " -fNoOwnBookUCI",
955 isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
956 storeVariant ? " -variant " : "",
957 storeVariant ? VariantName(gameInfo.variant) : "");
958 if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
959 firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
960 if(insert != q) insert[-1] = NULLCHAR;
961 snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
963 FloatToFront(&appData.recentEngineList, buf);
965 ReplaceEngine(cps, i);
971 int matched, min, sec;
973 * Parse timeControl resource
975 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
976 appData.movesPerSession)) {
978 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
979 DisplayFatalError(buf, 0, 2);
983 * Parse searchTime resource
985 if (*appData.searchTime != NULLCHAR) {
986 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
988 searchTime = min * 60;
989 } else if (matched == 2) {
990 searchTime = min * 60 + sec;
993 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
994 DisplayFatalError(buf, 0, 2);
1003 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1004 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1006 GetTimeMark(&programStartTime);
1007 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1008 appData.seedBase = random() + (random()<<15);
1009 pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1011 ClearProgramStats();
1012 programStats.ok_to_send = 1;
1013 programStats.seen_stat = 0;
1016 * Initialize game list
1022 * Internet chess server status
1024 if (appData.icsActive) {
1025 appData.matchMode = FALSE;
1026 appData.matchGames = 0;
1028 appData.noChessProgram = !appData.zippyPlay;
1030 appData.zippyPlay = FALSE;
1031 appData.zippyTalk = FALSE;
1032 appData.noChessProgram = TRUE;
1034 if (*appData.icsHelper != NULLCHAR) {
1035 appData.useTelnet = TRUE;
1036 appData.telnetProgram = appData.icsHelper;
1039 appData.zippyTalk = appData.zippyPlay = FALSE;
1042 /* [AS] Initialize pv info list [HGM] and game state */
1046 for( i=0; i<=framePtr; i++ ) {
1047 pvInfoList[i].depth = -1;
1048 boards[i][EP_STATUS] = EP_NONE;
1049 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1055 /* [AS] Adjudication threshold */
1056 adjudicateLossThreshold = appData.adjudicateLossThreshold;
1058 InitEngine(&first, 0);
1059 InitEngine(&second, 1);
1062 pairing.which = "pairing"; // pairing engine
1063 pairing.pr = NoProc;
1065 pairing.program = appData.pairingEngine;
1066 pairing.host = "localhost";
1069 if (appData.icsActive) {
1070 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
1071 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1072 appData.clockMode = FALSE;
1073 first.sendTime = second.sendTime = 0;
1077 /* Override some settings from environment variables, for backward
1078 compatibility. Unfortunately it's not feasible to have the env
1079 vars just set defaults, at least in xboard. Ugh.
1081 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1086 if (!appData.icsActive) {
1090 /* Check for variants that are supported only in ICS mode,
1091 or not at all. Some that are accepted here nevertheless
1092 have bugs; see comments below.
1094 VariantClass variant = StringToVariant(appData.variant);
1096 case VariantBughouse: /* need four players and two boards */
1097 case VariantKriegspiel: /* need to hide pieces and move details */
1098 /* case VariantFischeRandom: (Fabien: moved below) */
1099 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1100 if( (len >= MSG_SIZ) && appData.debugMode )
1101 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1103 DisplayFatalError(buf, 0, 2);
1106 case VariantUnknown:
1107 case VariantLoadable:
1117 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1118 if( (len >= MSG_SIZ) && appData.debugMode )
1119 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1121 DisplayFatalError(buf, 0, 2);
1124 case VariantXiangqi: /* [HGM] repetition rules not implemented */
1125 case VariantFairy: /* [HGM] TestLegality definitely off! */
1126 case VariantGothic: /* [HGM] should work */
1127 case VariantCapablanca: /* [HGM] should work */
1128 case VariantCourier: /* [HGM] initial forced moves not implemented */
1129 case VariantShogi: /* [HGM] could still mate with pawn drop */
1130 case VariantKnightmate: /* [HGM] should work */
1131 case VariantCylinder: /* [HGM] untested */
1132 case VariantFalcon: /* [HGM] untested */
1133 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1134 offboard interposition not understood */
1135 case VariantNormal: /* definitely works! */
1136 case VariantWildCastle: /* pieces not automatically shuffled */
1137 case VariantNoCastle: /* pieces not automatically shuffled */
1138 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1139 case VariantLosers: /* should work except for win condition,
1140 and doesn't know captures are mandatory */
1141 case VariantSuicide: /* should work except for win condition,
1142 and doesn't know captures are mandatory */
1143 case VariantGiveaway: /* should work except for win condition,
1144 and doesn't know captures are mandatory */
1145 case VariantTwoKings: /* should work */
1146 case VariantAtomic: /* should work except for win condition */
1147 case Variant3Check: /* should work except for win condition */
1148 case VariantShatranj: /* should work except for all win conditions */
1149 case VariantMakruk: /* should work except for draw countdown */
1150 case VariantBerolina: /* might work if TestLegality is off */
1151 case VariantCapaRandom: /* should work */
1152 case VariantJanus: /* should work */
1153 case VariantSuper: /* experimental */
1154 case VariantGreat: /* experimental, requires legality testing to be off */
1155 case VariantSChess: /* S-Chess, should work */
1156 case VariantGrand: /* should work */
1157 case VariantSpartan: /* should work */
1165 NextIntegerFromString (char ** str, long * value)
1170 while( *s == ' ' || *s == '\t' ) {
1176 if( *s >= '0' && *s <= '9' ) {
1177 while( *s >= '0' && *s <= '9' ) {
1178 *value = *value * 10 + (*s - '0');
1191 NextTimeControlFromString (char ** str, long * value)
1194 int result = NextIntegerFromString( str, &temp );
1197 *value = temp * 60; /* Minutes */
1198 if( **str == ':' ) {
1200 result = NextIntegerFromString( str, &temp );
1201 *value += temp; /* Seconds */
1209 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1210 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1211 int result = -1, type = 0; long temp, temp2;
1213 if(**str != ':') return -1; // old params remain in force!
1215 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1216 if( NextIntegerFromString( str, &temp ) ) return -1;
1217 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1220 /* time only: incremental or sudden-death time control */
1221 if(**str == '+') { /* increment follows; read it */
1223 if(**str == '!') type = *(*str)++; // Bronstein TC
1224 if(result = NextIntegerFromString( str, &temp2)) return -1;
1225 *inc = temp2 * 1000;
1226 if(**str == '.') { // read fraction of increment
1227 char *start = ++(*str);
1228 if(result = NextIntegerFromString( str, &temp2)) return -1;
1230 while(start++ < *str) temp2 /= 10;
1234 *moves = 0; *tc = temp * 1000; *incType = type;
1238 (*str)++; /* classical time control */
1239 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1251 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1252 { /* [HGM] get time to add from the multi-session time-control string */
1253 int incType, moves=1; /* kludge to force reading of first session */
1254 long time, increment;
1257 if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1259 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1260 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1261 if(movenr == -1) return time; /* last move before new session */
1262 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1263 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1264 if(!moves) return increment; /* current session is incremental */
1265 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1266 } while(movenr >= -1); /* try again for next session */
1268 return 0; // no new time quota on this move
1272 ParseTimeControl (char *tc, float ti, int mps)
1276 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1279 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1280 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1281 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1285 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1287 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1290 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1292 snprintf(buf, MSG_SIZ, ":%s", mytc);
1294 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1296 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1301 /* Parse second time control */
1304 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1312 timeControl_2 = tc2 * 1000;
1322 timeControl = tc1 * 1000;
1325 timeIncrement = ti * 1000; /* convert to ms */
1326 movesPerSession = 0;
1329 movesPerSession = mps;
1337 if (appData.debugMode) {
1338 fprintf(debugFP, "%s\n", programVersion);
1340 ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1342 set_cont_sequence(appData.wrapContSeq);
1343 if (appData.matchGames > 0) {
1344 appData.matchMode = TRUE;
1345 } else if (appData.matchMode) {
1346 appData.matchGames = 1;
1348 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1349 appData.matchGames = appData.sameColorGames;
1350 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1351 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1352 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1355 if (appData.noChessProgram || first.protocolVersion == 1) {
1358 /* kludge: allow timeout for initial "feature" commands */
1360 DisplayMessage("", _("Starting chess program"));
1361 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1366 CalculateIndex (int index, int gameNr)
1367 { // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1369 if(index > 0) return index; // fixed nmber
1370 if(index == 0) return 1;
1371 res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1372 if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1377 LoadGameOrPosition (int gameNr)
1378 { // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1379 if (*appData.loadGameFile != NULLCHAR) {
1380 if (!LoadGameFromFile(appData.loadGameFile,
1381 CalculateIndex(appData.loadGameIndex, gameNr),
1382 appData.loadGameFile, FALSE)) {
1383 DisplayFatalError(_("Bad game file"), 0, 1);
1386 } else if (*appData.loadPositionFile != NULLCHAR) {
1387 if (!LoadPositionFromFile(appData.loadPositionFile,
1388 CalculateIndex(appData.loadPositionIndex, gameNr),
1389 appData.loadPositionFile)) {
1390 DisplayFatalError(_("Bad position file"), 0, 1);
1398 ReserveGame (int gameNr, char resChar)
1400 FILE *tf = fopen(appData.tourneyFile, "r+");
1401 char *p, *q, c, buf[MSG_SIZ];
1402 if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1403 safeStrCpy(buf, lastMsg, MSG_SIZ);
1404 DisplayMessage(_("Pick new game"), "");
1405 flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1406 ParseArgsFromFile(tf);
1407 p = q = appData.results;
1408 if(appData.debugMode) {
1409 char *r = appData.participants;
1410 fprintf(debugFP, "results = '%s'\n", p);
1411 while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1412 fprintf(debugFP, "\n");
1414 while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1416 q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1417 safeStrCpy(q, p, strlen(p) + 2);
1418 if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1419 if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1420 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1421 if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1424 fseek(tf, -(strlen(p)+4), SEEK_END);
1426 if(c != '"') // depending on DOS or Unix line endings we can be one off
1427 fseek(tf, -(strlen(p)+2), SEEK_END);
1428 else fseek(tf, -(strlen(p)+3), SEEK_END);
1429 fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1430 DisplayMessage(buf, "");
1431 free(p); appData.results = q;
1432 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1433 (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1434 int round = appData.defaultMatchGames * appData.tourneyType;
1435 if(gameNr < 0 || appData.tourneyType < 1 || // gauntlet engine can always stay loaded as first engine
1436 appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1437 UnloadEngine(&first); // next game belongs to other pairing;
1438 UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1440 if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1444 MatchEvent (int mode)
1445 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1447 if(matchMode) { // already in match mode: switch it off
1449 if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1452 // if(gameMode != BeginningOfGame) {
1453 // DisplayError(_("You can only start a match from the initial position."), 0);
1457 if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1458 /* Set up machine vs. machine match */
1460 NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1461 if(appData.tourneyFile[0]) {
1463 if(nextGame > appData.matchGames) {
1465 if(strchr(appData.results, '*') == NULL) {
1467 appData.tourneyCycles++;
1468 if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1470 NextTourneyGame(-1, &dummy);
1472 if(nextGame <= appData.matchGames) {
1473 DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1475 ScheduleDelayedEvent(NextMatchGame, 10000);
1480 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1481 DisplayError(buf, 0);
1482 appData.tourneyFile[0] = 0;
1486 if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically
1487 DisplayFatalError(_("Can't have a match with no chess programs"),
1492 matchGame = roundNr = 1;
1493 first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1497 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1500 InitBackEnd3 P((void))
1502 GameMode initialMode;
1506 InitChessProgram(&first, startedFromSetupPosition);
1508 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1509 free(programVersion);
1510 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1511 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1512 FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1515 if (appData.icsActive) {
1517 /* [DM] Make a console window if needed [HGM] merged ifs */
1523 if (*appData.icsCommPort != NULLCHAR)
1524 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1525 appData.icsCommPort);
1527 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1528 appData.icsHost, appData.icsPort);
1530 if( (len >= MSG_SIZ) && appData.debugMode )
1531 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1533 DisplayFatalError(buf, err, 1);
1538 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1540 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1541 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1542 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1543 } else if (appData.noChessProgram) {
1549 if (*appData.cmailGameName != NULLCHAR) {
1551 OpenLoopback(&cmailPR);
1553 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1557 DisplayMessage("", "");
1558 if (StrCaseCmp(appData.initialMode, "") == 0) {
1559 initialMode = BeginningOfGame;
1560 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1561 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1562 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1563 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1566 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1567 initialMode = TwoMachinesPlay;
1568 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1569 initialMode = AnalyzeFile;
1570 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1571 initialMode = AnalyzeMode;
1572 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1573 initialMode = MachinePlaysWhite;
1574 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1575 initialMode = MachinePlaysBlack;
1576 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1577 initialMode = EditGame;
1578 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1579 initialMode = EditPosition;
1580 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1581 initialMode = Training;
1583 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1584 if( (len >= MSG_SIZ) && appData.debugMode )
1585 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1587 DisplayFatalError(buf, 0, 2);
1591 if (appData.matchMode) {
1592 if(appData.tourneyFile[0]) { // start tourney from command line
1594 if(f = fopen(appData.tourneyFile, "r")) {
1595 ParseArgsFromFile(f); // make sure tourney parmeters re known
1597 appData.clockMode = TRUE;
1599 } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1602 } else if (*appData.cmailGameName != NULLCHAR) {
1603 /* Set up cmail mode */
1604 ReloadCmailMsgEvent(TRUE);
1606 /* Set up other modes */
1607 if (initialMode == AnalyzeFile) {
1608 if (*appData.loadGameFile == NULLCHAR) {
1609 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1613 if (*appData.loadGameFile != NULLCHAR) {
1614 (void) LoadGameFromFile(appData.loadGameFile,
1615 appData.loadGameIndex,
1616 appData.loadGameFile, TRUE);
1617 } else if (*appData.loadPositionFile != NULLCHAR) {
1618 (void) LoadPositionFromFile(appData.loadPositionFile,
1619 appData.loadPositionIndex,
1620 appData.loadPositionFile);
1621 /* [HGM] try to make self-starting even after FEN load */
1622 /* to allow automatic setup of fairy variants with wtm */
1623 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1624 gameMode = BeginningOfGame;
1625 setboardSpoiledMachineBlack = 1;
1627 /* [HGM] loadPos: make that every new game uses the setup */
1628 /* from file as long as we do not switch variant */
1629 if(!blackPlaysFirst) {
1630 startedFromPositionFile = TRUE;
1631 CopyBoard(filePosition, boards[0]);
1634 if (initialMode == AnalyzeMode) {
1635 if (appData.noChessProgram) {
1636 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1639 if (appData.icsActive) {
1640 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1644 } else if (initialMode == AnalyzeFile) {
1645 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1646 ShowThinkingEvent();
1648 AnalysisPeriodicEvent(1);
1649 } else if (initialMode == MachinePlaysWhite) {
1650 if (appData.noChessProgram) {
1651 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1655 if (appData.icsActive) {
1656 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1660 MachineWhiteEvent();
1661 } else if (initialMode == MachinePlaysBlack) {
1662 if (appData.noChessProgram) {
1663 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1667 if (appData.icsActive) {
1668 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1672 MachineBlackEvent();
1673 } else if (initialMode == TwoMachinesPlay) {
1674 if (appData.noChessProgram) {
1675 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1679 if (appData.icsActive) {
1680 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1685 } else if (initialMode == EditGame) {
1687 } else if (initialMode == EditPosition) {
1688 EditPositionEvent();
1689 } else if (initialMode == Training) {
1690 if (*appData.loadGameFile == NULLCHAR) {
1691 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1700 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1702 DisplayBook(current+1);
1704 MoveHistorySet( movelist, first, last, current, pvInfoList );
1706 EvalGraphSet( first, last, current, pvInfoList );
1708 MakeEngineOutputTitle();
1712 * Establish will establish a contact to a remote host.port.
1713 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1714 * used to talk to the host.
1715 * Returns 0 if okay, error code if not.
1722 if (*appData.icsCommPort != NULLCHAR) {
1723 /* Talk to the host through a serial comm port */
1724 return OpenCommPort(appData.icsCommPort, &icsPR);
1726 } else if (*appData.gateway != NULLCHAR) {
1727 if (*appData.remoteShell == NULLCHAR) {
1728 /* Use the rcmd protocol to run telnet program on a gateway host */
1729 snprintf(buf, sizeof(buf), "%s %s %s",
1730 appData.telnetProgram, appData.icsHost, appData.icsPort);
1731 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1734 /* Use the rsh program to run telnet program on a gateway host */
1735 if (*appData.remoteUser == NULLCHAR) {
1736 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1737 appData.gateway, appData.telnetProgram,
1738 appData.icsHost, appData.icsPort);
1740 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1741 appData.remoteShell, appData.gateway,
1742 appData.remoteUser, appData.telnetProgram,
1743 appData.icsHost, appData.icsPort);
1745 return StartChildProcess(buf, "", &icsPR);
1748 } else if (appData.useTelnet) {
1749 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1752 /* TCP socket interface differs somewhat between
1753 Unix and NT; handle details in the front end.
1755 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1760 EscapeExpand (char *p, char *q)
1761 { // [HGM] initstring: routine to shape up string arguments
1762 while(*p++ = *q++) if(p[-1] == '\\')
1764 case 'n': p[-1] = '\n'; break;
1765 case 'r': p[-1] = '\r'; break;
1766 case 't': p[-1] = '\t'; break;
1767 case '\\': p[-1] = '\\'; break;
1768 case 0: *p = 0; return;
1769 default: p[-1] = q[-1]; break;
1774 show_bytes (FILE *fp, char *buf, int count)
1777 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1778 fprintf(fp, "\\%03o", *buf & 0xff);
1787 /* Returns an errno value */
1789 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1791 char buf[8192], *p, *q, *buflim;
1792 int left, newcount, outcount;
1794 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1795 *appData.gateway != NULLCHAR) {
1796 if (appData.debugMode) {
1797 fprintf(debugFP, ">ICS: ");
1798 show_bytes(debugFP, message, count);
1799 fprintf(debugFP, "\n");
1801 return OutputToProcess(pr, message, count, outError);
1804 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1811 if (appData.debugMode) {
1812 fprintf(debugFP, ">ICS: ");
1813 show_bytes(debugFP, buf, newcount);
1814 fprintf(debugFP, "\n");
1816 outcount = OutputToProcess(pr, buf, newcount, outError);
1817 if (outcount < newcount) return -1; /* to be sure */
1824 } else if (((unsigned char) *p) == TN_IAC) {
1825 *q++ = (char) TN_IAC;
1832 if (appData.debugMode) {
1833 fprintf(debugFP, ">ICS: ");
1834 show_bytes(debugFP, buf, newcount);
1835 fprintf(debugFP, "\n");
1837 outcount = OutputToProcess(pr, buf, newcount, outError);
1838 if (outcount < newcount) return -1; /* to be sure */
1843 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1845 int outError, outCount;
1846 static int gotEof = 0;
1848 /* Pass data read from player on to ICS */
1851 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1852 if (outCount < count) {
1853 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1855 } else if (count < 0) {
1856 RemoveInputSource(isr);
1857 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1858 } else if (gotEof++ > 0) {
1859 RemoveInputSource(isr);
1860 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1866 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1867 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1868 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1869 SendToICS("date\n");
1870 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1873 /* added routine for printf style output to ics */
1875 ics_printf (char *format, ...)
1877 char buffer[MSG_SIZ];
1880 va_start(args, format);
1881 vsnprintf(buffer, sizeof(buffer), format, args);
1882 buffer[sizeof(buffer)-1] = '\0';
1890 int count, outCount, outError;
1892 if (icsPR == NoProc) return;
1895 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1896 if (outCount < count) {
1897 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1901 /* This is used for sending logon scripts to the ICS. Sending
1902 without a delay causes problems when using timestamp on ICC
1903 (at least on my machine). */
1905 SendToICSDelayed (char *s, long msdelay)
1907 int count, outCount, outError;
1909 if (icsPR == NoProc) return;
1912 if (appData.debugMode) {
1913 fprintf(debugFP, ">ICS: ");
1914 show_bytes(debugFP, s, count);
1915 fprintf(debugFP, "\n");
1917 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1919 if (outCount < count) {
1920 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1925 /* Remove all highlighting escape sequences in s
1926 Also deletes any suffix starting with '('
1929 StripHighlightAndTitle (char *s)
1931 static char retbuf[MSG_SIZ];
1934 while (*s != NULLCHAR) {
1935 while (*s == '\033') {
1936 while (*s != NULLCHAR && !isalpha(*s)) s++;
1937 if (*s != NULLCHAR) s++;
1939 while (*s != NULLCHAR && *s != '\033') {
1940 if (*s == '(' || *s == '[') {
1951 /* Remove all highlighting escape sequences in s */
1953 StripHighlight (char *s)
1955 static char retbuf[MSG_SIZ];
1958 while (*s != NULLCHAR) {
1959 while (*s == '\033') {
1960 while (*s != NULLCHAR && !isalpha(*s)) s++;
1961 if (*s != NULLCHAR) s++;
1963 while (*s != NULLCHAR && *s != '\033') {
1971 char *variantNames[] = VARIANT_NAMES;
1973 VariantName (VariantClass v)
1975 return variantNames[v];
1979 /* Identify a variant from the strings the chess servers use or the
1980 PGN Variant tag names we use. */
1982 StringToVariant (char *e)
1986 VariantClass v = VariantNormal;
1987 int i, found = FALSE;
1993 /* [HGM] skip over optional board-size prefixes */
1994 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1995 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1996 while( *e++ != '_');
1999 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2003 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2004 if (StrCaseStr(e, variantNames[i])) {
2005 v = (VariantClass) i;
2012 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2013 || StrCaseStr(e, "wild/fr")
2014 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2015 v = VariantFischeRandom;
2016 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2017 (i = 1, p = StrCaseStr(e, "w"))) {
2019 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2026 case 0: /* FICS only, actually */
2028 /* Castling legal even if K starts on d-file */
2029 v = VariantWildCastle;
2034 /* Castling illegal even if K & R happen to start in
2035 normal positions. */
2036 v = VariantNoCastle;
2049 /* Castling legal iff K & R start in normal positions */
2055 /* Special wilds for position setup; unclear what to do here */
2056 v = VariantLoadable;
2059 /* Bizarre ICC game */
2060 v = VariantTwoKings;
2063 v = VariantKriegspiel;
2069 v = VariantFischeRandom;
2072 v = VariantCrazyhouse;
2075 v = VariantBughouse;
2081 /* Not quite the same as FICS suicide! */
2082 v = VariantGiveaway;
2088 v = VariantShatranj;
2091 /* Temporary names for future ICC types. The name *will* change in
2092 the next xboard/WinBoard release after ICC defines it. */
2130 v = VariantCapablanca;
2133 v = VariantKnightmate;
2139 v = VariantCylinder;
2145 v = VariantCapaRandom;
2148 v = VariantBerolina;
2160 /* Found "wild" or "w" in the string but no number;
2161 must assume it's normal chess. */
2165 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2166 if( (len >= MSG_SIZ) && appData.debugMode )
2167 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2169 DisplayError(buf, 0);
2175 if (appData.debugMode) {
2176 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2177 e, wnum, VariantName(v));
2182 static int leftover_start = 0, leftover_len = 0;
2183 char star_match[STAR_MATCH_N][MSG_SIZ];
2185 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2186 advance *index beyond it, and set leftover_start to the new value of
2187 *index; else return FALSE. If pattern contains the character '*', it
2188 matches any sequence of characters not containing '\r', '\n', or the
2189 character following the '*' (if any), and the matched sequence(s) are
2190 copied into star_match.
2193 looking_at ( char *buf, int *index, char *pattern)
2195 char *bufp = &buf[*index], *patternp = pattern;
2197 char *matchp = star_match[0];
2200 if (*patternp == NULLCHAR) {
2201 *index = leftover_start = bufp - buf;
2205 if (*bufp == NULLCHAR) return FALSE;
2206 if (*patternp == '*') {
2207 if (*bufp == *(patternp + 1)) {
2209 matchp = star_match[++star_count];
2213 } else if (*bufp == '\n' || *bufp == '\r') {
2215 if (*patternp == NULLCHAR)
2220 *matchp++ = *bufp++;
2224 if (*patternp != *bufp) return FALSE;
2231 SendToPlayer (char *data, int length)
2233 int error, outCount;
2234 outCount = OutputToProcess(NoProc, data, length, &error);
2235 if (outCount < length) {
2236 DisplayFatalError(_("Error writing to display"), error, 1);
2241 PackHolding (char packed[], char *holding)
2251 switch (runlength) {
2262 sprintf(q, "%d", runlength);
2274 /* Telnet protocol requests from the front end */
2276 TelnetRequest (unsigned char ddww, unsigned char option)
2278 unsigned char msg[3];
2279 int outCount, outError;
2281 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2283 if (appData.debugMode) {
2284 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2300 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2309 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2312 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2317 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2319 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2326 if (!appData.icsActive) return;
2327 TelnetRequest(TN_DO, TN_ECHO);
2333 if (!appData.icsActive) return;
2334 TelnetRequest(TN_DONT, TN_ECHO);
2338 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2340 /* put the holdings sent to us by the server on the board holdings area */
2341 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2345 if(gameInfo.holdingsWidth < 2) return;
2346 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2347 return; // prevent overwriting by pre-board holdings
2349 if( (int)lowestPiece >= BlackPawn ) {
2352 holdingsStartRow = BOARD_HEIGHT-1;
2355 holdingsColumn = BOARD_WIDTH-1;
2356 countsColumn = BOARD_WIDTH-2;
2357 holdingsStartRow = 0;
2361 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2362 board[i][holdingsColumn] = EmptySquare;
2363 board[i][countsColumn] = (ChessSquare) 0;
2365 while( (p=*holdings++) != NULLCHAR ) {
2366 piece = CharToPiece( ToUpper(p) );
2367 if(piece == EmptySquare) continue;
2368 /*j = (int) piece - (int) WhitePawn;*/
2369 j = PieceToNumber(piece);
2370 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2371 if(j < 0) continue; /* should not happen */
2372 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2373 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2374 board[holdingsStartRow+j*direction][countsColumn]++;
2380 VariantSwitch (Board board, VariantClass newVariant)
2382 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2383 static Board oldBoard;
2385 startedFromPositionFile = FALSE;
2386 if(gameInfo.variant == newVariant) return;
2388 /* [HGM] This routine is called each time an assignment is made to
2389 * gameInfo.variant during a game, to make sure the board sizes
2390 * are set to match the new variant. If that means adding or deleting
2391 * holdings, we shift the playing board accordingly
2392 * This kludge is needed because in ICS observe mode, we get boards
2393 * of an ongoing game without knowing the variant, and learn about the
2394 * latter only later. This can be because of the move list we requested,
2395 * in which case the game history is refilled from the beginning anyway,
2396 * but also when receiving holdings of a crazyhouse game. In the latter
2397 * case we want to add those holdings to the already received position.
2401 if (appData.debugMode) {
2402 fprintf(debugFP, "Switch board from %s to %s\n",
2403 VariantName(gameInfo.variant), VariantName(newVariant));
2404 setbuf(debugFP, NULL);
2406 shuffleOpenings = 0; /* [HGM] shuffle */
2407 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2411 newWidth = 9; newHeight = 9;
2412 gameInfo.holdingsSize = 7;
2413 case VariantBughouse:
2414 case VariantCrazyhouse:
2415 newHoldingsWidth = 2; break;
2419 newHoldingsWidth = 2;
2420 gameInfo.holdingsSize = 8;
2423 case VariantCapablanca:
2424 case VariantCapaRandom:
2427 newHoldingsWidth = gameInfo.holdingsSize = 0;
2430 if(newWidth != gameInfo.boardWidth ||
2431 newHeight != gameInfo.boardHeight ||
2432 newHoldingsWidth != gameInfo.holdingsWidth ) {
2434 /* shift position to new playing area, if needed */
2435 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2436 for(i=0; i<BOARD_HEIGHT; i++)
2437 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2438 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2440 for(i=0; i<newHeight; i++) {
2441 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2442 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2444 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2445 for(i=0; i<BOARD_HEIGHT; i++)
2446 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2447 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2450 board[HOLDINGS_SET] = 0;
2451 gameInfo.boardWidth = newWidth;
2452 gameInfo.boardHeight = newHeight;
2453 gameInfo.holdingsWidth = newHoldingsWidth;
2454 gameInfo.variant = newVariant;
2455 InitDrawingSizes(-2, 0);
2456 } else gameInfo.variant = newVariant;
2457 CopyBoard(oldBoard, board); // remember correctly formatted board
2458 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2459 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2462 static int loggedOn = FALSE;
2464 /*-- Game start info cache: --*/
2466 char gs_kind[MSG_SIZ];
2467 static char player1Name[128] = "";
2468 static char player2Name[128] = "";
2469 static char cont_seq[] = "\n\\ ";
2470 static int player1Rating = -1;
2471 static int player2Rating = -1;
2472 /*----------------------------*/
2474 ColorClass curColor = ColorNormal;
2475 int suppressKibitz = 0;
2478 Boolean soughtPending = FALSE;
2479 Boolean seekGraphUp;
2480 #define MAX_SEEK_ADS 200
2482 char *seekAdList[MAX_SEEK_ADS];
2483 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2484 float tcList[MAX_SEEK_ADS];
2485 char colorList[MAX_SEEK_ADS];
2486 int nrOfSeekAds = 0;
2487 int minRating = 1010, maxRating = 2800;
2488 int hMargin = 10, vMargin = 20, h, w;
2489 extern int squareSize, lineGap;
2494 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2495 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2496 if(r < minRating+100 && r >=0 ) r = minRating+100;
2497 if(r > maxRating) r = maxRating;
2498 if(tc < 1.f) tc = 1.f;
2499 if(tc > 95.f) tc = 95.f;
2500 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2501 y = ((double)r - minRating)/(maxRating - minRating)
2502 * (h-vMargin-squareSize/8-1) + vMargin;
2503 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2504 if(strstr(seekAdList[i], " u ")) color = 1;
2505 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2506 !strstr(seekAdList[i], "bullet") &&
2507 !strstr(seekAdList[i], "blitz") &&
2508 !strstr(seekAdList[i], "standard") ) color = 2;
2509 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2510 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2514 PlotSingleSeekAd (int i)
2522 AddAd (char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2524 char buf[MSG_SIZ], *ext = "";
2525 VariantClass v = StringToVariant(type);
2526 if(strstr(type, "wild")) {
2527 ext = type + 4; // append wild number
2528 if(v == VariantFischeRandom) type = "chess960"; else
2529 if(v == VariantLoadable) type = "setup"; else
2530 type = VariantName(v);
2532 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2533 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2534 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2535 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2536 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2537 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2538 seekNrList[nrOfSeekAds] = nr;
2539 zList[nrOfSeekAds] = 0;
2540 seekAdList[nrOfSeekAds++] = StrSave(buf);
2541 if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2546 EraseSeekDot (int i)
2548 int x = xList[i], y = yList[i], d=squareSize/4, k;
2549 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2550 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2551 // now replot every dot that overlapped
2552 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2553 int xx = xList[k], yy = yList[k];
2554 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2555 DrawSeekDot(xx, yy, colorList[k]);
2560 RemoveSeekAd (int nr)
2563 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2565 if(seekAdList[i]) free(seekAdList[i]);
2566 seekAdList[i] = seekAdList[--nrOfSeekAds];
2567 seekNrList[i] = seekNrList[nrOfSeekAds];
2568 ratingList[i] = ratingList[nrOfSeekAds];
2569 colorList[i] = colorList[nrOfSeekAds];
2570 tcList[i] = tcList[nrOfSeekAds];
2571 xList[i] = xList[nrOfSeekAds];
2572 yList[i] = yList[nrOfSeekAds];
2573 zList[i] = zList[nrOfSeekAds];
2574 seekAdList[nrOfSeekAds] = NULL;
2580 MatchSoughtLine (char *line)
2582 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2583 int nr, base, inc, u=0; char dummy;
2585 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2586 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2588 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2589 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2590 // match: compact and save the line
2591 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2601 if(!seekGraphUp) return FALSE;
2602 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2603 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2606 DrawSeekBackground(0, 0, w, h);
2607 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2608 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2609 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2610 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2612 DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2615 snprintf(buf, MSG_SIZ, "%d", i);
2616 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2619 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2620 for(i=1; i<100; i+=(i<10?1:5)) {
2621 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2622 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2623 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2625 snprintf(buf, MSG_SIZ, "%d", i);
2626 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2629 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2635 SeekGraphClick (ClickType click, int x, int y, int moving)
2637 static int lastDown = 0, displayed = 0, lastSecond;
2638 if(y < 0) return FALSE;
2639 if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2640 (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2641 if(!seekGraphUp) return FALSE;
2642 seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2643 DrawPosition(TRUE, NULL);
2646 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2647 if(click == Release || moving) return FALSE;
2649 soughtPending = TRUE;
2650 SendToICS(ics_prefix);
2651 SendToICS("sought\n"); // should this be "sought all"?
2652 } else { // issue challenge based on clicked ad
2653 int dist = 10000; int i, closest = 0, second = 0;
2654 for(i=0; i<nrOfSeekAds; i++) {
2655 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2656 if(d < dist) { dist = d; closest = i; }
2657 second += (d - zList[i] < 120); // count in-range ads
2658 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2662 second = (second > 1);
2663 if(displayed != closest || second != lastSecond) {
2664 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2665 lastSecond = second; displayed = closest;
2667 if(click == Press) {
2668 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2671 } // on press 'hit', only show info
2672 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2673 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2674 SendToICS(ics_prefix);
2676 return TRUE; // let incoming board of started game pop down the graph
2677 } else if(click == Release) { // release 'miss' is ignored
2678 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2679 if(moving == 2) { // right up-click
2680 nrOfSeekAds = 0; // refresh graph
2681 soughtPending = TRUE;
2682 SendToICS(ics_prefix);
2683 SendToICS("sought\n"); // should this be "sought all"?
2686 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2687 // press miss or release hit 'pop down' seek graph
2688 seekGraphUp = FALSE;
2689 DrawPosition(TRUE, NULL);
2695 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2697 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2698 #define STARTED_NONE 0
2699 #define STARTED_MOVES 1
2700 #define STARTED_BOARD 2
2701 #define STARTED_OBSERVE 3
2702 #define STARTED_HOLDINGS 4
2703 #define STARTED_CHATTER 5
2704 #define STARTED_COMMENT 6
2705 #define STARTED_MOVES_NOHIDE 7
2707 static int started = STARTED_NONE;
2708 static char parse[20000];
2709 static int parse_pos = 0;
2710 static char buf[BUF_SIZE + 1];
2711 static int firstTime = TRUE, intfSet = FALSE;
2712 static ColorClass prevColor = ColorNormal;
2713 static int savingComment = FALSE;
2714 static int cmatch = 0; // continuation sequence match
2721 int backup; /* [DM] For zippy color lines */
2723 char talker[MSG_SIZ]; // [HGM] chat
2726 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2728 if (appData.debugMode) {
2730 fprintf(debugFP, "<ICS: ");
2731 show_bytes(debugFP, data, count);
2732 fprintf(debugFP, "\n");
2736 if (appData.debugMode) { int f = forwardMostMove;
2737 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2738 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2739 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2742 /* If last read ended with a partial line that we couldn't parse,
2743 prepend it to the new read and try again. */
2744 if (leftover_len > 0) {
2745 for (i=0; i<leftover_len; i++)
2746 buf[i] = buf[leftover_start + i];
2749 /* copy new characters into the buffer */
2750 bp = buf + leftover_len;
2751 buf_len=leftover_len;
2752 for (i=0; i<count; i++)
2755 if (data[i] == '\r')
2758 // join lines split by ICS?
2759 if (!appData.noJoin)
2762 Joining just consists of finding matches against the
2763 continuation sequence, and discarding that sequence
2764 if found instead of copying it. So, until a match
2765 fails, there's nothing to do since it might be the
2766 complete sequence, and thus, something we don't want
2769 if (data[i] == cont_seq[cmatch])
2772 if (cmatch == strlen(cont_seq))
2774 cmatch = 0; // complete match. just reset the counter
2777 it's possible for the ICS to not include the space
2778 at the end of the last word, making our [correct]
2779 join operation fuse two separate words. the server
2780 does this when the space occurs at the width setting.
2782 if (!buf_len || buf[buf_len-1] != ' ')
2793 match failed, so we have to copy what matched before
2794 falling through and copying this character. In reality,
2795 this will only ever be just the newline character, but
2796 it doesn't hurt to be precise.
2798 strncpy(bp, cont_seq, cmatch);
2810 buf[buf_len] = NULLCHAR;
2811 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2816 while (i < buf_len) {
2817 /* Deal with part of the TELNET option negotiation
2818 protocol. We refuse to do anything beyond the
2819 defaults, except that we allow the WILL ECHO option,
2820 which ICS uses to turn off password echoing when we are
2821 directly connected to it. We reject this option
2822 if localLineEditing mode is on (always on in xboard)
2823 and we are talking to port 23, which might be a real
2824 telnet server that will try to keep WILL ECHO on permanently.
2826 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2827 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2828 unsigned char option;
2830 switch ((unsigned char) buf[++i]) {
2832 if (appData.debugMode)
2833 fprintf(debugFP, "\n<WILL ");
2834 switch (option = (unsigned char) buf[++i]) {
2836 if (appData.debugMode)
2837 fprintf(debugFP, "ECHO ");
2838 /* Reply only if this is a change, according
2839 to the protocol rules. */
2840 if (remoteEchoOption) break;
2841 if (appData.localLineEditing &&
2842 atoi(appData.icsPort) == TN_PORT) {
2843 TelnetRequest(TN_DONT, TN_ECHO);
2846 TelnetRequest(TN_DO, TN_ECHO);
2847 remoteEchoOption = TRUE;
2851 if (appData.debugMode)
2852 fprintf(debugFP, "%d ", option);
2853 /* Whatever this is, we don't want it. */
2854 TelnetRequest(TN_DONT, option);
2859 if (appData.debugMode)
2860 fprintf(debugFP, "\n<WONT ");
2861 switch (option = (unsigned char) buf[++i]) {
2863 if (appData.debugMode)
2864 fprintf(debugFP, "ECHO ");
2865 /* Reply only if this is a change, according
2866 to the protocol rules. */
2867 if (!remoteEchoOption) break;
2869 TelnetRequest(TN_DONT, TN_ECHO);
2870 remoteEchoOption = FALSE;
2873 if (appData.debugMode)
2874 fprintf(debugFP, "%d ", (unsigned char) option);
2875 /* Whatever this is, it must already be turned
2876 off, because we never agree to turn on
2877 anything non-default, so according to the
2878 protocol rules, we don't reply. */
2883 if (appData.debugMode)
2884 fprintf(debugFP, "\n<DO ");
2885 switch (option = (unsigned char) buf[++i]) {
2887 /* Whatever this is, we refuse to do it. */
2888 if (appData.debugMode)
2889 fprintf(debugFP, "%d ", option);
2890 TelnetRequest(TN_WONT, option);
2895 if (appData.debugMode)
2896 fprintf(debugFP, "\n<DONT ");
2897 switch (option = (unsigned char) buf[++i]) {
2899 if (appData.debugMode)
2900 fprintf(debugFP, "%d ", option);
2901 /* Whatever this is, we are already not doing
2902 it, because we never agree to do anything
2903 non-default, so according to the protocol
2904 rules, we don't reply. */
2909 if (appData.debugMode)
2910 fprintf(debugFP, "\n<IAC ");
2911 /* Doubled IAC; pass it through */
2915 if (appData.debugMode)
2916 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2917 /* Drop all other telnet commands on the floor */
2920 if (oldi > next_out)
2921 SendToPlayer(&buf[next_out], oldi - next_out);
2927 /* OK, this at least will *usually* work */
2928 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2932 if (loggedOn && !intfSet) {
2933 if (ics_type == ICS_ICC) {
2934 snprintf(str, MSG_SIZ,
2935 "/set-quietly interface %s\n/set-quietly style 12\n",
2937 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2938 strcat(str, "/set-2 51 1\n/set seek 1\n");
2939 } else if (ics_type == ICS_CHESSNET) {
2940 snprintf(str, MSG_SIZ, "/style 12\n");
2942 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2943 strcat(str, programVersion);
2944 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2945 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2946 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2948 strcat(str, "$iset nohighlight 1\n");
2950 strcat(str, "$iset lock 1\n$style 12\n");
2953 NotifyFrontendLogin();
2957 if (started == STARTED_COMMENT) {
2958 /* Accumulate characters in comment */
2959 parse[parse_pos++] = buf[i];
2960 if (buf[i] == '\n') {
2961 parse[parse_pos] = NULLCHAR;
2962 if(chattingPartner>=0) {
2964 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2965 OutputChatMessage(chattingPartner, mess);
2966 chattingPartner = -1;
2967 next_out = i+1; // [HGM] suppress printing in ICS window
2969 if(!suppressKibitz) // [HGM] kibitz
2970 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2971 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2972 int nrDigit = 0, nrAlph = 0, j;
2973 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2974 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2975 parse[parse_pos] = NULLCHAR;
2976 // try to be smart: if it does not look like search info, it should go to
2977 // ICS interaction window after all, not to engine-output window.
2978 for(j=0; j<parse_pos; j++) { // count letters and digits
2979 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2980 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2981 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2983 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2984 int depth=0; float score;
2985 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2986 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2987 pvInfoList[forwardMostMove-1].depth = depth;
2988 pvInfoList[forwardMostMove-1].score = 100*score;
2990 OutputKibitz(suppressKibitz, parse);
2993 if(gameMode == IcsObserving) // restore original ICS messages
2994 snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
2996 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2997 SendToPlayer(tmp, strlen(tmp));
2999 next_out = i+1; // [HGM] suppress printing in ICS window
3001 started = STARTED_NONE;
3003 /* Don't match patterns against characters in comment */
3008 if (started == STARTED_CHATTER) {
3009 if (buf[i] != '\n') {
3010 /* Don't match patterns against characters in chatter */
3014 started = STARTED_NONE;
3015 if(suppressKibitz) next_out = i+1;
3018 /* Kludge to deal with rcmd protocol */
3019 if (firstTime && looking_at(buf, &i, "\001*")) {
3020 DisplayFatalError(&buf[1], 0, 1);
3026 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3029 if (appData.debugMode)
3030 fprintf(debugFP, "ics_type %d\n", ics_type);
3033 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3034 ics_type = ICS_FICS;
3036 if (appData.debugMode)
3037 fprintf(debugFP, "ics_type %d\n", ics_type);
3040 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3041 ics_type = ICS_CHESSNET;
3043 if (appData.debugMode)
3044 fprintf(debugFP, "ics_type %d\n", ics_type);
3049 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3050 looking_at(buf, &i, "Logging you in as \"*\"") ||
3051 looking_at(buf, &i, "will be \"*\""))) {
3052 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3056 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3058 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3059 DisplayIcsInteractionTitle(buf);
3060 have_set_title = TRUE;
3063 /* skip finger notes */
3064 if (started == STARTED_NONE &&
3065 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3066 (buf[i] == '1' && buf[i+1] == '0')) &&
3067 buf[i+2] == ':' && buf[i+3] == ' ') {
3068 started = STARTED_CHATTER;
3074 // [HGM] seekgraph: recognize sought lines and end-of-sought message
3075 if(appData.seekGraph) {
3076 if(soughtPending && MatchSoughtLine(buf+i)) {
3077 i = strstr(buf+i, "rated") - buf;
3078 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3079 next_out = leftover_start = i;
3080 started = STARTED_CHATTER;
3081 suppressKibitz = TRUE;
3084 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3085 && looking_at(buf, &i, "* ads displayed")) {
3086 soughtPending = FALSE;
3091 if(appData.autoRefresh) {
3092 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3093 int s = (ics_type == ICS_ICC); // ICC format differs
3095 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3096 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3097 looking_at(buf, &i, "*% "); // eat prompt
3098 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3099 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3100 next_out = i; // suppress
3103 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3104 char *p = star_match[0];
3106 if(seekGraphUp) RemoveSeekAd(atoi(p));
3107 while(*p && *p++ != ' '); // next
3109 looking_at(buf, &i, "*% "); // eat prompt
3110 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3117 /* skip formula vars */
3118 if (started == STARTED_NONE &&
3119 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3120 started = STARTED_CHATTER;
3125 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3126 if (appData.autoKibitz && started == STARTED_NONE &&
3127 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3128 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3129 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3130 looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3131 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3132 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3133 suppressKibitz = TRUE;
3134 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3136 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3137 && (gameMode == IcsPlayingWhite)) ||
3138 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3139 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3140 started = STARTED_CHATTER; // own kibitz we simply discard
3142 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3143 parse_pos = 0; parse[0] = NULLCHAR;
3144 savingComment = TRUE;
3145 suppressKibitz = gameMode != IcsObserving ? 2 :
3146 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3150 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3151 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3152 && atoi(star_match[0])) {
3153 // suppress the acknowledgements of our own autoKibitz
3155 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3156 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3157 SendToPlayer(star_match[0], strlen(star_match[0]));
3158 if(looking_at(buf, &i, "*% ")) // eat prompt
3159 suppressKibitz = FALSE;
3163 } // [HGM] kibitz: end of patch
3165 if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3167 // [HGM] chat: intercept tells by users for which we have an open chat window
3169 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3170 looking_at(buf, &i, "* whispers:") ||
3171 looking_at(buf, &i, "* kibitzes:") ||
3172 looking_at(buf, &i, "* shouts:") ||
3173 looking_at(buf, &i, "* c-shouts:") ||
3174 looking_at(buf, &i, "--> * ") ||
3175 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3176 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3177 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3178 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3180 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3181 chattingPartner = -1;
3183 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3184 for(p=0; p<MAX_CHAT; p++) {
3185 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3186 talker[0] = '['; strcat(talker, "] ");
3187 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3188 chattingPartner = p; break;
3191 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3192 for(p=0; p<MAX_CHAT; p++) {
3193 if(!strcmp("kibitzes", chatPartner[p])) {
3194 talker[0] = '['; strcat(talker, "] ");
3195 chattingPartner = p; break;
3198 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3199 for(p=0; p<MAX_CHAT; p++) {
3200 if(!strcmp("whispers", chatPartner[p])) {
3201 talker[0] = '['; strcat(talker, "] ");
3202 chattingPartner = p; break;
3205 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3206 if(buf[i-8] == '-' && buf[i-3] == 't')
3207 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3208 if(!strcmp("c-shouts", chatPartner[p])) {
3209 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3210 chattingPartner = p; break;
3213 if(chattingPartner < 0)
3214 for(p=0; p<MAX_CHAT; p++) {
3215 if(!strcmp("shouts", chatPartner[p])) {
3216 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3217 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3218 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3219 chattingPartner = p; break;
3223 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3224 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3225 talker[0] = 0; Colorize(ColorTell, FALSE);
3226 chattingPartner = p; break;
3228 if(chattingPartner<0) i = oldi; else {
3229 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3230 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3231 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3232 started = STARTED_COMMENT;
3233 parse_pos = 0; parse[0] = NULLCHAR;
3234 savingComment = 3 + chattingPartner; // counts as TRUE
3235 suppressKibitz = TRUE;
3238 } // [HGM] chat: end of patch
3241 if (appData.zippyTalk || appData.zippyPlay) {
3242 /* [DM] Backup address for color zippy lines */
3244 if (loggedOn == TRUE)
3245 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3246 (appData.zippyPlay && ZippyMatch(buf, &backup)));
3248 } // [DM] 'else { ' deleted
3250 /* Regular tells and says */
3251 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3252 looking_at(buf, &i, "* (your partner) tells you: ") ||
3253 looking_at(buf, &i, "* says: ") ||
3254 /* Don't color "message" or "messages" output */
3255 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3256 looking_at(buf, &i, "*. * at *:*: ") ||
3257 looking_at(buf, &i, "--* (*:*): ") ||
3258 /* Message notifications (same color as tells) */
3259 looking_at(buf, &i, "* has left a message ") ||
3260 looking_at(buf, &i, "* just sent you a message:\n") ||
3261 /* Whispers and kibitzes */
3262 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3263 looking_at(buf, &i, "* kibitzes: ") ||
3265 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3267 if (tkind == 1 && strchr(star_match[0], ':')) {
3268 /* Avoid "tells you:" spoofs in channels */
3271 if (star_match[0][0] == NULLCHAR ||
3272 strchr(star_match[0], ' ') ||
3273 (tkind == 3 && strchr(star_match[1], ' '))) {
3274 /* Reject bogus matches */
3277 if (appData.colorize) {
3278 if (oldi > next_out) {
3279 SendToPlayer(&buf[next_out], oldi - next_out);
3284 Colorize(ColorTell, FALSE);
3285 curColor = ColorTell;
3288 Colorize(ColorKibitz, FALSE);
3289 curColor = ColorKibitz;
3292 p = strrchr(star_match[1], '(');
3299 Colorize(ColorChannel1, FALSE);
3300 curColor = ColorChannel1;
3302 Colorize(ColorChannel, FALSE);
3303 curColor = ColorChannel;
3307 curColor = ColorNormal;
3311 if (started == STARTED_NONE && appData.autoComment &&
3312 (gameMode == IcsObserving ||
3313 gameMode == IcsPlayingWhite ||
3314 gameMode == IcsPlayingBlack)) {
3315 parse_pos = i - oldi;
3316 memcpy(parse, &buf[oldi], parse_pos);
3317 parse[parse_pos] = NULLCHAR;
3318 started = STARTED_COMMENT;
3319 savingComment = TRUE;
3321 started = STARTED_CHATTER;
3322 savingComment = FALSE;
3329 if (looking_at(buf, &i, "* s-shouts: ") ||
3330 looking_at(buf, &i, "* c-shouts: ")) {
3331 if (appData.colorize) {
3332 if (oldi > next_out) {
3333 SendToPlayer(&buf[next_out], oldi - next_out);
3336 Colorize(ColorSShout, FALSE);
3337 curColor = ColorSShout;
3340 started = STARTED_CHATTER;
3344 if (looking_at(buf, &i, "--->")) {
3349 if (looking_at(buf, &i, "* shouts: ") ||
3350 looking_at(buf, &i, "--> ")) {
3351 if (appData.colorize) {
3352 if (oldi > next_out) {
3353 SendToPlayer(&buf[next_out], oldi - next_out);
3356 Colorize(ColorShout, FALSE);
3357 curColor = ColorShout;
3360 started = STARTED_CHATTER;
3364 if (looking_at( buf, &i, "Challenge:")) {
3365 if (appData.colorize) {
3366 if (oldi > next_out) {
3367 SendToPlayer(&buf[next_out], oldi - next_out);
3370 Colorize(ColorChallenge, FALSE);
3371 curColor = ColorChallenge;
3377 if (looking_at(buf, &i, "* offers you") ||
3378 looking_at(buf, &i, "* offers to be") ||
3379 looking_at(buf, &i, "* would like to") ||
3380 looking_at(buf, &i, "* requests to") ||
3381 looking_at(buf, &i, "Your opponent offers") ||
3382 looking_at(buf, &i, "Your opponent requests")) {
3384 if (appData.colorize) {
3385 if (oldi > next_out) {
3386 SendToPlayer(&buf[next_out], oldi - next_out);
3389 Colorize(ColorRequest, FALSE);
3390 curColor = ColorRequest;
3395 if (looking_at(buf, &i, "* (*) seeking")) {
3396 if (appData.colorize) {
3397 if (oldi > next_out) {
3398 SendToPlayer(&buf[next_out], oldi - next_out);
3401 Colorize(ColorSeek, FALSE);
3402 curColor = ColorSeek;
3407 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3409 if (looking_at(buf, &i, "\\ ")) {
3410 if (prevColor != ColorNormal) {
3411 if (oldi > next_out) {
3412 SendToPlayer(&buf[next_out], oldi - next_out);
3415 Colorize(prevColor, TRUE);
3416 curColor = prevColor;
3418 if (savingComment) {
3419 parse_pos = i - oldi;
3420 memcpy(parse, &buf[oldi], parse_pos);
3421 parse[parse_pos] = NULLCHAR;
3422 started = STARTED_COMMENT;
3423 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3424 chattingPartner = savingComment - 3; // kludge to remember the box
3426 started = STARTED_CHATTER;
3431 if (looking_at(buf, &i, "Black Strength :") ||
3432 looking_at(buf, &i, "<<< style 10 board >>>") ||
3433 looking_at(buf, &i, "<10>") ||
3434 looking_at(buf, &i, "#@#")) {
3435 /* Wrong board style */
3437 SendToICS(ics_prefix);
3438 SendToICS("set style 12\n");
3439 SendToICS(ics_prefix);
3440 SendToICS("refresh\n");
3444 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3446 have_sent_ICS_logon = 1;
3450 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3451 (looking_at(buf, &i, "\n<12> ") ||
3452 looking_at(buf, &i, "<12> "))) {
3454 if (oldi > next_out) {
3455 SendToPlayer(&buf[next_out], oldi - next_out);
3458 started = STARTED_BOARD;
3463 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3464 looking_at(buf, &i, "<b1> ")) {
3465 if (oldi > next_out) {
3466 SendToPlayer(&buf[next_out], oldi - next_out);
3469 started = STARTED_HOLDINGS;
3474 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3476 /* Header for a move list -- first line */
3478 switch (ics_getting_history) {
3482 case BeginningOfGame:
3483 /* User typed "moves" or "oldmoves" while we
3484 were idle. Pretend we asked for these
3485 moves and soak them up so user can step
3486 through them and/or save them.
3489 gameMode = IcsObserving;
3492 ics_getting_history = H_GOT_UNREQ_HEADER;
3494 case EditGame: /*?*/
3495 case EditPosition: /*?*/
3496 /* Should above feature work in these modes too? */
3497 /* For now it doesn't */
3498 ics_getting_history = H_GOT_UNWANTED_HEADER;
3501 ics_getting_history = H_GOT_UNWANTED_HEADER;
3506 /* Is this the right one? */
3507 if (gameInfo.white && gameInfo.black &&
3508 strcmp(gameInfo.white, star_match[0]) == 0 &&
3509 strcmp(gameInfo.black, star_match[2]) == 0) {
3511 ics_getting_history = H_GOT_REQ_HEADER;
3514 case H_GOT_REQ_HEADER:
3515 case H_GOT_UNREQ_HEADER:
3516 case H_GOT_UNWANTED_HEADER:
3517 case H_GETTING_MOVES:
3518 /* Should not happen */
3519 DisplayError(_("Error gathering move list: two headers"), 0);
3520 ics_getting_history = H_FALSE;
3524 /* Save player ratings into gameInfo if needed */
3525 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3526 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3527 (gameInfo.whiteRating == -1 ||
3528 gameInfo.blackRating == -1)) {
3530 gameInfo.whiteRating = string_to_rating(star_match[1]);
3531 gameInfo.blackRating = string_to_rating(star_match[3]);
3532 if (appData.debugMode)
3533 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3534 gameInfo.whiteRating, gameInfo.blackRating);
3539 if (looking_at(buf, &i,
3540 "* * match, initial time: * minute*, increment: * second")) {
3541 /* Header for a move list -- second line */
3542 /* Initial board will follow if this is a wild game */
3543 if (gameInfo.event != NULL) free(gameInfo.event);
3544 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3545 gameInfo.event = StrSave(str);
3546 /* [HGM] we switched variant. Translate boards if needed. */
3547 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3551 if (looking_at(buf, &i, "Move ")) {
3552 /* Beginning of a move list */
3553 switch (ics_getting_history) {
3555 /* Normally should not happen */
3556 /* Maybe user hit reset while we were parsing */
3559 /* Happens if we are ignoring a move list that is not
3560 * the one we just requested. Common if the user
3561 * tries to observe two games without turning off
3564 case H_GETTING_MOVES:
3565 /* Should not happen */
3566 DisplayError(_("Error gathering move list: nested"), 0);
3567 ics_getting_history = H_FALSE;
3569 case H_GOT_REQ_HEADER:
3570 ics_getting_history = H_GETTING_MOVES;
3571 started = STARTED_MOVES;
3573 if (oldi > next_out) {
3574 SendToPlayer(&buf[next_out], oldi - next_out);
3577 case H_GOT_UNREQ_HEADER:
3578 ics_getting_history = H_GETTING_MOVES;
3579 started = STARTED_MOVES_NOHIDE;
3582 case H_GOT_UNWANTED_HEADER:
3583 ics_getting_history = H_FALSE;
3589 if (looking_at(buf, &i, "% ") ||
3590 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3591 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3592 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3593 soughtPending = FALSE;
3597 if(suppressKibitz) next_out = i;
3598 savingComment = FALSE;
3602 case STARTED_MOVES_NOHIDE:
3603 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3604 parse[parse_pos + i - oldi] = NULLCHAR;
3605 ParseGameHistory(parse);
3607 if (appData.zippyPlay && first.initDone) {
3608 FeedMovesToProgram(&first, forwardMostMove);
3609 if (gameMode == IcsPlayingWhite) {
3610 if (WhiteOnMove(forwardMostMove)) {
3611 if (first.sendTime) {
3612 if (first.useColors) {
3613 SendToProgram("black\n", &first);
3615 SendTimeRemaining(&first, TRUE);
3617 if (first.useColors) {
3618 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3620 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3621 first.maybeThinking = TRUE;
3623 if (first.usePlayother) {
3624 if (first.sendTime) {
3625 SendTimeRemaining(&first, TRUE);
3627 SendToProgram("playother\n", &first);
3633 } else if (gameMode == IcsPlayingBlack) {
3634 if (!WhiteOnMove(forwardMostMove)) {
3635 if (first.sendTime) {
3636 if (first.useColors) {
3637 SendToProgram("white\n", &first);
3639 SendTimeRemaining(&first, FALSE);
3641 if (first.useColors) {
3642 SendToProgram("black\n", &first);
3644 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3645 first.maybeThinking = TRUE;
3647 if (first.usePlayother) {
3648 if (first.sendTime) {
3649 SendTimeRemaining(&first, FALSE);
3651 SendToProgram("playother\n", &first);
3660 if (gameMode == IcsObserving && ics_gamenum == -1) {
3661 /* Moves came from oldmoves or moves command
3662 while we weren't doing anything else.
3664 currentMove = forwardMostMove;
3665 ClearHighlights();/*!!could figure this out*/
3666 flipView = appData.flipView;
3667 DrawPosition(TRUE, boards[currentMove]);
3668 DisplayBothClocks();
3669 snprintf(str, MSG_SIZ, "%s %s %s",
3670 gameInfo.white, _("vs."), gameInfo.black);
3674 /* Moves were history of an active game */
3675 if (gameInfo.resultDetails != NULL) {
3676 free(gameInfo.resultDetails);
3677 gameInfo.resultDetails = NULL;
3680 HistorySet(parseList, backwardMostMove,
3681 forwardMostMove, currentMove-1);
3682 DisplayMove(currentMove - 1);
3683 if (started == STARTED_MOVES) next_out = i;
3684 started = STARTED_NONE;
3685 ics_getting_history = H_FALSE;
3688 case STARTED_OBSERVE:
3689 started = STARTED_NONE;
3690 SendToICS(ics_prefix);
3691 SendToICS("refresh\n");
3697 if(bookHit) { // [HGM] book: simulate book reply
3698 static char bookMove[MSG_SIZ]; // a bit generous?
3700 programStats.nodes = programStats.depth = programStats.time =
3701 programStats.score = programStats.got_only_move = 0;
3702 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3704 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3705 strcat(bookMove, bookHit);
3706 HandleMachineMove(bookMove, &first);
3711 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3712 started == STARTED_HOLDINGS ||
3713 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3714 /* Accumulate characters in move list or board */
3715 parse[parse_pos++] = buf[i];
3718 /* Start of game messages. Mostly we detect start of game
3719 when the first board image arrives. On some versions
3720 of the ICS, though, we need to do a "refresh" after starting
3721 to observe in order to get the current board right away. */
3722 if (looking_at(buf, &i, "Adding game * to observation list")) {
3723 started = STARTED_OBSERVE;
3727 /* Handle auto-observe */
3728 if (appData.autoObserve &&
3729 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3730 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3732 /* Choose the player that was highlighted, if any. */
3733 if (star_match[0][0] == '\033' ||
3734 star_match[1][0] != '\033') {
3735 player = star_match[0];
3737 player = star_match[2];
3739 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3740 ics_prefix, StripHighlightAndTitle(player));
3743 /* Save ratings from notify string */
3744 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3745 player1Rating = string_to_rating(star_match[1]);
3746 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3747 player2Rating = string_to_rating(star_match[3]);
3749 if (appData.debugMode)
3751 "Ratings from 'Game notification:' %s %d, %s %d\n",
3752 player1Name, player1Rating,
3753 player2Name, player2Rating);
3758 /* Deal with automatic examine mode after a game,
3759 and with IcsObserving -> IcsExamining transition */
3760 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3761 looking_at(buf, &i, "has made you an examiner of game *")) {
3763 int gamenum = atoi(star_match[0]);
3764 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3765 gamenum == ics_gamenum) {
3766 /* We were already playing or observing this game;
3767 no need to refetch history */
3768 gameMode = IcsExamining;
3770 pauseExamForwardMostMove = forwardMostMove;
3771 } else if (currentMove < forwardMostMove) {
3772 ForwardInner(forwardMostMove);
3775 /* I don't think this case really can happen */
3776 SendToICS(ics_prefix);
3777 SendToICS("refresh\n");
3782 /* Error messages */
3783 // if (ics_user_moved) {
3784 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3785 if (looking_at(buf, &i, "Illegal move") ||
3786 looking_at(buf, &i, "Not a legal move") ||
3787 looking_at(buf, &i, "Your king is in check") ||
3788 looking_at(buf, &i, "It isn't your turn") ||
3789 looking_at(buf, &i, "It is not your move")) {
3791 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3792 currentMove = forwardMostMove-1;
3793 DisplayMove(currentMove - 1); /* before DMError */
3794 DrawPosition(FALSE, boards[currentMove]);
3795 SwitchClocks(forwardMostMove-1); // [HGM] race
3796 DisplayBothClocks();
3798 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3804 if (looking_at(buf, &i, "still have time") ||
3805 looking_at(buf, &i, "not out of time") ||
3806 looking_at(buf, &i, "either player is out of time") ||
3807 looking_at(buf, &i, "has timeseal; checking")) {
3808 /* We must have called his flag a little too soon */
3809 whiteFlag = blackFlag = FALSE;
3813 if (looking_at(buf, &i, "added * seconds to") ||
3814 looking_at(buf, &i, "seconds were added to")) {
3815 /* Update the clocks */
3816 SendToICS(ics_prefix);
3817 SendToICS("refresh\n");
3821 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3822 ics_clock_paused = TRUE;
3827 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3828 ics_clock_paused = FALSE;
3833 /* Grab player ratings from the Creating: message.
3834 Note we have to check for the special case when
3835 the ICS inserts things like [white] or [black]. */
3836 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3837 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3839 0 player 1 name (not necessarily white)
3841 2 empty, white, or black (IGNORED)
3842 3 player 2 name (not necessarily black)
3845 The names/ratings are sorted out when the game
3846 actually starts (below).
3848 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3849 player1Rating = string_to_rating(star_match[1]);
3850 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3851 player2Rating = string_to_rating(star_match[4]);
3853 if (appData.debugMode)
3855 "Ratings from 'Creating:' %s %d, %s %d\n",
3856 player1Name, player1Rating,
3857 player2Name, player2Rating);
3862 /* Improved generic start/end-of-game messages */
3863 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3864 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3865 /* If tkind == 0: */
3866 /* star_match[0] is the game number */
3867 /* [1] is the white player's name */