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 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 /* A point in time */
152 long sec; /* Assuming this is >= 32 bits */
153 int ms; /* Assuming this is >= 16 bits */
156 int establish P((void));
157 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
158 char *buf, int count, int error));
159 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
160 char *buf, int count, int error));
161 void ics_printf P((char *format, ...));
162 void SendToICS P((char *s));
163 void SendToICSDelayed P((char *s, long msdelay));
164 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
165 void HandleMachineMove P((char *message, ChessProgramState *cps));
166 int AutoPlayOneMove P((void));
167 int LoadGameOneMove P((ChessMove readAhead));
168 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
169 int LoadPositionFromFile P((char *filename, int n, char *title));
170 int SavePositionToFile P((char *filename));
171 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
172 void ShowMove P((int fromX, int fromY, int toX, int toY));
173 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
174 /*char*/int promoChar));
175 void BackwardInner P((int target));
176 void ForwardInner P((int target));
177 int Adjudicate P((ChessProgramState *cps));
178 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
179 void EditPositionDone P((Boolean fakeRights));
180 void PrintOpponents P((FILE *fp));
181 void PrintPosition P((FILE *fp, int move));
182 void StartChessProgram P((ChessProgramState *cps));
183 void SendToProgram P((char *message, ChessProgramState *cps));
184 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
185 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
186 char *buf, int count, int error));
187 void SendTimeControl P((ChessProgramState *cps,
188 int mps, long tc, int inc, int sd, int st));
189 char *TimeControlTagValue P((void));
190 void Attention P((ChessProgramState *cps));
191 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
192 int ResurrectChessProgram P((void));
193 void DisplayComment P((int moveNumber, char *text));
194 void DisplayMove P((int moveNumber));
196 void ParseGameHistory P((char *game));
197 void ParseBoard12 P((char *string));
198 void KeepAlive P((void));
199 void StartClocks P((void));
200 void SwitchClocks P((int nr));
201 void StopClocks P((void));
202 void ResetClocks P((void));
203 char *PGNDate P((void));
204 void SetGameInfo P((void));
205 int RegisterMove P((void));
206 void MakeRegisteredMove P((void));
207 void TruncateGame P((void));
208 int looking_at P((char *, int *, char *));
209 void CopyPlayerNameIntoFileName P((char **, char *));
210 char *SavePart P((char *));
211 int SaveGameOldStyle P((FILE *));
212 int SaveGamePGN P((FILE *));
213 void GetTimeMark P((TimeMark *));
214 long SubtractTimeMarks P((TimeMark *, TimeMark *));
215 int CheckFlags P((void));
216 long NextTickLength P((long));
217 void CheckTimeControl P((void));
218 void show_bytes P((FILE *, char *, int));
219 int string_to_rating P((char *str));
220 void ParseFeatures P((char* args, ChessProgramState *cps));
221 void InitBackEnd3 P((void));
222 void FeatureDone P((ChessProgramState* cps, int val));
223 void InitChessProgram P((ChessProgramState *cps, int setup));
224 void OutputKibitz(int window, char *text);
225 int PerpetualChase(int first, int last);
226 int EngineOutputIsUp();
227 void InitDrawingSizes(int x, int y);
228 void NextMatchGame P((void));
229 int NextTourneyGame P((int nr, int *swap));
230 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
231 FILE *WriteTourneyFile P((char *results, FILE *f));
232 void DisplayTwoMachinesTitle P(());
235 extern void ConsoleCreate();
238 ChessProgramState *WhitePlayer();
239 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
240 int VerifyDisplayMode P(());
242 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
243 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
244 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
245 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
246 void ics_update_width P((int new_width));
247 extern char installDir[MSG_SIZ];
248 VariantClass startVariant; /* [HGM] nicks: initial variant */
251 extern int tinyLayout, smallLayout;
252 ChessProgramStats programStats;
253 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
255 static int exiting = 0; /* [HGM] moved to top */
256 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
257 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
258 Board partnerBoard; /* [HGM] bughouse: for peeking at partner game */
259 int partnerHighlight[2];
260 Boolean partnerBoardValid = 0;
261 char partnerStatus[MSG_SIZ];
263 Boolean originalFlip;
264 Boolean twoBoards = 0;
265 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
266 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
267 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
268 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
269 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
270 int opponentKibitzes;
271 int lastSavedGame; /* [HGM] save: ID of game */
272 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
273 extern int chatCount;
275 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
276 char lastMsg[MSG_SIZ];
277 ChessSquare pieceSweep = EmptySquare;
278 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
279 int promoDefaultAltered;
281 /* States for ics_getting_history */
283 #define H_REQUESTED 1
284 #define H_GOT_REQ_HEADER 2
285 #define H_GOT_UNREQ_HEADER 3
286 #define H_GETTING_MOVES 4
287 #define H_GOT_UNWANTED_HEADER 5
289 /* whosays values for GameEnds */
298 /* Maximum number of games in a cmail message */
299 #define CMAIL_MAX_GAMES 20
301 /* Different types of move when calling RegisterMove */
303 #define CMAIL_RESIGN 1
305 #define CMAIL_ACCEPT 3
307 /* Different types of result to remember for each game */
308 #define CMAIL_NOT_RESULT 0
309 #define CMAIL_OLD_RESULT 1
310 #define CMAIL_NEW_RESULT 2
312 /* Telnet protocol constants */
323 safeStrCpy( char *dst, const char *src, size_t count )
326 assert( dst != NULL );
327 assert( src != NULL );
330 for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
331 if( i == count && dst[count-1] != NULLCHAR)
333 dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
334 if(appData.debugMode)
335 fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
341 /* Some compiler can't cast u64 to double
342 * This function do the job for us:
344 * We use the highest bit for cast, this only
345 * works if the highest bit is not
346 * in use (This should not happen)
348 * We used this for all compiler
351 u64ToDouble(u64 value)
354 u64 tmp = value & u64Const(0x7fffffffffffffff);
355 r = (double)(s64)tmp;
356 if (value & u64Const(0x8000000000000000))
357 r += 9.2233720368547758080e18; /* 2^63 */
361 /* Fake up flags for now, as we aren't keeping track of castling
362 availability yet. [HGM] Change of logic: the flag now only
363 indicates the type of castlings allowed by the rule of the game.
364 The actual rights themselves are maintained in the array
365 castlingRights, as part of the game history, and are not probed
371 int flags = F_ALL_CASTLE_OK;
372 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
373 switch (gameInfo.variant) {
375 flags &= ~F_ALL_CASTLE_OK;
376 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
377 flags |= F_IGNORE_CHECK;
379 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
382 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
384 case VariantKriegspiel:
385 flags |= F_KRIEGSPIEL_CAPTURE;
387 case VariantCapaRandom:
388 case VariantFischeRandom:
389 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
390 case VariantNoCastle:
391 case VariantShatranj:
395 flags &= ~F_ALL_CASTLE_OK;
403 FILE *gameFileFP, *debugFP;
406 [AS] Note: sometimes, the sscanf() function is used to parse the input
407 into a fixed-size buffer. Because of this, we must be prepared to
408 receive strings as long as the size of the input buffer, which is currently
409 set to 4K for Windows and 8K for the rest.
410 So, we must either allocate sufficiently large buffers here, or
411 reduce the size of the input buffer in the input reading part.
414 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
415 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
416 char thinkOutput1[MSG_SIZ*10];
418 ChessProgramState first, second, pairing;
420 /* premove variables */
423 int premoveFromX = 0;
424 int premoveFromY = 0;
425 int premovePromoChar = 0;
427 Boolean alarmSounded;
428 /* end premove variables */
430 char *ics_prefix = "$";
431 int ics_type = ICS_GENERIC;
433 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
434 int pauseExamForwardMostMove = 0;
435 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
436 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
437 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
438 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
439 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
440 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
441 int whiteFlag = FALSE, blackFlag = FALSE;
442 int userOfferedDraw = FALSE;
443 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
444 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
445 int cmailMoveType[CMAIL_MAX_GAMES];
446 long ics_clock_paused = 0;
447 ProcRef icsPR = NoProc, cmailPR = NoProc;
448 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
449 GameMode gameMode = BeginningOfGame;
450 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
451 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
452 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
453 int hiddenThinkOutputState = 0; /* [AS] */
454 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
455 int adjudicateLossPlies = 6;
456 char white_holding[64], black_holding[64];
457 TimeMark lastNodeCountTime;
458 long lastNodeCount=0;
459 int shiftKey; // [HGM] set by mouse handler
461 int have_sent_ICS_logon = 0;
463 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
464 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
465 Boolean adjustedClock;
466 long timeControl_2; /* [AS] Allow separate time controls */
467 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
468 long timeRemaining[2][MAX_MOVES];
469 int matchGame = 0, nextGame = 0, roundNr = 0;
470 Boolean waitingForGame = FALSE;
471 TimeMark programStartTime, pauseStart;
472 char ics_handle[MSG_SIZ];
473 int have_set_title = 0;
475 /* animateTraining preserves the state of appData.animate
476 * when Training mode is activated. This allows the
477 * response to be animated when appData.animate == TRUE and
478 * appData.animateDragging == TRUE.
480 Boolean animateTraining;
486 Board boards[MAX_MOVES];
487 /* [HGM] Following 7 needed for accurate legality tests: */
488 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
489 signed char initialRights[BOARD_FILES];
490 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
491 int initialRulePlies, FENrulePlies;
492 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
494 Boolean shuffleOpenings;
495 int mute; // mute all sounds
497 // [HGM] vari: next 12 to save and restore variations
498 #define MAX_VARIATIONS 10
499 int framePtr = MAX_MOVES-1; // points to free stack entry
501 int savedFirst[MAX_VARIATIONS];
502 int savedLast[MAX_VARIATIONS];
503 int savedFramePtr[MAX_VARIATIONS];
504 char *savedDetails[MAX_VARIATIONS];
505 ChessMove savedResult[MAX_VARIATIONS];
507 void PushTail P((int firstMove, int lastMove));
508 Boolean PopTail P((Boolean annotate));
509 void PushInner P((int firstMove, int lastMove));
510 void PopInner P((Boolean annotate));
511 void CleanupTail P((void));
513 ChessSquare FIDEArray[2][BOARD_FILES] = {
514 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
515 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
516 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
517 BlackKing, BlackBishop, BlackKnight, BlackRook }
520 ChessSquare twoKingsArray[2][BOARD_FILES] = {
521 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
522 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
523 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
524 BlackKing, BlackKing, BlackKnight, BlackRook }
527 ChessSquare KnightmateArray[2][BOARD_FILES] = {
528 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
529 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
530 { BlackRook, BlackMan, BlackBishop, BlackQueen,
531 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
534 ChessSquare SpartanArray[2][BOARD_FILES] = {
535 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
536 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
537 { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
538 BlackDragon, BlackKing, BlackAngel, BlackAlfil }
541 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
542 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
543 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
544 { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
545 BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
548 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
549 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
550 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
551 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
552 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
555 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
556 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
557 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
558 { BlackRook, BlackKnight, BlackMan, BlackFerz,
559 BlackKing, BlackMan, BlackKnight, BlackRook }
563 #if (BOARD_FILES>=10)
564 ChessSquare ShogiArray[2][BOARD_FILES] = {
565 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
566 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
567 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
568 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
571 ChessSquare XiangqiArray[2][BOARD_FILES] = {
572 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
573 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
574 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
575 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
578 ChessSquare CapablancaArray[2][BOARD_FILES] = {
579 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
580 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
581 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
582 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
585 ChessSquare GreatArray[2][BOARD_FILES] = {
586 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
587 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
588 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
589 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
592 ChessSquare JanusArray[2][BOARD_FILES] = {
593 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
594 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
595 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
596 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
599 ChessSquare GrandArray[2][BOARD_FILES] = {
600 { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
601 WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
602 { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
603 BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
607 ChessSquare GothicArray[2][BOARD_FILES] = {
608 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
609 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
610 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
611 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
614 #define GothicArray CapablancaArray
618 ChessSquare FalconArray[2][BOARD_FILES] = {
619 { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
620 WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
621 { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
622 BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
625 #define FalconArray CapablancaArray
628 #else // !(BOARD_FILES>=10)
629 #define XiangqiPosition FIDEArray
630 #define CapablancaArray FIDEArray
631 #define GothicArray FIDEArray
632 #define GreatArray FIDEArray
633 #endif // !(BOARD_FILES>=10)
635 #if (BOARD_FILES>=12)
636 ChessSquare CourierArray[2][BOARD_FILES] = {
637 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
638 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
639 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
640 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
642 #else // !(BOARD_FILES>=12)
643 #define CourierArray CapablancaArray
644 #endif // !(BOARD_FILES>=12)
647 Board initialPosition;
650 /* Convert str to a rating. Checks for special cases of "----",
652 "++++", etc. Also strips ()'s */
654 string_to_rating(str)
657 while(*str && !isdigit(*str)) ++str;
659 return 0; /* One of the special "no rating" cases */
667 /* Init programStats */
668 programStats.movelist[0] = 0;
669 programStats.depth = 0;
670 programStats.nr_moves = 0;
671 programStats.moves_left = 0;
672 programStats.nodes = 0;
673 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
674 programStats.score = 0;
675 programStats.got_only_move = 0;
676 programStats.got_fail = 0;
677 programStats.line_is_book = 0;
682 { // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
683 if (appData.firstPlaysBlack) {
684 first.twoMachinesColor = "black\n";
685 second.twoMachinesColor = "white\n";
687 first.twoMachinesColor = "white\n";
688 second.twoMachinesColor = "black\n";
691 first.other = &second;
692 second.other = &first;
695 if(appData.timeOddsMode) {
696 norm = appData.timeOdds[0];
697 if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
699 first.timeOdds = appData.timeOdds[0]/norm;
700 second.timeOdds = appData.timeOdds[1]/norm;
703 if(programVersion) free(programVersion);
704 if (appData.noChessProgram) {
705 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
706 sprintf(programVersion, "%s", PACKAGE_STRING);
708 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
709 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
710 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
715 UnloadEngine(ChessProgramState *cps)
717 /* Kill off first chess program */
718 if (cps->isr != NULL)
719 RemoveInputSource(cps->isr);
722 if (cps->pr != NoProc) {
724 DoSleep( appData.delayBeforeQuit );
725 SendToProgram("quit\n", cps);
726 DoSleep( appData.delayAfterQuit );
727 DestroyChildProcess(cps->pr, cps->useSigterm);
730 if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
734 ClearOptions(ChessProgramState *cps)
737 cps->nrOptions = cps->comboCnt = 0;
738 for(i=0; i<MAX_OPTIONS; i++) {
739 cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
740 cps->option[i].textValue = 0;
744 char *engineNames[] = {
750 InitEngine(ChessProgramState *cps, int n)
751 { // [HGM] all engine initialiation put in a function that does one engine
755 cps->which = engineNames[n];
756 cps->maybeThinking = FALSE;
760 cps->sendDrawOffers = 1;
762 cps->program = appData.chessProgram[n];
763 cps->host = appData.host[n];
764 cps->dir = appData.directory[n];
765 cps->initString = appData.engInitString[n];
766 cps->computerString = appData.computerString[n];
767 cps->useSigint = TRUE;
768 cps->useSigterm = TRUE;
769 cps->reuse = appData.reuse[n];
770 cps->nps = appData.NPS[n]; // [HGM] nps: copy nodes per second
771 cps->useSetboard = FALSE;
773 cps->usePing = FALSE;
776 cps->usePlayother = FALSE;
777 cps->useColors = TRUE;
778 cps->useUsermove = FALSE;
779 cps->sendICS = FALSE;
780 cps->sendName = appData.icsActive;
781 cps->sdKludge = FALSE;
782 cps->stKludge = FALSE;
783 TidyProgramName(cps->program, cps->host, cps->tidy);
785 safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
786 cps->analysisSupport = 2; /* detect */
787 cps->analyzing = FALSE;
788 cps->initDone = FALSE;
790 /* New features added by Tord: */
791 cps->useFEN960 = FALSE;
792 cps->useOOCastle = TRUE;
793 /* End of new features added by Tord. */
794 cps->fenOverride = appData.fenOverride[n];
796 /* [HGM] time odds: set factor for each machine */
797 cps->timeOdds = appData.timeOdds[n];
799 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
800 cps->accumulateTC = appData.accumulateTC[n];
801 cps->maxNrOfSessions = 1;
806 cps->supportsNPS = UNKNOWN;
807 cps->memSize = FALSE;
808 cps->maxCores = FALSE;
809 cps->egtFormats[0] = NULLCHAR;
812 cps->optionSettings = appData.engOptions[n];
814 cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
815 cps->isUCI = appData.isUCI[n]; /* [AS] */
816 cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
818 if (appData.protocolVersion[n] > PROTOVER
819 || appData.protocolVersion[n] < 1)
824 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
825 appData.protocolVersion[n]);
826 if( (len > MSG_SIZ) && appData.debugMode )
827 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
829 DisplayFatalError(buf, 0, 2);
833 cps->protocolVersion = appData.protocolVersion[n];
836 InitEngineUCI( installDir, cps ); // [HGM] moved here from winboard.c, to make available in xboard
839 ChessProgramState *savCps;
845 if(WaitForEngine(savCps, LoadEngine)) return;
846 CommonEngineInit(); // recalculate time odds
847 if(gameInfo.variant != StringToVariant(appData.variant)) {
848 // we changed variant when loading the engine; this forces us to reset
849 Reset(TRUE, savCps != &first);
850 EditGameEvent(); // for consistency with other path, as Reset changes mode
852 InitChessProgram(savCps, FALSE);
853 SendToProgram("force\n", savCps);
854 DisplayMessage("", "");
855 if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
856 for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
862 ReplaceEngine(ChessProgramState *cps, int n)
866 appData.noChessProgram = FALSE;
867 appData.clockMode = TRUE;
870 if(n) return; // only startup first engine immediately; second can wait
871 savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
875 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
876 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
878 static char resetOptions[] =
879 "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
880 "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
881 "-firstOptions \"\" -firstNPS -1 -fn \"\"";
884 Load(ChessProgramState *cps, int i)
886 char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
887 if(engineLine && engineLine[0]) { // an engine was selected from the combo box
888 snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
889 SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
890 ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
891 ParseArgsFromString(buf);
893 ReplaceEngine(cps, i);
897 while(q = strchr(p, SLASH)) p = q+1;
898 if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
899 if(engineDir[0] != NULLCHAR)
900 appData.directory[i] = engineDir;
901 else if(p != engineName) { // derive directory from engine path, when not given
903 appData.directory[i] = strdup(engineName);
905 } else appData.directory[i] = ".";
907 if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
908 snprintf(command, MSG_SIZ, "%s %s", p, params);
911 appData.chessProgram[i] = strdup(p);
912 appData.isUCI[i] = isUCI;
913 appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
914 appData.hasOwnBookUCI[i] = hasBook;
915 if(!nickName[0]) useNick = FALSE;
916 if(useNick) ASSIGN(appData.pgnName[i], nickName);
920 q = firstChessProgramNames;
921 if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
922 quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
923 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
924 quote, p, quote, appData.directory[i],
925 useNick ? " -fn \"" : "",
926 useNick ? nickName : "",
928 v1 ? " -firstProtocolVersion 1" : "",
929 hasBook ? "" : " -fNoOwnBookUCI",
930 isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
931 storeVariant ? " -variant " : "",
932 storeVariant ? VariantName(gameInfo.variant) : "");
933 firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
934 snprintf(firstChessProgramNames, len, "%s%s", q, buf);
937 ReplaceEngine(cps, i);
943 int matched, min, sec;
945 * Parse timeControl resource
947 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
948 appData.movesPerSession)) {
950 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
951 DisplayFatalError(buf, 0, 2);
955 * Parse searchTime resource
957 if (*appData.searchTime != NULLCHAR) {
958 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
960 searchTime = min * 60;
961 } else if (matched == 2) {
962 searchTime = min * 60 + sec;
965 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
966 DisplayFatalError(buf, 0, 2);
975 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
976 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
978 GetTimeMark(&programStartTime);
979 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
980 appData.seedBase = random() + (random()<<15);
981 pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
984 programStats.ok_to_send = 1;
985 programStats.seen_stat = 0;
988 * Initialize game list
994 * Internet chess server status
996 if (appData.icsActive) {
997 appData.matchMode = FALSE;
998 appData.matchGames = 0;
1000 appData.noChessProgram = !appData.zippyPlay;
1002 appData.zippyPlay = FALSE;
1003 appData.zippyTalk = FALSE;
1004 appData.noChessProgram = TRUE;
1006 if (*appData.icsHelper != NULLCHAR) {
1007 appData.useTelnet = TRUE;
1008 appData.telnetProgram = appData.icsHelper;
1011 appData.zippyTalk = appData.zippyPlay = FALSE;
1014 /* [AS] Initialize pv info list [HGM] and game state */
1018 for( i=0; i<=framePtr; i++ ) {
1019 pvInfoList[i].depth = -1;
1020 boards[i][EP_STATUS] = EP_NONE;
1021 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1027 /* [AS] Adjudication threshold */
1028 adjudicateLossThreshold = appData.adjudicateLossThreshold;
1030 InitEngine(&first, 0);
1031 InitEngine(&second, 1);
1034 pairing.which = "pairing"; // pairing engine
1035 pairing.pr = NoProc;
1037 pairing.program = appData.pairingEngine;
1038 pairing.host = "localhost";
1041 if (appData.icsActive) {
1042 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
1043 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1044 appData.clockMode = FALSE;
1045 first.sendTime = second.sendTime = 0;
1049 /* Override some settings from environment variables, for backward
1050 compatibility. Unfortunately it's not feasible to have the env
1051 vars just set defaults, at least in xboard. Ugh.
1053 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1058 if (!appData.icsActive) {
1062 /* Check for variants that are supported only in ICS mode,
1063 or not at all. Some that are accepted here nevertheless
1064 have bugs; see comments below.
1066 VariantClass variant = StringToVariant(appData.variant);
1068 case VariantBughouse: /* need four players and two boards */
1069 case VariantKriegspiel: /* need to hide pieces and move details */
1070 /* case VariantFischeRandom: (Fabien: moved below) */
1071 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1072 if( (len > MSG_SIZ) && appData.debugMode )
1073 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1075 DisplayFatalError(buf, 0, 2);
1078 case VariantUnknown:
1079 case VariantLoadable:
1089 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1090 if( (len > MSG_SIZ) && appData.debugMode )
1091 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1093 DisplayFatalError(buf, 0, 2);
1096 case VariantXiangqi: /* [HGM] repetition rules not implemented */
1097 case VariantFairy: /* [HGM] TestLegality definitely off! */
1098 case VariantGothic: /* [HGM] should work */
1099 case VariantCapablanca: /* [HGM] should work */
1100 case VariantCourier: /* [HGM] initial forced moves not implemented */
1101 case VariantShogi: /* [HGM] could still mate with pawn drop */
1102 case VariantKnightmate: /* [HGM] should work */
1103 case VariantCylinder: /* [HGM] untested */
1104 case VariantFalcon: /* [HGM] untested */
1105 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1106 offboard interposition not understood */
1107 case VariantNormal: /* definitely works! */
1108 case VariantWildCastle: /* pieces not automatically shuffled */
1109 case VariantNoCastle: /* pieces not automatically shuffled */
1110 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1111 case VariantLosers: /* should work except for win condition,
1112 and doesn't know captures are mandatory */
1113 case VariantSuicide: /* should work except for win condition,
1114 and doesn't know captures are mandatory */
1115 case VariantGiveaway: /* should work except for win condition,
1116 and doesn't know captures are mandatory */
1117 case VariantTwoKings: /* should work */
1118 case VariantAtomic: /* should work except for win condition */
1119 case Variant3Check: /* should work except for win condition */
1120 case VariantShatranj: /* should work except for all win conditions */
1121 case VariantMakruk: /* should work except for draw countdown */
1122 case VariantBerolina: /* might work if TestLegality is off */
1123 case VariantCapaRandom: /* should work */
1124 case VariantJanus: /* should work */
1125 case VariantSuper: /* experimental */
1126 case VariantGreat: /* experimental, requires legality testing to be off */
1127 case VariantSChess: /* S-Chess, should work */
1128 case VariantGrand: /* should work */
1129 case VariantSpartan: /* should work */
1136 int NextIntegerFromString( char ** str, long * value )
1141 while( *s == ' ' || *s == '\t' ) {
1147 if( *s >= '0' && *s <= '9' ) {
1148 while( *s >= '0' && *s <= '9' ) {
1149 *value = *value * 10 + (*s - '0');
1161 int NextTimeControlFromString( char ** str, long * value )
1164 int result = NextIntegerFromString( str, &temp );
1167 *value = temp * 60; /* Minutes */
1168 if( **str == ':' ) {
1170 result = NextIntegerFromString( str, &temp );
1171 *value += temp; /* Seconds */
1178 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1179 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1180 int result = -1, type = 0; long temp, temp2;
1182 if(**str != ':') return -1; // old params remain in force!
1184 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1185 if( NextIntegerFromString( str, &temp ) ) return -1;
1186 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1189 /* time only: incremental or sudden-death time control */
1190 if(**str == '+') { /* increment follows; read it */
1192 if(**str == '!') type = *(*str)++; // Bronstein TC
1193 if(result = NextIntegerFromString( str, &temp2)) return -1;
1194 *inc = temp2 * 1000;
1195 if(**str == '.') { // read fraction of increment
1196 char *start = ++(*str);
1197 if(result = NextIntegerFromString( str, &temp2)) return -1;
1199 while(start++ < *str) temp2 /= 10;
1203 *moves = 0; *tc = temp * 1000; *incType = type;
1207 (*str)++; /* classical time control */
1208 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1219 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1220 { /* [HGM] get time to add from the multi-session time-control string */
1221 int incType, moves=1; /* kludge to force reading of first session */
1222 long time, increment;
1225 if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1226 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1228 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1229 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1230 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1231 if(movenr == -1) return time; /* last move before new session */
1232 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1233 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1234 if(!moves) return increment; /* current session is incremental */
1235 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1236 } while(movenr >= -1); /* try again for next session */
1238 return 0; // no new time quota on this move
1242 ParseTimeControl(tc, ti, mps)
1249 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1252 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1253 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1254 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1258 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1260 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1263 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1265 snprintf(buf, MSG_SIZ, ":%s", mytc);
1267 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1269 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1274 /* Parse second time control */
1277 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1285 timeControl_2 = tc2 * 1000;
1295 timeControl = tc1 * 1000;
1298 timeIncrement = ti * 1000; /* convert to ms */
1299 movesPerSession = 0;
1302 movesPerSession = mps;
1310 if (appData.debugMode) {
1311 fprintf(debugFP, "%s\n", programVersion);
1314 set_cont_sequence(appData.wrapContSeq);
1315 if (appData.matchGames > 0) {
1316 appData.matchMode = TRUE;
1317 } else if (appData.matchMode) {
1318 appData.matchGames = 1;
1320 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1321 appData.matchGames = appData.sameColorGames;
1322 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1323 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1324 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1327 if (appData.noChessProgram || first.protocolVersion == 1) {
1330 /* kludge: allow timeout for initial "feature" commands */
1332 DisplayMessage("", _("Starting chess program"));
1333 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1338 CalculateIndex(int index, int gameNr)
1339 { // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1341 if(index > 0) return index; // fixed nmber
1342 if(index == 0) return 1;
1343 res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1344 if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1349 LoadGameOrPosition(int gameNr)
1350 { // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1351 if (*appData.loadGameFile != NULLCHAR) {
1352 if (!LoadGameFromFile(appData.loadGameFile,
1353 CalculateIndex(appData.loadGameIndex, gameNr),
1354 appData.loadGameFile, FALSE)) {
1355 DisplayFatalError(_("Bad game file"), 0, 1);
1358 } else if (*appData.loadPositionFile != NULLCHAR) {
1359 if (!LoadPositionFromFile(appData.loadPositionFile,
1360 CalculateIndex(appData.loadPositionIndex, gameNr),
1361 appData.loadPositionFile)) {
1362 DisplayFatalError(_("Bad position file"), 0, 1);
1370 ReserveGame(int gameNr, char resChar)
1372 FILE *tf = fopen(appData.tourneyFile, "r+");
1373 char *p, *q, c, buf[MSG_SIZ];
1374 if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1375 safeStrCpy(buf, lastMsg, MSG_SIZ);
1376 DisplayMessage(_("Pick new game"), "");
1377 flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1378 ParseArgsFromFile(tf);
1379 p = q = appData.results;
1380 if(appData.debugMode) {
1381 char *r = appData.participants;
1382 fprintf(debugFP, "results = '%s'\n", p);
1383 while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1384 fprintf(debugFP, "\n");
1386 while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1388 q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1389 safeStrCpy(q, p, strlen(p) + 2);
1390 if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1391 if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1392 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1393 if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1396 fseek(tf, -(strlen(p)+4), SEEK_END);
1398 if(c != '"') // depending on DOS or Unix line endings we can be one off
1399 fseek(tf, -(strlen(p)+2), SEEK_END);
1400 else fseek(tf, -(strlen(p)+3), SEEK_END);
1401 fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1402 DisplayMessage(buf, "");
1403 free(p); appData.results = q;
1404 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1405 (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1406 UnloadEngine(&first); // next game belongs to other pairing;
1407 UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1412 MatchEvent(int mode)
1413 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1415 if(matchMode) { // already in match mode: switch it off
1417 if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1420 // if(gameMode != BeginningOfGame) {
1421 // DisplayError(_("You can only start a match from the initial position."), 0);
1425 if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1426 /* Set up machine vs. machine match */
1428 NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1429 if(appData.tourneyFile[0]) {
1431 if(nextGame > appData.matchGames) {
1433 if(strchr(appData.results, '*') == NULL) {
1435 appData.tourneyCycles++;
1436 if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1438 NextTourneyGame(-1, &dummy);
1440 if(nextGame <= appData.matchGames) {
1441 DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1443 ScheduleDelayedEvent(NextMatchGame, 10000);
1448 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1449 DisplayError(buf, 0);
1450 appData.tourneyFile[0] = 0;
1454 if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically
1455 DisplayFatalError(_("Can't have a match with no chess programs"),
1460 matchGame = roundNr = 1;
1461 first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1466 InitBackEnd3 P((void))
1468 GameMode initialMode;
1472 InitChessProgram(&first, startedFromSetupPosition);
1474 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1475 free(programVersion);
1476 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1477 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1480 if (appData.icsActive) {
1482 /* [DM] Make a console window if needed [HGM] merged ifs */
1488 if (*appData.icsCommPort != NULLCHAR)
1489 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1490 appData.icsCommPort);
1492 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1493 appData.icsHost, appData.icsPort);
1495 if( (len > MSG_SIZ) && appData.debugMode )
1496 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1498 DisplayFatalError(buf, err, 1);
1503 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1505 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1506 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1507 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1508 } else if (appData.noChessProgram) {
1514 if (*appData.cmailGameName != NULLCHAR) {
1516 OpenLoopback(&cmailPR);
1518 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1522 DisplayMessage("", "");
1523 if (StrCaseCmp(appData.initialMode, "") == 0) {
1524 initialMode = BeginningOfGame;
1525 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1526 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1527 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1528 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1531 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1532 initialMode = TwoMachinesPlay;
1533 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1534 initialMode = AnalyzeFile;
1535 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1536 initialMode = AnalyzeMode;
1537 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1538 initialMode = MachinePlaysWhite;
1539 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1540 initialMode = MachinePlaysBlack;
1541 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1542 initialMode = EditGame;
1543 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1544 initialMode = EditPosition;
1545 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1546 initialMode = Training;
1548 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1549 if( (len > MSG_SIZ) && appData.debugMode )
1550 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1552 DisplayFatalError(buf, 0, 2);
1556 if (appData.matchMode) {
1557 if(appData.tourneyFile[0]) { // start tourney from command line
1559 if(f = fopen(appData.tourneyFile, "r")) {
1560 ParseArgsFromFile(f); // make sure tourney parmeters re known
1562 appData.clockMode = TRUE;
1564 } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1567 } else if (*appData.cmailGameName != NULLCHAR) {
1568 /* Set up cmail mode */
1569 ReloadCmailMsgEvent(TRUE);
1571 /* Set up other modes */
1572 if (initialMode == AnalyzeFile) {
1573 if (*appData.loadGameFile == NULLCHAR) {
1574 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1578 if (*appData.loadGameFile != NULLCHAR) {
1579 (void) LoadGameFromFile(appData.loadGameFile,
1580 appData.loadGameIndex,
1581 appData.loadGameFile, TRUE);
1582 } else if (*appData.loadPositionFile != NULLCHAR) {
1583 (void) LoadPositionFromFile(appData.loadPositionFile,
1584 appData.loadPositionIndex,
1585 appData.loadPositionFile);
1586 /* [HGM] try to make self-starting even after FEN load */
1587 /* to allow automatic setup of fairy variants with wtm */
1588 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1589 gameMode = BeginningOfGame;
1590 setboardSpoiledMachineBlack = 1;
1592 /* [HGM] loadPos: make that every new game uses the setup */
1593 /* from file as long as we do not switch variant */
1594 if(!blackPlaysFirst) {
1595 startedFromPositionFile = TRUE;
1596 CopyBoard(filePosition, boards[0]);
1599 if (initialMode == AnalyzeMode) {
1600 if (appData.noChessProgram) {
1601 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1604 if (appData.icsActive) {
1605 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1609 } else if (initialMode == AnalyzeFile) {
1610 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1611 ShowThinkingEvent();
1613 AnalysisPeriodicEvent(1);
1614 } else if (initialMode == MachinePlaysWhite) {
1615 if (appData.noChessProgram) {
1616 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1620 if (appData.icsActive) {
1621 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1625 MachineWhiteEvent();
1626 } else if (initialMode == MachinePlaysBlack) {
1627 if (appData.noChessProgram) {
1628 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1632 if (appData.icsActive) {
1633 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1637 MachineBlackEvent();
1638 } else if (initialMode == TwoMachinesPlay) {
1639 if (appData.noChessProgram) {
1640 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1644 if (appData.icsActive) {
1645 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1650 } else if (initialMode == EditGame) {
1652 } else if (initialMode == EditPosition) {
1653 EditPositionEvent();
1654 } else if (initialMode == Training) {
1655 if (*appData.loadGameFile == NULLCHAR) {
1656 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1665 HistorySet( char movelist[][2*MOVE_LEN], int first, int last, int current )
1667 DisplayBook(current+1);
1669 MoveHistorySet( movelist, first, last, current, pvInfoList );
1671 EvalGraphSet( first, last, current, pvInfoList );
1673 MakeEngineOutputTitle();
1677 * Establish will establish a contact to a remote host.port.
1678 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1679 * used to talk to the host.
1680 * Returns 0 if okay, error code if not.
1687 if (*appData.icsCommPort != NULLCHAR) {
1688 /* Talk to the host through a serial comm port */
1689 return OpenCommPort(appData.icsCommPort, &icsPR);
1691 } else if (*appData.gateway != NULLCHAR) {
1692 if (*appData.remoteShell == NULLCHAR) {
1693 /* Use the rcmd protocol to run telnet program on a gateway host */
1694 snprintf(buf, sizeof(buf), "%s %s %s",
1695 appData.telnetProgram, appData.icsHost, appData.icsPort);
1696 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1699 /* Use the rsh program to run telnet program on a gateway host */
1700 if (*appData.remoteUser == NULLCHAR) {
1701 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1702 appData.gateway, appData.telnetProgram,
1703 appData.icsHost, appData.icsPort);
1705 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1706 appData.remoteShell, appData.gateway,
1707 appData.remoteUser, appData.telnetProgram,
1708 appData.icsHost, appData.icsPort);
1710 return StartChildProcess(buf, "", &icsPR);
1713 } else if (appData.useTelnet) {
1714 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1717 /* TCP socket interface differs somewhat between
1718 Unix and NT; handle details in the front end.
1720 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1724 void EscapeExpand(char *p, char *q)
1725 { // [HGM] initstring: routine to shape up string arguments
1726 while(*p++ = *q++) if(p[-1] == '\\')
1728 case 'n': p[-1] = '\n'; break;
1729 case 'r': p[-1] = '\r'; break;
1730 case 't': p[-1] = '\t'; break;
1731 case '\\': p[-1] = '\\'; break;
1732 case 0: *p = 0; return;
1733 default: p[-1] = q[-1]; break;
1738 show_bytes(fp, buf, count)
1744 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1745 fprintf(fp, "\\%03o", *buf & 0xff);
1754 /* Returns an errno value */
1756 OutputMaybeTelnet(pr, message, count, outError)
1762 char buf[8192], *p, *q, *buflim;
1763 int left, newcount, outcount;
1765 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1766 *appData.gateway != NULLCHAR) {
1767 if (appData.debugMode) {
1768 fprintf(debugFP, ">ICS: ");
1769 show_bytes(debugFP, message, count);
1770 fprintf(debugFP, "\n");
1772 return OutputToProcess(pr, message, count, outError);
1775 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1782 if (appData.debugMode) {
1783 fprintf(debugFP, ">ICS: ");
1784 show_bytes(debugFP, buf, newcount);
1785 fprintf(debugFP, "\n");
1787 outcount = OutputToProcess(pr, buf, newcount, outError);
1788 if (outcount < newcount) return -1; /* to be sure */
1795 } else if (((unsigned char) *p) == TN_IAC) {
1796 *q++ = (char) TN_IAC;
1803 if (appData.debugMode) {
1804 fprintf(debugFP, ">ICS: ");
1805 show_bytes(debugFP, buf, newcount);
1806 fprintf(debugFP, "\n");
1808 outcount = OutputToProcess(pr, buf, newcount, outError);
1809 if (outcount < newcount) return -1; /* to be sure */
1814 read_from_player(isr, closure, message, count, error)
1821 int outError, outCount;
1822 static int gotEof = 0;
1824 /* Pass data read from player on to ICS */
1827 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1828 if (outCount < count) {
1829 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1831 } else if (count < 0) {
1832 RemoveInputSource(isr);
1833 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1834 } else if (gotEof++ > 0) {
1835 RemoveInputSource(isr);
1836 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1842 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1843 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1844 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1845 SendToICS("date\n");
1846 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1849 /* added routine for printf style output to ics */
1850 void ics_printf(char *format, ...)
1852 char buffer[MSG_SIZ];
1855 va_start(args, format);
1856 vsnprintf(buffer, sizeof(buffer), format, args);
1857 buffer[sizeof(buffer)-1] = '\0';
1866 int count, outCount, outError;
1868 if (icsPR == NoProc) return;
1871 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1872 if (outCount < count) {
1873 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1877 /* This is used for sending logon scripts to the ICS. Sending
1878 without a delay causes problems when using timestamp on ICC
1879 (at least on my machine). */
1881 SendToICSDelayed(s,msdelay)
1885 int count, outCount, outError;
1887 if (icsPR == NoProc) return;
1890 if (appData.debugMode) {
1891 fprintf(debugFP, ">ICS: ");
1892 show_bytes(debugFP, s, count);
1893 fprintf(debugFP, "\n");
1895 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1897 if (outCount < count) {
1898 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1903 /* Remove all highlighting escape sequences in s
1904 Also deletes any suffix starting with '('
1907 StripHighlightAndTitle(s)
1910 static char retbuf[MSG_SIZ];
1913 while (*s != NULLCHAR) {
1914 while (*s == '\033') {
1915 while (*s != NULLCHAR && !isalpha(*s)) s++;
1916 if (*s != NULLCHAR) s++;
1918 while (*s != NULLCHAR && *s != '\033') {
1919 if (*s == '(' || *s == '[') {
1930 /* Remove all highlighting escape sequences in s */
1935 static char retbuf[MSG_SIZ];
1938 while (*s != NULLCHAR) {
1939 while (*s == '\033') {
1940 while (*s != NULLCHAR && !isalpha(*s)) s++;
1941 if (*s != NULLCHAR) s++;
1943 while (*s != NULLCHAR && *s != '\033') {
1951 char *variantNames[] = VARIANT_NAMES;
1956 return variantNames[v];
1960 /* Identify a variant from the strings the chess servers use or the
1961 PGN Variant tag names we use. */
1968 VariantClass v = VariantNormal;
1969 int i, found = FALSE;
1975 /* [HGM] skip over optional board-size prefixes */
1976 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1977 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1978 while( *e++ != '_');
1981 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1985 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1986 if (StrCaseStr(e, variantNames[i])) {
1987 v = (VariantClass) i;
1994 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1995 || StrCaseStr(e, "wild/fr")
1996 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1997 v = VariantFischeRandom;
1998 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1999 (i = 1, p = StrCaseStr(e, "w"))) {
2001 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2008 case 0: /* FICS only, actually */
2010 /* Castling legal even if K starts on d-file */
2011 v = VariantWildCastle;
2016 /* Castling illegal even if K & R happen to start in
2017 normal positions. */
2018 v = VariantNoCastle;
2031 /* Castling legal iff K & R start in normal positions */
2037 /* Special wilds for position setup; unclear what to do here */
2038 v = VariantLoadable;
2041 /* Bizarre ICC game */
2042 v = VariantTwoKings;
2045 v = VariantKriegspiel;
2051 v = VariantFischeRandom;
2054 v = VariantCrazyhouse;
2057 v = VariantBughouse;
2063 /* Not quite the same as FICS suicide! */
2064 v = VariantGiveaway;
2070 v = VariantShatranj;
2073 /* Temporary names for future ICC types. The name *will* change in
2074 the next xboard/WinBoard release after ICC defines it. */
2112 v = VariantCapablanca;
2115 v = VariantKnightmate;
2121 v = VariantCylinder;
2127 v = VariantCapaRandom;
2130 v = VariantBerolina;
2142 /* Found "wild" or "w" in the string but no number;
2143 must assume it's normal chess. */
2147 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2148 if( (len > MSG_SIZ) && appData.debugMode )
2149 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2151 DisplayError(buf, 0);
2157 if (appData.debugMode) {
2158 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2159 e, wnum, VariantName(v));
2164 static int leftover_start = 0, leftover_len = 0;
2165 char star_match[STAR_MATCH_N][MSG_SIZ];
2167 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2168 advance *index beyond it, and set leftover_start to the new value of
2169 *index; else return FALSE. If pattern contains the character '*', it
2170 matches any sequence of characters not containing '\r', '\n', or the
2171 character following the '*' (if any), and the matched sequence(s) are
2172 copied into star_match.
2175 looking_at(buf, index, pattern)
2180 char *bufp = &buf[*index], *patternp = pattern;
2182 char *matchp = star_match[0];
2185 if (*patternp == NULLCHAR) {
2186 *index = leftover_start = bufp - buf;
2190 if (*bufp == NULLCHAR) return FALSE;
2191 if (*patternp == '*') {
2192 if (*bufp == *(patternp + 1)) {
2194 matchp = star_match[++star_count];
2198 } else if (*bufp == '\n' || *bufp == '\r') {
2200 if (*patternp == NULLCHAR)
2205 *matchp++ = *bufp++;
2209 if (*patternp != *bufp) return FALSE;
2216 SendToPlayer(data, length)
2220 int error, outCount;
2221 outCount = OutputToProcess(NoProc, data, length, &error);
2222 if (outCount < length) {
2223 DisplayFatalError(_("Error writing to display"), error, 1);
2228 PackHolding(packed, holding)
2240 switch (runlength) {
2251 sprintf(q, "%d", runlength);
2263 /* Telnet protocol requests from the front end */
2265 TelnetRequest(ddww, option)
2266 unsigned char ddww, option;
2268 unsigned char msg[3];
2269 int outCount, outError;
2271 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2273 if (appData.debugMode) {
2274 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2290 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2299 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2302 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2307 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2309 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2316 if (!appData.icsActive) return;
2317 TelnetRequest(TN_DO, TN_ECHO);
2323 if (!appData.icsActive) return;
2324 TelnetRequest(TN_DONT, TN_ECHO);
2328 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2330 /* put the holdings sent to us by the server on the board holdings area */
2331 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2335 if(gameInfo.holdingsWidth < 2) return;
2336 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2337 return; // prevent overwriting by pre-board holdings
2339 if( (int)lowestPiece >= BlackPawn ) {
2342 holdingsStartRow = BOARD_HEIGHT-1;
2345 holdingsColumn = BOARD_WIDTH-1;
2346 countsColumn = BOARD_WIDTH-2;
2347 holdingsStartRow = 0;
2351 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2352 board[i][holdingsColumn] = EmptySquare;
2353 board[i][countsColumn] = (ChessSquare) 0;
2355 while( (p=*holdings++) != NULLCHAR ) {
2356 piece = CharToPiece( ToUpper(p) );
2357 if(piece == EmptySquare) continue;
2358 /*j = (int) piece - (int) WhitePawn;*/
2359 j = PieceToNumber(piece);
2360 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2361 if(j < 0) continue; /* should not happen */
2362 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2363 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2364 board[holdingsStartRow+j*direction][countsColumn]++;
2370 VariantSwitch(Board board, VariantClass newVariant)
2372 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2373 static Board oldBoard;
2375 startedFromPositionFile = FALSE;
2376 if(gameInfo.variant == newVariant) return;
2378 /* [HGM] This routine is called each time an assignment is made to
2379 * gameInfo.variant during a game, to make sure the board sizes
2380 * are set to match the new variant. If that means adding or deleting
2381 * holdings, we shift the playing board accordingly
2382 * This kludge is needed because in ICS observe mode, we get boards
2383 * of an ongoing game without knowing the variant, and learn about the
2384 * latter only later. This can be because of the move list we requested,
2385 * in which case the game history is refilled from the beginning anyway,
2386 * but also when receiving holdings of a crazyhouse game. In the latter
2387 * case we want to add those holdings to the already received position.
2391 if (appData.debugMode) {
2392 fprintf(debugFP, "Switch board from %s to %s\n",
2393 VariantName(gameInfo.variant), VariantName(newVariant));
2394 setbuf(debugFP, NULL);
2396 shuffleOpenings = 0; /* [HGM] shuffle */
2397 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2401 newWidth = 9; newHeight = 9;
2402 gameInfo.holdingsSize = 7;
2403 case VariantBughouse:
2404 case VariantCrazyhouse:
2405 newHoldingsWidth = 2; break;
2409 newHoldingsWidth = 2;
2410 gameInfo.holdingsSize = 8;
2413 case VariantCapablanca:
2414 case VariantCapaRandom:
2417 newHoldingsWidth = gameInfo.holdingsSize = 0;
2420 if(newWidth != gameInfo.boardWidth ||
2421 newHeight != gameInfo.boardHeight ||
2422 newHoldingsWidth != gameInfo.holdingsWidth ) {
2424 /* shift position to new playing area, if needed */
2425 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2426 for(i=0; i<BOARD_HEIGHT; i++)
2427 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2428 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2430 for(i=0; i<newHeight; i++) {
2431 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2432 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2434 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2435 for(i=0; i<BOARD_HEIGHT; i++)
2436 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2437 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2440 gameInfo.boardWidth = newWidth;
2441 gameInfo.boardHeight = newHeight;
2442 gameInfo.holdingsWidth = newHoldingsWidth;
2443 gameInfo.variant = newVariant;
2444 InitDrawingSizes(-2, 0);
2445 } else gameInfo.variant = newVariant;
2446 CopyBoard(oldBoard, board); // remember correctly formatted board
2447 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2448 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2451 static int loggedOn = FALSE;
2453 /*-- Game start info cache: --*/
2455 char gs_kind[MSG_SIZ];
2456 static char player1Name[128] = "";
2457 static char player2Name[128] = "";
2458 static char cont_seq[] = "\n\\ ";
2459 static int player1Rating = -1;
2460 static int player2Rating = -1;
2461 /*----------------------------*/
2463 ColorClass curColor = ColorNormal;
2464 int suppressKibitz = 0;
2467 Boolean soughtPending = FALSE;
2468 Boolean seekGraphUp;
2469 #define MAX_SEEK_ADS 200
2471 char *seekAdList[MAX_SEEK_ADS];
2472 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2473 float tcList[MAX_SEEK_ADS];
2474 char colorList[MAX_SEEK_ADS];
2475 int nrOfSeekAds = 0;
2476 int minRating = 1010, maxRating = 2800;
2477 int hMargin = 10, vMargin = 20, h, w;
2478 extern int squareSize, lineGap;
2483 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2484 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2485 if(r < minRating+100 && r >=0 ) r = minRating+100;
2486 if(r > maxRating) r = maxRating;
2487 if(tc < 1.) tc = 1.;
2488 if(tc > 95.) tc = 95.;
2489 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2490 y = ((double)r - minRating)/(maxRating - minRating)
2491 * (h-vMargin-squareSize/8-1) + vMargin;
2492 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2493 if(strstr(seekAdList[i], " u ")) color = 1;
2494 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2495 !strstr(seekAdList[i], "bullet") &&
2496 !strstr(seekAdList[i], "blitz") &&
2497 !strstr(seekAdList[i], "standard") ) color = 2;
2498 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2499 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2503 AddAd(char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2505 char buf[MSG_SIZ], *ext = "";
2506 VariantClass v = StringToVariant(type);
2507 if(strstr(type, "wild")) {
2508 ext = type + 4; // append wild number
2509 if(v == VariantFischeRandom) type = "chess960"; else
2510 if(v == VariantLoadable) type = "setup"; else
2511 type = VariantName(v);
2513 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2514 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2515 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2516 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2517 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2518 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2519 seekNrList[nrOfSeekAds] = nr;
2520 zList[nrOfSeekAds] = 0;
2521 seekAdList[nrOfSeekAds++] = StrSave(buf);
2522 if(plot) PlotSeekAd(nrOfSeekAds-1);
2529 int x = xList[i], y = yList[i], d=squareSize/4, k;
2530 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2531 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2532 // now replot every dot that overlapped
2533 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2534 int xx = xList[k], yy = yList[k];
2535 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2536 DrawSeekDot(xx, yy, colorList[k]);
2541 RemoveSeekAd(int nr)
2544 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2546 if(seekAdList[i]) free(seekAdList[i]);
2547 seekAdList[i] = seekAdList[--nrOfSeekAds];
2548 seekNrList[i] = seekNrList[nrOfSeekAds];
2549 ratingList[i] = ratingList[nrOfSeekAds];
2550 colorList[i] = colorList[nrOfSeekAds];
2551 tcList[i] = tcList[nrOfSeekAds];
2552 xList[i] = xList[nrOfSeekAds];
2553 yList[i] = yList[nrOfSeekAds];
2554 zList[i] = zList[nrOfSeekAds];
2555 seekAdList[nrOfSeekAds] = NULL;
2561 MatchSoughtLine(char *line)
2563 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2564 int nr, base, inc, u=0; char dummy;
2566 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2567 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2569 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2570 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2571 // match: compact and save the line
2572 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2582 if(!seekGraphUp) return FALSE;
2583 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2584 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2586 DrawSeekBackground(0, 0, w, h);
2587 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2588 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2589 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2590 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2592 DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2595 snprintf(buf, MSG_SIZ, "%d", i);
2596 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2599 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2600 for(i=1; i<100; i+=(i<10?1:5)) {
2601 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2602 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2603 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2605 snprintf(buf, MSG_SIZ, "%d", i);
2606 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2609 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2613 int SeekGraphClick(ClickType click, int x, int y, int moving)
2615 static int lastDown = 0, displayed = 0, lastSecond;
2616 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2617 if(click == Release || moving) return FALSE;
2619 soughtPending = TRUE;
2620 SendToICS(ics_prefix);
2621 SendToICS("sought\n"); // should this be "sought all"?
2622 } else { // issue challenge based on clicked ad
2623 int dist = 10000; int i, closest = 0, second = 0;
2624 for(i=0; i<nrOfSeekAds; i++) {
2625 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2626 if(d < dist) { dist = d; closest = i; }
2627 second += (d - zList[i] < 120); // count in-range ads
2628 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2632 second = (second > 1);
2633 if(displayed != closest || second != lastSecond) {
2634 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2635 lastSecond = second; displayed = closest;
2637 if(click == Press) {
2638 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2641 } // on press 'hit', only show info
2642 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2643 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2644 SendToICS(ics_prefix);
2646 return TRUE; // let incoming board of started game pop down the graph
2647 } else if(click == Release) { // release 'miss' is ignored
2648 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2649 if(moving == 2) { // right up-click
2650 nrOfSeekAds = 0; // refresh graph
2651 soughtPending = TRUE;
2652 SendToICS(ics_prefix);
2653 SendToICS("sought\n"); // should this be "sought all"?
2656 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2657 // press miss or release hit 'pop down' seek graph
2658 seekGraphUp = FALSE;
2659 DrawPosition(TRUE, NULL);
2665 read_from_ics(isr, closure, data, count, error)
2672 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2673 #define STARTED_NONE 0
2674 #define STARTED_MOVES 1
2675 #define STARTED_BOARD 2
2676 #define STARTED_OBSERVE 3
2677 #define STARTED_HOLDINGS 4
2678 #define STARTED_CHATTER 5
2679 #define STARTED_COMMENT 6
2680 #define STARTED_MOVES_NOHIDE 7
2682 static int started = STARTED_NONE;
2683 static char parse[20000];
2684 static int parse_pos = 0;
2685 static char buf[BUF_SIZE + 1];
2686 static int firstTime = TRUE, intfSet = FALSE;
2687 static ColorClass prevColor = ColorNormal;
2688 static int savingComment = FALSE;
2689 static int cmatch = 0; // continuation sequence match
2696 int backup; /* [DM] For zippy color lines */
2698 char talker[MSG_SIZ]; // [HGM] chat
2701 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2703 if (appData.debugMode) {
2705 fprintf(debugFP, "<ICS: ");
2706 show_bytes(debugFP, data, count);
2707 fprintf(debugFP, "\n");
2711 if (appData.debugMode) { int f = forwardMostMove;
2712 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2713 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2714 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2717 /* If last read ended with a partial line that we couldn't parse,
2718 prepend it to the new read and try again. */
2719 if (leftover_len > 0) {
2720 for (i=0; i<leftover_len; i++)
2721 buf[i] = buf[leftover_start + i];
2724 /* copy new characters into the buffer */
2725 bp = buf + leftover_len;
2726 buf_len=leftover_len;
2727 for (i=0; i<count; i++)
2730 if (data[i] == '\r')
2733 // join lines split by ICS?
2734 if (!appData.noJoin)
2737 Joining just consists of finding matches against the
2738 continuation sequence, and discarding that sequence
2739 if found instead of copying it. So, until a match
2740 fails, there's nothing to do since it might be the
2741 complete sequence, and thus, something we don't want
2744 if (data[i] == cont_seq[cmatch])
2747 if (cmatch == strlen(cont_seq))
2749 cmatch = 0; // complete match. just reset the counter
2752 it's possible for the ICS to not include the space
2753 at the end of the last word, making our [correct]
2754 join operation fuse two separate words. the server
2755 does this when the space occurs at the width setting.
2757 if (!buf_len || buf[buf_len-1] != ' ')
2768 match failed, so we have to copy what matched before
2769 falling through and copying this character. In reality,
2770 this will only ever be just the newline character, but
2771 it doesn't hurt to be precise.
2773 strncpy(bp, cont_seq, cmatch);
2785 buf[buf_len] = NULLCHAR;
2786 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2791 while (i < buf_len) {
2792 /* Deal with part of the TELNET option negotiation
2793 protocol. We refuse to do anything beyond the
2794 defaults, except that we allow the WILL ECHO option,
2795 which ICS uses to turn off password echoing when we are
2796 directly connected to it. We reject this option
2797 if localLineEditing mode is on (always on in xboard)
2798 and we are talking to port 23, which might be a real
2799 telnet server that will try to keep WILL ECHO on permanently.
2801 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2802 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2803 unsigned char option;
2805 switch ((unsigned char) buf[++i]) {
2807 if (appData.debugMode)
2808 fprintf(debugFP, "\n<WILL ");
2809 switch (option = (unsigned char) buf[++i]) {
2811 if (appData.debugMode)
2812 fprintf(debugFP, "ECHO ");
2813 /* Reply only if this is a change, according
2814 to the protocol rules. */
2815 if (remoteEchoOption) break;
2816 if (appData.localLineEditing &&
2817 atoi(appData.icsPort) == TN_PORT) {
2818 TelnetRequest(TN_DONT, TN_ECHO);
2821 TelnetRequest(TN_DO, TN_ECHO);
2822 remoteEchoOption = TRUE;
2826 if (appData.debugMode)
2827 fprintf(debugFP, "%d ", option);
2828 /* Whatever this is, we don't want it. */
2829 TelnetRequest(TN_DONT, option);
2834 if (appData.debugMode)
2835 fprintf(debugFP, "\n<WONT ");
2836 switch (option = (unsigned char) buf[++i]) {
2838 if (appData.debugMode)
2839 fprintf(debugFP, "ECHO ");
2840 /* Reply only if this is a change, according
2841 to the protocol rules. */
2842 if (!remoteEchoOption) break;
2844 TelnetRequest(TN_DONT, TN_ECHO);
2845 remoteEchoOption = FALSE;
2848 if (appData.debugMode)
2849 fprintf(debugFP, "%d ", (unsigned char) option);
2850 /* Whatever this is, it must already be turned
2851 off, because we never agree to turn on
2852 anything non-default, so according to the
2853 protocol rules, we don't reply. */
2858 if (appData.debugMode)
2859 fprintf(debugFP, "\n<DO ");
2860 switch (option = (unsigned char) buf[++i]) {
2862 /* Whatever this is, we refuse to do it. */
2863 if (appData.debugMode)
2864 fprintf(debugFP, "%d ", option);
2865 TelnetRequest(TN_WONT, option);
2870 if (appData.debugMode)
2871 fprintf(debugFP, "\n<DONT ");
2872 switch (option = (unsigned char) buf[++i]) {
2874 if (appData.debugMode)
2875 fprintf(debugFP, "%d ", option);
2876 /* Whatever this is, we are already not doing
2877 it, because we never agree to do anything
2878 non-default, so according to the protocol
2879 rules, we don't reply. */
2884 if (appData.debugMode)
2885 fprintf(debugFP, "\n<IAC ");
2886 /* Doubled IAC; pass it through */
2890 if (appData.debugMode)
2891 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2892 /* Drop all other telnet commands on the floor */
2895 if (oldi > next_out)
2896 SendToPlayer(&buf[next_out], oldi - next_out);
2902 /* OK, this at least will *usually* work */
2903 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2907 if (loggedOn && !intfSet) {
2908 if (ics_type == ICS_ICC) {
2909 snprintf(str, MSG_SIZ,
2910 "/set-quietly interface %s\n/set-quietly style 12\n",
2912 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2913 strcat(str, "/set-2 51 1\n/set seek 1\n");
2914 } else if (ics_type == ICS_CHESSNET) {
2915 snprintf(str, MSG_SIZ, "/style 12\n");
2917 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2918 strcat(str, programVersion);
2919 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2920 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2921 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2923 strcat(str, "$iset nohighlight 1\n");
2925 strcat(str, "$iset lock 1\n$style 12\n");
2928 NotifyFrontendLogin();
2932 if (started == STARTED_COMMENT) {
2933 /* Accumulate characters in comment */
2934 parse[parse_pos++] = buf[i];
2935 if (buf[i] == '\n') {
2936 parse[parse_pos] = NULLCHAR;
2937 if(chattingPartner>=0) {
2939 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2940 OutputChatMessage(chattingPartner, mess);
2941 chattingPartner = -1;
2942 next_out = i+1; // [HGM] suppress printing in ICS window
2944 if(!suppressKibitz) // [HGM] kibitz
2945 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2946 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2947 int nrDigit = 0, nrAlph = 0, j;
2948 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2949 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2950 parse[parse_pos] = NULLCHAR;
2951 // try to be smart: if it does not look like search info, it should go to
2952 // ICS interaction window after all, not to engine-output window.
2953 for(j=0; j<parse_pos; j++) { // count letters and digits
2954 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2955 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2956 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2958 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2959 int depth=0; float score;
2960 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2961 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2962 pvInfoList[forwardMostMove-1].depth = depth;
2963 pvInfoList[forwardMostMove-1].score = 100*score;
2965 OutputKibitz(suppressKibitz, parse);
2968 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2969 SendToPlayer(tmp, strlen(tmp));
2971 next_out = i+1; // [HGM] suppress printing in ICS window
2973 started = STARTED_NONE;
2975 /* Don't match patterns against characters in comment */
2980 if (started == STARTED_CHATTER) {
2981 if (buf[i] != '\n') {
2982 /* Don't match patterns against characters in chatter */
2986 started = STARTED_NONE;
2987 if(suppressKibitz) next_out = i+1;
2990 /* Kludge to deal with rcmd protocol */
2991 if (firstTime && looking_at(buf, &i, "\001*")) {
2992 DisplayFatalError(&buf[1], 0, 1);
2998 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3001 if (appData.debugMode)
3002 fprintf(debugFP, "ics_type %d\n", ics_type);
3005 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3006 ics_type = ICS_FICS;
3008 if (appData.debugMode)
3009 fprintf(debugFP, "ics_type %d\n", ics_type);
3012 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3013 ics_type = ICS_CHESSNET;
3015 if (appData.debugMode)
3016 fprintf(debugFP, "ics_type %d\n", ics_type);
3021 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3022 looking_at(buf, &i, "Logging you in as \"*\"") ||
3023 looking_at(buf, &i, "will be \"*\""))) {
3024 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3028 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3030 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3031 DisplayIcsInteractionTitle(buf);
3032 have_set_title = TRUE;
3035 /* skip finger notes */
3036 if (started == STARTED_NONE &&
3037 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3038 (buf[i] == '1' && buf[i+1] == '0')) &&
3039 buf[i+2] == ':' && buf[i+3] == ' ') {
3040 started = STARTED_CHATTER;
3046 // [HGM] seekgraph: recognize sought lines and end-of-sought message
3047 if(appData.seekGraph) {
3048 if(soughtPending && MatchSoughtLine(buf+i)) {
3049 i = strstr(buf+i, "rated") - buf;
3050 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3051 next_out = leftover_start = i;
3052 started = STARTED_CHATTER;
3053 suppressKibitz = TRUE;
3056 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3057 && looking_at(buf, &i, "* ads displayed")) {
3058 soughtPending = FALSE;
3063 if(appData.autoRefresh) {
3064 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3065 int s = (ics_type == ICS_ICC); // ICC format differs
3067 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3068 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3069 looking_at(buf, &i, "*% "); // eat prompt
3070 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3071 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3072 next_out = i; // suppress
3075 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3076 char *p = star_match[0];
3078 if(seekGraphUp) RemoveSeekAd(atoi(p));
3079 while(*p && *p++ != ' '); // next
3081 looking_at(buf, &i, "*% "); // eat prompt
3082 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3089 /* skip formula vars */
3090 if (started == STARTED_NONE &&
3091 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3092 started = STARTED_CHATTER;
3097 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3098 if (appData.autoKibitz && started == STARTED_NONE &&
3099 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3100 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3101 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3102 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3103 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3104 suppressKibitz = TRUE;
3105 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3107 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3108 && (gameMode == IcsPlayingWhite)) ||
3109 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3110 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3111 started = STARTED_CHATTER; // own kibitz we simply discard
3113 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3114 parse_pos = 0; parse[0] = NULLCHAR;
3115 savingComment = TRUE;
3116 suppressKibitz = gameMode != IcsObserving ? 2 :
3117 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3121 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3122 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3123 && atoi(star_match[0])) {
3124 // suppress the acknowledgements of our own autoKibitz
3126 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3127 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3128 SendToPlayer(star_match[0], strlen(star_match[0]));
3129 if(looking_at(buf, &i, "*% ")) // eat prompt
3130 suppressKibitz = FALSE;
3134 } // [HGM] kibitz: end of patch
3136 // [HGM] chat: intercept tells by users for which we have an open chat window
3138 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3139 looking_at(buf, &i, "* whispers:") ||
3140 looking_at(buf, &i, "* kibitzes:") ||
3141 looking_at(buf, &i, "* shouts:") ||
3142 looking_at(buf, &i, "* c-shouts:") ||
3143 looking_at(buf, &i, "--> * ") ||
3144 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3145 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3146 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3147 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3149 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3150 chattingPartner = -1;
3152 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3153 for(p=0; p<MAX_CHAT; p++) {
3154 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3155 talker[0] = '['; strcat(talker, "] ");
3156 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3157 chattingPartner = p; break;
3160 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3161 for(p=0; p<MAX_CHAT; p++) {
3162 if(!strcmp("kibitzes", chatPartner[p])) {
3163 talker[0] = '['; strcat(talker, "] ");
3164 chattingPartner = p; break;
3167 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3168 for(p=0; p<MAX_CHAT; p++) {
3169 if(!strcmp("whispers", chatPartner[p])) {
3170 talker[0] = '['; strcat(talker, "] ");
3171 chattingPartner = p; break;
3174 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3175 if(buf[i-8] == '-' && buf[i-3] == 't')
3176 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3177 if(!strcmp("c-shouts", chatPartner[p])) {
3178 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3179 chattingPartner = p; break;
3182 if(chattingPartner < 0)
3183 for(p=0; p<MAX_CHAT; p++) {
3184 if(!strcmp("shouts", chatPartner[p])) {
3185 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3186 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3187 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3188 chattingPartner = p; break;
3192 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3193 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3194 talker[0] = 0; Colorize(ColorTell, FALSE);
3195 chattingPartner = p; break;
3197 if(chattingPartner<0) i = oldi; else {
3198 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3199 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3200 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3201 started = STARTED_COMMENT;
3202 parse_pos = 0; parse[0] = NULLCHAR;
3203 savingComment = 3 + chattingPartner; // counts as TRUE
3204 suppressKibitz = TRUE;
3207 } // [HGM] chat: end of patch
3210 if (appData.zippyTalk || appData.zippyPlay) {
3211 /* [DM] Backup address for color zippy lines */
3213 if (loggedOn == TRUE)
3214 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3215 (appData.zippyPlay && ZippyMatch(buf, &backup)));
3217 } // [DM] 'else { ' deleted
3219 /* Regular tells and says */
3220 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3221 looking_at(buf, &i, "* (your partner) tells you: ") ||
3222 looking_at(buf, &i, "* says: ") ||
3223 /* Don't color "message" or "messages" output */
3224 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3225 looking_at(buf, &i, "*. * at *:*: ") ||
3226 looking_at(buf, &i, "--* (*:*): ") ||
3227 /* Message notifications (same color as tells) */
3228 looking_at(buf, &i, "* has left a message ") ||
3229 looking_at(buf, &i, "* just sent you a message:\n") ||
3230 /* Whispers and kibitzes */
3231 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3232 looking_at(buf, &i, "* kibitzes: ") ||
3234 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3236 if (tkind == 1 && strchr(star_match[0], ':')) {
3237 /* Avoid "tells you:" spoofs in channels */
3240 if (star_match[0][0] == NULLCHAR ||
3241 strchr(star_match[0], ' ') ||
3242 (tkind == 3 && strchr(star_match[1], ' '))) {
3243 /* Reject bogus matches */
3246 if (appData.colorize) {
3247 if (oldi > next_out) {
3248 SendToPlayer(&buf[next_out], oldi - next_out);
3253 Colorize(ColorTell, FALSE);
3254 curColor = ColorTell;
3257 Colorize(ColorKibitz, FALSE);
3258 curColor = ColorKibitz;
3261 p = strrchr(star_match[1], '(');
3268 Colorize(ColorChannel1, FALSE);
3269 curColor = ColorChannel1;
3271 Colorize(ColorChannel, FALSE);
3272 curColor = ColorChannel;
3276 curColor = ColorNormal;
3280 if (started == STARTED_NONE && appData.autoComment &&
3281 (gameMode == IcsObserving ||
3282 gameMode == IcsPlayingWhite ||
3283 gameMode == IcsPlayingBlack)) {
3284 parse_pos = i - oldi;
3285 memcpy(parse, &buf[oldi], parse_pos);
3286 parse[parse_pos] = NULLCHAR;
3287 started = STARTED_COMMENT;
3288 savingComment = TRUE;
3290 started = STARTED_CHATTER;
3291 savingComment = FALSE;
3298 if (looking_at(buf, &i, "* s-shouts: ") ||
3299 looking_at(buf, &i, "* c-shouts: ")) {
3300 if (appData.colorize) {
3301 if (oldi > next_out) {
3302 SendToPlayer(&buf[next_out], oldi - next_out);
3305 Colorize(ColorSShout, FALSE);
3306 curColor = ColorSShout;
3309 started = STARTED_CHATTER;
3313 if (looking_at(buf, &i, "--->")) {
3318 if (looking_at(buf, &i, "* shouts: ") ||
3319 looking_at(buf, &i, "--> ")) {
3320 if (appData.colorize) {
3321 if (oldi > next_out) {
3322 SendToPlayer(&buf[next_out], oldi - next_out);
3325 Colorize(ColorShout, FALSE);
3326 curColor = ColorShout;
3329 started = STARTED_CHATTER;
3333 if (looking_at( buf, &i, "Challenge:")) {
3334 if (appData.colorize) {
3335 if (oldi > next_out) {
3336 SendToPlayer(&buf[next_out], oldi - next_out);
3339 Colorize(ColorChallenge, FALSE);
3340 curColor = ColorChallenge;
3346 if (looking_at(buf, &i, "* offers you") ||
3347 looking_at(buf, &i, "* offers to be") ||
3348 looking_at(buf, &i, "* would like to") ||
3349 looking_at(buf, &i, "* requests to") ||
3350 looking_at(buf, &i, "Your opponent offers") ||
3351 looking_at(buf, &i, "Your opponent requests")) {
3353 if (appData.colorize) {
3354 if (oldi > next_out) {
3355 SendToPlayer(&buf[next_out], oldi - next_out);
3358 Colorize(ColorRequest, FALSE);
3359 curColor = ColorRequest;
3364 if (looking_at(buf, &i, "* (*) seeking")) {
3365 if (appData.colorize) {
3366 if (oldi > next_out) {
3367 SendToPlayer(&buf[next_out], oldi - next_out);
3370 Colorize(ColorSeek, FALSE);
3371 curColor = ColorSeek;
3376 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3378 if (looking_at(buf, &i, "\\ ")) {
3379 if (prevColor != ColorNormal) {
3380 if (oldi > next_out) {
3381 SendToPlayer(&buf[next_out], oldi - next_out);
3384 Colorize(prevColor, TRUE);
3385 curColor = prevColor;
3387 if (savingComment) {
3388 parse_pos = i - oldi;
3389 memcpy(parse, &buf[oldi], parse_pos);
3390 parse[parse_pos] = NULLCHAR;
3391 started = STARTED_COMMENT;
3392 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3393 chattingPartner = savingComment - 3; // kludge to remember the box
3395 started = STARTED_CHATTER;
3400 if (looking_at(buf, &i, "Black Strength :") ||
3401 looking_at(buf, &i, "<<< style 10 board >>>") ||
3402 looking_at(buf, &i, "<10>") ||
3403 looking_at(buf, &i, "#@#")) {
3404 /* Wrong board style */
3406 SendToICS(ics_prefix);
3407 SendToICS("set style 12\n");
3408 SendToICS(ics_prefix);
3409 SendToICS("refresh\n");
3413 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3415 have_sent_ICS_logon = 1;
3419 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3420 (looking_at(buf, &i, "\n<12>