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 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
60 int flock(int f, int code);
66 #define DoSleep( n ) if( (n) >= 0) sleep(n)
77 #include <sys/types.h>
86 #else /* not STDC_HEADERS */
89 # else /* not HAVE_STRING_H */
91 # endif /* not HAVE_STRING_H */
92 #endif /* not STDC_HEADERS */
95 # include <sys/fcntl.h>
96 #else /* not HAVE_SYS_FCNTL_H */
99 # endif /* HAVE_FCNTL_H */
100 #endif /* not HAVE_SYS_FCNTL_H */
102 #if TIME_WITH_SYS_TIME
103 # include <sys/time.h>
107 # include <sys/time.h>
113 #if defined(_amigados) && !defined(__GNUC__)
118 extern int gettimeofday(struct timeval *, struct timezone *);
126 #include "frontend.h"
133 #include "backendz.h"
137 # define _(s) gettext (s)
138 # define N_(s) gettext_noop (s)
139 # define T_(s) gettext(s)
152 /* A point in time */
154 long sec; /* Assuming this is >= 32 bits */
155 int ms; /* Assuming this is >= 16 bits */
158 int establish P((void));
159 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
160 char *buf, int count, int error));
161 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
162 char *buf, int count, int error));
163 void ics_printf P((char *format, ...));
164 void SendToICS P((char *s));
165 void SendToICSDelayed P((char *s, long msdelay));
166 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
167 void HandleMachineMove P((char *message, ChessProgramState *cps));
168 int AutoPlayOneMove P((void));
169 int LoadGameOneMove P((ChessMove readAhead));
170 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
171 int LoadPositionFromFile P((char *filename, int n, char *title));
172 int SavePositionToFile P((char *filename));
173 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
175 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
176 void ShowMove P((int fromX, int fromY, int toX, int toY));
177 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
178 /*char*/int promoChar));
179 void BackwardInner P((int target));
180 void ForwardInner P((int target));
181 int Adjudicate P((ChessProgramState *cps));
182 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
183 void EditPositionDone P((Boolean fakeRights));
184 void PrintOpponents P((FILE *fp));
185 void PrintPosition P((FILE *fp, int move));
186 void StartChessProgram P((ChessProgramState *cps));
187 void SendToProgram P((char *message, ChessProgramState *cps));
188 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
189 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
190 char *buf, int count, int error));
191 void SendTimeControl P((ChessProgramState *cps,
192 int mps, long tc, int inc, int sd, int st));
193 char *TimeControlTagValue P((void));
194 void Attention P((ChessProgramState *cps));
195 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
196 int ResurrectChessProgram P((void));
197 void DisplayComment P((int moveNumber, char *text));
198 void DisplayMove P((int moveNumber));
200 void ParseGameHistory P((char *game));
201 void ParseBoard12 P((char *string));
202 void KeepAlive P((void));
203 void StartClocks P((void));
204 void SwitchClocks P((int nr));
205 void StopClocks P((void));
206 void ResetClocks P((void));
207 char *PGNDate P((void));
208 void SetGameInfo P((void));
209 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
210 int RegisterMove P((void));
211 void MakeRegisteredMove P((void));
212 void TruncateGame P((void));
213 int looking_at P((char *, int *, char *));
214 void CopyPlayerNameIntoFileName P((char **, char *));
215 char *SavePart P((char *));
216 int SaveGameOldStyle P((FILE *));
217 int SaveGamePGN P((FILE *));
218 void GetTimeMark P((TimeMark *));
219 long SubtractTimeMarks P((TimeMark *, TimeMark *));
220 int CheckFlags P((void));
221 long NextTickLength P((long));
222 void CheckTimeControl P((void));
223 void show_bytes P((FILE *, char *, int));
224 int string_to_rating P((char *str));
225 void ParseFeatures P((char* args, ChessProgramState *cps));
226 void InitBackEnd3 P((void));
227 void FeatureDone P((ChessProgramState* cps, int val));
228 void InitChessProgram P((ChessProgramState *cps, int setup));
229 void OutputKibitz(int window, char *text);
230 int PerpetualChase(int first, int last);
231 int EngineOutputIsUp();
232 void InitDrawingSizes(int x, int y);
233 void NextMatchGame P((void));
234 int NextTourneyGame P((int nr, int *swap));
235 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
236 FILE *WriteTourneyFile P((char *results));
237 void DisplayTwoMachinesTitle P(());
240 extern void ConsoleCreate();
243 ChessProgramState *WhitePlayer();
244 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
245 int VerifyDisplayMode P(());
247 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
248 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
249 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
250 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
251 void ics_update_width P((int new_width));
252 extern char installDir[MSG_SIZ];
253 VariantClass startVariant; /* [HGM] nicks: initial variant */
256 extern int tinyLayout, smallLayout;
257 ChessProgramStats programStats;
258 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
260 static int exiting = 0; /* [HGM] moved to top */
261 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
262 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
263 Board partnerBoard; /* [HGM] bughouse: for peeking at partner game */
264 int partnerHighlight[2];
265 Boolean partnerBoardValid = 0;
266 char partnerStatus[MSG_SIZ];
268 Boolean originalFlip;
269 Boolean twoBoards = 0;
270 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
271 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
272 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
273 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
274 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
275 int opponentKibitzes;
276 int lastSavedGame; /* [HGM] save: ID of game */
277 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
278 extern int chatCount;
280 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
281 char lastMsg[MSG_SIZ];
282 ChessSquare pieceSweep = EmptySquare;
283 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
284 int promoDefaultAltered;
286 /* States for ics_getting_history */
288 #define H_REQUESTED 1
289 #define H_GOT_REQ_HEADER 2
290 #define H_GOT_UNREQ_HEADER 3
291 #define H_GETTING_MOVES 4
292 #define H_GOT_UNWANTED_HEADER 5
294 /* whosays values for GameEnds */
303 /* Maximum number of games in a cmail message */
304 #define CMAIL_MAX_GAMES 20
306 /* Different types of move when calling RegisterMove */
308 #define CMAIL_RESIGN 1
310 #define CMAIL_ACCEPT 3
312 /* Different types of result to remember for each game */
313 #define CMAIL_NOT_RESULT 0
314 #define CMAIL_OLD_RESULT 1
315 #define CMAIL_NEW_RESULT 2
317 /* Telnet protocol constants */
328 safeStrCpy( char *dst, const char *src, size_t count )
331 assert( dst != NULL );
332 assert( src != NULL );
335 for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
336 if( i == count && dst[count-1] != NULLCHAR)
338 dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
339 if(appData.debugMode)
340 fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
346 /* Some compiler can't cast u64 to double
347 * This function do the job for us:
349 * We use the highest bit for cast, this only
350 * works if the highest bit is not
351 * in use (This should not happen)
353 * We used this for all compiler
356 u64ToDouble(u64 value)
359 u64 tmp = value & u64Const(0x7fffffffffffffff);
360 r = (double)(s64)tmp;
361 if (value & u64Const(0x8000000000000000))
362 r += 9.2233720368547758080e18; /* 2^63 */
366 /* Fake up flags for now, as we aren't keeping track of castling
367 availability yet. [HGM] Change of logic: the flag now only
368 indicates the type of castlings allowed by the rule of the game.
369 The actual rights themselves are maintained in the array
370 castlingRights, as part of the game history, and are not probed
376 int flags = F_ALL_CASTLE_OK;
377 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
378 switch (gameInfo.variant) {
380 flags &= ~F_ALL_CASTLE_OK;
381 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
382 flags |= F_IGNORE_CHECK;
384 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
387 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
389 case VariantKriegspiel:
390 flags |= F_KRIEGSPIEL_CAPTURE;
392 case VariantCapaRandom:
393 case VariantFischeRandom:
394 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
395 case VariantNoCastle:
396 case VariantShatranj:
400 flags &= ~F_ALL_CASTLE_OK;
408 FILE *gameFileFP, *debugFP;
411 [AS] Note: sometimes, the sscanf() function is used to parse the input
412 into a fixed-size buffer. Because of this, we must be prepared to
413 receive strings as long as the size of the input buffer, which is currently
414 set to 4K for Windows and 8K for the rest.
415 So, we must either allocate sufficiently large buffers here, or
416 reduce the size of the input buffer in the input reading part.
419 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
420 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
421 char thinkOutput1[MSG_SIZ*10];
423 ChessProgramState first, second, pairing;
425 /* premove variables */
428 int premoveFromX = 0;
429 int premoveFromY = 0;
430 int premovePromoChar = 0;
432 Boolean alarmSounded;
433 /* end premove variables */
435 char *ics_prefix = "$";
436 int ics_type = ICS_GENERIC;
438 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
439 int pauseExamForwardMostMove = 0;
440 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
441 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
442 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
443 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
444 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
445 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
446 int whiteFlag = FALSE, blackFlag = FALSE;
447 int userOfferedDraw = FALSE;
448 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
449 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
450 int cmailMoveType[CMAIL_MAX_GAMES];
451 long ics_clock_paused = 0;
452 ProcRef icsPR = NoProc, cmailPR = NoProc;
453 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
454 GameMode gameMode = BeginningOfGame;
455 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
456 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
457 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
458 int hiddenThinkOutputState = 0; /* [AS] */
459 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
460 int adjudicateLossPlies = 6;
461 char white_holding[64], black_holding[64];
462 TimeMark lastNodeCountTime;
463 long lastNodeCount=0;
464 int shiftKey; // [HGM] set by mouse handler
466 int have_sent_ICS_logon = 0;
468 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
469 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
470 long timeControl_2; /* [AS] Allow separate time controls */
471 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
472 long timeRemaining[2][MAX_MOVES];
473 int matchGame = 0, nextGame = 0, roundNr = 0;
474 Boolean waitingForGame = FALSE;
475 TimeMark programStartTime, pauseStart;
476 char ics_handle[MSG_SIZ];
477 int have_set_title = 0;
479 /* animateTraining preserves the state of appData.animate
480 * when Training mode is activated. This allows the
481 * response to be animated when appData.animate == TRUE and
482 * appData.animateDragging == TRUE.
484 Boolean animateTraining;
490 Board boards[MAX_MOVES];
491 /* [HGM] Following 7 needed for accurate legality tests: */
492 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
493 signed char initialRights[BOARD_FILES];
494 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
495 int initialRulePlies, FENrulePlies;
496 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
498 Boolean shuffleOpenings;
499 int mute; // mute all sounds
501 // [HGM] vari: next 12 to save and restore variations
502 #define MAX_VARIATIONS 10
503 int framePtr = MAX_MOVES-1; // points to free stack entry
505 int savedFirst[MAX_VARIATIONS];
506 int savedLast[MAX_VARIATIONS];
507 int savedFramePtr[MAX_VARIATIONS];
508 char *savedDetails[MAX_VARIATIONS];
509 ChessMove savedResult[MAX_VARIATIONS];
511 void PushTail P((int firstMove, int lastMove));
512 Boolean PopTail P((Boolean annotate));
513 void PushInner P((int firstMove, int lastMove));
514 void PopInner P((Boolean annotate));
515 void CleanupTail P((void));
517 ChessSquare FIDEArray[2][BOARD_FILES] = {
518 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
519 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
520 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
521 BlackKing, BlackBishop, BlackKnight, BlackRook }
524 ChessSquare twoKingsArray[2][BOARD_FILES] = {
525 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
526 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
527 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
528 BlackKing, BlackKing, BlackKnight, BlackRook }
531 ChessSquare KnightmateArray[2][BOARD_FILES] = {
532 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
533 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
534 { BlackRook, BlackMan, BlackBishop, BlackQueen,
535 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
538 ChessSquare SpartanArray[2][BOARD_FILES] = {
539 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
540 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
541 { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
542 BlackDragon, BlackKing, BlackAngel, BlackAlfil }
545 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
546 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
547 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
548 { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
549 BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
552 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
553 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
554 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
555 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
556 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
559 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
560 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
561 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
562 { BlackRook, BlackKnight, BlackMan, BlackFerz,
563 BlackKing, BlackMan, BlackKnight, BlackRook }
567 #if (BOARD_FILES>=10)
568 ChessSquare ShogiArray[2][BOARD_FILES] = {
569 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
570 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
571 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
572 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
575 ChessSquare XiangqiArray[2][BOARD_FILES] = {
576 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
577 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
578 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
579 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
582 ChessSquare CapablancaArray[2][BOARD_FILES] = {
583 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
584 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
585 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
586 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
589 ChessSquare GreatArray[2][BOARD_FILES] = {
590 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
591 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
592 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
593 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
596 ChessSquare JanusArray[2][BOARD_FILES] = {
597 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
598 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
599 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
600 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
603 ChessSquare GrandArray[2][BOARD_FILES] = {
604 { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
605 WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
606 { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
607 BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
611 ChessSquare GothicArray[2][BOARD_FILES] = {
612 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
613 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
614 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
615 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
618 #define GothicArray CapablancaArray
622 ChessSquare FalconArray[2][BOARD_FILES] = {
623 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
624 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
625 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
626 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
629 #define FalconArray CapablancaArray
632 #else // !(BOARD_FILES>=10)
633 #define XiangqiPosition FIDEArray
634 #define CapablancaArray FIDEArray
635 #define GothicArray FIDEArray
636 #define GreatArray FIDEArray
637 #endif // !(BOARD_FILES>=10)
639 #if (BOARD_FILES>=12)
640 ChessSquare CourierArray[2][BOARD_FILES] = {
641 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
642 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
643 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
644 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
646 #else // !(BOARD_FILES>=12)
647 #define CourierArray CapablancaArray
648 #endif // !(BOARD_FILES>=12)
651 Board initialPosition;
654 /* Convert str to a rating. Checks for special cases of "----",
656 "++++", etc. Also strips ()'s */
658 string_to_rating(str)
661 while(*str && !isdigit(*str)) ++str;
663 return 0; /* One of the special "no rating" cases */
671 /* Init programStats */
672 programStats.movelist[0] = 0;
673 programStats.depth = 0;
674 programStats.nr_moves = 0;
675 programStats.moves_left = 0;
676 programStats.nodes = 0;
677 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
678 programStats.score = 0;
679 programStats.got_only_move = 0;
680 programStats.got_fail = 0;
681 programStats.line_is_book = 0;
686 { // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
687 if (appData.firstPlaysBlack) {
688 first.twoMachinesColor = "black\n";
689 second.twoMachinesColor = "white\n";
691 first.twoMachinesColor = "white\n";
692 second.twoMachinesColor = "black\n";
695 first.other = &second;
696 second.other = &first;
699 if(appData.timeOddsMode) {
700 norm = appData.timeOdds[0];
701 if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
703 first.timeOdds = appData.timeOdds[0]/norm;
704 second.timeOdds = appData.timeOdds[1]/norm;
707 if(programVersion) free(programVersion);
708 if (appData.noChessProgram) {
709 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
710 sprintf(programVersion, "%s", PACKAGE_STRING);
712 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
713 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
714 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
719 UnloadEngine(ChessProgramState *cps)
721 /* Kill off first chess program */
722 if (cps->isr != NULL)
723 RemoveInputSource(cps->isr);
726 if (cps->pr != NoProc) {
728 DoSleep( appData.delayBeforeQuit );
729 SendToProgram("quit\n", cps);
730 DoSleep( appData.delayAfterQuit );
731 DestroyChildProcess(cps->pr, cps->useSigterm);
734 if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
738 ClearOptions(ChessProgramState *cps)
741 cps->nrOptions = cps->comboCnt = 0;
742 for(i=0; i<MAX_OPTIONS; i++) {
743 cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
744 cps->option[i].textValue = 0;
748 char *engineNames[] = {
754 InitEngine(ChessProgramState *cps, int n)
755 { // [HGM] all engine initialiation put in a function that does one engine
759 cps->which = engineNames[n];
760 cps->maybeThinking = FALSE;
764 cps->sendDrawOffers = 1;
766 cps->program = appData.chessProgram[n];
767 cps->host = appData.host[n];
768 cps->dir = appData.directory[n];
769 cps->initString = appData.engInitString[n];
770 cps->computerString = appData.computerString[n];
771 cps->useSigint = TRUE;
772 cps->useSigterm = TRUE;
773 cps->reuse = appData.reuse[n];
774 cps->nps = appData.NPS[n]; // [HGM] nps: copy nodes per second
775 cps->useSetboard = FALSE;
777 cps->usePing = FALSE;
780 cps->usePlayother = FALSE;
781 cps->useColors = TRUE;
782 cps->useUsermove = FALSE;
783 cps->sendICS = FALSE;
784 cps->sendName = appData.icsActive;
785 cps->sdKludge = FALSE;
786 cps->stKludge = FALSE;
787 TidyProgramName(cps->program, cps->host, cps->tidy);
789 safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
790 cps->analysisSupport = 2; /* detect */
791 cps->analyzing = FALSE;
792 cps->initDone = FALSE;
794 /* New features added by Tord: */
795 cps->useFEN960 = FALSE;
796 cps->useOOCastle = TRUE;
797 /* End of new features added by Tord. */
798 cps->fenOverride = appData.fenOverride[n];
800 /* [HGM] time odds: set factor for each machine */
801 cps->timeOdds = appData.timeOdds[n];
803 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
804 cps->accumulateTC = appData.accumulateTC[n];
805 cps->maxNrOfSessions = 1;
810 cps->supportsNPS = UNKNOWN;
811 cps->memSize = FALSE;
812 cps->maxCores = FALSE;
813 cps->egtFormats[0] = NULLCHAR;
816 cps->optionSettings = appData.engOptions[n];
818 cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
819 cps->isUCI = appData.isUCI[n]; /* [AS] */
820 cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
822 if (appData.protocolVersion[n] > PROTOVER
823 || appData.protocolVersion[n] < 1)
828 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
829 appData.protocolVersion[n]);
830 if( (len > MSG_SIZ) && appData.debugMode )
831 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
833 DisplayFatalError(buf, 0, 2);
837 cps->protocolVersion = appData.protocolVersion[n];
840 InitEngineUCI( installDir, cps ); // [HGM] moved here from winboard.c, to make available in xboard
843 ChessProgramState *savCps;
849 if(WaitForEngine(savCps, LoadEngine)) return;
850 CommonEngineInit(); // recalculate time odds
851 if(gameInfo.variant != StringToVariant(appData.variant)) {
852 // we changed variant when loading the engine; this forces us to reset
853 Reset(TRUE, savCps != &first);
854 EditGameEvent(); // for consistency with other path, as Reset changes mode
856 InitChessProgram(savCps, FALSE);
857 SendToProgram("force\n", savCps);
858 DisplayMessage("", "");
859 if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
860 for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
866 ReplaceEngine(ChessProgramState *cps, int n)
870 appData.noChessProgram = FALSE;
871 appData.clockMode = TRUE;
874 if(n) return; // only startup first engine immediately; second can wait
875 savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
879 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
880 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
882 static char resetOptions[] =
883 "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
884 "-firstOptions \"\" -firstNPS -1 -fn \"\"";
887 Load(ChessProgramState *cps, int i)
889 char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
890 if(engineLine[0]) { // an engine was selected from the combo box
891 snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
892 SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
893 ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL;
894 ParseArgsFromString(buf);
896 ReplaceEngine(cps, i);
900 while(q = strchr(p, SLASH)) p = q+1;
901 if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
902 if(engineDir[0] != NULLCHAR)
903 appData.directory[i] = engineDir;
904 else if(p != engineName) { // derive directory from engine path, when not given
906 appData.directory[i] = strdup(engineName);
908 } else appData.directory[i] = ".";
909 if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
911 snprintf(command, MSG_SIZ, "%s %s", p, params);
914 appData.chessProgram[i] = strdup(p);
915 appData.isUCI[i] = isUCI;
916 appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
917 appData.hasOwnBookUCI[i] = hasBook;
918 if(!nickName[0]) useNick = FALSE;
919 if(useNick) ASSIGN(appData.pgnName[i], nickName);
923 q = firstChessProgramNames;
924 if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
925 quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
926 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
927 quote, p, quote, appData.directory[i],
928 useNick ? " -fn \"" : "",
929 useNick ? nickName : "",
931 v1 ? " -firstProtocolVersion 1" : "",
932 hasBook ? "" : " -fNoOwnBookUCI",
933 isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
934 storeVariant ? " -variant " : "",
935 storeVariant ? VariantName(gameInfo.variant) : "");
936 firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
937 snprintf(firstChessProgramNames, len, "%s%s", q, buf);
940 ReplaceEngine(cps, i);
946 int matched, min, sec;
948 * Parse timeControl resource
950 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
951 appData.movesPerSession)) {
953 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
954 DisplayFatalError(buf, 0, 2);
958 * Parse searchTime resource
960 if (*appData.searchTime != NULLCHAR) {
961 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
963 searchTime = min * 60;
964 } else if (matched == 2) {
965 searchTime = min * 60 + sec;
968 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
969 DisplayFatalError(buf, 0, 2);
978 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
979 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
981 GetTimeMark(&programStartTime);
982 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
983 pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
986 programStats.ok_to_send = 1;
987 programStats.seen_stat = 0;
990 * Initialize game list
996 * Internet chess server status
998 if (appData.icsActive) {
999 appData.matchMode = FALSE;
1000 appData.matchGames = 0;
1002 appData.noChessProgram = !appData.zippyPlay;
1004 appData.zippyPlay = FALSE;
1005 appData.zippyTalk = FALSE;
1006 appData.noChessProgram = TRUE;
1008 if (*appData.icsHelper != NULLCHAR) {
1009 appData.useTelnet = TRUE;
1010 appData.telnetProgram = appData.icsHelper;
1013 appData.zippyTalk = appData.zippyPlay = FALSE;
1016 /* [AS] Initialize pv info list [HGM] and game state */
1020 for( i=0; i<=framePtr; i++ ) {
1021 pvInfoList[i].depth = -1;
1022 boards[i][EP_STATUS] = EP_NONE;
1023 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1029 /* [AS] Adjudication threshold */
1030 adjudicateLossThreshold = appData.adjudicateLossThreshold;
1032 InitEngine(&first, 0);
1033 InitEngine(&second, 1);
1036 pairing.which = "pairing"; // pairing engine
1037 pairing.pr = NoProc;
1039 pairing.program = appData.pairingEngine;
1040 pairing.host = "localhost";
1043 if (appData.icsActive) {
1044 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
1045 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1046 appData.clockMode = FALSE;
1047 first.sendTime = second.sendTime = 0;
1051 /* Override some settings from environment variables, for backward
1052 compatibility. Unfortunately it's not feasible to have the env
1053 vars just set defaults, at least in xboard. Ugh.
1055 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1060 if (!appData.icsActive) {
1064 /* Check for variants that are supported only in ICS mode,
1065 or not at all. Some that are accepted here nevertheless
1066 have bugs; see comments below.
1068 VariantClass variant = StringToVariant(appData.variant);
1070 case VariantBughouse: /* need four players and two boards */
1071 case VariantKriegspiel: /* need to hide pieces and move details */
1072 /* case VariantFischeRandom: (Fabien: moved below) */
1073 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1074 if( (len > MSG_SIZ) && appData.debugMode )
1075 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1077 DisplayFatalError(buf, 0, 2);
1080 case VariantUnknown:
1081 case VariantLoadable:
1091 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1092 if( (len > MSG_SIZ) && appData.debugMode )
1093 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1095 DisplayFatalError(buf, 0, 2);
1098 case VariantXiangqi: /* [HGM] repetition rules not implemented */
1099 case VariantFairy: /* [HGM] TestLegality definitely off! */
1100 case VariantGothic: /* [HGM] should work */
1101 case VariantCapablanca: /* [HGM] should work */
1102 case VariantCourier: /* [HGM] initial forced moves not implemented */
1103 case VariantShogi: /* [HGM] could still mate with pawn drop */
1104 case VariantKnightmate: /* [HGM] should work */
1105 case VariantCylinder: /* [HGM] untested */
1106 case VariantFalcon: /* [HGM] untested */
1107 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1108 offboard interposition not understood */
1109 case VariantNormal: /* definitely works! */
1110 case VariantWildCastle: /* pieces not automatically shuffled */
1111 case VariantNoCastle: /* pieces not automatically shuffled */
1112 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1113 case VariantLosers: /* should work except for win condition,
1114 and doesn't know captures are mandatory */
1115 case VariantSuicide: /* should work except for win condition,
1116 and doesn't know captures are mandatory */
1117 case VariantGiveaway: /* should work except for win condition,
1118 and doesn't know captures are mandatory */
1119 case VariantTwoKings: /* should work */
1120 case VariantAtomic: /* should work except for win condition */
1121 case Variant3Check: /* should work except for win condition */
1122 case VariantShatranj: /* should work except for all win conditions */
1123 case VariantMakruk: /* should work except for draw countdown */
1124 case VariantBerolina: /* might work if TestLegality is off */
1125 case VariantCapaRandom: /* should work */
1126 case VariantJanus: /* should work */
1127 case VariantSuper: /* experimental */
1128 case VariantGreat: /* experimental, requires legality testing to be off */
1129 case VariantSChess: /* S-Chess, should work */
1130 case VariantGrand: /* should work */
1131 case VariantSpartan: /* should work */
1138 int NextIntegerFromString( char ** str, long * value )
1143 while( *s == ' ' || *s == '\t' ) {
1149 if( *s >= '0' && *s <= '9' ) {
1150 while( *s >= '0' && *s <= '9' ) {
1151 *value = *value * 10 + (*s - '0');
1163 int NextTimeControlFromString( char ** str, long * value )
1166 int result = NextIntegerFromString( str, &temp );
1169 *value = temp * 60; /* Minutes */
1170 if( **str == ':' ) {
1172 result = NextIntegerFromString( str, &temp );
1173 *value += temp; /* Seconds */
1180 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1181 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1182 int result = -1, type = 0; long temp, temp2;
1184 if(**str != ':') return -1; // old params remain in force!
1186 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1187 if( NextIntegerFromString( str, &temp ) ) return -1;
1188 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1191 /* time only: incremental or sudden-death time control */
1192 if(**str == '+') { /* increment follows; read it */
1194 if(**str == '!') type = *(*str)++; // Bronstein TC
1195 if(result = NextIntegerFromString( str, &temp2)) return -1;
1196 *inc = temp2 * 1000;
1197 if(**str == '.') { // read fraction of increment
1198 char *start = ++(*str);
1199 if(result = NextIntegerFromString( str, &temp2)) return -1;
1201 while(start++ < *str) temp2 /= 10;
1205 *moves = 0; *tc = temp * 1000; *incType = type;
1209 (*str)++; /* classical time control */
1210 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1221 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1222 { /* [HGM] get time to add from the multi-session time-control string */
1223 int incType, moves=1; /* kludge to force reading of first session */
1224 long time, increment;
1227 if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1228 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1230 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1231 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1232 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1233 if(movenr == -1) return time; /* last move before new session */
1234 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1235 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1236 if(!moves) return increment; /* current session is incremental */
1237 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1238 } while(movenr >= -1); /* try again for next session */
1240 return 0; // no new time quota on this move
1244 ParseTimeControl(tc, ti, mps)
1251 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1254 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1255 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1256 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1260 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1262 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1265 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1267 snprintf(buf, MSG_SIZ, ":%s", mytc);
1269 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1271 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1276 /* Parse second time control */
1279 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1287 timeControl_2 = tc2 * 1000;
1297 timeControl = tc1 * 1000;
1300 timeIncrement = ti * 1000; /* convert to ms */
1301 movesPerSession = 0;
1304 movesPerSession = mps;
1312 if (appData.debugMode) {
1313 fprintf(debugFP, "%s\n", programVersion);
1316 set_cont_sequence(appData.wrapContSeq);
1317 if (appData.matchGames > 0) {
1318 appData.matchMode = TRUE;
1319 } else if (appData.matchMode) {
1320 appData.matchGames = 1;
1322 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1323 appData.matchGames = appData.sameColorGames;
1324 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1325 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1326 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1329 if (appData.noChessProgram || first.protocolVersion == 1) {
1332 /* kludge: allow timeout for initial "feature" commands */
1334 DisplayMessage("", _("Starting chess program"));
1335 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1340 CalculateIndex(int index, int gameNr)
1341 { // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1343 if(index > 0) return index; // fixed nmber
1344 if(index == 0) return 1;
1345 res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1346 if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1351 LoadGameOrPosition(int gameNr)
1352 { // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1353 if (*appData.loadGameFile != NULLCHAR) {
1354 if (!LoadGameFromFile(appData.loadGameFile,
1355 CalculateIndex(appData.loadGameIndex, gameNr),
1356 appData.loadGameFile, FALSE)) {
1357 DisplayFatalError(_("Bad game file"), 0, 1);
1360 } else if (*appData.loadPositionFile != NULLCHAR) {
1361 if (!LoadPositionFromFile(appData.loadPositionFile,
1362 CalculateIndex(appData.loadPositionIndex, gameNr),
1363 appData.loadPositionFile)) {
1364 DisplayFatalError(_("Bad position file"), 0, 1);
1372 ReserveGame(int gameNr, char resChar)
1374 FILE *tf = fopen(appData.tourneyFile, "r+");
1375 char *p, *q, c, buf[MSG_SIZ];
1376 if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1377 safeStrCpy(buf, lastMsg, MSG_SIZ);
1378 DisplayMessage(_("Pick new game"), "");
1379 flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1380 ParseArgsFromFile(tf);
1381 p = q = appData.results;
1382 if(appData.debugMode) {
1383 char *r = appData.participants;
1384 fprintf(debugFP, "results = '%s'\n", p);
1385 while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1386 fprintf(debugFP, "\n");
1388 while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1390 q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1391 safeStrCpy(q, p, strlen(p) + 2);
1392 if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1393 if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1394 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1395 if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1398 fseek(tf, -(strlen(p)+4), SEEK_END);
1400 if(c != '"') // depending on DOS or Unix line endings we can be one off
1401 fseek(tf, -(strlen(p)+2), SEEK_END);
1402 else fseek(tf, -(strlen(p)+3), SEEK_END);
1403 fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1404 DisplayMessage(buf, "");
1405 free(p); appData.results = q;
1406 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1407 (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1408 UnloadEngine(&first); // next game belongs to other pairing;
1409 UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1414 MatchEvent(int mode)
1415 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1417 if(matchMode) { // already in match mode: switch it off
1419 if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1422 // if(gameMode != BeginningOfGame) {
1423 // DisplayError(_("You can only start a match from the initial position."), 0);
1427 if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1428 /* Set up machine vs. machine match */
1430 NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1431 if(appData.tourneyFile[0]) {
1433 if(nextGame > appData.matchGames) {
1435 if(strchr(appData.results, '*') == NULL) {
1437 appData.tourneyCycles++;
1438 if(f = WriteTourneyFile(appData.results)) { // make a tourney file with increased number of cycles
1440 NextTourneyGame(-1, &dummy);
1442 if(nextGame <= appData.matchGames) {
1443 DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1445 ScheduleDelayedEvent(NextMatchGame, 10000);
1450 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1451 DisplayError(buf, 0);
1452 appData.tourneyFile[0] = 0;
1456 if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically
1457 DisplayFatalError(_("Can't have a match with no chess programs"),
1462 matchGame = roundNr = 1;
1463 first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1468 InitBackEnd3 P((void))
1470 GameMode initialMode;
1474 InitChessProgram(&first, startedFromSetupPosition);
1476 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1477 free(programVersion);
1478 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1479 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1482 if (appData.icsActive) {
1484 /* [DM] Make a console window if needed [HGM] merged ifs */
1490 if (*appData.icsCommPort != NULLCHAR)
1491 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1492 appData.icsCommPort);
1494 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1495 appData.icsHost, appData.icsPort);
1497 if( (len > MSG_SIZ) && appData.debugMode )
1498 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1500 DisplayFatalError(buf, err, 1);
1505 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1507 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1508 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1509 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1510 } else if (appData.noChessProgram) {
1516 if (*appData.cmailGameName != NULLCHAR) {
1518 OpenLoopback(&cmailPR);
1520 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1524 DisplayMessage("", "");
1525 if (StrCaseCmp(appData.initialMode, "") == 0) {
1526 initialMode = BeginningOfGame;
1527 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1528 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1529 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1530 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1533 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1534 initialMode = TwoMachinesPlay;
1535 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1536 initialMode = AnalyzeFile;
1537 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1538 initialMode = AnalyzeMode;
1539 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1540 initialMode = MachinePlaysWhite;
1541 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1542 initialMode = MachinePlaysBlack;
1543 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1544 initialMode = EditGame;
1545 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1546 initialMode = EditPosition;
1547 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1548 initialMode = Training;
1550 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1551 if( (len > MSG_SIZ) && appData.debugMode )
1552 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1554 DisplayFatalError(buf, 0, 2);
1558 if (appData.matchMode) {
1559 if(appData.tourneyFile[0]) { // start tourney from command line
1561 if(f = fopen(appData.tourneyFile, "r")) {
1562 ParseArgsFromFile(f); // make sure tourney parmeters re known
1564 appData.clockMode = TRUE;
1566 } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1569 } else if (*appData.cmailGameName != NULLCHAR) {
1570 /* Set up cmail mode */
1571 ReloadCmailMsgEvent(TRUE);
1573 /* Set up other modes */
1574 if (initialMode == AnalyzeFile) {
1575 if (*appData.loadGameFile == NULLCHAR) {
1576 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1580 if (*appData.loadGameFile != NULLCHAR) {
1581 (void) LoadGameFromFile(appData.loadGameFile,
1582 appData.loadGameIndex,
1583 appData.loadGameFile, TRUE);
1584 } else if (*appData.loadPositionFile != NULLCHAR) {
1585 (void) LoadPositionFromFile(appData.loadPositionFile,
1586 appData.loadPositionIndex,
1587 appData.loadPositionFile);
1588 /* [HGM] try to make self-starting even after FEN load */
1589 /* to allow automatic setup of fairy variants with wtm */
1590 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1591 gameMode = BeginningOfGame;
1592 setboardSpoiledMachineBlack = 1;
1594 /* [HGM] loadPos: make that every new game uses the setup */
1595 /* from file as long as we do not switch variant */
1596 if(!blackPlaysFirst) {
1597 startedFromPositionFile = TRUE;
1598 CopyBoard(filePosition, boards[0]);
1601 if (initialMode == AnalyzeMode) {
1602 if (appData.noChessProgram) {
1603 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1606 if (appData.icsActive) {
1607 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1611 } else if (initialMode == AnalyzeFile) {
1612 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1613 ShowThinkingEvent();
1615 AnalysisPeriodicEvent(1);
1616 } else if (initialMode == MachinePlaysWhite) {
1617 if (appData.noChessProgram) {
1618 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1622 if (appData.icsActive) {
1623 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1627 MachineWhiteEvent();
1628 } else if (initialMode == MachinePlaysBlack) {
1629 if (appData.noChessProgram) {
1630 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1634 if (appData.icsActive) {
1635 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1639 MachineBlackEvent();
1640 } else if (initialMode == TwoMachinesPlay) {
1641 if (appData.noChessProgram) {
1642 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1646 if (appData.icsActive) {
1647 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1652 } else if (initialMode == EditGame) {
1654 } else if (initialMode == EditPosition) {
1655 EditPositionEvent();
1656 } else if (initialMode == Training) {
1657 if (*appData.loadGameFile == NULLCHAR) {
1658 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1667 * Establish will establish a contact to a remote host.port.
1668 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1669 * used to talk to the host.
1670 * Returns 0 if okay, error code if not.
1677 if (*appData.icsCommPort != NULLCHAR) {
1678 /* Talk to the host through a serial comm port */
1679 return OpenCommPort(appData.icsCommPort, &icsPR);
1681 } else if (*appData.gateway != NULLCHAR) {
1682 if (*appData.remoteShell == NULLCHAR) {
1683 /* Use the rcmd protocol to run telnet program on a gateway host */
1684 snprintf(buf, sizeof(buf), "%s %s %s",
1685 appData.telnetProgram, appData.icsHost, appData.icsPort);
1686 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1689 /* Use the rsh program to run telnet program on a gateway host */
1690 if (*appData.remoteUser == NULLCHAR) {
1691 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1692 appData.gateway, appData.telnetProgram,
1693 appData.icsHost, appData.icsPort);
1695 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1696 appData.remoteShell, appData.gateway,
1697 appData.remoteUser, appData.telnetProgram,
1698 appData.icsHost, appData.icsPort);
1700 return StartChildProcess(buf, "", &icsPR);
1703 } else if (appData.useTelnet) {
1704 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1707 /* TCP socket interface differs somewhat between
1708 Unix and NT; handle details in the front end.
1710 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1714 void EscapeExpand(char *p, char *q)
1715 { // [HGM] initstring: routine to shape up string arguments
1716 while(*p++ = *q++) if(p[-1] == '\\')
1718 case 'n': p[-1] = '\n'; break;
1719 case 'r': p[-1] = '\r'; break;
1720 case 't': p[-1] = '\t'; break;
1721 case '\\': p[-1] = '\\'; break;
1722 case 0: *p = 0; return;
1723 default: p[-1] = q[-1]; break;
1728 show_bytes(fp, buf, count)
1734 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1735 fprintf(fp, "\\%03o", *buf & 0xff);
1744 /* Returns an errno value */
1746 OutputMaybeTelnet(pr, message, count, outError)
1752 char buf[8192], *p, *q, *buflim;
1753 int left, newcount, outcount;
1755 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1756 *appData.gateway != NULLCHAR) {
1757 if (appData.debugMode) {
1758 fprintf(debugFP, ">ICS: ");
1759 show_bytes(debugFP, message, count);
1760 fprintf(debugFP, "\n");
1762 return OutputToProcess(pr, message, count, outError);
1765 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1772 if (appData.debugMode) {
1773 fprintf(debugFP, ">ICS: ");
1774 show_bytes(debugFP, buf, newcount);
1775 fprintf(debugFP, "\n");
1777 outcount = OutputToProcess(pr, buf, newcount, outError);
1778 if (outcount < newcount) return -1; /* to be sure */
1785 } else if (((unsigned char) *p) == TN_IAC) {
1786 *q++ = (char) TN_IAC;
1793 if (appData.debugMode) {
1794 fprintf(debugFP, ">ICS: ");
1795 show_bytes(debugFP, buf, newcount);
1796 fprintf(debugFP, "\n");
1798 outcount = OutputToProcess(pr, buf, newcount, outError);
1799 if (outcount < newcount) return -1; /* to be sure */
1804 read_from_player(isr, closure, message, count, error)
1811 int outError, outCount;
1812 static int gotEof = 0;
1814 /* Pass data read from player on to ICS */
1817 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1818 if (outCount < count) {
1819 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1821 } else if (count < 0) {
1822 RemoveInputSource(isr);
1823 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1824 } else if (gotEof++ > 0) {
1825 RemoveInputSource(isr);
1826 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1832 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1833 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1834 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1835 SendToICS("date\n");
1836 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1839 /* added routine for printf style output to ics */
1840 void ics_printf(char *format, ...)
1842 char buffer[MSG_SIZ];
1845 va_start(args, format);
1846 vsnprintf(buffer, sizeof(buffer), format, args);
1847 buffer[sizeof(buffer)-1] = '\0';
1856 int count, outCount, outError;
1858 if (icsPR == NULL) return;
1861 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1862 if (outCount < count) {
1863 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1867 /* This is used for sending logon scripts to the ICS. Sending
1868 without a delay causes problems when using timestamp on ICC
1869 (at least on my machine). */
1871 SendToICSDelayed(s,msdelay)
1875 int count, outCount, outError;
1877 if (icsPR == NULL) return;
1880 if (appData.debugMode) {
1881 fprintf(debugFP, ">ICS: ");
1882 show_bytes(debugFP, s, count);
1883 fprintf(debugFP, "\n");
1885 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1887 if (outCount < count) {
1888 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1893 /* Remove all highlighting escape sequences in s
1894 Also deletes any suffix starting with '('
1897 StripHighlightAndTitle(s)
1900 static char retbuf[MSG_SIZ];
1903 while (*s != NULLCHAR) {
1904 while (*s == '\033') {
1905 while (*s != NULLCHAR && !isalpha(*s)) s++;
1906 if (*s != NULLCHAR) s++;
1908 while (*s != NULLCHAR && *s != '\033') {
1909 if (*s == '(' || *s == '[') {
1920 /* Remove all highlighting escape sequences in s */
1925 static char retbuf[MSG_SIZ];
1928 while (*s != NULLCHAR) {
1929 while (*s == '\033') {
1930 while (*s != NULLCHAR && !isalpha(*s)) s++;
1931 if (*s != NULLCHAR) s++;
1933 while (*s != NULLCHAR && *s != '\033') {
1941 char *variantNames[] = VARIANT_NAMES;
1946 return variantNames[v];
1950 /* Identify a variant from the strings the chess servers use or the
1951 PGN Variant tag names we use. */
1958 VariantClass v = VariantNormal;
1959 int i, found = FALSE;
1965 /* [HGM] skip over optional board-size prefixes */
1966 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1967 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1968 while( *e++ != '_');
1971 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1975 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1976 if (StrCaseStr(e, variantNames[i])) {
1977 v = (VariantClass) i;
1984 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1985 || StrCaseStr(e, "wild/fr")
1986 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1987 v = VariantFischeRandom;
1988 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1989 (i = 1, p = StrCaseStr(e, "w"))) {
1991 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1998 case 0: /* FICS only, actually */
2000 /* Castling legal even if K starts on d-file */
2001 v = VariantWildCastle;
2006 /* Castling illegal even if K & R happen to start in
2007 normal positions. */
2008 v = VariantNoCastle;
2021 /* Castling legal iff K & R start in normal positions */
2027 /* Special wilds for position setup; unclear what to do here */
2028 v = VariantLoadable;
2031 /* Bizarre ICC game */
2032 v = VariantTwoKings;
2035 v = VariantKriegspiel;
2041 v = VariantFischeRandom;
2044 v = VariantCrazyhouse;
2047 v = VariantBughouse;
2053 /* Not quite the same as FICS suicide! */
2054 v = VariantGiveaway;
2060 v = VariantShatranj;
2063 /* Temporary names for future ICC types. The name *will* change in
2064 the next xboard/WinBoard release after ICC defines it. */
2102 v = VariantCapablanca;
2105 v = VariantKnightmate;
2111 v = VariantCylinder;
2117 v = VariantCapaRandom;
2120 v = VariantBerolina;
2132 /* Found "wild" or "w" in the string but no number;
2133 must assume it's normal chess. */
2137 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2138 if( (len > MSG_SIZ) && appData.debugMode )
2139 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2141 DisplayError(buf, 0);
2147 if (appData.debugMode) {
2148 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2149 e, wnum, VariantName(v));
2154 static int leftover_start = 0, leftover_len = 0;
2155 char star_match[STAR_MATCH_N][MSG_SIZ];
2157 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2158 advance *index beyond it, and set leftover_start to the new value of
2159 *index; else return FALSE. If pattern contains the character '*', it
2160 matches any sequence of characters not containing '\r', '\n', or the
2161 character following the '*' (if any), and the matched sequence(s) are
2162 copied into star_match.
2165 looking_at(buf, index, pattern)
2170 char *bufp = &buf[*index], *patternp = pattern;
2172 char *matchp = star_match[0];
2175 if (*patternp == NULLCHAR) {
2176 *index = leftover_start = bufp - buf;
2180 if (*bufp == NULLCHAR) return FALSE;
2181 if (*patternp == '*') {
2182 if (*bufp == *(patternp + 1)) {
2184 matchp = star_match[++star_count];
2188 } else if (*bufp == '\n' || *bufp == '\r') {
2190 if (*patternp == NULLCHAR)
2195 *matchp++ = *bufp++;
2199 if (*patternp != *bufp) return FALSE;
2206 SendToPlayer(data, length)
2210 int error, outCount;
2211 outCount = OutputToProcess(NoProc, data, length, &error);
2212 if (outCount < length) {
2213 DisplayFatalError(_("Error writing to display"), error, 1);
2218 PackHolding(packed, holding)
2230 switch (runlength) {
2241 sprintf(q, "%d", runlength);
2253 /* Telnet protocol requests from the front end */
2255 TelnetRequest(ddww, option)
2256 unsigned char ddww, option;
2258 unsigned char msg[3];
2259 int outCount, outError;
2261 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2263 if (appData.debugMode) {
2264 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2280 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2289 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2292 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2297 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2299 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2306 if (!appData.icsActive) return;
2307 TelnetRequest(TN_DO, TN_ECHO);
2313 if (!appData.icsActive) return;
2314 TelnetRequest(TN_DONT, TN_ECHO);
2318 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2320 /* put the holdings sent to us by the server on the board holdings area */
2321 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2325 if(gameInfo.holdingsWidth < 2) return;
2326 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2327 return; // prevent overwriting by pre-board holdings
2329 if( (int)lowestPiece >= BlackPawn ) {
2332 holdingsStartRow = BOARD_HEIGHT-1;
2335 holdingsColumn = BOARD_WIDTH-1;
2336 countsColumn = BOARD_WIDTH-2;
2337 holdingsStartRow = 0;
2341 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2342 board[i][holdingsColumn] = EmptySquare;
2343 board[i][countsColumn] = (ChessSquare) 0;
2345 while( (p=*holdings++) != NULLCHAR ) {
2346 piece = CharToPiece( ToUpper(p) );
2347 if(piece == EmptySquare) continue;
2348 /*j = (int) piece - (int) WhitePawn;*/
2349 j = PieceToNumber(piece);
2350 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2351 if(j < 0) continue; /* should not happen */
2352 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2353 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2354 board[holdingsStartRow+j*direction][countsColumn]++;
2360 VariantSwitch(Board board, VariantClass newVariant)
2362 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2363 static Board oldBoard;
2365 startedFromPositionFile = FALSE;
2366 if(gameInfo.variant == newVariant) return;
2368 /* [HGM] This routine is called each time an assignment is made to
2369 * gameInfo.variant during a game, to make sure the board sizes
2370 * are set to match the new variant. If that means adding or deleting
2371 * holdings, we shift the playing board accordingly
2372 * This kludge is needed because in ICS observe mode, we get boards
2373 * of an ongoing game without knowing the variant, and learn about the
2374 * latter only later. This can be because of the move list we requested,
2375 * in which case the game history is refilled from the beginning anyway,
2376 * but also when receiving holdings of a crazyhouse game. In the latter
2377 * case we want to add those holdings to the already received position.
2381 if (appData.debugMode) {
2382 fprintf(debugFP, "Switch board from %s to %s\n",
2383 VariantName(gameInfo.variant), VariantName(newVariant));
2384 setbuf(debugFP, NULL);
2386 shuffleOpenings = 0; /* [HGM] shuffle */
2387 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2391 newWidth = 9; newHeight = 9;
2392 gameInfo.holdingsSize = 7;
2393 case VariantBughouse:
2394 case VariantCrazyhouse:
2395 newHoldingsWidth = 2; break;
2399 newHoldingsWidth = 2;
2400 gameInfo.holdingsSize = 8;
2403 case VariantCapablanca:
2404 case VariantCapaRandom:
2407 newHoldingsWidth = gameInfo.holdingsSize = 0;
2410 if(newWidth != gameInfo.boardWidth ||
2411 newHeight != gameInfo.boardHeight ||
2412 newHoldingsWidth != gameInfo.holdingsWidth ) {
2414 /* shift position to new playing area, if needed */
2415 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2416 for(i=0; i<BOARD_HEIGHT; i++)
2417 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2418 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2420 for(i=0; i<newHeight; i++) {
2421 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2422 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2424 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2425 for(i=0; i<BOARD_HEIGHT; i++)
2426 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2427 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2430 gameInfo.boardWidth = newWidth;
2431 gameInfo.boardHeight = newHeight;
2432 gameInfo.holdingsWidth = newHoldingsWidth;
2433 gameInfo.variant = newVariant;
2434 InitDrawingSizes(-2, 0);
2435 } else gameInfo.variant = newVariant;
2436 CopyBoard(oldBoard, board); // remember correctly formatted board
2437 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2438 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2441 static int loggedOn = FALSE;
2443 /*-- Game start info cache: --*/
2445 char gs_kind[MSG_SIZ];
2446 static char player1Name[128] = "";
2447 static char player2Name[128] = "";
2448 static char cont_seq[] = "\n\\ ";
2449 static int player1Rating = -1;
2450 static int player2Rating = -1;
2451 /*----------------------------*/
2453 ColorClass curColor = ColorNormal;
2454 int suppressKibitz = 0;
2457 Boolean soughtPending = FALSE;
2458 Boolean seekGraphUp;
2459 #define MAX_SEEK_ADS 200
2461 char *seekAdList[MAX_SEEK_ADS];
2462 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2463 float tcList[MAX_SEEK_ADS];
2464 char colorList[MAX_SEEK_ADS];
2465 int nrOfSeekAds = 0;
2466 int minRating = 1010, maxRating = 2800;
2467 int hMargin = 10, vMargin = 20, h, w;
2468 extern int squareSize, lineGap;
2473 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2474 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2475 if(r < minRating+100 && r >=0 ) r = minRating+100;
2476 if(r > maxRating) r = maxRating;
2477 if(tc < 1.) tc = 1.;
2478 if(tc > 95.) tc = 95.;
2479 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2480 y = ((double)r - minRating)/(maxRating - minRating)
2481 * (h-vMargin-squareSize/8-1) + vMargin;
2482 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2483 if(strstr(seekAdList[i], " u ")) color = 1;
2484 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2485 !strstr(seekAdList[i], "bullet") &&
2486 !strstr(seekAdList[i], "blitz") &&
2487 !strstr(seekAdList[i], "standard") ) color = 2;
2488 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2489 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2493 AddAd(char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2495 char buf[MSG_SIZ], *ext = "";
2496 VariantClass v = StringToVariant(type);
2497 if(strstr(type, "wild")) {
2498 ext = type + 4; // append wild number
2499 if(v == VariantFischeRandom) type = "chess960"; else
2500 if(v == VariantLoadable) type = "setup"; else
2501 type = VariantName(v);
2503 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2504 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2505 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2506 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2507 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2508 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2509 seekNrList[nrOfSeekAds] = nr;
2510 zList[nrOfSeekAds] = 0;
2511 seekAdList[nrOfSeekAds++] = StrSave(buf);
2512 if(plot) PlotSeekAd(nrOfSeekAds-1);
2519 int x = xList[i], y = yList[i], d=squareSize/4, k;
2520 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2521 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2522 // now replot every dot that overlapped
2523 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2524 int xx = xList[k], yy = yList[k];
2525 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2526 DrawSeekDot(xx, yy, colorList[k]);
2531 RemoveSeekAd(int nr)
2534 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2536 if(seekAdList[i]) free(seekAdList[i]);
2537 seekAdList[i] = seekAdList[--nrOfSeekAds];
2538 seekNrList[i] = seekNrList[nrOfSeekAds];
2539 ratingList[i] = ratingList[nrOfSeekAds];
2540 colorList[i] = colorList[nrOfSeekAds];
2541 tcList[i] = tcList[nrOfSeekAds];
2542 xList[i] = xList[nrOfSeekAds];
2543 yList[i] = yList[nrOfSeekAds];
2544 zList[i] = zList[nrOfSeekAds];
2545 seekAdList[nrOfSeekAds] = NULL;
2551 MatchSoughtLine(char *line)
2553 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2554 int nr, base, inc, u=0; char dummy;
2556 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2557 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2559 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2560 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2561 // match: compact and save the line
2562 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2572 if(!seekGraphUp) return FALSE;
2573 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2574 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2576 DrawSeekBackground(0, 0, w, h);
2577 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2578 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2579 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2580 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2582 DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2585 snprintf(buf, MSG_SIZ, "%d", i);
2586 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2589 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2590 for(i=1; i<100; i+=(i<10?1:5)) {
2591 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2592 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2593 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2595 snprintf(buf, MSG_SIZ, "%d", i);
2596 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2599 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2603 int SeekGraphClick(ClickType click, int x, int y, int moving)
2605 static int lastDown = 0, displayed = 0, lastSecond;
2606 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2607 if(click == Release || moving) return FALSE;
2609 soughtPending = TRUE;
2610 SendToICS(ics_prefix);
2611 SendToICS("sought\n"); // should this be "sought all"?
2612 } else { // issue challenge based on clicked ad
2613 int dist = 10000; int i, closest = 0, second = 0;
2614 for(i=0; i<nrOfSeekAds; i++) {
2615 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2616 if(d < dist) { dist = d; closest = i; }
2617 second += (d - zList[i] < 120); // count in-range ads
2618 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2622 second = (second > 1);
2623 if(displayed != closest || second != lastSecond) {
2624 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2625 lastSecond = second; displayed = closest;
2627 if(click == Press) {
2628 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2631 } // on press 'hit', only show info
2632 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2633 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2634 SendToICS(ics_prefix);
2636 return TRUE; // let incoming board of started game pop down the graph
2637 } else if(click == Release) { // release 'miss' is ignored
2638 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2639 if(moving == 2) { // right up-click
2640 nrOfSeekAds = 0; // refresh graph
2641 soughtPending = TRUE;
2642 SendToICS(ics_prefix);
2643 SendToICS("sought\n"); // should this be "sought all"?
2646 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2647 // press miss or release hit 'pop down' seek graph
2648 seekGraphUp = FALSE;
2649 DrawPosition(TRUE, NULL);
2655 read_from_ics(isr, closure, data, count, error)
2662 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2663 #define STARTED_NONE 0
2664 #define STARTED_MOVES 1
2665 #define STARTED_BOARD 2
2666 #define STARTED_OBSERVE 3
2667 #define STARTED_HOLDINGS 4
2668 #define STARTED_CHATTER 5
2669 #define STARTED_COMMENT 6
2670 #define STARTED_MOVES_NOHIDE 7
2672 static int started = STARTED_NONE;
2673 static char parse[20000];
2674 static int parse_pos = 0;
2675 static char buf[BUF_SIZE + 1];
2676 static int firstTime = TRUE, intfSet = FALSE;
2677 static ColorClass prevColor = ColorNormal;
2678 static int savingComment = FALSE;
2679 static int cmatch = 0; // continuation sequence match
2686 int backup; /* [DM] For zippy color lines */
2688 char talker[MSG_SIZ]; // [HGM] chat
2691 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2693 if (appData.debugMode) {
2695 fprintf(debugFP, "<ICS: ");
2696 show_bytes(debugFP, data, count);
2697 fprintf(debugFP, "\n");
2701 if (appData.debugMode) { int f = forwardMostMove;
2702 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2703 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2704 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2707 /* If last read ended with a partial line that we couldn't parse,
2708 prepend it to the new read and try again. */
2709 if (leftover_len > 0) {
2710 for (i=0; i<leftover_len; i++)
2711 buf[i] = buf[leftover_start + i];
2714 /* copy new characters into the buffer */
2715 bp = buf + leftover_len;
2716 buf_len=leftover_len;
2717 for (i=0; i<count; i++)
2720 if (data[i] == '\r')
2723 // join lines split by ICS?
2724 if (!appData.noJoin)
2727 Joining just consists of finding matches against the
2728 continuation sequence, and discarding that sequence
2729 if found instead of copying it. So, until a match
2730 fails, there's nothing to do since it might be the
2731 complete sequence, and thus, something we don't want
2734 if (data[i] == cont_seq[cmatch])
2737 if (cmatch == strlen(cont_seq))
2739 cmatch = 0; // complete match. just reset the counter
2742 it's possible for the ICS to not include the space
2743 at the end of the last word, making our [correct]
2744 join operation fuse two separate words. the server
2745 does this when the space occurs at the width setting.
2747 if (!buf_len || buf[buf_len-1] != ' ')
2758 match failed, so we have to copy what matched before
2759 falling through and copying this character. In reality,
2760 this will only ever be just the newline character, but
2761 it doesn't hurt to be precise.
2763 strncpy(bp, cont_seq, cmatch);
2775 buf[buf_len] = NULLCHAR;
2776 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2781 while (i < buf_len) {
2782 /* Deal with part of the TELNET option negotiation
2783 protocol. We refuse to do anything beyond the
2784 defaults, except that we allow the WILL ECHO option,
2785 which ICS uses to turn off password echoing when we are
2786 directly connected to it. We reject this option
2787 if localLineEditing mode is on (always on in xboard)
2788 and we are talking to port 23, which might be a real
2789 telnet server that will try to keep WILL ECHO on permanently.
2791 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2792 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2793 unsigned char option;
2795 switch ((unsigned char) buf[++i]) {
2797 if (appData.debugMode)
2798 fprintf(debugFP, "\n<WILL ");
2799 switch (option = (unsigned char) buf[++i]) {
2801 if (appData.debugMode)
2802 fprintf(debugFP, "ECHO ");
2803 /* Reply only if this is a change, according
2804 to the protocol rules. */
2805 if (remoteEchoOption) break;
2806 if (appData.localLineEditing &&
2807 atoi(appData.icsPort) == TN_PORT) {
2808 TelnetRequest(TN_DONT, TN_ECHO);
2811 TelnetRequest(TN_DO, TN_ECHO);
2812 remoteEchoOption = TRUE;
2816 if (appData.debugMode)
2817 fprintf(debugFP, "%d ", option);
2818 /* Whatever this is, we don't want it. */
2819 TelnetRequest(TN_DONT, option);
2824 if (appData.debugMode)
2825 fprintf(debugFP, "\n<WONT ");
2826 switch (option = (unsigned char) buf[++i]) {
2828 if (appData.debugMode)
2829 fprintf(debugFP, "ECHO ");
2830 /* Reply only if this is a change, according
2831 to the protocol rules. */
2832 if (!remoteEchoOption) break;
2834 TelnetRequest(TN_DONT, TN_ECHO);
2835 remoteEchoOption = FALSE;
2838 if (appData.debugMode)
2839 fprintf(debugFP, "%d ", (unsigned char) option);
2840 /* Whatever this is, it must already be turned
2841 off, because we never agree to turn on
2842 anything non-default, so according to the
2843 protocol rules, we don't reply. */
2848 if (appData.debugMode)
2849 fprintf(debugFP, "\n<DO ");
2850 switch (option = (unsigned char) buf[++i]) {
2852 /* Whatever this is, we refuse to do it. */
2853 if (appData.debugMode)
2854 fprintf(debugFP, "%d ", option);
2855 TelnetRequest(TN_WONT, option);
2860 if (appData.debugMode)
2861 fprintf(debugFP, "\n<DONT ");
2862 switch (option = (unsigned char) buf[++i]) {
2864 if (appData.debugMode)
2865 fprintf(debugFP, "%d ", option);
2866 /* Whatever this is, we are already not doing
2867 it, because we never agree to do anything
2868 non-default, so according to the protocol
2869 rules, we don't reply. */
2874 if (appData.debugMode)
2875 fprintf(debugFP, "\n<IAC ");
2876 /* Doubled IAC; pass it through */
2880 if (appData.debugMode)
2881 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2882 /* Drop all other telnet commands on the floor */
2885 if (oldi > next_out)
2886 SendToPlayer(&buf[next_out], oldi - next_out);
2892 /* OK, this at least will *usually* work */
2893 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2897 if (loggedOn && !intfSet) {
2898 if (ics_type == ICS_ICC) {
2899 snprintf(str, MSG_SIZ,
2900 "/set-quietly interface %s\n/set-quietly style 12\n",
2902 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2903 strcat(str, "/set-2 51 1\n/set seek 1\n");
2904 } else if (ics_type == ICS_CHESSNET) {
2905 snprintf(str, MSG_SIZ, "/style 12\n");
2907 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2908 strcat(str, programVersion);
2909 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2910 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2911 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2913 strcat(str, "$iset nohighlight 1\n");
2915 strcat(str, "$iset lock 1\n$style 12\n");
2918 NotifyFrontendLogin();
2922 if (started == STARTED_COMMENT) {
2923 /* Accumulate characters in comment */
2924 parse[parse_pos++] = buf[i];
2925 if (buf[i] == '\n') {
2926 parse[parse_pos] = NULLCHAR;
2927 if(chattingPartner>=0) {
2929 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2930 OutputChatMessage(chattingPartner, mess);
2931 chattingPartner = -1;
2932 next_out = i+1; // [HGM] suppress printing in ICS window
2934 if(!suppressKibitz) // [HGM] kibitz
2935 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2936 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2937 int nrDigit = 0, nrAlph = 0, j;
2938 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2939 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2940 parse[parse_pos] = NULLCHAR;
2941 // try to be smart: if it does not look like search info, it should go to
2942 // ICS interaction window after all, not to engine-output window.
2943 for(j=0; j<parse_pos; j++) { // count letters and digits
2944 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2945 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2946 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2948 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2949 int depth=0; float score;
2950 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2951 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2952 pvInfoList[forwardMostMove-1].depth = depth;
2953 pvInfoList[forwardMostMove-1].score = 100*score;
2955 OutputKibitz(suppressKibitz, parse);
2958 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2959 SendToPlayer(tmp, strlen(tmp));
2961 next_out = i+1; // [HGM] suppress printing in ICS window
2963 started = STARTED_NONE;
2965 /* Don't match patterns against characters in comment */
2970 if (started == STARTED_CHATTER) {
2971 if (buf[i] != '\n') {
2972 /* Don't match patterns against characters in chatter */
2976 started = STARTED_NONE;
2977 if(suppressKibitz) next_out = i+1;
2980 /* Kludge to deal with rcmd protocol */
2981 if (firstTime && looking_at(buf, &i, "\001*")) {
2982 DisplayFatalError(&buf[1], 0, 1);
2988 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2991 if (appData.debugMode)
2992 fprintf(debugFP, "ics_type %d\n", ics_type);
2995 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2996 ics_type = ICS_FICS;
2998 if (appData.debugMode)
2999 fprintf(debugFP, "ics_type %d\n", ics_type);
3002 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3003 ics_type = ICS_CHESSNET;
3005 if (appData.debugMode)
3006 fprintf(debugFP, "ics_type %d\n", ics_type);
3011 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3012 looking_at(buf, &i, "Logging you in as \"*\"") ||
3013 looking_at(buf, &i, "will be \"*\""))) {
3014 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3018 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3020 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3021 DisplayIcsInteractionTitle(buf);
3022 have_set_title = TRUE;
3025 /* skip finger notes */
3026 if (started == STARTED_NONE &&
3027 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3028 (buf[i] == '1' && buf[i+1] == '0')) &&
3029 buf[i+2] == ':' && buf[i+3] == ' ') {
3030 started = STARTED_CHATTER;
3036 // [HGM] seekgraph: recognize sought lines and end-of-sought message
3037 if(appData.seekGraph) {
3038 if(soughtPending && MatchSoughtLine(buf+i)) {
3039 i = strstr(buf+i, "rated") - buf;
3040 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3041 next_out = leftover_start = i;
3042 started = STARTED_CHATTER;
3043 suppressKibitz = TRUE;
3046 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3047 && looking_at(buf, &i, "* ads displayed")) {
3048 soughtPending = FALSE;
3053 if(appData.autoRefresh) {
3054 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3055 int s = (ics_type == ICS_ICC); // ICC format differs
3057 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3058 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3059 looking_at(buf, &i, "*% "); // eat prompt
3060 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3061 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3062 next_out = i; // suppress
3065 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3066 char *p = star_match[0];
3068 if(seekGraphUp) RemoveSeekAd(atoi(p));
3069 while(*p && *p++ != ' '); // next
3071 looking_at(buf, &i, "*% "); // eat prompt
3072 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3079 /* skip formula vars */
3080 if (started == STARTED_NONE &&
3081 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3082 started = STARTED_CHATTER;
3087 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3088 if (appData.autoKibitz && started == STARTED_NONE &&
3089 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3090 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3091 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3092 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3093 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3094 suppressKibitz = TRUE;
3095 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3097 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3098 && (gameMode == IcsPlayingWhite)) ||
3099 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3100 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3101 started = STARTED_CHATTER; // own kibitz we simply discard
3103 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3104 parse_pos = 0; parse[0] = NULLCHAR;
3105 savingComment = TRUE;
3106 suppressKibitz = gameMode != IcsObserving ? 2 :
3107 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3111 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3112 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3113 && atoi(star_match[0])) {
3114 // suppress the acknowledgements of our own autoKibitz
3116 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3117 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3118 SendToPlayer(star_match[0], strlen(star_match[0]));
3119 if(looking_at(buf, &i, "*% ")) // eat prompt
3120 suppressKibitz = FALSE;
3124 } // [HGM] kibitz: end of patch
3126 // [HGM] chat: intercept tells by users for which we have an open chat window
3128 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3129 looking_at(buf, &i, "* whispers:") ||
3130 looking_at(buf, &i, "* kibitzes:") ||
3131 looking_at(buf, &i, "* shouts:") ||
3132 looking_at(buf, &i, "* c-shouts:") ||
3133 looking_at(buf, &i, "--> * ") ||
3134 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3135 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3136 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3137 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3139 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3140 chattingPartner = -1;
3142 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3143 for(p=0; p<MAX_CHAT; p++) {
3144 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3145 talker[0] = '['; strcat(talker, "] ");
3146 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3147 chattingPartner = p; break;
3150 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3151 for(p=0; p<MAX_CHAT; p++) {
3152 if(!strcmp("kibitzes", chatPartner[p])) {
3153 talker[0] = '['; strcat(talker, "] ");
3154 chattingPartner = p; break;
3157 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3158 for(p=0; p<MAX_CHAT; p++) {
3159 if(!strcmp("whispers", chatPartner[p])) {
3160 talker[0] = '['; strcat(talker, "] ");
3161 chattingPartner = p; break;
3164 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3165 if(buf[i-8] == '-' && buf[i-3] == 't')
3166 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3167 if(!strcmp("c-shouts", chatPartner[p])) {
3168 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3169 chattingPartner = p; break;
3172 if(chattingPartner < 0)
3173 for(p=0; p<MAX_CHAT; p++) {
3174 if(!strcmp("shouts", chatPartner[p])) {
3175 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3176 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3177 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3178 chattingPartner = p; break;
3182 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3183 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3184 talker[0] = 0; Colorize(ColorTell, FALSE);
3185 chattingPartner = p; break;
3187 if(chattingPartner<0) i = oldi; else {
3188 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3189 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3190 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3191 started = STARTED_COMMENT;
3192 parse_pos = 0; parse[0] = NULLCHAR;
3193 savingComment = 3 + chattingPartner; // counts as TRUE
3194 suppressKibitz = TRUE;
3197 } // [HGM] chat: end of patch
3200 if (appData.zippyTalk || appData.zippyPlay) {
3201 /* [DM] Backup address for color zippy lines */
3203 if (loggedOn == TRUE)
3204 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3205 (appData.zippyPlay && ZippyMatch(buf, &backup)));
3207 } // [DM] 'else { ' deleted
3209 /* Regular tells and says */
3210 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3211 looking_at(buf, &i, "* (your partner) tells you: ") ||
3212 looking_at(buf, &i, "* says: ") ||
3213 /* Don't color "message" or "messages" output */
3214 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3215 looking_at(buf, &i, "*. * at *:*: ") ||
3216 looking_at(buf, &i, "--* (*:*): ") ||
3217 /* Message notifications (same color as tells) */
3218 looking_at(buf, &i, "* has left a message ") ||
3219 looking_at(buf, &i, "* just sent you a message:\n") ||
3220 /* Whispers and kibitzes */
3221 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3222 looking_at(buf, &i, "* kibitzes: ") ||
3224 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3226 if (tkind == 1 && strchr(star_match[0], ':')) {
3227 /* Avoid "tells you:" spoofs in channels */
3230 if (star_match[0][0] == NULLCHAR ||
3231 strchr(star_match[0], ' ') ||
3232 (tkind == 3 && strchr(star_match[1], ' '))) {
3233 /* Reject bogus matches */
3236 if (appData.colorize) {
3237 if (oldi > next_out) {
3238 SendToPlayer(&buf[next_out], oldi - next_out);
3243 Colorize(ColorTell, FALSE);
3244 curColor = ColorTell;
3247 Colorize(ColorKibitz, FALSE);
3248 curColor = ColorKibitz;
3251 p = strrchr(star_match[1], '(');
3258 Colorize(ColorChannel1, FALSE);
3259 curColor = ColorChannel1;
3261 Colorize(ColorChannel, FALSE);
3262 curColor = ColorChannel;
3266 curColor = ColorNormal;
3270 if (started == STARTED_NONE && appData.autoComment &&
3271 (gameMode == IcsObserving ||
3272 gameMode == IcsPlayingWhite ||
3273 gameMode == IcsPlayingBlack)) {
3274 parse_pos = i - oldi;
3275 memcpy(parse, &buf[oldi], parse_pos);
3276 parse[parse_pos] = NULLCHAR;
3277 started = STARTED_COMMENT;
3278 savingComment = TRUE;
3280 started = STARTED_CHATTER;
3281 savingComment = FALSE;
3288 if (looking_at(buf, &i, "* s-shouts: ") ||
3289 looking_at(buf, &i, "* c-shouts: ")) {
3290 if (appData.colorize) {
3291 if (oldi > next_out) {
3292 SendToPlayer(&buf[next_out], oldi - next_out);
3295 Colorize(ColorSShout, FALSE);
3296 curColor = ColorSShout;
3299 started = STARTED_CHATTER;
3303 if (looking_at(buf, &i, "--->")) {
3308 if (looking_at(buf, &i, "* shouts: ") ||
3309 looking_at(buf, &i, "--> ")) {
3310 if (appData.colorize) {
3311 if (oldi > next_out) {
3312 SendToPlayer(&buf[next_out], oldi - next_out);
3315 Colorize(ColorShout, FALSE);
3316 curColor = ColorShout;
3319 started = STARTED_CHATTER;
3323 if (looking_at( buf, &i, "Challenge:")) {
3324 if (appData.colorize) {
3325 if (oldi > next_out) {
3326 SendToPlayer(&buf[next_out], oldi - next_out);
3329 Colorize(ColorChallenge, FALSE);
3330 curColor = ColorChallenge;
3336 if (looking_at(buf, &i, "* offers you") ||
3337 looking_at(buf, &i, "* offers to be") ||
3338 looking_at(buf, &i, "* would like to") ||
3339 looking_at(buf, &i, "* requests to") ||
3340 looking_at(buf, &i, "Your opponent offers") ||
3341 looking_at(buf, &i, "Your opponent requests")) {
3343 if (appData.colorize) {
3344 if (oldi > next_out) {
3345 SendToPlayer(&buf[next_out], oldi - next_out);
3348 Colorize(ColorRequest, FALSE);
3349 curColor = ColorRequest;
3354 if (looking_at(buf, &i, "* (*) seeking")) {
3355 if (appData.colorize) {
3356 if (oldi > next_out) {
3357 SendToPlayer(&buf[next_out], oldi - next_out);
3360 Colorize(ColorSeek, FALSE);
3361 curColor = ColorSeek;
3366 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3368 if (looking_at(buf, &i, "\\ ")) {
3369 if (prevColor != ColorNormal) {
3370 if (oldi > next_out) {
3371 SendToPlayer(&buf[next_out], oldi - next_out);
3374 Colorize(prevColor, TRUE);
3375 curColor = prevColor;
3377 if (savingComment) {
3378 parse_pos = i - oldi;
3379 memcpy(parse, &buf[oldi], parse_pos);
3380 parse[parse_pos] = NULLCHAR;
3381 started = STARTED_COMMENT;
3382 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3383 chattingPartner = savingComment - 3; // kludge to remember the box
3385 started = STARTED_CHATTER;
3390 if (looking_at(buf, &i, "Black Strength :") ||
3391 looking_at(buf, &i, "<<< style 10 board >>>") ||
3392 looking_at(buf, &i, "<10>") ||
3393 looking_at(buf, &i, "#@#")) {
3394 /* Wrong board style */
3396 SendToICS(ics_prefix);
3397 SendToICS("set style 12\n");
3398 SendToICS(ics_prefix);
3399 SendToICS("refresh\n");
3403 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3405 have_sent_ICS_logon = 1;
3409 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3410 (looking_at(buf, &i, "\n<12> ") ||
3411 looking_at(buf, &i, "<12> "))) {
3413 if (oldi > next_out) {
3414 SendToPlayer(&buf[next_out], oldi - next_out);