2 * backend.c -- Common back end for X and Windows NT versions of
4 * Copyright 1991 by Digital Equipment Corporation, Maynard,
7 * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8 * 2007, 2008, 2009, 2010, 2011, 2012 Free Software Foundation, Inc.
10 * Enhancements Copyright 2005 Alessandro Scotti
12 * The following terms apply to Digital Equipment Corporation's copyright
14 * ------------------------------------------------------------------------
17 * Permission to use, copy, modify, and distribute this software and its
18 * documentation for any purpose and without fee is hereby granted,
19 * provided that the above copyright notice appear in all copies and that
20 * both that copyright notice and this permission notice appear in
21 * supporting documentation, and that the name of Digital not be
22 * used in advertising or publicity pertaining to distribution of the
23 * software without specific, written prior permission.
25 * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27 * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
32 * ------------------------------------------------------------------------
34 * The following terms apply to the enhanced version of XBoard
35 * distributed by the Free Software Foundation:
36 * ------------------------------------------------------------------------
38 * GNU XBoard is free software: you can redistribute it and/or modify
39 * it under the terms of the GNU General Public License as published by
40 * the Free Software Foundation, either version 3 of the License, or (at
41 * your option) any later version.
43 * GNU XBoard is distributed in the hope that it will be useful, but
44 * WITHOUT ANY WARRANTY; without even the implied warranty of
45 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46 * General Public License for more details.
48 * You should have received a copy of the GNU General Public License
49 * along with this program. If not, see http://www.gnu.org/licenses/. *
51 *------------------------------------------------------------------------
52 ** See the file ChangeLog for a revision history. */
54 /* [AS] Also useful here for debugging */
58 int flock(int f, int code);
75 #include <sys/types.h>
84 #else /* not STDC_HEADERS */
87 # else /* not HAVE_STRING_H */
89 # endif /* not HAVE_STRING_H */
90 #endif /* not STDC_HEADERS */
93 # include <sys/fcntl.h>
94 #else /* not HAVE_SYS_FCNTL_H */
97 # endif /* HAVE_FCNTL_H */
98 #endif /* not HAVE_SYS_FCNTL_H */
100 #if TIME_WITH_SYS_TIME
101 # include <sys/time.h>
105 # include <sys/time.h>
111 #if defined(_amigados) && !defined(__GNUC__)
116 extern int gettimeofday(struct timeval *, struct timezone *);
124 #include "frontend.h"
131 #include "backendz.h"
135 # define _(s) gettext (s)
136 # define N_(s) gettext_noop (s)
137 # define T_(s) gettext(s)
150 int establish P((void));
151 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
152 char *buf, int count, int error));
153 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
154 char *buf, int count, int error));
155 void ics_printf P((char *format, ...));
156 void SendToICS P((char *s));
157 void SendToICSDelayed P((char *s, long msdelay));
158 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
159 void HandleMachineMove P((char *message, ChessProgramState *cps));
160 int AutoPlayOneMove P((void));
161 int LoadGameOneMove P((ChessMove readAhead));
162 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
163 int LoadPositionFromFile P((char *filename, int n, char *title));
164 int SavePositionToFile P((char *filename));
165 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
166 void ShowMove P((int fromX, int fromY, int toX, int toY));
167 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
168 /*char*/int promoChar));
169 void BackwardInner P((int target));
170 void ForwardInner P((int target));
171 int Adjudicate P((ChessProgramState *cps));
172 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
173 void EditPositionDone P((Boolean fakeRights));
174 void PrintOpponents P((FILE *fp));
175 void PrintPosition P((FILE *fp, int move));
176 void StartChessProgram P((ChessProgramState *cps));
177 void SendToProgram P((char *message, ChessProgramState *cps));
178 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
179 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
180 char *buf, int count, int error));
181 void SendTimeControl P((ChessProgramState *cps,
182 int mps, long tc, int inc, int sd, int st));
183 char *TimeControlTagValue P((void));
184 void Attention P((ChessProgramState *cps));
185 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
186 int ResurrectChessProgram P((void));
187 void DisplayComment P((int moveNumber, char *text));
188 void DisplayMove P((int moveNumber));
190 void ParseGameHistory P((char *game));
191 void ParseBoard12 P((char *string));
192 void KeepAlive P((void));
193 void StartClocks P((void));
194 void SwitchClocks P((int nr));
195 void StopClocks P((void));
196 void ResetClocks P((void));
197 char *PGNDate P((void));
198 void SetGameInfo P((void));
199 int RegisterMove P((void));
200 void MakeRegisteredMove P((void));
201 void TruncateGame P((void));
202 int looking_at P((char *, int *, char *));
203 void CopyPlayerNameIntoFileName P((char **, char *));
204 char *SavePart P((char *));
205 int SaveGameOldStyle P((FILE *));
206 int SaveGamePGN P((FILE *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
220 void NextMatchGame P((void));
221 int NextTourneyGame P((int nr, int *swap));
222 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
223 FILE *WriteTourneyFile P((char *results, FILE *f));
224 void DisplayTwoMachinesTitle P(());
225 static void ExcludeClick P((int index));
228 extern void ConsoleCreate();
231 ChessProgramState *WhitePlayer();
232 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
233 int VerifyDisplayMode P(());
235 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
236 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
237 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
238 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
239 void ics_update_width P((int new_width));
240 extern char installDir[MSG_SIZ];
241 VariantClass startVariant; /* [HGM] nicks: initial variant */
244 extern int tinyLayout, smallLayout;
245 ChessProgramStats programStats;
246 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
248 static int exiting = 0; /* [HGM] moved to top */
249 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
250 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
251 Board partnerBoard; /* [HGM] bughouse: for peeking at partner game */
252 int partnerHighlight[2];
253 Boolean partnerBoardValid = 0;
254 char partnerStatus[MSG_SIZ];
256 Boolean originalFlip;
257 Boolean twoBoards = 0;
258 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
259 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
260 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
261 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
262 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
263 int opponentKibitzes;
264 int lastSavedGame; /* [HGM] save: ID of game */
265 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
266 extern int chatCount;
268 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
269 char lastMsg[MSG_SIZ];
270 ChessSquare pieceSweep = EmptySquare;
271 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
272 int promoDefaultAltered;
274 /* States for ics_getting_history */
276 #define H_REQUESTED 1
277 #define H_GOT_REQ_HEADER 2
278 #define H_GOT_UNREQ_HEADER 3
279 #define H_GETTING_MOVES 4
280 #define H_GOT_UNWANTED_HEADER 5
282 /* whosays values for GameEnds */
291 /* Maximum number of games in a cmail message */
292 #define CMAIL_MAX_GAMES 20
294 /* Different types of move when calling RegisterMove */
296 #define CMAIL_RESIGN 1
298 #define CMAIL_ACCEPT 3
300 /* Different types of result to remember for each game */
301 #define CMAIL_NOT_RESULT 0
302 #define CMAIL_OLD_RESULT 1
303 #define CMAIL_NEW_RESULT 2
305 /* Telnet protocol constants */
316 safeStrCpy (char *dst, const char *src, size_t count)
319 assert( dst != NULL );
320 assert( src != NULL );
323 for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
324 if( i == count && dst[count-1] != NULLCHAR)
326 dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
327 if(appData.debugMode)
328 fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
334 /* Some compiler can't cast u64 to double
335 * This function do the job for us:
337 * We use the highest bit for cast, this only
338 * works if the highest bit is not
339 * in use (This should not happen)
341 * We used this for all compiler
344 u64ToDouble (u64 value)
347 u64 tmp = value & u64Const(0x7fffffffffffffff);
348 r = (double)(s64)tmp;
349 if (value & u64Const(0x8000000000000000))
350 r += 9.2233720368547758080e18; /* 2^63 */
354 /* Fake up flags for now, as we aren't keeping track of castling
355 availability yet. [HGM] Change of logic: the flag now only
356 indicates the type of castlings allowed by the rule of the game.
357 The actual rights themselves are maintained in the array
358 castlingRights, as part of the game history, and are not probed
364 int flags = F_ALL_CASTLE_OK;
365 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
366 switch (gameInfo.variant) {
368 flags &= ~F_ALL_CASTLE_OK;
369 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
370 flags |= F_IGNORE_CHECK;
372 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
375 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
377 case VariantKriegspiel:
378 flags |= F_KRIEGSPIEL_CAPTURE;
380 case VariantCapaRandom:
381 case VariantFischeRandom:
382 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
383 case VariantNoCastle:
384 case VariantShatranj:
388 flags &= ~F_ALL_CASTLE_OK;
396 FILE *gameFileFP, *debugFP, *serverFP;
397 char *currentDebugFile; // [HGM] debug split: to remember name
400 [AS] Note: sometimes, the sscanf() function is used to parse the input
401 into a fixed-size buffer. Because of this, we must be prepared to
402 receive strings as long as the size of the input buffer, which is currently
403 set to 4K for Windows and 8K for the rest.
404 So, we must either allocate sufficiently large buffers here, or
405 reduce the size of the input buffer in the input reading part.
408 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
409 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
410 char thinkOutput1[MSG_SIZ*10];
412 ChessProgramState first, second, pairing;
414 /* premove variables */
417 int premoveFromX = 0;
418 int premoveFromY = 0;
419 int premovePromoChar = 0;
421 Boolean alarmSounded;
422 /* end premove variables */
424 char *ics_prefix = "$";
425 int ics_type = ICS_GENERIC;
427 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
428 int pauseExamForwardMostMove = 0;
429 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
430 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
431 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
432 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
433 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
434 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
435 int whiteFlag = FALSE, blackFlag = FALSE;
436 int userOfferedDraw = FALSE;
437 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
438 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
439 int cmailMoveType[CMAIL_MAX_GAMES];
440 long ics_clock_paused = 0;
441 ProcRef icsPR = NoProc, cmailPR = NoProc;
442 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
443 GameMode gameMode = BeginningOfGame;
444 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
445 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
446 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
447 int hiddenThinkOutputState = 0; /* [AS] */
448 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
449 int adjudicateLossPlies = 6;
450 char white_holding[64], black_holding[64];
451 TimeMark lastNodeCountTime;
452 long lastNodeCount=0;
453 int shiftKey; // [HGM] set by mouse handler
455 int have_sent_ICS_logon = 0;
457 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
458 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
459 Boolean adjustedClock;
460 long timeControl_2; /* [AS] Allow separate time controls */
461 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
462 long timeRemaining[2][MAX_MOVES];
463 int matchGame = 0, nextGame = 0, roundNr = 0;
464 Boolean waitingForGame = FALSE;
465 TimeMark programStartTime, pauseStart;
466 char ics_handle[MSG_SIZ];
467 int have_set_title = 0;
469 /* animateTraining preserves the state of appData.animate
470 * when Training mode is activated. This allows the
471 * response to be animated when appData.animate == TRUE and
472 * appData.animateDragging == TRUE.
474 Boolean animateTraining;
480 Board boards[MAX_MOVES];
481 /* [HGM] Following 7 needed for accurate legality tests: */
482 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
483 signed char initialRights[BOARD_FILES];
484 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
485 int initialRulePlies, FENrulePlies;
486 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
488 Boolean shuffleOpenings;
489 int mute; // mute all sounds
491 // [HGM] vari: next 12 to save and restore variations
492 #define MAX_VARIATIONS 10
493 int framePtr = MAX_MOVES-1; // points to free stack entry
495 int savedFirst[MAX_VARIATIONS];
496 int savedLast[MAX_VARIATIONS];
497 int savedFramePtr[MAX_VARIATIONS];
498 char *savedDetails[MAX_VARIATIONS];
499 ChessMove savedResult[MAX_VARIATIONS];
501 void PushTail P((int firstMove, int lastMove));
502 Boolean PopTail P((Boolean annotate));
503 void PushInner P((int firstMove, int lastMove));
504 void PopInner P((Boolean annotate));
505 void CleanupTail P((void));
507 ChessSquare FIDEArray[2][BOARD_FILES] = {
508 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
509 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
510 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
511 BlackKing, BlackBishop, BlackKnight, BlackRook }
514 ChessSquare twoKingsArray[2][BOARD_FILES] = {
515 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
516 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
517 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
518 BlackKing, BlackKing, BlackKnight, BlackRook }
521 ChessSquare KnightmateArray[2][BOARD_FILES] = {
522 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
523 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
524 { BlackRook, BlackMan, BlackBishop, BlackQueen,
525 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
528 ChessSquare SpartanArray[2][BOARD_FILES] = {
529 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
530 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
531 { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
532 BlackDragon, BlackKing, BlackAngel, BlackAlfil }
535 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
536 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
537 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
538 { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
539 BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
542 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
543 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
544 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
545 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
546 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
549 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
550 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
551 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
552 { BlackRook, BlackKnight, BlackMan, BlackFerz,
553 BlackKing, BlackMan, BlackKnight, BlackRook }
557 #if (BOARD_FILES>=10)
558 ChessSquare ShogiArray[2][BOARD_FILES] = {
559 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
560 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
561 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
562 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
565 ChessSquare XiangqiArray[2][BOARD_FILES] = {
566 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
567 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
568 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
569 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
572 ChessSquare CapablancaArray[2][BOARD_FILES] = {
573 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
574 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
575 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
576 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
579 ChessSquare GreatArray[2][BOARD_FILES] = {
580 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
581 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
582 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
583 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
586 ChessSquare JanusArray[2][BOARD_FILES] = {
587 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
588 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
589 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
590 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
593 ChessSquare GrandArray[2][BOARD_FILES] = {
594 { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
595 WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
596 { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
597 BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
601 ChessSquare GothicArray[2][BOARD_FILES] = {
602 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
603 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
604 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
605 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
608 #define GothicArray CapablancaArray
612 ChessSquare FalconArray[2][BOARD_FILES] = {
613 { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
614 WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
615 { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
616 BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
619 #define FalconArray CapablancaArray
622 #else // !(BOARD_FILES>=10)
623 #define XiangqiPosition FIDEArray
624 #define CapablancaArray FIDEArray
625 #define GothicArray FIDEArray
626 #define GreatArray FIDEArray
627 #endif // !(BOARD_FILES>=10)
629 #if (BOARD_FILES>=12)
630 ChessSquare CourierArray[2][BOARD_FILES] = {
631 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
632 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
633 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
634 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
636 #else // !(BOARD_FILES>=12)
637 #define CourierArray CapablancaArray
638 #endif // !(BOARD_FILES>=12)
641 Board initialPosition;
644 /* Convert str to a rating. Checks for special cases of "----",
646 "++++", etc. Also strips ()'s */
648 string_to_rating (char *str)
650 while(*str && !isdigit(*str)) ++str;
652 return 0; /* One of the special "no rating" cases */
660 /* Init programStats */
661 programStats.movelist[0] = 0;
662 programStats.depth = 0;
663 programStats.nr_moves = 0;
664 programStats.moves_left = 0;
665 programStats.nodes = 0;
666 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
667 programStats.score = 0;
668 programStats.got_only_move = 0;
669 programStats.got_fail = 0;
670 programStats.line_is_book = 0;
675 { // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
676 if (appData.firstPlaysBlack) {
677 first.twoMachinesColor = "black\n";
678 second.twoMachinesColor = "white\n";
680 first.twoMachinesColor = "white\n";
681 second.twoMachinesColor = "black\n";
684 first.other = &second;
685 second.other = &first;
688 if(appData.timeOddsMode) {
689 norm = appData.timeOdds[0];
690 if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
692 first.timeOdds = appData.timeOdds[0]/norm;
693 second.timeOdds = appData.timeOdds[1]/norm;
696 if(programVersion) free(programVersion);
697 if (appData.noChessProgram) {
698 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
699 sprintf(programVersion, "%s", PACKAGE_STRING);
701 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
702 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
703 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
708 UnloadEngine (ChessProgramState *cps)
710 /* Kill off first chess program */
711 if (cps->isr != NULL)
712 RemoveInputSource(cps->isr);
715 if (cps->pr != NoProc) {
717 DoSleep( appData.delayBeforeQuit );
718 SendToProgram("quit\n", cps);
719 DoSleep( appData.delayAfterQuit );
720 DestroyChildProcess(cps->pr, cps->useSigterm);
723 if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
727 ClearOptions (ChessProgramState *cps)
730 cps->nrOptions = cps->comboCnt = 0;
731 for(i=0; i<MAX_OPTIONS; i++) {
732 cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
733 cps->option[i].textValue = 0;
737 char *engineNames[] = {
738 /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
739 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
741 /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
742 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
747 InitEngine (ChessProgramState *cps, int n)
748 { // [HGM] all engine initialiation put in a function that does one engine
752 cps->which = engineNames[n];
753 cps->maybeThinking = FALSE;
757 cps->sendDrawOffers = 1;
759 cps->program = appData.chessProgram[n];
760 cps->host = appData.host[n];
761 cps->dir = appData.directory[n];
762 cps->initString = appData.engInitString[n];
763 cps->computerString = appData.computerString[n];
764 cps->useSigint = TRUE;
765 cps->useSigterm = TRUE;
766 cps->reuse = appData.reuse[n];
767 cps->nps = appData.NPS[n]; // [HGM] nps: copy nodes per second
768 cps->useSetboard = FALSE;
770 cps->usePing = FALSE;
773 cps->usePlayother = FALSE;
774 cps->useColors = TRUE;
775 cps->useUsermove = FALSE;
776 cps->sendICS = FALSE;
777 cps->sendName = appData.icsActive;
778 cps->sdKludge = FALSE;
779 cps->stKludge = FALSE;
780 TidyProgramName(cps->program, cps->host, cps->tidy);
782 safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
783 cps->analysisSupport = 2; /* detect */
784 cps->analyzing = FALSE;
785 cps->initDone = FALSE;
787 /* New features added by Tord: */
788 cps->useFEN960 = FALSE;
789 cps->useOOCastle = TRUE;
790 /* End of new features added by Tord. */
791 cps->fenOverride = appData.fenOverride[n];
793 /* [HGM] time odds: set factor for each machine */
794 cps->timeOdds = appData.timeOdds[n];
796 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
797 cps->accumulateTC = appData.accumulateTC[n];
798 cps->maxNrOfSessions = 1;
803 cps->supportsNPS = UNKNOWN;
804 cps->memSize = FALSE;
805 cps->maxCores = FALSE;
806 cps->egtFormats[0] = NULLCHAR;
809 cps->optionSettings = appData.engOptions[n];
811 cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
812 cps->isUCI = appData.isUCI[n]; /* [AS] */
813 cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
815 if (appData.protocolVersion[n] > PROTOVER
816 || appData.protocolVersion[n] < 1)
821 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
822 appData.protocolVersion[n]);
823 if( (len >= MSG_SIZ) && appData.debugMode )
824 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
826 DisplayFatalError(buf, 0, 2);
830 cps->protocolVersion = appData.protocolVersion[n];
833 InitEngineUCI( installDir, cps ); // [HGM] moved here from winboard.c, to make available in xboard
834 ParseFeatures(appData.featureDefaults, cps);
837 ChessProgramState *savCps;
843 if(WaitForEngine(savCps, LoadEngine)) return;
844 CommonEngineInit(); // recalculate time odds
845 if(gameInfo.variant != StringToVariant(appData.variant)) {
846 // we changed variant when loading the engine; this forces us to reset
847 Reset(TRUE, savCps != &first);
848 EditGameEvent(); // for consistency with other path, as Reset changes mode
850 InitChessProgram(savCps, FALSE);
851 SendToProgram("force\n", savCps);
852 DisplayMessage("", "");
853 if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
854 for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
860 ReplaceEngine (ChessProgramState *cps, int n)
864 appData.noChessProgram = FALSE;
865 appData.clockMode = TRUE;
868 if(n) return; // only startup first engine immediately; second can wait
869 savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
873 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
874 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
876 static char resetOptions[] =
877 "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
878 "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
879 "-firstOptions \"\" -firstNPS -1 -fn \"\"";
882 FloatToFront(char **list, char *engineLine)
884 char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
886 if(appData.recentEngines <= 0) return;
887 TidyProgramName(engineLine, "localhost", tidy+1);
888 tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
889 strncpy(buf+1, *list, MSG_SIZ-50);
890 if(p = strstr(buf, tidy)) { // tidy name appears in list
891 q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
892 while(*p++ = *++q); // squeeze out
894 strcat(tidy, buf+1); // put list behind tidy name
895 p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
896 if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
897 ASSIGN(*list, tidy+1);
901 Load (ChessProgramState *cps, int i)
903 char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
904 if(engineLine && engineLine[0]) { // an engine was selected from the combo box
905 snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
906 SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
907 ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
908 appData.firstProtocolVersion = PROTOVER;
909 ParseArgsFromString(buf);
911 ReplaceEngine(cps, i);
912 FloatToFront(&appData.recentEngineList, engineLine);
916 while(q = strchr(p, SLASH)) p = q+1;
917 if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
918 if(engineDir[0] != NULLCHAR)
919 appData.directory[i] = engineDir;
920 else if(p != engineName) { // derive directory from engine path, when not given
922 appData.directory[i] = strdup(engineName);
924 if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
925 } else appData.directory[i] = ".";
927 if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
928 snprintf(command, MSG_SIZ, "%s %s", p, params);
931 appData.chessProgram[i] = strdup(p);
932 appData.isUCI[i] = isUCI;
933 appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
934 appData.hasOwnBookUCI[i] = hasBook;
935 if(!nickName[0]) useNick = FALSE;
936 if(useNick) ASSIGN(appData.pgnName[i], nickName);
940 q = firstChessProgramNames;
941 if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
942 quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
943 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
944 quote, p, quote, appData.directory[i],
945 useNick ? " -fn \"" : "",
946 useNick ? nickName : "",
948 v1 ? " -firstProtocolVersion 1" : "",
949 hasBook ? "" : " -fNoOwnBookUCI",
950 isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
951 storeVariant ? " -variant " : "",
952 storeVariant ? VariantName(gameInfo.variant) : "");
953 firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
954 snprintf(firstChessProgramNames, len, "%s%s", q, buf);
956 FloatToFront(&appData.recentEngineList, buf);
958 ReplaceEngine(cps, i);
964 int matched, min, sec;
966 * Parse timeControl resource
968 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
969 appData.movesPerSession)) {
971 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
972 DisplayFatalError(buf, 0, 2);
976 * Parse searchTime resource
978 if (*appData.searchTime != NULLCHAR) {
979 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
981 searchTime = min * 60;
982 } else if (matched == 2) {
983 searchTime = min * 60 + sec;
986 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
987 DisplayFatalError(buf, 0, 2);
996 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
997 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
999 GetTimeMark(&programStartTime);
1000 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1001 appData.seedBase = random() + (random()<<15);
1002 pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1004 ClearProgramStats();
1005 programStats.ok_to_send = 1;
1006 programStats.seen_stat = 0;
1009 * Initialize game list
1015 * Internet chess server status
1017 if (appData.icsActive) {
1018 appData.matchMode = FALSE;
1019 appData.matchGames = 0;
1021 appData.noChessProgram = !appData.zippyPlay;
1023 appData.zippyPlay = FALSE;
1024 appData.zippyTalk = FALSE;
1025 appData.noChessProgram = TRUE;
1027 if (*appData.icsHelper != NULLCHAR) {
1028 appData.useTelnet = TRUE;
1029 appData.telnetProgram = appData.icsHelper;
1032 appData.zippyTalk = appData.zippyPlay = FALSE;
1035 /* [AS] Initialize pv info list [HGM] and game state */
1039 for( i=0; i<=framePtr; i++ ) {
1040 pvInfoList[i].depth = -1;
1041 boards[i][EP_STATUS] = EP_NONE;
1042 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1048 /* [AS] Adjudication threshold */
1049 adjudicateLossThreshold = appData.adjudicateLossThreshold;
1051 InitEngine(&first, 0);
1052 InitEngine(&second, 1);
1055 pairing.which = "pairing"; // pairing engine
1056 pairing.pr = NoProc;
1058 pairing.program = appData.pairingEngine;
1059 pairing.host = "localhost";
1062 if (appData.icsActive) {
1063 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
1064 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1065 appData.clockMode = FALSE;
1066 first.sendTime = second.sendTime = 0;
1070 /* Override some settings from environment variables, for backward
1071 compatibility. Unfortunately it's not feasible to have the env
1072 vars just set defaults, at least in xboard. Ugh.
1074 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1079 if (!appData.icsActive) {
1083 /* Check for variants that are supported only in ICS mode,
1084 or not at all. Some that are accepted here nevertheless
1085 have bugs; see comments below.
1087 VariantClass variant = StringToVariant(appData.variant);
1089 case VariantBughouse: /* need four players and two boards */
1090 case VariantKriegspiel: /* need to hide pieces and move details */
1091 /* case VariantFischeRandom: (Fabien: moved below) */
1092 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1093 if( (len >= MSG_SIZ) && appData.debugMode )
1094 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1096 DisplayFatalError(buf, 0, 2);
1099 case VariantUnknown:
1100 case VariantLoadable:
1110 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1111 if( (len >= MSG_SIZ) && appData.debugMode )
1112 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1114 DisplayFatalError(buf, 0, 2);
1117 case VariantXiangqi: /* [HGM] repetition rules not implemented */
1118 case VariantFairy: /* [HGM] TestLegality definitely off! */
1119 case VariantGothic: /* [HGM] should work */
1120 case VariantCapablanca: /* [HGM] should work */
1121 case VariantCourier: /* [HGM] initial forced moves not implemented */
1122 case VariantShogi: /* [HGM] could still mate with pawn drop */
1123 case VariantKnightmate: /* [HGM] should work */
1124 case VariantCylinder: /* [HGM] untested */
1125 case VariantFalcon: /* [HGM] untested */
1126 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1127 offboard interposition not understood */
1128 case VariantNormal: /* definitely works! */
1129 case VariantWildCastle: /* pieces not automatically shuffled */
1130 case VariantNoCastle: /* pieces not automatically shuffled */
1131 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1132 case VariantLosers: /* should work except for win condition,
1133 and doesn't know captures are mandatory */
1134 case VariantSuicide: /* should work except for win condition,
1135 and doesn't know captures are mandatory */
1136 case VariantGiveaway: /* should work except for win condition,
1137 and doesn't know captures are mandatory */
1138 case VariantTwoKings: /* should work */
1139 case VariantAtomic: /* should work except for win condition */
1140 case Variant3Check: /* should work except for win condition */
1141 case VariantShatranj: /* should work except for all win conditions */
1142 case VariantMakruk: /* should work except for draw countdown */
1143 case VariantBerolina: /* might work if TestLegality is off */
1144 case VariantCapaRandom: /* should work */
1145 case VariantJanus: /* should work */
1146 case VariantSuper: /* experimental */
1147 case VariantGreat: /* experimental, requires legality testing to be off */
1148 case VariantSChess: /* S-Chess, should work */
1149 case VariantGrand: /* should work */
1150 case VariantSpartan: /* should work */
1158 NextIntegerFromString (char ** str, long * value)
1163 while( *s == ' ' || *s == '\t' ) {
1169 if( *s >= '0' && *s <= '9' ) {
1170 while( *s >= '0' && *s <= '9' ) {
1171 *value = *value * 10 + (*s - '0');
1184 NextTimeControlFromString (char ** str, long * value)
1187 int result = NextIntegerFromString( str, &temp );
1190 *value = temp * 60; /* Minutes */
1191 if( **str == ':' ) {
1193 result = NextIntegerFromString( str, &temp );
1194 *value += temp; /* Seconds */
1202 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1203 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1204 int result = -1, type = 0; long temp, temp2;
1206 if(**str != ':') return -1; // old params remain in force!
1208 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1209 if( NextIntegerFromString( str, &temp ) ) return -1;
1210 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1213 /* time only: incremental or sudden-death time control */
1214 if(**str == '+') { /* increment follows; read it */
1216 if(**str == '!') type = *(*str)++; // Bronstein TC
1217 if(result = NextIntegerFromString( str, &temp2)) return -1;
1218 *inc = temp2 * 1000;
1219 if(**str == '.') { // read fraction of increment
1220 char *start = ++(*str);
1221 if(result = NextIntegerFromString( str, &temp2)) return -1;
1223 while(start++ < *str) temp2 /= 10;
1227 *moves = 0; *tc = temp * 1000; *incType = type;
1231 (*str)++; /* classical time control */
1232 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1244 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1245 { /* [HGM] get time to add from the multi-session time-control string */
1246 int incType, moves=1; /* kludge to force reading of first session */
1247 long time, increment;
1250 if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1252 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1253 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1254 if(movenr == -1) return time; /* last move before new session */
1255 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1256 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1257 if(!moves) return increment; /* current session is incremental */
1258 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1259 } while(movenr >= -1); /* try again for next session */
1261 return 0; // no new time quota on this move
1265 ParseTimeControl (char *tc, float ti, int mps)
1269 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1272 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1273 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1274 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1278 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1280 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1283 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1285 snprintf(buf, MSG_SIZ, ":%s", mytc);
1287 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1289 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1294 /* Parse second time control */
1297 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1305 timeControl_2 = tc2 * 1000;
1315 timeControl = tc1 * 1000;
1318 timeIncrement = ti * 1000; /* convert to ms */
1319 movesPerSession = 0;
1322 movesPerSession = mps;
1330 if (appData.debugMode) {
1331 fprintf(debugFP, "%s\n", programVersion);
1333 ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1335 set_cont_sequence(appData.wrapContSeq);
1336 if (appData.matchGames > 0) {
1337 appData.matchMode = TRUE;
1338 } else if (appData.matchMode) {
1339 appData.matchGames = 1;
1341 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1342 appData.matchGames = appData.sameColorGames;
1343 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1344 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1345 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1348 if (appData.noChessProgram || first.protocolVersion == 1) {
1351 /* kludge: allow timeout for initial "feature" commands */
1353 DisplayMessage("", _("Starting chess program"));
1354 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1359 CalculateIndex (int index, int gameNr)
1360 { // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1362 if(index > 0) return index; // fixed nmber
1363 if(index == 0) return 1;
1364 res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1365 if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1370 LoadGameOrPosition (int gameNr)
1371 { // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1372 if (*appData.loadGameFile != NULLCHAR) {
1373 if (!LoadGameFromFile(appData.loadGameFile,
1374 CalculateIndex(appData.loadGameIndex, gameNr),
1375 appData.loadGameFile, FALSE)) {
1376 DisplayFatalError(_("Bad game file"), 0, 1);
1379 } else if (*appData.loadPositionFile != NULLCHAR) {
1380 if (!LoadPositionFromFile(appData.loadPositionFile,
1381 CalculateIndex(appData.loadPositionIndex, gameNr),
1382 appData.loadPositionFile)) {
1383 DisplayFatalError(_("Bad position file"), 0, 1);
1391 ReserveGame (int gameNr, char resChar)
1393 FILE *tf = fopen(appData.tourneyFile, "r+");
1394 char *p, *q, c, buf[MSG_SIZ];
1395 if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1396 safeStrCpy(buf, lastMsg, MSG_SIZ);
1397 DisplayMessage(_("Pick new game"), "");
1398 flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1399 ParseArgsFromFile(tf);
1400 p = q = appData.results;
1401 if(appData.debugMode) {
1402 char *r = appData.participants;
1403 fprintf(debugFP, "results = '%s'\n", p);
1404 while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1405 fprintf(debugFP, "\n");
1407 while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1409 q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1410 safeStrCpy(q, p, strlen(p) + 2);
1411 if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1412 if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1413 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1414 if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1417 fseek(tf, -(strlen(p)+4), SEEK_END);
1419 if(c != '"') // depending on DOS or Unix line endings we can be one off
1420 fseek(tf, -(strlen(p)+2), SEEK_END);
1421 else fseek(tf, -(strlen(p)+3), SEEK_END);
1422 fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1423 DisplayMessage(buf, "");
1424 free(p); appData.results = q;
1425 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1426 (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1427 int round = appData.defaultMatchGames * appData.tourneyType;
1428 if(gameNr < 0 || appData.tourneyType < 1 || // gauntlet engine can always stay loaded as first engine
1429 appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1430 UnloadEngine(&first); // next game belongs to other pairing;
1431 UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1433 if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1437 MatchEvent (int mode)
1438 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1440 if(matchMode) { // already in match mode: switch it off
1442 if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1445 // if(gameMode != BeginningOfGame) {
1446 // DisplayError(_("You can only start a match from the initial position."), 0);
1450 if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1451 /* Set up machine vs. machine match */
1453 NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1454 if(appData.tourneyFile[0]) {
1456 if(nextGame > appData.matchGames) {
1458 if(strchr(appData.results, '*') == NULL) {
1460 appData.tourneyCycles++;
1461 if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1463 NextTourneyGame(-1, &dummy);
1465 if(nextGame <= appData.matchGames) {
1466 DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1468 ScheduleDelayedEvent(NextMatchGame, 10000);
1473 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1474 DisplayError(buf, 0);
1475 appData.tourneyFile[0] = 0;
1479 if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically
1480 DisplayFatalError(_("Can't have a match with no chess programs"),
1485 matchGame = roundNr = 1;
1486 first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1490 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1493 InitBackEnd3 P((void))
1495 GameMode initialMode;
1499 InitChessProgram(&first, startedFromSetupPosition);
1501 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1502 free(programVersion);
1503 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1504 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1505 FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1508 if (appData.icsActive) {
1510 /* [DM] Make a console window if needed [HGM] merged ifs */
1516 if (*appData.icsCommPort != NULLCHAR)
1517 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1518 appData.icsCommPort);
1520 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1521 appData.icsHost, appData.icsPort);
1523 if( (len >= MSG_SIZ) && appData.debugMode )
1524 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1526 DisplayFatalError(buf, err, 1);
1531 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1533 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1534 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1535 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1536 } else if (appData.noChessProgram) {
1542 if (*appData.cmailGameName != NULLCHAR) {
1544 OpenLoopback(&cmailPR);
1546 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1550 DisplayMessage("", "");
1551 if (StrCaseCmp(appData.initialMode, "") == 0) {
1552 initialMode = BeginningOfGame;
1553 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1554 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1555 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1556 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1559 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1560 initialMode = TwoMachinesPlay;
1561 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1562 initialMode = AnalyzeFile;
1563 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1564 initialMode = AnalyzeMode;
1565 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1566 initialMode = MachinePlaysWhite;
1567 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1568 initialMode = MachinePlaysBlack;
1569 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1570 initialMode = EditGame;
1571 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1572 initialMode = EditPosition;
1573 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1574 initialMode = Training;
1576 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1577 if( (len >= MSG_SIZ) && appData.debugMode )
1578 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1580 DisplayFatalError(buf, 0, 2);
1584 if (appData.matchMode) {
1585 if(appData.tourneyFile[0]) { // start tourney from command line
1587 if(f = fopen(appData.tourneyFile, "r")) {
1588 ParseArgsFromFile(f); // make sure tourney parmeters re known
1590 appData.clockMode = TRUE;
1592 } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1595 } else if (*appData.cmailGameName != NULLCHAR) {
1596 /* Set up cmail mode */
1597 ReloadCmailMsgEvent(TRUE);
1599 /* Set up other modes */
1600 if (initialMode == AnalyzeFile) {
1601 if (*appData.loadGameFile == NULLCHAR) {
1602 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1606 if (*appData.loadGameFile != NULLCHAR) {
1607 (void) LoadGameFromFile(appData.loadGameFile,
1608 appData.loadGameIndex,
1609 appData.loadGameFile, TRUE);
1610 } else if (*appData.loadPositionFile != NULLCHAR) {
1611 (void) LoadPositionFromFile(appData.loadPositionFile,
1612 appData.loadPositionIndex,
1613 appData.loadPositionFile);
1614 /* [HGM] try to make self-starting even after FEN load */
1615 /* to allow automatic setup of fairy variants with wtm */
1616 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1617 gameMode = BeginningOfGame;
1618 setboardSpoiledMachineBlack = 1;
1620 /* [HGM] loadPos: make that every new game uses the setup */
1621 /* from file as long as we do not switch variant */
1622 if(!blackPlaysFirst) {
1623 startedFromPositionFile = TRUE;
1624 CopyBoard(filePosition, boards[0]);
1627 if (initialMode == AnalyzeMode) {
1628 if (appData.noChessProgram) {
1629 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1632 if (appData.icsActive) {
1633 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1637 } else if (initialMode == AnalyzeFile) {
1638 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1639 ShowThinkingEvent();
1641 AnalysisPeriodicEvent(1);
1642 } else if (initialMode == MachinePlaysWhite) {
1643 if (appData.noChessProgram) {
1644 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1648 if (appData.icsActive) {
1649 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1653 MachineWhiteEvent();
1654 } else if (initialMode == MachinePlaysBlack) {
1655 if (appData.noChessProgram) {
1656 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1660 if (appData.icsActive) {
1661 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1665 MachineBlackEvent();
1666 } else if (initialMode == TwoMachinesPlay) {
1667 if (appData.noChessProgram) {
1668 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1672 if (appData.icsActive) {
1673 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1678 } else if (initialMode == EditGame) {
1680 } else if (initialMode == EditPosition) {
1681 EditPositionEvent();
1682 } else if (initialMode == Training) {
1683 if (*appData.loadGameFile == NULLCHAR) {
1684 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1693 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1695 DisplayBook(current+1);
1697 MoveHistorySet( movelist, first, last, current, pvInfoList );
1699 EvalGraphSet( first, last, current, pvInfoList );
1701 MakeEngineOutputTitle();
1705 * Establish will establish a contact to a remote host.port.
1706 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1707 * used to talk to the host.
1708 * Returns 0 if okay, error code if not.
1715 if (*appData.icsCommPort != NULLCHAR) {
1716 /* Talk to the host through a serial comm port */
1717 return OpenCommPort(appData.icsCommPort, &icsPR);
1719 } else if (*appData.gateway != NULLCHAR) {
1720 if (*appData.remoteShell == NULLCHAR) {
1721 /* Use the rcmd protocol to run telnet program on a gateway host */
1722 snprintf(buf, sizeof(buf), "%s %s %s",
1723 appData.telnetProgram, appData.icsHost, appData.icsPort);
1724 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1727 /* Use the rsh program to run telnet program on a gateway host */
1728 if (*appData.remoteUser == NULLCHAR) {
1729 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1730 appData.gateway, appData.telnetProgram,
1731 appData.icsHost, appData.icsPort);
1733 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1734 appData.remoteShell, appData.gateway,
1735 appData.remoteUser, appData.telnetProgram,
1736 appData.icsHost, appData.icsPort);
1738 return StartChildProcess(buf, "", &icsPR);
1741 } else if (appData.useTelnet) {
1742 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1745 /* TCP socket interface differs somewhat between
1746 Unix and NT; handle details in the front end.
1748 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1753 EscapeExpand (char *p, char *q)
1754 { // [HGM] initstring: routine to shape up string arguments
1755 while(*p++ = *q++) if(p[-1] == '\\')
1757 case 'n': p[-1] = '\n'; break;
1758 case 'r': p[-1] = '\r'; break;
1759 case 't': p[-1] = '\t'; break;
1760 case '\\': p[-1] = '\\'; break;
1761 case 0: *p = 0; return;
1762 default: p[-1] = q[-1]; break;
1767 show_bytes (FILE *fp, char *buf, int count)
1770 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1771 fprintf(fp, "\\%03o", *buf & 0xff);
1780 /* Returns an errno value */
1782 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1784 char buf[8192], *p, *q, *buflim;
1785 int left, newcount, outcount;
1787 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1788 *appData.gateway != NULLCHAR) {
1789 if (appData.debugMode) {
1790 fprintf(debugFP, ">ICS: ");
1791 show_bytes(debugFP, message, count);
1792 fprintf(debugFP, "\n");
1794 return OutputToProcess(pr, message, count, outError);
1797 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1804 if (appData.debugMode) {
1805 fprintf(debugFP, ">ICS: ");
1806 show_bytes(debugFP, buf, newcount);
1807 fprintf(debugFP, "\n");
1809 outcount = OutputToProcess(pr, buf, newcount, outError);
1810 if (outcount < newcount) return -1; /* to be sure */
1817 } else if (((unsigned char) *p) == TN_IAC) {
1818 *q++ = (char) TN_IAC;
1825 if (appData.debugMode) {
1826 fprintf(debugFP, ">ICS: ");
1827 show_bytes(debugFP, buf, newcount);
1828 fprintf(debugFP, "\n");
1830 outcount = OutputToProcess(pr, buf, newcount, outError);
1831 if (outcount < newcount) return -1; /* to be sure */
1836 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1838 int outError, outCount;
1839 static int gotEof = 0;
1841 /* Pass data read from player on to ICS */
1844 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1845 if (outCount < count) {
1846 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1848 } else if (count < 0) {
1849 RemoveInputSource(isr);
1850 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1851 } else if (gotEof++ > 0) {
1852 RemoveInputSource(isr);
1853 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1859 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1860 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1861 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1862 SendToICS("date\n");
1863 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1866 /* added routine for printf style output to ics */
1868 ics_printf (char *format, ...)
1870 char buffer[MSG_SIZ];
1873 va_start(args, format);
1874 vsnprintf(buffer, sizeof(buffer), format, args);
1875 buffer[sizeof(buffer)-1] = '\0';
1883 int count, outCount, outError;
1885 if (icsPR == NoProc) return;
1888 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1889 if (outCount < count) {
1890 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1894 /* This is used for sending logon scripts to the ICS. Sending
1895 without a delay causes problems when using timestamp on ICC
1896 (at least on my machine). */
1898 SendToICSDelayed (char *s, long msdelay)
1900 int count, outCount, outError;
1902 if (icsPR == NoProc) return;
1905 if (appData.debugMode) {
1906 fprintf(debugFP, ">ICS: ");
1907 show_bytes(debugFP, s, count);
1908 fprintf(debugFP, "\n");
1910 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1912 if (outCount < count) {
1913 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1918 /* Remove all highlighting escape sequences in s
1919 Also deletes any suffix starting with '('
1922 StripHighlightAndTitle (char *s)
1924 static char retbuf[MSG_SIZ];
1927 while (*s != NULLCHAR) {
1928 while (*s == '\033') {
1929 while (*s != NULLCHAR && !isalpha(*s)) s++;
1930 if (*s != NULLCHAR) s++;
1932 while (*s != NULLCHAR && *s != '\033') {
1933 if (*s == '(' || *s == '[') {
1944 /* Remove all highlighting escape sequences in s */
1946 StripHighlight (char *s)
1948 static char retbuf[MSG_SIZ];
1951 while (*s != NULLCHAR) {
1952 while (*s == '\033') {
1953 while (*s != NULLCHAR && !isalpha(*s)) s++;
1954 if (*s != NULLCHAR) s++;
1956 while (*s != NULLCHAR && *s != '\033') {
1964 char *variantNames[] = VARIANT_NAMES;
1966 VariantName (VariantClass v)
1968 return variantNames[v];
1972 /* Identify a variant from the strings the chess servers use or the
1973 PGN Variant tag names we use. */
1975 StringToVariant (char *e)
1979 VariantClass v = VariantNormal;
1980 int i, found = FALSE;
1986 /* [HGM] skip over optional board-size prefixes */
1987 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1988 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1989 while( *e++ != '_');
1992 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1996 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1997 if (StrCaseStr(e, variantNames[i])) {
1998 v = (VariantClass) i;
2005 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2006 || StrCaseStr(e, "wild/fr")
2007 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2008 v = VariantFischeRandom;
2009 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2010 (i = 1, p = StrCaseStr(e, "w"))) {
2012 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2019 case 0: /* FICS only, actually */
2021 /* Castling legal even if K starts on d-file */
2022 v = VariantWildCastle;
2027 /* Castling illegal even if K & R happen to start in
2028 normal positions. */
2029 v = VariantNoCastle;
2042 /* Castling legal iff K & R start in normal positions */
2048 /* Special wilds for position setup; unclear what to do here */
2049 v = VariantLoadable;
2052 /* Bizarre ICC game */
2053 v = VariantTwoKings;
2056 v = VariantKriegspiel;
2062 v = VariantFischeRandom;
2065 v = VariantCrazyhouse;
2068 v = VariantBughouse;
2074 /* Not quite the same as FICS suicide! */
2075 v = VariantGiveaway;
2081 v = VariantShatranj;
2084 /* Temporary names for future ICC types. The name *will* change in
2085 the next xboard/WinBoard release after ICC defines it. */
2123 v = VariantCapablanca;
2126 v = VariantKnightmate;
2132 v = VariantCylinder;
2138 v = VariantCapaRandom;
2141 v = VariantBerolina;
2153 /* Found "wild" or "w" in the string but no number;
2154 must assume it's normal chess. */
2158 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2159 if( (len >= MSG_SIZ) && appData.debugMode )
2160 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2162 DisplayError(buf, 0);
2168 if (appData.debugMode) {
2169 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2170 e, wnum, VariantName(v));
2175 static int leftover_start = 0, leftover_len = 0;
2176 char star_match[STAR_MATCH_N][MSG_SIZ];
2178 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2179 advance *index beyond it, and set leftover_start to the new value of
2180 *index; else return FALSE. If pattern contains the character '*', it
2181 matches any sequence of characters not containing '\r', '\n', or the
2182 character following the '*' (if any), and the matched sequence(s) are
2183 copied into star_match.
2186 looking_at ( char *buf, int *index, char *pattern)
2188 char *bufp = &buf[*index], *patternp = pattern;
2190 char *matchp = star_match[0];
2193 if (*patternp == NULLCHAR) {
2194 *index = leftover_start = bufp - buf;
2198 if (*bufp == NULLCHAR) return FALSE;
2199 if (*patternp == '*') {
2200 if (*bufp == *(patternp + 1)) {
2202 matchp = star_match[++star_count];
2206 } else if (*bufp == '\n' || *bufp == '\r') {
2208 if (*patternp == NULLCHAR)
2213 *matchp++ = *bufp++;
2217 if (*patternp != *bufp) return FALSE;
2224 SendToPlayer (char *data, int length)
2226 int error, outCount;
2227 outCount = OutputToProcess(NoProc, data, length, &error);
2228 if (outCount < length) {
2229 DisplayFatalError(_("Error writing to display"), error, 1);
2234 PackHolding (char packed[], char *holding)
2244 switch (runlength) {
2255 sprintf(q, "%d", runlength);
2267 /* Telnet protocol requests from the front end */
2269 TelnetRequest (unsigned char ddww, unsigned char option)
2271 unsigned char msg[3];
2272 int outCount, outError;
2274 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2276 if (appData.debugMode) {
2277 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2293 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2302 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2305 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2310 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2312 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2319 if (!appData.icsActive) return;
2320 TelnetRequest(TN_DO, TN_ECHO);
2326 if (!appData.icsActive) return;
2327 TelnetRequest(TN_DONT, TN_ECHO);
2331 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2333 /* put the holdings sent to us by the server on the board holdings area */
2334 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2338 if(gameInfo.holdingsWidth < 2) return;
2339 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2340 return; // prevent overwriting by pre-board holdings
2342 if( (int)lowestPiece >= BlackPawn ) {
2345 holdingsStartRow = BOARD_HEIGHT-1;
2348 holdingsColumn = BOARD_WIDTH-1;
2349 countsColumn = BOARD_WIDTH-2;
2350 holdingsStartRow = 0;
2354 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2355 board[i][holdingsColumn] = EmptySquare;
2356 board[i][countsColumn] = (ChessSquare) 0;
2358 while( (p=*holdings++) != NULLCHAR ) {
2359 piece = CharToPiece( ToUpper(p) );
2360 if(piece == EmptySquare) continue;
2361 /*j = (int) piece - (int) WhitePawn;*/
2362 j = PieceToNumber(piece);
2363 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2364 if(j < 0) continue; /* should not happen */
2365 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2366 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2367 board[holdingsStartRow+j*direction][countsColumn]++;
2373 VariantSwitch (Board board, VariantClass newVariant)
2375 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2376 static Board oldBoard;
2378 startedFromPositionFile = FALSE;
2379 if(gameInfo.variant == newVariant) return;
2381 /* [HGM] This routine is called each time an assignment is made to
2382 * gameInfo.variant during a game, to make sure the board sizes
2383 * are set to match the new variant. If that means adding or deleting
2384 * holdings, we shift the playing board accordingly
2385 * This kludge is needed because in ICS observe mode, we get boards
2386 * of an ongoing game without knowing the variant, and learn about the
2387 * latter only later. This can be because of the move list we requested,
2388 * in which case the game history is refilled from the beginning anyway,
2389 * but also when receiving holdings of a crazyhouse game. In the latter
2390 * case we want to add those holdings to the already received position.
2394 if (appData.debugMode) {
2395 fprintf(debugFP, "Switch board from %s to %s\n",
2396 VariantName(gameInfo.variant), VariantName(newVariant));
2397 setbuf(debugFP, NULL);
2399 shuffleOpenings = 0; /* [HGM] shuffle */
2400 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2404 newWidth = 9; newHeight = 9;
2405 gameInfo.holdingsSize = 7;
2406 case VariantBughouse:
2407 case VariantCrazyhouse:
2408 newHoldingsWidth = 2; break;
2412 newHoldingsWidth = 2;
2413 gameInfo.holdingsSize = 8;
2416 case VariantCapablanca:
2417 case VariantCapaRandom:
2420 newHoldingsWidth = gameInfo.holdingsSize = 0;
2423 if(newWidth != gameInfo.boardWidth ||
2424 newHeight != gameInfo.boardHeight ||
2425 newHoldingsWidth != gameInfo.holdingsWidth ) {
2427 /* shift position to new playing area, if needed */
2428 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2429 for(i=0; i<BOARD_HEIGHT; i++)
2430 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2431 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2433 for(i=0; i<newHeight; i++) {
2434 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2435 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2437 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2438 for(i=0; i<BOARD_HEIGHT; i++)
2439 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2440 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2443 gameInfo.boardWidth = newWidth;
2444 gameInfo.boardHeight = newHeight;
2445 gameInfo.holdingsWidth = newHoldingsWidth;
2446 gameInfo.variant = newVariant;
2447 InitDrawingSizes(-2, 0);
2448 } else gameInfo.variant = newVariant;
2449 CopyBoard(oldBoard, board); // remember correctly formatted board
2450 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2451 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2454 static int loggedOn = FALSE;
2456 /*-- Game start info cache: --*/
2458 char gs_kind[MSG_SIZ];
2459 static char player1Name[128] = "";
2460 static char player2Name[128] = "";
2461 static char cont_seq[] = "\n\\ ";
2462 static int player1Rating = -1;
2463 static int player2Rating = -1;
2464 /*----------------------------*/
2466 ColorClass curColor = ColorNormal;
2467 int suppressKibitz = 0;
2470 Boolean soughtPending = FALSE;
2471 Boolean seekGraphUp;
2472 #define MAX_SEEK_ADS 200
2474 char *seekAdList[MAX_SEEK_ADS];
2475 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2476 float tcList[MAX_SEEK_ADS];
2477 char colorList[MAX_SEEK_ADS];
2478 int nrOfSeekAds = 0;
2479 int minRating = 1010, maxRating = 2800;
2480 int hMargin = 10, vMargin = 20, h, w;
2481 extern int squareSize, lineGap;
2486 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2487 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2488 if(r < minRating+100 && r >=0 ) r = minRating+100;
2489 if(r > maxRating) r = maxRating;
2490 if(tc < 1.) tc = 1.;
2491 if(tc > 95.) tc = 95.;
2492 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2493 y = ((double)r - minRating)/(maxRating - minRating)
2494 * (h-vMargin-squareSize/8-1) + vMargin;
2495 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2496 if(strstr(seekAdList[i], " u ")) color = 1;
2497 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2498 !strstr(seekAdList[i], "bullet") &&
2499 !strstr(seekAdList[i], "blitz") &&
2500 !strstr(seekAdList[i], "standard") ) color = 2;
2501 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2502 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2506 AddAd (char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2508 char buf[MSG_SIZ], *ext = "";
2509 VariantClass v = StringToVariant(type);
2510 if(strstr(type, "wild")) {
2511 ext = type + 4; // append wild number
2512 if(v == VariantFischeRandom) type = "chess960"; else
2513 if(v == VariantLoadable) type = "setup"; else
2514 type = VariantName(v);
2516 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2517 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2518 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2519 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2520 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2521 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2522 seekNrList[nrOfSeekAds] = nr;
2523 zList[nrOfSeekAds] = 0;
2524 seekAdList[nrOfSeekAds++] = StrSave(buf);
2525 if(plot) PlotSeekAd(nrOfSeekAds-1);
2530 EraseSeekDot (int i)
2532 int x = xList[i], y = yList[i], d=squareSize/4, k;
2533 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2534 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2535 // now replot every dot that overlapped
2536 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2537 int xx = xList[k], yy = yList[k];
2538 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2539 DrawSeekDot(xx, yy, colorList[k]);
2544 RemoveSeekAd (int nr)
2547 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2549 if(seekAdList[i]) free(seekAdList[i]);
2550 seekAdList[i] = seekAdList[--nrOfSeekAds];
2551 seekNrList[i] = seekNrList[nrOfSeekAds];
2552 ratingList[i] = ratingList[nrOfSeekAds];
2553 colorList[i] = colorList[nrOfSeekAds];
2554 tcList[i] = tcList[nrOfSeekAds];
2555 xList[i] = xList[nrOfSeekAds];
2556 yList[i] = yList[nrOfSeekAds];
2557 zList[i] = zList[nrOfSeekAds];
2558 seekAdList[nrOfSeekAds] = NULL;
2564 MatchSoughtLine (char *line)
2566 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2567 int nr, base, inc, u=0; char dummy;
2569 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2570 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2572 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2573 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2574 // match: compact and save the line
2575 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2585 if(!seekGraphUp) return FALSE;
2586 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2587 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2589 DrawSeekBackground(0, 0, w, h);
2590 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2591 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2592 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2593 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2595 DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2598 snprintf(buf, MSG_SIZ, "%d", i);
2599 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2602 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2603 for(i=1; i<100; i+=(i<10?1:5)) {
2604 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2605 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2606 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2608 snprintf(buf, MSG_SIZ, "%d", i);
2609 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2612 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2617 SeekGraphClick (ClickType click, int x, int y, int moving)
2619 static int lastDown = 0, displayed = 0, lastSecond;
2620 if(y < 0) return FALSE;
2621 if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2622 (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2623 if(!seekGraphUp) return FALSE;
2624 seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2625 DrawPosition(TRUE, NULL);
2628 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2629 if(click == Release || moving) return FALSE;
2631 soughtPending = TRUE;
2632 SendToICS(ics_prefix);
2633 SendToICS("sought\n"); // should this be "sought all"?
2634 } else { // issue challenge based on clicked ad
2635 int dist = 10000; int i, closest = 0, second = 0;
2636 for(i=0; i<nrOfSeekAds; i++) {
2637 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2638 if(d < dist) { dist = d; closest = i; }
2639 second += (d - zList[i] < 120); // count in-range ads
2640 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2644 second = (second > 1);
2645 if(displayed != closest || second != lastSecond) {
2646 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2647 lastSecond = second; displayed = closest;
2649 if(click == Press) {
2650 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2653 } // on press 'hit', only show info
2654 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2655 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2656 SendToICS(ics_prefix);
2658 return TRUE; // let incoming board of started game pop down the graph
2659 } else if(click == Release) { // release 'miss' is ignored
2660 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2661 if(moving == 2) { // right up-click
2662 nrOfSeekAds = 0; // refresh graph
2663 soughtPending = TRUE;
2664 SendToICS(ics_prefix);
2665 SendToICS("sought\n"); // should this be "sought all"?
2668 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2669 // press miss or release hit 'pop down' seek graph
2670 seekGraphUp = FALSE;
2671 DrawPosition(TRUE, NULL);
2677 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2679 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2680 #define STARTED_NONE 0
2681 #define STARTED_MOVES 1
2682 #define STARTED_BOARD 2
2683 #define STARTED_OBSERVE 3
2684 #define STARTED_HOLDINGS 4
2685 #define STARTED_CHATTER 5
2686 #define STARTED_COMMENT 6
2687 #define STARTED_MOVES_NOHIDE 7
2689 static int started = STARTED_NONE;
2690 static char parse[20000];
2691 static int parse_pos = 0;
2692 static char buf[BUF_SIZE + 1];
2693 static int firstTime = TRUE, intfSet = FALSE;
2694 static ColorClass prevColor = ColorNormal;
2695 static int savingComment = FALSE;
2696 static int cmatch = 0; // continuation sequence match
2703 int backup; /* [DM] For zippy color lines */
2705 char talker[MSG_SIZ]; // [HGM] chat
2708 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2710 if (appData.debugMode) {
2712 fprintf(debugFP, "<ICS: ");
2713 show_bytes(debugFP, data, count);
2714 fprintf(debugFP, "\n");
2718 if (appData.debugMode) { int f = forwardMostMove;
2719 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2720 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2721 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2724 /* If last read ended with a partial line that we couldn't parse,
2725 prepend it to the new read and try again. */
2726 if (leftover_len > 0) {
2727 for (i=0; i<leftover_len; i++)
2728 buf[i] = buf[leftover_start + i];
2731 /* copy new characters into the buffer */
2732 bp = buf + leftover_len;
2733 buf_len=leftover_len;
2734 for (i=0; i<count; i++)
2737 if (data[i] == '\r')
2740 // join lines split by ICS?
2741 if (!appData.noJoin)
2744 Joining just consists of finding matches against the
2745 continuation sequence, and discarding that sequence
2746 if found instead of copying it. So, until a match
2747 fails, there's nothing to do since it might be the
2748 complete sequence, and thus, something we don't want
2751 if (data[i] == cont_seq[cmatch])
2754 if (cmatch == strlen(cont_seq))
2756 cmatch = 0; // complete match. just reset the counter
2759 it's possible for the ICS to not include the space
2760 at the end of the last word, making our [correct]
2761 join operation fuse two separate words. the server
2762 does this when the space occurs at the width setting.
2764 if (!buf_len || buf[buf_len-1] != ' ')
2775 match failed, so we have to copy what matched before
2776 falling through and copying this character. In reality,
2777 this will only ever be just the newline character, but
2778 it doesn't hurt to be precise.
2780 strncpy(bp, cont_seq, cmatch);
2792 buf[buf_len] = NULLCHAR;
2793 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2798 while (i < buf_len) {
2799 /* Deal with part of the TELNET option negotiation
2800 protocol. We refuse to do anything beyond the
2801 defaults, except that we allow the WILL ECHO option,
2802 which ICS uses to turn off password echoing when we are
2803 directly connected to it. We reject this option
2804 if localLineEditing mode is on (always on in xboard)
2805 and we are talking to port 23, which might be a real
2806 telnet server that will try to keep WILL ECHO on permanently.
2808 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2809 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2810 unsigned char option;
2812 switch ((unsigned char) buf[++i]) {
2814 if (appData.debugMode)
2815 fprintf(debugFP, "\n<WILL ");
2816 switch (option = (unsigned char) buf[++i]) {
2818 if (appData.debugMode)
2819 fprintf(debugFP, "ECHO ");
2820 /* Reply only if this is a change, according
2821 to the protocol rules. */
2822 if (remoteEchoOption) break;
2823 if (appData.localLineEditing &&
2824 atoi(appData.icsPort) == TN_PORT) {
2825 TelnetRequest(TN_DONT, TN_ECHO);
2828 TelnetRequest(TN_DO, TN_ECHO);
2829 remoteEchoOption = TRUE;
2833 if (appData.debugMode)
2834 fprintf(debugFP, "%d ", option);
2835 /* Whatever this is, we don't want it. */
2836 TelnetRequest(TN_DONT, option);
2841 if (appData.debugMode)
2842 fprintf(debugFP, "\n<WONT ");
2843 switch (option = (unsigned char) buf[++i]) {
2845 if (appData.debugMode)
2846 fprintf(debugFP, "ECHO ");
2847 /* Reply only if this is a change, according
2848 to the protocol rules. */
2849 if (!remoteEchoOption) break;
2851 TelnetRequest(TN_DONT, TN_ECHO);
2852 remoteEchoOption = FALSE;
2855 if (appData.debugMode)
2856 fprintf(debugFP, "%d ", (unsigned char) option);
2857 /* Whatever this is, it must already be turned
2858 off, because we never agree to turn on
2859 anything non-default, so according to the
2860 protocol rules, we don't reply. */
2865 if (appData.debugMode)
2866 fprintf(debugFP, "\n<DO ");
2867 switch (option = (unsigned char) buf[++i]) {
2869 /* Whatever this is, we refuse to do it. */
2870 if (appData.debugMode)
2871 fprintf(debugFP, "%d ", option);
2872 TelnetRequest(TN_WONT, option);
2877 if (appData.debugMode)
2878 fprintf(debugFP, "\n<DONT ");
2879 switch (option = (unsigned char) buf[++i]) {
2881 if (appData.debugMode)
2882 fprintf(debugFP, "%d ", option);
2883 /* Whatever this is, we are already not doing
2884 it, because we never agree to do anything
2885 non-default, so according to the protocol
2886 rules, we don't reply. */
2891 if (appData.debugMode)
2892 fprintf(debugFP, "\n<IAC ");
2893 /* Doubled IAC; pass it through */
2897 if (appData.debugMode)
2898 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2899 /* Drop all other telnet commands on the floor */
2902 if (oldi > next_out)
2903 SendToPlayer(&buf[next_out], oldi - next_out);
2909 /* OK, this at least will *usually* work */
2910 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2914 if (loggedOn && !intfSet) {
2915 if (ics_type == ICS_ICC) {
2916 snprintf(str, MSG_SIZ,
2917 "/set-quietly interface %s\n/set-quietly style 12\n",
2919 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2920 strcat(str, "/set-2 51 1\n/set seek 1\n");
2921 } else if (ics_type == ICS_CHESSNET) {
2922 snprintf(str, MSG_SIZ, "/style 12\n");
2924 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2925 strcat(str, programVersion);
2926 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2927 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2928 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2930 strcat(str, "$iset nohighlight 1\n");
2932 strcat(str, "$iset lock 1\n$style 12\n");
2935 NotifyFrontendLogin();
2939 if (started == STARTED_COMMENT) {
2940 /* Accumulate characters in comment */
2941 parse[parse_pos++] = buf[i];
2942 if (buf[i] == '\n') {
2943 parse[parse_pos] = NULLCHAR;
2944 if(chattingPartner>=0) {
2946 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2947 OutputChatMessage(chattingPartner, mess);
2948 chattingPartner = -1;
2949 next_out = i+1; // [HGM] suppress printing in ICS window
2951 if(!suppressKibitz) // [HGM] kibitz
2952 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2953 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2954 int nrDigit = 0, nrAlph = 0, j;
2955 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2956 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2957 parse[parse_pos] = NULLCHAR;
2958 // try to be smart: if it does not look like search info, it should go to
2959 // ICS interaction window after all, not to engine-output window.
2960 for(j=0; j<parse_pos; j++) { // count letters and digits
2961 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2962 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2963 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2965 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2966 int depth=0; float score;
2967 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2968 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2969 pvInfoList[forwardMostMove-1].depth = depth;
2970 pvInfoList[forwardMostMove-1].score = 100*score;
2972 OutputKibitz(suppressKibitz, parse);
2975 if(gameMode == IcsObserving) // restore original ICS messages
2976 snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
2978 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2979 SendToPlayer(tmp, strlen(tmp));
2981 next_out = i+1; // [HGM] suppress printing in ICS window
2983 started = STARTED_NONE;
2985 /* Don't match patterns against characters in comment */
2990 if (started == STARTED_CHATTER) {
2991 if (buf[i] != '\n') {
2992 /* Don't match patterns against characters in chatter */
2996 started = STARTED_NONE;
2997 if(suppressKibitz) next_out = i+1;
3000 /* Kludge to deal with rcmd protocol */
3001 if (firstTime && looking_at(buf, &i, "\001*")) {
3002 DisplayFatalError(&buf[1], 0, 1);
3008 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3011 if (appData.debugMode)
3012 fprintf(debugFP, "ics_type %d\n", ics_type);
3015 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3016 ics_type = ICS_FICS;
3018 if (appData.debugMode)
3019 fprintf(debugFP, "ics_type %d\n", ics_type);
3022 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3023 ics_type = ICS_CHESSNET;
3025 if (appData.debugMode)
3026 fprintf(debugFP, "ics_type %d\n", ics_type);
3031 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3032 looking_at(buf, &i, "Logging you in as \"*\"") ||
3033 looking_at(buf, &i, "will be \"*\""))) {
3034 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3038 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3040 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3041 DisplayIcsInteractionTitle(buf);
3042 have_set_title = TRUE;
3045 /* skip finger notes */
3046 if (started == STARTED_NONE &&
3047 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3048 (buf[i] == '1' && buf[i+1] == '0')) &&
3049 buf[i+2] == ':' && buf[i+3] == ' ') {
3050 started = STARTED_CHATTER;
3056 // [HGM] seekgraph: recognize sought lines and end-of-sought message
3057 if(appData.seekGraph) {
3058 if(soughtPending && MatchSoughtLine(buf+i)) {
3059 i = strstr(buf+i, "rated") - buf;
3060 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3061 next_out = leftover_start = i;
3062 started = STARTED_CHATTER;
3063 suppressKibitz = TRUE;
3066 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3067 && looking_at(buf, &i, "* ads displayed")) {
3068 soughtPending = FALSE;
3073 if(appData.autoRefresh) {
3074 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3075 int s = (ics_type == ICS_ICC); // ICC format differs
3077 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3078 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3079 looking_at(buf, &i, "*% "); // eat prompt
3080 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3081 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3082 next_out = i; // suppress
3085 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3086 char *p = star_match[0];
3088 if(seekGraphUp) RemoveSeekAd(atoi(p));
3089 while(*p && *p++ != ' '); // next
3091 looking_at(buf, &i, "*% "); // eat prompt
3092 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3099 /* skip formula vars */
3100 if (started == STARTED_NONE &&
3101 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3102 started = STARTED_CHATTER;
3107 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3108 if (appData.autoKibitz && started == STARTED_NONE &&
3109 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3110 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3111 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3112 looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3113 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3114 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3115 suppressKibitz = TRUE;
3116 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3118 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3119 && (gameMode == IcsPlayingWhite)) ||
3120 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3121 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3122 started = STARTED_CHATTER; // own kibitz we simply discard
3124 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3125 parse_pos = 0; parse[0] = NULLCHAR;
3126 savingComment = TRUE;
3127 suppressKibitz = gameMode != IcsObserving ? 2 :
3128 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3132 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3133 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3134 && atoi(star_match[0])) {
3135 // suppress the acknowledgements of our own autoKibitz
3137 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3138 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3139 SendToPlayer(star_match[0], strlen(star_match[0]));
3140 if(looking_at(buf, &i, "*% ")) // eat prompt
3141 suppressKibitz = FALSE;
3145 } // [HGM] kibitz: end of patch
3147 if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3149 // [HGM] chat: intercept tells by users for which we have an open chat window
3151 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3152 looking_at(buf, &i, "* whispers:") ||
3153 looking_at(buf, &i, "* kibitzes:") ||
3154 looking_at(buf, &i, "* shouts:") ||
3155 looking_at(buf, &i, "* c-shouts:") ||
3156 looking_at(buf, &i, "--> * ") ||
3157 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3158 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3159 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3160 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3162 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3163 chattingPartner = -1;
3165 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3166 for(p=0; p<MAX_CHAT; p++) {
3167 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3168 talker[0] = '['; strcat(talker, "] ");
3169 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3170 chattingPartner = p; break;
3173 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3174 for(p=0; p<MAX_CHAT; p++) {
3175 if(!strcmp("kibitzes", chatPartner[p])) {
3176 talker[0] = '['; strcat(talker, "] ");
3177 chattingPartner = p; break;
3180 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3181 for(p=0; p<MAX_CHAT; p++) {
3182 if(!strcmp("whispers", chatPartner[p])) {
3183 talker[0] = '['; strcat(talker, "] ");
3184 chattingPartner = p; break;
3187 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3188 if(buf[i-8] == '-' && buf[i-3] == 't')
3189 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3190 if(!strcmp("c-shouts", chatPartner[p])) {
3191 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3192 chattingPartner = p; break;
3195 if(chattingPartner < 0)
3196 for(p=0; p<MAX_CHAT; p++) {
3197 if(!strcmp("shouts", chatPartner[p])) {
3198 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3199 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3200 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3201 chattingPartner = p; break;
3205 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3206 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3207 talker[0] = 0; Colorize(ColorTell, FALSE);
3208 chattingPartner = p; break;
3210 if(chattingPartner<0) i = oldi; else {
3211 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3212 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3213 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3214 started = STARTED_COMMENT;
3215 parse_pos = 0; parse[0] = NULLCHAR;
3216 savingComment = 3 + chattingPartner; // counts as TRUE
3217 suppressKibitz = TRUE;
3220 } // [HGM] chat: end of patch
3223 if (appData.zippyTalk || appData.zippyPlay) {
3224 /* [DM] Backup address for color zippy lines */
3226 if (loggedOn == TRUE)
3227 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3228 (appData.zippyPlay && ZippyMatch(buf, &backup)));
3230 } // [DM] 'else { ' deleted
3232 /* Regular tells and says */
3233 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3234 looking_at(buf, &i, "* (your partner) tells you: ") ||
3235 looking_at(buf, &i, "* says: ") ||
3236 /* Don't color "message" or "messages" output */
3237 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3238 looking_at(buf, &i, "*. * at *:*: ") ||
3239 looking_at(buf, &i, "--* (*:*): ") ||
3240 /* Message notifications (same color as tells) */
3241 looking_at(buf, &i, "* has left a message ") ||
3242 looking_at(buf, &i, "* just sent you a message:\n") ||
3243 /* Whispers and kibitzes */
3244 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3245 looking_at(buf, &i, "* kibitzes: ") ||
3247 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3249 if (tkind == 1 && strchr(star_match[0], ':')) {
3250 /* Avoid "tells you:" spoofs in channels */
3253 if (star_match[0][0] == NULLCHAR ||
3254 strchr(star_match[0], ' ') ||
3255 (tkind == 3 && strchr(star_match[1], ' '))) {
3256 /* Reject bogus matches */
3259 if (appData.colorize) {
3260 if (oldi > next_out) {
3261 SendToPlayer(&buf[next_out], oldi - next_out);
3266 Colorize(ColorTell, FALSE);
3267 curColor = ColorTell;
3270 Colorize(ColorKibitz, FALSE);
3271 curColor = ColorKibitz;
3274 p = strrchr(star_match[1], '(');
3281 Colorize(ColorChannel1, FALSE);
3282 curColor = ColorChannel1;
3284 Colorize(ColorChannel, FALSE);
3285 curColor = ColorChannel;
3289 curColor = ColorNormal;
3293 if (started == STARTED_NONE && appData.autoComment &&
3294 (gameMode == IcsObserving ||
3295 gameMode == IcsPlayingWhite ||
3296 gameMode == IcsPlayingBlack)) {
3297 parse_pos = i - oldi;
3298 memcpy(parse, &buf[oldi], parse_pos);
3299 parse[parse_pos] = NULLCHAR;
3300 started = STARTED_COMMENT;
3301 savingComment = TRUE;
3303 started = STARTED_CHATTER;
3304 savingComment = FALSE;
3311 if (looking_at(buf, &i, "* s-shouts: ") ||
3312 looking_at(buf, &i, "* c-shouts: ")) {
3313 if (appData.colorize) {
3314 if (oldi > next_out) {
3315 SendToPlayer(&buf[next_out], oldi - next_out);
3318 Colorize(ColorSShout, FALSE);
3319 curColor = ColorSShout;
3322 started = STARTED_CHATTER;
3326 if (looking_at(buf, &i, "--->")) {
3331 if (looking_at(buf, &i, "* shouts: ") ||
3332 looking_at(buf, &i, "--> ")) {
3333 if (appData.colorize) {
3334 if (oldi > next_out) {
3335 SendToPlayer(&buf[next_out], oldi - next_out);
3338 Colorize(ColorShout, FALSE);
3339 curColor = ColorShout;
3342 started = STARTED_CHATTER;
3346 if (looking_at( buf, &i, "Challenge:")) {
3347 if (appData.colorize) {
3348 if (oldi > next_out) {
3349 SendToPlayer(&buf[next_out], oldi - next_out);
3352 Colorize(ColorChallenge, FALSE);
3353 curColor = ColorChallenge;
3359 if (looking_at(buf, &i, "* offers you") ||
3360 looking_at(buf, &i, "* offers to be") ||
3361 looking_at(buf, &i, "* would like to") ||
3362 looking_at(buf, &i, "* requests to") ||
3363 looking_at(buf, &i, "Your opponent offers") ||
3364 looking_at(buf, &i, "Your opponent requests")) {
3366 if (appData.colorize) {
3367 if (oldi > next_out) {
3368 SendToPlayer(&buf[next_out], oldi - next_out);
3371 Colorize(ColorRequest, FALSE);
3372 curColor = ColorRequest;
3377 if (looking_at(buf, &i, "* (*) seeking")) {
3378 if (appData.colorize) {
3379 if (oldi > next_out) {
3380 SendToPlayer(&buf[next_out], oldi - next_out);
3383 Colorize(ColorSeek, FALSE);
3384 curColor = ColorSeek;
3389 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3391 if (looking_at(buf, &i, "\\ ")) {
3392 if (prevColor != ColorNormal) {
3393 if (oldi > next_out) {
3394 SendToPlayer(&buf[next_out], oldi - next_out);
3397 Colorize(prevColor, TRUE);
3398 curColor = prevColor;
3400 if (savingComment) {
3401 parse_pos = i - oldi;
3402 memcpy(parse, &buf[oldi], parse_pos);
3403 parse[parse_pos] = NULLCHAR;
3404 started = STARTED_COMMENT;
3405 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3406 chattingPartner = savingComment - 3; // kludge to remember the box
3408 started = STARTED_CHATTER;
3413 if (looking_at(buf, &i, "Black Strength :") ||