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)
2520 AddAd (char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2522 char buf[MSG_SIZ], *ext = "";
2523 VariantClass v = StringToVariant(type);
2524 if(strstr(type, "wild")) {
2525 ext = type + 4; // append wild number
2526 if(v == VariantFischeRandom) type = "chess960"; else
2527 if(v == VariantLoadable) type = "setup"; else
2528 type = VariantName(v);
2530 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2531 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2532 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2533 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2534 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2535 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2536 seekNrList[nrOfSeekAds] = nr;
2537 zList[nrOfSeekAds] = 0;
2538 seekAdList[nrOfSeekAds++] = StrSave(buf);
2539 if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2544 EraseSeekDot (int i)
2546 int x = xList[i], y = yList[i], d=squareSize/4, k;
2547 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2548 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2549 // now replot every dot that overlapped
2550 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2551 int xx = xList[k], yy = yList[k];
2552 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2553 DrawSeekDot(xx, yy, colorList[k]);
2558 RemoveSeekAd (int nr)
2561 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2563 if(seekAdList[i]) free(seekAdList[i]);
2564 seekAdList[i] = seekAdList[--nrOfSeekAds];
2565 seekNrList[i] = seekNrList[nrOfSeekAds];
2566 ratingList[i] = ratingList[nrOfSeekAds];
2567 colorList[i] = colorList[nrOfSeekAds];
2568 tcList[i] = tcList[nrOfSeekAds];
2569 xList[i] = xList[nrOfSeekAds];
2570 yList[i] = yList[nrOfSeekAds];
2571 zList[i] = zList[nrOfSeekAds];
2572 seekAdList[nrOfSeekAds] = NULL;
2578 MatchSoughtLine (char *line)
2580 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2581 int nr, base, inc, u=0; char dummy;
2583 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2584 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2586 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2587 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2588 // match: compact and save the line
2589 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2599 if(!seekGraphUp) return FALSE;
2600 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2601 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2603 DrawSeekBackground(0, 0, w, h);
2604 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2605 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2606 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2607 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2609 DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2612 snprintf(buf, MSG_SIZ, "%d", i);
2613 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2616 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2617 for(i=1; i<100; i+=(i<10?1:5)) {
2618 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2619 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2620 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2622 snprintf(buf, MSG_SIZ, "%d", i);
2623 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2626 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2631 SeekGraphClick (ClickType click, int x, int y, int moving)
2633 static int lastDown = 0, displayed = 0, lastSecond;
2634 if(y < 0) return FALSE;
2635 if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2636 (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2637 if(!seekGraphUp) return FALSE;
2638 seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2639 DrawPosition(TRUE, NULL);
2642 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2643 if(click == Release || moving) return FALSE;
2645 soughtPending = TRUE;
2646 SendToICS(ics_prefix);
2647 SendToICS("sought\n"); // should this be "sought all"?
2648 } else { // issue challenge based on clicked ad
2649 int dist = 10000; int i, closest = 0, second = 0;
2650 for(i=0; i<nrOfSeekAds; i++) {
2651 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2652 if(d < dist) { dist = d; closest = i; }
2653 second += (d - zList[i] < 120); // count in-range ads
2654 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2658 second = (second > 1);
2659 if(displayed != closest || second != lastSecond) {
2660 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2661 lastSecond = second; displayed = closest;
2663 if(click == Press) {
2664 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2667 } // on press 'hit', only show info
2668 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2669 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2670 SendToICS(ics_prefix);
2672 return TRUE; // let incoming board of started game pop down the graph
2673 } else if(click == Release) { // release 'miss' is ignored
2674 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2675 if(moving == 2) { // right up-click
2676 nrOfSeekAds = 0; // refresh graph
2677 soughtPending = TRUE;
2678 SendToICS(ics_prefix);
2679 SendToICS("sought\n"); // should this be "sought all"?
2682 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2683 // press miss or release hit 'pop down' seek graph
2684 seekGraphUp = FALSE;
2685 DrawPosition(TRUE, NULL);
2691 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2693 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2694 #define STARTED_NONE 0
2695 #define STARTED_MOVES 1
2696 #define STARTED_BOARD 2
2697 #define STARTED_OBSERVE 3
2698 #define STARTED_HOLDINGS 4
2699 #define STARTED_CHATTER 5
2700 #define STARTED_COMMENT 6
2701 #define STARTED_MOVES_NOHIDE 7
2703 static int started = STARTED_NONE;
2704 static char parse[20000];
2705 static int parse_pos = 0;
2706 static char buf[BUF_SIZE + 1];
2707 static int firstTime = TRUE, intfSet = FALSE;
2708 static ColorClass prevColor = ColorNormal;
2709 static int savingComment = FALSE;
2710 static int cmatch = 0; // continuation sequence match
2717 int backup; /* [DM] For zippy color lines */
2719 char talker[MSG_SIZ]; // [HGM] chat
2722 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2724 if (appData.debugMode) {
2726 fprintf(debugFP, "<ICS: ");
2727 show_bytes(debugFP, data, count);
2728 fprintf(debugFP, "\n");
2732 if (appData.debugMode) { int f = forwardMostMove;
2733 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2734 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2735 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2738 /* If last read ended with a partial line that we couldn't parse,
2739 prepend it to the new read and try again. */
2740 if (leftover_len > 0) {
2741 for (i=0; i<leftover_len; i++)
2742 buf[i] = buf[leftover_start + i];
2745 /* copy new characters into the buffer */
2746 bp = buf + leftover_len;
2747 buf_len=leftover_len;
2748 for (i=0; i<count; i++)
2751 if (data[i] == '\r')
2754 // join lines split by ICS?
2755 if (!appData.noJoin)
2758 Joining just consists of finding matches against the
2759 continuation sequence, and discarding that sequence
2760 if found instead of copying it. So, until a match
2761 fails, there's nothing to do since it might be the
2762 complete sequence, and thus, something we don't want
2765 if (data[i] == cont_seq[cmatch])
2768 if (cmatch == strlen(cont_seq))
2770 cmatch = 0; // complete match. just reset the counter
2773 it's possible for the ICS to not include the space
2774 at the end of the last word, making our [correct]
2775 join operation fuse two separate words. the server
2776 does this when the space occurs at the width setting.
2778 if (!buf_len || buf[buf_len-1] != ' ')
2789 match failed, so we have to copy what matched before
2790 falling through and copying this character. In reality,
2791 this will only ever be just the newline character, but
2792 it doesn't hurt to be precise.
2794 strncpy(bp, cont_seq, cmatch);
2806 buf[buf_len] = NULLCHAR;
2807 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2812 while (i < buf_len) {
2813 /* Deal with part of the TELNET option negotiation
2814 protocol. We refuse to do anything beyond the
2815 defaults, except that we allow the WILL ECHO option,
2816 which ICS uses to turn off password echoing when we are
2817 directly connected to it. We reject this option
2818 if localLineEditing mode is on (always on in xboard)
2819 and we are talking to port 23, which might be a real
2820 telnet server that will try to keep WILL ECHO on permanently.
2822 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2823 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2824 unsigned char option;
2826 switch ((unsigned char) buf[++i]) {
2828 if (appData.debugMode)
2829 fprintf(debugFP, "\n<WILL ");
2830 switch (option = (unsigned char) buf[++i]) {
2832 if (appData.debugMode)
2833 fprintf(debugFP, "ECHO ");
2834 /* Reply only if this is a change, according
2835 to the protocol rules. */
2836 if (remoteEchoOption) break;
2837 if (appData.localLineEditing &&
2838 atoi(appData.icsPort) == TN_PORT) {
2839 TelnetRequest(TN_DONT, TN_ECHO);
2842 TelnetRequest(TN_DO, TN_ECHO);
2843 remoteEchoOption = TRUE;
2847 if (appData.debugMode)
2848 fprintf(debugFP, "%d ", option);
2849 /* Whatever this is, we don't want it. */
2850 TelnetRequest(TN_DONT, option);
2855 if (appData.debugMode)
2856 fprintf(debugFP, "\n<WONT ");
2857 switch (option = (unsigned char) buf[++i]) {
2859 if (appData.debugMode)
2860 fprintf(debugFP, "ECHO ");
2861 /* Reply only if this is a change, according
2862 to the protocol rules. */
2863 if (!remoteEchoOption) break;
2865 TelnetRequest(TN_DONT, TN_ECHO);
2866 remoteEchoOption = FALSE;
2869 if (appData.debugMode)
2870 fprintf(debugFP, "%d ", (unsigned char) option);
2871 /* Whatever this is, it must already be turned
2872 off, because we never agree to turn on
2873 anything non-default, so according to the
2874 protocol rules, we don't reply. */
2879 if (appData.debugMode)
2880 fprintf(debugFP, "\n<DO ");
2881 switch (option = (unsigned char) buf[++i]) {
2883 /* Whatever this is, we refuse to do it. */
2884 if (appData.debugMode)
2885 fprintf(debugFP, "%d ", option);
2886 TelnetRequest(TN_WONT, option);
2891 if (appData.debugMode)
2892 fprintf(debugFP, "\n<DONT ");
2893 switch (option = (unsigned char) buf[++i]) {
2895 if (appData.debugMode)
2896 fprintf(debugFP, "%d ", option);
2897 /* Whatever this is, we are already not doing
2898 it, because we never agree to do anything
2899 non-default, so according to the protocol
2900 rules, we don't reply. */
2905 if (appData.debugMode)
2906 fprintf(debugFP, "\n<IAC ");
2907 /* Doubled IAC; pass it through */
2911 if (appData.debugMode)
2912 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2913 /* Drop all other telnet commands on the floor */
2916 if (oldi > next_out)
2917 SendToPlayer(&buf[next_out], oldi - next_out);
2923 /* OK, this at least will *usually* work */
2924 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2928 if (loggedOn && !intfSet) {
2929 if (ics_type == ICS_ICC) {
2930 snprintf(str, MSG_SIZ,
2931 "/set-quietly interface %s\n/set-quietly style 12\n",
2933 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2934 strcat(str, "/set-2 51 1\n/set seek 1\n");
2935 } else if (ics_type == ICS_CHESSNET) {
2936 snprintf(str, MSG_SIZ, "/style 12\n");
2938 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2939 strcat(str, programVersion);
2940 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2941 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2942 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2944 strcat(str, "$iset nohighlight 1\n");
2946 strcat(str, "$iset lock 1\n$style 12\n");
2949 NotifyFrontendLogin();
2953 if (started == STARTED_COMMENT) {
2954 /* Accumulate characters in comment */
2955 parse[parse_pos++] = buf[i];
2956 if (buf[i] == '\n') {
2957 parse[parse_pos] = NULLCHAR;
2958 if(chattingPartner>=0) {
2960 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2961 OutputChatMessage(chattingPartner, mess);
2962 chattingPartner = -1;
2963 next_out = i+1; // [HGM] suppress printing in ICS window
2965 if(!suppressKibitz) // [HGM] kibitz
2966 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2967 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2968 int nrDigit = 0, nrAlph = 0, j;
2969 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2970 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2971 parse[parse_pos] = NULLCHAR;
2972 // try to be smart: if it does not look like search info, it should go to
2973 // ICS interaction window after all, not to engine-output window.
2974 for(j=0; j<parse_pos; j++) { // count letters and digits
2975 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2976 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2977 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2979 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2980 int depth=0; float score;
2981 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2982 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2983 pvInfoList[forwardMostMove-1].depth = depth;
2984 pvInfoList[forwardMostMove-1].score = 100*score;
2986 OutputKibitz(suppressKibitz, parse);
2989 if(gameMode == IcsObserving) // restore original ICS messages
2990 snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
2992 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2993 SendToPlayer(tmp, strlen(tmp));
2995 next_out = i+1; // [HGM] suppress printing in ICS window
2997 started = STARTED_NONE;
2999 /* Don't match patterns against characters in comment */
3004 if (started == STARTED_CHATTER) {
3005 if (buf[i] != '\n') {
3006 /* Don't match patterns against characters in chatter */
3010 started = STARTED_NONE;
3011 if(suppressKibitz) next_out = i+1;
3014 /* Kludge to deal with rcmd protocol */
3015 if (firstTime && looking_at(buf, &i, "\001*")) {
3016 DisplayFatalError(&buf[1], 0, 1);
3022 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3025 if (appData.debugMode)
3026 fprintf(debugFP, "ics_type %d\n", ics_type);
3029 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3030 ics_type = ICS_FICS;
3032 if (appData.debugMode)
3033 fprintf(debugFP, "ics_type %d\n", ics_type);
3036 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3037 ics_type = ICS_CHESSNET;
3039 if (appData.debugMode)
3040 fprintf(debugFP, "ics_type %d\n", ics_type);
3045 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3046 looking_at(buf, &i, "Logging you in as \"*\"") ||
3047 looking_at(buf, &i, "will be \"*\""))) {
3048 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3052 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3054 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3055 DisplayIcsInteractionTitle(buf);
3056 have_set_title = TRUE;
3059 /* skip finger notes */
3060 if (started == STARTED_NONE &&
3061 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3062 (buf[i] == '1' && buf[i+1] == '0')) &&
3063 buf[i+2] == ':' && buf[i+3] == ' ') {
3064 started = STARTED_CHATTER;
3070 // [HGM] seekgraph: recognize sought lines and end-of-sought message
3071 if(appData.seekGraph) {
3072 if(soughtPending && MatchSoughtLine(buf+i)) {
3073 i = strstr(buf+i, "rated") - buf;
3074 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3075 next_out = leftover_start = i;
3076 started = STARTED_CHATTER;
3077 suppressKibitz = TRUE;
3080 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3081 && looking_at(buf, &i, "* ads displayed")) {
3082 soughtPending = FALSE;
3087 if(appData.autoRefresh) {
3088 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3089 int s = (ics_type == ICS_ICC); // ICC format differs
3091 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3092 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3093 looking_at(buf, &i, "*% "); // eat prompt
3094 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3095 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3096 next_out = i; // suppress
3099 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3100 char *p = star_match[0];
3102 if(seekGraphUp) RemoveSeekAd(atoi(p));
3103 while(*p && *p++ != ' '); // next
3105 looking_at(buf, &i, "*% "); // eat prompt
3106 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3113 /* skip formula vars */
3114 if (started == STARTED_NONE &&
3115 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3116 started = STARTED_CHATTER;
3121 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3122 if (appData.autoKibitz && started == STARTED_NONE &&
3123 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3124 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3125 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3126 looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3127 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3128 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3129 suppressKibitz = TRUE;
3130 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3132 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3133 && (gameMode == IcsPlayingWhite)) ||
3134 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3135 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3136 started = STARTED_CHATTER; // own kibitz we simply discard
3138 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3139 parse_pos = 0; parse[0] = NULLCHAR;
3140 savingComment = TRUE;
3141 suppressKibitz = gameMode != IcsObserving ? 2 :
3142 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3146 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3147 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3148 && atoi(star_match[0])) {
3149 // suppress the acknowledgements of our own autoKibitz
3151 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3152 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3153 SendToPlayer(star_match[0], strlen(star_match[0]));
3154 if(looking_at(buf, &i, "*% ")) // eat prompt
3155 suppressKibitz = FALSE;
3159 } // [HGM] kibitz: end of patch
3161 if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3163 // [HGM] chat: intercept tells by users for which we have an open chat window
3165 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3166 looking_at(buf, &i, "* whispers:") ||
3167 looking_at(buf, &i, "* kibitzes:") ||
3168 looking_at(buf, &i, "* shouts:") ||
3169 looking_at(buf, &i, "* c-shouts:") ||
3170 looking_at(buf, &i, "--> * ") ||
3171 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3172 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3173 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3174 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3176 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3177 chattingPartner = -1;
3179 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3180 for(p=0; p<MAX_CHAT; p++) {
3181 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3182 talker[0] = '['; strcat(talker, "] ");
3183 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3184 chattingPartner = p; break;
3187 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3188 for(p=0; p<MAX_CHAT; p++) {
3189 if(!strcmp("kibitzes", chatPartner[p])) {
3190 talker[0] = '['; strcat(talker, "] ");
3191 chattingPartner = p; break;
3194 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3195 for(p=0; p<MAX_CHAT; p++) {
3196 if(!strcmp("whispers", chatPartner[p])) {
3197 talker[0] = '['; strcat(talker, "] ");
3198 chattingPartner = p; break;
3201 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3202 if(buf[i-8] == '-' && buf[i-3] == 't')
3203 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3204 if(!strcmp("c-shouts", chatPartner[p])) {
3205 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3206 chattingPartner = p; break;
3209 if(chattingPartner < 0)
3210 for(p=0; p<MAX_CHAT; p++) {
3211 if(!strcmp("shouts", chatPartner[p])) {
3212 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3213 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3214 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3215 chattingPartner = p; break;
3219 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3220 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3221 talker[0] = 0; Colorize(ColorTell, FALSE);
3222 chattingPartner = p; break;
3224 if(chattingPartner<0) i = oldi; else {
3225 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3226 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3227 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3228 started = STARTED_COMMENT;
3229 parse_pos = 0; parse[0] = NULLCHAR;
3230 savingComment = 3 + chattingPartner; // counts as TRUE
3231 suppressKibitz = TRUE;
3234 } // [HGM] chat: end of patch
3237 if (appData.zippyTalk || appData.zippyPlay) {
3238 /* [DM] Backup address for color zippy lines */
3240 if (loggedOn == TRUE)
3241 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3242 (appData.zippyPlay && ZippyMatch(buf, &backup)));
3244 } // [DM] 'else { ' deleted
3246 /* Regular tells and says */
3247 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3248 looking_at(buf, &i, "* (your partner) tells you: ") ||
3249 looking_at(buf, &i, "* says: ") ||
3250 /* Don't color "message" or "messages" output */
3251 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3252 looking_at(buf, &i, "*. * at *:*: ") ||
3253 looking_at(buf, &i, "--* (*:*): ") ||
3254 /* Message notifications (same color as tells) */
3255 looking_at(buf, &i, "* has left a message ") ||
3256 looking_at(buf, &i, "* just sent you a message:\n") ||
3257 /* Whispers and kibitzes */
3258 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3259 looking_at(buf, &i, "* kibitzes: ") ||
3261 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3263 if (tkind == 1 && strchr(star_match[0], ':')) {
3264 /* Avoid "tells you:" spoofs in channels */
3267 if (star_match[0][0] == NULLCHAR ||
3268 strchr(star_match[0], ' ') ||
3269 (tkind == 3 && strchr(star_match[1], ' '))) {
3270 /* Reject bogus matches */
3273 if (appData.colorize) {
3274 if (oldi > next_out) {
3275 SendToPlayer(&buf[next_out], oldi - next_out);
3280 Colorize(ColorTell, FALSE);
3281 curColor = ColorTell;
3284 Colorize(ColorKibitz, FALSE);
3285 curColor = ColorKibitz;
3288 p = strrchr(star_match[1], '(');
3295 Colorize(ColorChannel1, FALSE);
3296 curColor = ColorChannel1;
3298 Colorize(ColorChannel, FALSE);
3299 curColor = ColorChannel;
3303 curColor = ColorNormal;
3307 if (started == STARTED_NONE && appData.autoComment &&
3308 (gameMode == IcsObserving ||
3309 gameMode == IcsPlayingWhite ||
3310 gameMode == IcsPlayingBlack)) {
3311 parse_pos = i - oldi;
3312 memcpy(parse, &buf[oldi], parse_pos);
3313 parse[parse_pos] = NULLCHAR;
3314 started = STARTED_COMMENT;
3315 savingComment = TRUE;
3317 started = STARTED_CHATTER;
3318 savingComment = FALSE;
3325 if (looking_at(buf, &i, "* s-shouts: ") ||
3326 looking_at(buf, &i, "* c-shouts: ")) {
3327 if (appData.colorize) {
3328 if (oldi > next_out) {
3329 SendToPlayer(&buf[next_out], oldi - next_out);
3332 Colorize(ColorSShout, FALSE);
3333 curColor = ColorSShout;
3336 started = STARTED_CHATTER;
3340 if (looking_at(buf, &i, "--->")) {
3345 if (looking_at(buf, &i, "* shouts: ") ||
3346 looking_at(buf, &i, "--> ")) {
3347 if (appData.colorize) {
3348 if (oldi > next_out) {
3349 SendToPlayer(&buf[next_out], oldi - next_out);
3352 Colorize(ColorShout, FALSE);
3353 curColor = ColorShout;
3356 started = STARTED_CHATTER;
3360 if (looking_at( buf, &i, "Challenge:")) {
3361 if (appData.colorize) {
3362 if (oldi > next_out) {
3363 SendToPlayer(&buf[next_out], oldi - next_out);
3366 Colorize(ColorChallenge, FALSE);
3367 curColor = ColorChallenge;
3373 if (looking_at(buf, &i, "* offers you") ||
3374 looking_at(buf, &i, "* offers to be") ||
3375 looking_at(buf, &i, "* would like to") ||
3376 looking_at(buf, &i, "* requests to") ||
3377 looking_at(buf, &i, "Your opponent offers") ||
3378 looking_at(buf, &i, "Your opponent requests")) {
3380 if (appData.colorize) {
3381 if (oldi > next_out) {
3382 SendToPlayer(&buf[next_out], oldi - next_out);
3385 Colorize(ColorRequest, FALSE);
3386 curColor = ColorRequest;
3391 if (looking_at(buf, &i, "* (*) seeking")) {
3392 if (appData.colorize) {
3393 if (oldi > next_out) {
3394 SendToPlayer(&buf[next_out], oldi - next_out);
3397 Colorize(ColorSeek, FALSE);
3398 curColor = ColorSeek;
3403 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3405 if (looking_at(buf, &i, "\\ ")) {
3406 if (prevColor != ColorNormal) {
3407 if (oldi > next_out) {
3408 SendToPlayer(&buf[next_out], oldi - next_out);
3411 Colorize(prevColor, TRUE);
3412 curColor = prevColor;
3414 if (savingComment) {
3415 parse_pos = i - oldi;
3416 memcpy(parse, &buf[oldi], parse_pos);
3417 parse[parse_pos] = NULLCHAR;
3418 started = STARTED_COMMENT;
3419 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3420 chattingPartner = savingComment - 3; // kludge to remember the box
3422 started = STARTED_CHATTER;
3427 if (looking_at(buf, &i, "Black Strength :") ||
3428 looking_at(buf, &i, "<<< style 10 board >>>") ||
3429 looking_at(buf, &i, "<10>") ||
3430 looking_at(buf, &i, "#@#")) {
3431 /* Wrong board style */
3433 SendToICS(ics_prefix);
3434 SendToICS("set style 12\n");
3435 SendToICS(ics_prefix);
3436 SendToICS("refresh\n");
3440 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3442 have_sent_ICS_logon = 1;
3446 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3447 (looking_at(buf, &i, "\n<12> ") ||
3448 looking_at(buf, &i, "<12> "))) {
3450 if (oldi > next_out) {
3451 SendToPlayer(&buf[next_out], oldi - next_out);
3454 started = STARTED_BOARD;
3459 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3460 looking_at(buf, &i, "<b1> ")) {
3461 if (oldi > next_out) {
3462 SendToPlayer(&buf[next_out], oldi - next_out);
3465 started = STARTED_HOLDINGS;
3470 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3472 /* Header for a move list -- first line */
3474 switch (ics_getting_history) {
3478 case BeginningOfGame:
3479 /* User typed "moves" or "oldmoves" while we
3480 were idle. Pretend we asked for these
3481 moves and soak them up so user can step
3482 through them and/or save them.
3485 gameMode = IcsObserving;
3488 ics_getting_history = H_GOT_UNREQ_HEADER;
3490 case EditGame: /*?*/
3491 case EditPosition: /*?*/
3492 /* Should above feature work in these modes too? */
3493 /* For now it doesn't */
3494 ics_getting_history = H_GOT_UNWANTED_HEADER;
3497 ics_getting_history = H_GOT_UNWANTED_HEADER;
3502 /* Is this the right one? */
3503 if (gameInfo.white && gameInfo.black &&
3504 strcmp(gameInfo.white, star_match[0]) == 0 &&
3505 strcmp(gameInfo.black, star_match[2]) == 0) {
3507 ics_getting_history = H_GOT_REQ_HEADER;
3510 case H_GOT_REQ_HEADER:
3511 case H_GOT_UNREQ_HEADER:
3512 case H_GOT_UNWANTED_HEADER:
3513 case H_GETTING_MOVES:
3514 /* Should not happen */
3515 DisplayError(_("Error gathering move list: two headers"), 0);
3516 ics_getting_history = H_FALSE;
3520 /* Save player ratings into gameInfo if needed */
3521 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3522 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3523 (gameInfo.whiteRating == -1 ||
3524 gameInfo.blackRating == -1)) {
3526 gameInfo.whiteRating = string_to_rating(star_match[1]);
3527 gameInfo.blackRating = string_to_rating(star_match[3]);
3528 if (appData.debugMode)
3529 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3530 gameInfo.whiteRating, gameInfo.blackRating);
3535 if (looking_at(buf, &i,
3536 "* * match, initial time: * minute*, increment: * second")) {
3537 /* Header for a move list -- second line */
3538 /* Initial board will follow if this is a wild game */
3539 if (gameInfo.event != NULL) free(gameInfo.event);
3540 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3541 gameInfo.event = StrSave(str);
3542 /* [HGM] we switched variant. Translate boards if needed. */
3543 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3547 if (looking_at(buf, &i, "Move ")) {
3548 /* Beginning of a move list */
3549 switch (ics_getting_history) {
3551 /* Normally should not happen */
3552 /* Maybe user hit reset while we were parsing */
3555 /* Happens if we are ignoring a move list that is not
3556 * the one we just requested. Common if the user
3557 * tries to observe two games without turning off
3560 case H_GETTING_MOVES:
3561 /* Should not happen */
3562 DisplayError(_("Error gathering move list: nested"), 0);
3563 ics_getting_history = H_FALSE;
3565 case H_GOT_REQ_HEADER:
3566 ics_getting_history = H_GETTING_MOVES;
3567 started = STARTED_MOVES;
3569 if (oldi > next_out) {
3570 SendToPlayer(&buf[next_out], oldi - next_out);
3573 case H_GOT_UNREQ_HEADER:
3574 ics_getting_history = H_GETTING_MOVES;
3575 started = STARTED_MOVES_NOHIDE;
3578 case H_GOT_UNWANTED_HEADER:
3579 ics_getting_history = H_FALSE;
3585 if (looking_at(buf, &i, "% ") ||
3586 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3587 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3588 if(soughtPending && nrOfSeekAds) { // [HGM] seekgraph: on ICC sought-list has no termination line
3589 soughtPending = FALSE;
3593 if(suppressKibitz) next_out = i;
3594 savingComment = FALSE;
3598 case STARTED_MOVES_NOHIDE:
3599 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3600 parse[parse_pos + i - oldi] = NULLCHAR;
3601 ParseGameHistory(parse);
3603 if (appData.zippyPlay && first.initDone) {
3604 FeedMovesToProgram(&first, forwardMostMove);
3605 if (gameMode == IcsPlayingWhite) {
3606 if (WhiteOnMove(forwardMostMove)) {
3607 if (first.sendTime) {
3608 if (first.useColors) {
3609 SendToProgram("black\n", &first);
3611 SendTimeRemaining(&first, TRUE);
3613 if (first.useColors) {
3614 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3616 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3617 first.maybeThinking = TRUE;
3619 if (first.usePlayother) {
3620 if (first.sendTime) {
3621 SendTimeRemaining(&first, TRUE);
3623 SendToProgram("playother\n", &first);
3629 } else if (gameMode == IcsPlayingBlack) {
3630 if (!WhiteOnMove(forwardMostMove)) {
3631 if (first.sendTime) {
3632 if (first.useColors) {
3633 SendToProgram("white\n", &first);
3635 SendTimeRemaining(&first, FALSE);
3637 if (first.useColors) {
3638 SendToProgram("black\n", &first);
3640 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3641 first.maybeThinking = TRUE;
3643 if (first.usePlayother) {
3644 if (first.sendTime) {
3645 SendTimeRemaining(&first, FALSE);
3647 SendToProgram("playother\n", &first);
3656 if (gameMode == IcsObserving && ics_gamenum == -1) {
3657 /* Moves came from oldmoves or moves command
3658 while we weren't doing anything else.
3660 currentMove = forwardMostMove;
3661 ClearHighlights();/*!!could figure this out*/
3662 flipView = appData.flipView;
3663 DrawPosition(TRUE, boards[currentMove]);
3664 DisplayBothClocks();
3665 snprintf(str, MSG_SIZ, "%s %s %s",
3666 gameInfo.white, _("vs."), gameInfo.black);
3670 /* Moves were history of an active game */
3671 if (gameInfo.resultDetails != NULL) {
3672 free(gameInfo.resultDetails);
3673 gameInfo.resultDetails = NULL;
3676 HistorySet(parseList, backwardMostMove,
3677 forwardMostMove, currentMove-1);
3678 DisplayMove(currentMove - 1);
3679 if (started == STARTED_MOVES) next_out = i;
3680 started = STARTED_NONE;
3681 ics_getting_history = H_FALSE;
3684 case STARTED_OBSERVE:
3685 started = STARTED_NONE;
3686 SendToICS(ics_prefix);
3687 SendToICS("refresh\n");
3693 if(bookHit) { // [HGM] book: simulate book reply
3694 static char bookMove[MSG_SIZ]; // a bit generous?
3696 programStats.nodes = programStats.depth = programStats.time =
3697 programStats.score = programStats.got_only_move = 0;
3698 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3700 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3701 strcat(bookMove, bookHit);
3702 HandleMachineMove(bookMove, &first);
3707 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3708 started == STARTED_HOLDINGS ||
3709 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3710 /* Accumulate characters in move list or board */
3711 parse[parse_pos++] = buf[i];
3714 /* Start of game messages. Mostly we detect start of game
3715 when the first board image arrives. On some versions
3716 of the ICS, though, we need to do a "refresh" after starting
3717 to observe in order to get the current board right away. */
3718 if (looking_at(buf, &i, "Adding game * to observation list")) {
3719 started = STARTED_OBSERVE;
3723 /* Handle auto-observe */
3724 if (appData.autoObserve &&
3725 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3726 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3728 /* Choose the player that was highlighted, if any. */
3729 if (star_match[0][0] == '\033' ||
3730 star_match[1][0] != '\033') {
3731 player = star_match[0];
3733 player = star_match[2];
3735 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3736 ics_prefix, StripHighlightAndTitle(player));
3739 /* Save ratings from notify string */
3740 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3741 player1Rating = string_to_rating(star_match[1]);
3742 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3743 player2Rating = string_to_rating(star_match[3]);
3745 if (appData.debugMode)
3747 "Ratings from 'Game notification:' %s %d, %s %d\n",
3748 player1Name, player1Rating,
3749 player2Name, player2Rating);
3754 /* Deal with automatic examine mode after a game,
3755 and with IcsObserving -> IcsExamining transition */
3756 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3757 looking_at(buf, &i, "has made you an examiner of game *")) {
3759 int gamenum = atoi(star_match[0]);
3760 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3761 gamenum == ics_gamenum) {
3762 /* We were already playing or observing this game;
3763 no need to refetch history */
3764 gameMode = IcsExamining;
3766 pauseExamForwardMostMove = forwardMostMove;
3767 } else if (currentMove < forwardMostMove) {
3768 ForwardInner(forwardMostMove);
3771 /* I don't think this case really can happen */
3772 SendToICS(ics_prefix);
3773 SendToICS("refresh\n");
3778 /* Error messages */
3779 // if (ics_user_moved) {
3780 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3781 if (looking_at(buf, &i, "Illegal move") ||
3782 looking_at(buf, &i, "Not a legal move") ||
3783 looking_at(buf, &i, "Your king is in check") ||
3784 looking_at(buf, &i, "It isn't your turn") ||
3785 looking_at(buf, &i, "It is not your move")) {
3787 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3788 currentMove = forwardMostMove-1;
3789 DisplayMove(currentMove - 1); /* before DMError */
3790 DrawPosition(FALSE, boards[currentMove]);
3791 SwitchClocks(forwardMostMove-1); // [HGM] race
3792 DisplayBothClocks();
3794 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3800 if (looking_at(buf, &i, "still have time") ||
3801 looking_at(buf, &i, "not out of time") ||
3802 looking_at(buf, &i, "either player is out of time") ||
3803 looking_at(buf, &i, "has timeseal; checking")) {
3804 /* We must have called his flag a little too soon */
3805 whiteFlag = blackFlag = FALSE;
3809 if (looking_at(buf, &i, "added * seconds to") ||
3810 looking_at(buf, &i, "seconds were added to")) {
3811 /* Update the clocks */
3812 SendToICS(ics_prefix);
3813 SendToICS("refresh\n");
3817 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3818 ics_clock_paused = TRUE;
3823 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3824 ics_clock_paused = FALSE;
3829 /* Grab player ratings from the Creating: message.
3830 Note we have to check for the special case when
3831 the ICS inserts things like [white] or [black]. */
3832 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3833 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3835 0 player 1 name (not necessarily white)
3837 2 empty, white, or black (IGNORED)
3838 3 player 2 name (not necessarily black)
3841 The names/ratings are sorted out when the game
3842 actually starts (below).
3844 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3845 player1Rating = string_to_rating(star_match[1]);
3846 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3847 player2Rating = string_to_rating(star_match[4]);
3849 if (appData.debugMode)
3851 "Ratings from 'Creating:' %s %d, %s %d\n",
3852 player1Name, player1Rating,
3853 player2Name, player2Rating);
3858 /* Improved generic start/end-of-game messages */
3859 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3860 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3861 /* If tkind == 0: */
3862 /* star_match[0] is the game number */
3863 /* [1] is the white player's name */
3864 /* [2] is the black player's name */
3865 /* For end-of-game: */