2 * backend.c -- Common back end for X and Windows NT versions of
4 * Copyright 1991 by Digital Equipment Corporation, Maynard,
7 * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8 * 2007, 2008, 2009, 2010, 2011, 2012 Free Software Foundation, Inc.
10 * Enhancements Copyright 2005 Alessandro Scotti
12 * The following terms apply to Digital Equipment Corporation's copyright
14 * ------------------------------------------------------------------------
17 * Permission to use, copy, modify, and distribute this software and its
18 * documentation for any purpose and without fee is hereby granted,
19 * provided that the above copyright notice appear in all copies and that
20 * both that copyright notice and this permission notice appear in
21 * supporting documentation, and that the name of Digital not be
22 * used in advertising or publicity pertaining to distribution of the
23 * software without specific, written prior permission.
25 * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27 * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
32 * ------------------------------------------------------------------------
34 * The following terms apply to the enhanced version of XBoard
35 * distributed by the Free Software Foundation:
36 * ------------------------------------------------------------------------
38 * GNU XBoard is free software: you can redistribute it and/or modify
39 * it under the terms of the GNU General Public License as published by
40 * the Free Software Foundation, either version 3 of the License, or (at
41 * your option) any later version.
43 * GNU XBoard is distributed in the hope that it will be useful, but
44 * WITHOUT ANY WARRANTY; without even the implied warranty of
45 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46 * General Public License for more details.
48 * You should have received a copy of the GNU General Public License
49 * along with this program. If not, see http://www.gnu.org/licenses/. *
51 *------------------------------------------------------------------------
52 ** See the file ChangeLog for a revision history. */
54 /* [AS] Also useful here for debugging */
58 int flock(int f, int code);
75 #include <sys/types.h>
84 #else /* not STDC_HEADERS */
87 # else /* not HAVE_STRING_H */
89 # endif /* not HAVE_STRING_H */
90 #endif /* not STDC_HEADERS */
93 # include <sys/fcntl.h>
94 #else /* not HAVE_SYS_FCNTL_H */
97 # endif /* HAVE_FCNTL_H */
98 #endif /* not HAVE_SYS_FCNTL_H */
100 #if TIME_WITH_SYS_TIME
101 # include <sys/time.h>
105 # include <sys/time.h>
111 #if defined(_amigados) && !defined(__GNUC__)
116 extern int gettimeofday(struct timeval *, struct timezone *);
124 #include "frontend.h"
131 #include "backendz.h"
135 # define _(s) gettext (s)
136 # define N_(s) gettext_noop (s)
137 # define T_(s) gettext(s)
150 int establish P((void));
151 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
152 char *buf, int count, int error));
153 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
154 char *buf, int count, int error));
155 void ics_printf P((char *format, ...));
156 void SendToICS P((char *s));
157 void SendToICSDelayed P((char *s, long msdelay));
158 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
159 void HandleMachineMove P((char *message, ChessProgramState *cps));
160 int AutoPlayOneMove P((void));
161 int LoadGameOneMove P((ChessMove readAhead));
162 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
163 int LoadPositionFromFile P((char *filename, int n, char *title));
164 int SavePositionToFile P((char *filename));
165 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
166 void ShowMove P((int fromX, int fromY, int toX, int toY));
167 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
168 /*char*/int promoChar));
169 void BackwardInner P((int target));
170 void ForwardInner P((int target));
171 int Adjudicate P((ChessProgramState *cps));
172 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
173 void EditPositionDone P((Boolean fakeRights));
174 void PrintOpponents P((FILE *fp));
175 void PrintPosition P((FILE *fp, int move));
176 void StartChessProgram P((ChessProgramState *cps));
177 void SendToProgram P((char *message, ChessProgramState *cps));
178 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
179 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
180 char *buf, int count, int error));
181 void SendTimeControl P((ChessProgramState *cps,
182 int mps, long tc, int inc, int sd, int st));
183 char *TimeControlTagValue P((void));
184 void Attention P((ChessProgramState *cps));
185 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
186 int ResurrectChessProgram P((void));
187 void DisplayComment P((int moveNumber, char *text));
188 void DisplayMove P((int moveNumber));
190 void ParseGameHistory P((char *game));
191 void ParseBoard12 P((char *string));
192 void KeepAlive P((void));
193 void StartClocks P((void));
194 void SwitchClocks P((int nr));
195 void StopClocks P((void));
196 void ResetClocks P((void));
197 char *PGNDate P((void));
198 void SetGameInfo P((void));
199 int RegisterMove P((void));
200 void MakeRegisteredMove P((void));
201 void TruncateGame P((void));
202 int looking_at P((char *, int *, char *));
203 void CopyPlayerNameIntoFileName P((char **, char *));
204 char *SavePart P((char *));
205 int SaveGameOldStyle P((FILE *));
206 int SaveGamePGN P((FILE *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
220 void NextMatchGame P((void));
221 int NextTourneyGame P((int nr, int *swap));
222 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
223 FILE *WriteTourneyFile P((char *results, FILE *f));
224 void DisplayTwoMachinesTitle P(());
227 extern void ConsoleCreate();
230 ChessProgramState *WhitePlayer();
231 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
232 int VerifyDisplayMode P(());
234 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
235 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
236 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
237 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
238 void ics_update_width P((int new_width));
239 extern char installDir[MSG_SIZ];
240 VariantClass startVariant; /* [HGM] nicks: initial variant */
243 extern int tinyLayout, smallLayout;
244 ChessProgramStats programStats;
245 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
247 static int exiting = 0; /* [HGM] moved to top */
248 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
249 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
250 Board partnerBoard; /* [HGM] bughouse: for peeking at partner game */
251 int partnerHighlight[2];
252 Boolean partnerBoardValid = 0;
253 char partnerStatus[MSG_SIZ];
255 Boolean originalFlip;
256 Boolean twoBoards = 0;
257 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
258 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
259 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
260 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
261 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
262 int opponentKibitzes;
263 int lastSavedGame; /* [HGM] save: ID of game */
264 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
265 extern int chatCount;
267 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
268 char lastMsg[MSG_SIZ];
269 ChessSquare pieceSweep = EmptySquare;
270 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
271 int promoDefaultAltered;
273 /* States for ics_getting_history */
275 #define H_REQUESTED 1
276 #define H_GOT_REQ_HEADER 2
277 #define H_GOT_UNREQ_HEADER 3
278 #define H_GETTING_MOVES 4
279 #define H_GOT_UNWANTED_HEADER 5
281 /* whosays values for GameEnds */
290 /* Maximum number of games in a cmail message */
291 #define CMAIL_MAX_GAMES 20
293 /* Different types of move when calling RegisterMove */
295 #define CMAIL_RESIGN 1
297 #define CMAIL_ACCEPT 3
299 /* Different types of result to remember for each game */
300 #define CMAIL_NOT_RESULT 0
301 #define CMAIL_OLD_RESULT 1
302 #define CMAIL_NEW_RESULT 2
304 /* Telnet protocol constants */
315 safeStrCpy (char *dst, const char *src, size_t count)
318 assert( dst != NULL );
319 assert( src != NULL );
322 for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
323 if( i == count && dst[count-1] != NULLCHAR)
325 dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
326 if(appData.debugMode)
327 fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
333 /* Some compiler can't cast u64 to double
334 * This function do the job for us:
336 * We use the highest bit for cast, this only
337 * works if the highest bit is not
338 * in use (This should not happen)
340 * We used this for all compiler
343 u64ToDouble (u64 value)
346 u64 tmp = value & u64Const(0x7fffffffffffffff);
347 r = (double)(s64)tmp;
348 if (value & u64Const(0x8000000000000000))
349 r += 9.2233720368547758080e18; /* 2^63 */
353 /* Fake up flags for now, as we aren't keeping track of castling
354 availability yet. [HGM] Change of logic: the flag now only
355 indicates the type of castlings allowed by the rule of the game.
356 The actual rights themselves are maintained in the array
357 castlingRights, as part of the game history, and are not probed
363 int flags = F_ALL_CASTLE_OK;
364 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
365 switch (gameInfo.variant) {
367 flags &= ~F_ALL_CASTLE_OK;
368 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
369 flags |= F_IGNORE_CHECK;
371 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
374 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
376 case VariantKriegspiel:
377 flags |= F_KRIEGSPIEL_CAPTURE;
379 case VariantCapaRandom:
380 case VariantFischeRandom:
381 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
382 case VariantNoCastle:
383 case VariantShatranj:
387 flags &= ~F_ALL_CASTLE_OK;
395 FILE *gameFileFP, *debugFP, *serverFP;
396 char *currentDebugFile; // [HGM] debug split: to remember name
399 [AS] Note: sometimes, the sscanf() function is used to parse the input
400 into a fixed-size buffer. Because of this, we must be prepared to
401 receive strings as long as the size of the input buffer, which is currently
402 set to 4K for Windows and 8K for the rest.
403 So, we must either allocate sufficiently large buffers here, or
404 reduce the size of the input buffer in the input reading part.
407 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
408 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
409 char thinkOutput1[MSG_SIZ*10];
411 ChessProgramState first, second, pairing;
413 /* premove variables */
416 int premoveFromX = 0;
417 int premoveFromY = 0;
418 int premovePromoChar = 0;
420 Boolean alarmSounded;
421 /* end premove variables */
423 char *ics_prefix = "$";
424 int ics_type = ICS_GENERIC;
426 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
427 int pauseExamForwardMostMove = 0;
428 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
429 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
430 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
431 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
432 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
433 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
434 int whiteFlag = FALSE, blackFlag = FALSE;
435 int userOfferedDraw = FALSE;
436 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
437 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
438 int cmailMoveType[CMAIL_MAX_GAMES];
439 long ics_clock_paused = 0;
440 ProcRef icsPR = NoProc, cmailPR = NoProc;
441 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
442 GameMode gameMode = BeginningOfGame;
443 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
444 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
445 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
446 int hiddenThinkOutputState = 0; /* [AS] */
447 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
448 int adjudicateLossPlies = 6;
449 char white_holding[64], black_holding[64];
450 TimeMark lastNodeCountTime;
451 long lastNodeCount=0;
452 int shiftKey; // [HGM] set by mouse handler
454 int have_sent_ICS_logon = 0;
456 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
457 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
458 Boolean adjustedClock;
459 long timeControl_2; /* [AS] Allow separate time controls */
460 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
461 long timeRemaining[2][MAX_MOVES];
462 int matchGame = 0, nextGame = 0, roundNr = 0;
463 Boolean waitingForGame = FALSE;
464 TimeMark programStartTime, pauseStart;
465 char ics_handle[MSG_SIZ];
466 int have_set_title = 0;
468 /* animateTraining preserves the state of appData.animate
469 * when Training mode is activated. This allows the
470 * response to be animated when appData.animate == TRUE and
471 * appData.animateDragging == TRUE.
473 Boolean animateTraining;
479 Board boards[MAX_MOVES];
480 /* [HGM] Following 7 needed for accurate legality tests: */
481 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
482 signed char initialRights[BOARD_FILES];
483 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
484 int initialRulePlies, FENrulePlies;
485 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
487 Boolean shuffleOpenings;
488 int mute; // mute all sounds
490 // [HGM] vari: next 12 to save and restore variations
491 #define MAX_VARIATIONS 10
492 int framePtr = MAX_MOVES-1; // points to free stack entry
494 int savedFirst[MAX_VARIATIONS];
495 int savedLast[MAX_VARIATIONS];
496 int savedFramePtr[MAX_VARIATIONS];
497 char *savedDetails[MAX_VARIATIONS];
498 ChessMove savedResult[MAX_VARIATIONS];
500 void PushTail P((int firstMove, int lastMove));
501 Boolean PopTail P((Boolean annotate));
502 void PushInner P((int firstMove, int lastMove));
503 void PopInner P((Boolean annotate));
504 void CleanupTail P((void));
506 ChessSquare FIDEArray[2][BOARD_FILES] = {
507 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
508 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
509 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
510 BlackKing, BlackBishop, BlackKnight, BlackRook }
513 ChessSquare twoKingsArray[2][BOARD_FILES] = {
514 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
515 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
516 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
517 BlackKing, BlackKing, BlackKnight, BlackRook }
520 ChessSquare KnightmateArray[2][BOARD_FILES] = {
521 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
522 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
523 { BlackRook, BlackMan, BlackBishop, BlackQueen,
524 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
527 ChessSquare SpartanArray[2][BOARD_FILES] = {
528 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
529 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
530 { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
531 BlackDragon, BlackKing, BlackAngel, BlackAlfil }
534 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
535 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
536 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
537 { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
538 BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
541 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
542 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
543 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
544 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
545 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
548 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
549 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
550 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
551 { BlackRook, BlackKnight, BlackMan, BlackFerz,
552 BlackKing, BlackMan, BlackKnight, BlackRook }
556 #if (BOARD_FILES>=10)
557 ChessSquare ShogiArray[2][BOARD_FILES] = {
558 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
559 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
560 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
561 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
564 ChessSquare XiangqiArray[2][BOARD_FILES] = {
565 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
566 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
567 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
568 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
571 ChessSquare CapablancaArray[2][BOARD_FILES] = {
572 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
573 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
574 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
575 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
578 ChessSquare GreatArray[2][BOARD_FILES] = {
579 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
580 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
581 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
582 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
585 ChessSquare JanusArray[2][BOARD_FILES] = {
586 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
587 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
588 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
589 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
592 ChessSquare GrandArray[2][BOARD_FILES] = {
593 { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
594 WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
595 { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
596 BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
600 ChessSquare GothicArray[2][BOARD_FILES] = {
601 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
602 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
603 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
604 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
607 #define GothicArray CapablancaArray
611 ChessSquare FalconArray[2][BOARD_FILES] = {
612 { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
613 WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
614 { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
615 BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
618 #define FalconArray CapablancaArray
621 #else // !(BOARD_FILES>=10)
622 #define XiangqiPosition FIDEArray
623 #define CapablancaArray FIDEArray
624 #define GothicArray FIDEArray
625 #define GreatArray FIDEArray
626 #endif // !(BOARD_FILES>=10)
628 #if (BOARD_FILES>=12)
629 ChessSquare CourierArray[2][BOARD_FILES] = {
630 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
631 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
632 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
633 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
635 #else // !(BOARD_FILES>=12)
636 #define CourierArray CapablancaArray
637 #endif // !(BOARD_FILES>=12)
640 Board initialPosition;
643 /* Convert str to a rating. Checks for special cases of "----",
645 "++++", etc. Also strips ()'s */
647 string_to_rating (char *str)
649 while(*str && !isdigit(*str)) ++str;
651 return 0; /* One of the special "no rating" cases */
659 /* Init programStats */
660 programStats.movelist[0] = 0;
661 programStats.depth = 0;
662 programStats.nr_moves = 0;
663 programStats.moves_left = 0;
664 programStats.nodes = 0;
665 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
666 programStats.score = 0;
667 programStats.got_only_move = 0;
668 programStats.got_fail = 0;
669 programStats.line_is_book = 0;
674 { // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
675 if (appData.firstPlaysBlack) {
676 first.twoMachinesColor = "black\n";
677 second.twoMachinesColor = "white\n";
679 first.twoMachinesColor = "white\n";
680 second.twoMachinesColor = "black\n";
683 first.other = &second;
684 second.other = &first;
687 if(appData.timeOddsMode) {
688 norm = appData.timeOdds[0];
689 if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
691 first.timeOdds = appData.timeOdds[0]/norm;
692 second.timeOdds = appData.timeOdds[1]/norm;
695 if(programVersion) free(programVersion);
696 if (appData.noChessProgram) {
697 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
698 sprintf(programVersion, "%s", PACKAGE_STRING);
700 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
701 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
702 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
707 UnloadEngine (ChessProgramState *cps)
709 /* Kill off first chess program */
710 if (cps->isr != NULL)
711 RemoveInputSource(cps->isr);
714 if (cps->pr != NoProc) {
716 DoSleep( appData.delayBeforeQuit );
717 SendToProgram("quit\n", cps);
718 DoSleep( appData.delayAfterQuit );
719 DestroyChildProcess(cps->pr, cps->useSigterm);
722 if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
726 ClearOptions (ChessProgramState *cps)
729 cps->nrOptions = cps->comboCnt = 0;
730 for(i=0; i<MAX_OPTIONS; i++) {
731 cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
732 cps->option[i].textValue = 0;
736 char *engineNames[] = {
737 /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
738 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
740 /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
741 such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
746 InitEngine (ChessProgramState *cps, int n)
747 { // [HGM] all engine initialiation put in a function that does one engine
751 cps->which = engineNames[n];
752 cps->maybeThinking = FALSE;
756 cps->sendDrawOffers = 1;
758 cps->program = appData.chessProgram[n];
759 cps->host = appData.host[n];
760 cps->dir = appData.directory[n];
761 cps->initString = appData.engInitString[n];
762 cps->computerString = appData.computerString[n];
763 cps->useSigint = TRUE;
764 cps->useSigterm = TRUE;
765 cps->reuse = appData.reuse[n];
766 cps->nps = appData.NPS[n]; // [HGM] nps: copy nodes per second
767 cps->useSetboard = FALSE;
769 cps->usePing = FALSE;
772 cps->usePlayother = FALSE;
773 cps->useColors = TRUE;
774 cps->useUsermove = FALSE;
775 cps->sendICS = FALSE;
776 cps->sendName = appData.icsActive;
777 cps->sdKludge = FALSE;
778 cps->stKludge = FALSE;
779 TidyProgramName(cps->program, cps->host, cps->tidy);
781 safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
782 cps->analysisSupport = 2; /* detect */
783 cps->analyzing = FALSE;
784 cps->initDone = FALSE;
786 /* New features added by Tord: */
787 cps->useFEN960 = FALSE;
788 cps->useOOCastle = TRUE;
789 /* End of new features added by Tord. */
790 cps->fenOverride = appData.fenOverride[n];
792 /* [HGM] time odds: set factor for each machine */
793 cps->timeOdds = appData.timeOdds[n];
795 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
796 cps->accumulateTC = appData.accumulateTC[n];
797 cps->maxNrOfSessions = 1;
802 cps->supportsNPS = UNKNOWN;
803 cps->memSize = FALSE;
804 cps->maxCores = FALSE;
805 cps->egtFormats[0] = NULLCHAR;
808 cps->optionSettings = appData.engOptions[n];
810 cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
811 cps->isUCI = appData.isUCI[n]; /* [AS] */
812 cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
814 if (appData.protocolVersion[n] > PROTOVER
815 || appData.protocolVersion[n] < 1)
820 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
821 appData.protocolVersion[n]);
822 if( (len >= MSG_SIZ) && appData.debugMode )
823 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
825 DisplayFatalError(buf, 0, 2);
829 cps->protocolVersion = appData.protocolVersion[n];
832 InitEngineUCI( installDir, cps ); // [HGM] moved here from winboard.c, to make available in xboard
833 ParseFeatures(appData.featureDefaults, cps);
836 ChessProgramState *savCps;
842 if(WaitForEngine(savCps, LoadEngine)) return;
843 CommonEngineInit(); // recalculate time odds
844 if(gameInfo.variant != StringToVariant(appData.variant)) {
845 // we changed variant when loading the engine; this forces us to reset
846 Reset(TRUE, savCps != &first);
847 EditGameEvent(); // for consistency with other path, as Reset changes mode
849 InitChessProgram(savCps, FALSE);
850 SendToProgram("force\n", savCps);
851 DisplayMessage("", "");
852 if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
853 for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
859 ReplaceEngine (ChessProgramState *cps, int n)
863 appData.noChessProgram = FALSE;
864 appData.clockMode = TRUE;
867 if(n) return; // only startup first engine immediately; second can wait
868 savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
872 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
873 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
875 static char resetOptions[] =
876 "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
877 "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
878 "-firstOptions \"\" -firstNPS -1 -fn \"\"";
881 FloatToFront(char **list, char *engineLine)
883 char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
885 if(appData.recentEngines <= 0) return;
886 TidyProgramName(engineLine, "localhost", tidy+1);
887 tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
888 strncpy(buf+1, *list, MSG_SIZ-50);
889 if(p = strstr(buf, tidy)) { // tidy name appears in list
890 q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
891 while(*p++ = *++q); // squeeze out
893 strcat(tidy, buf+1); // put list behind tidy name
894 p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
895 if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
896 ASSIGN(*list, tidy+1);
900 Load (ChessProgramState *cps, int i)
902 char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
903 if(engineLine && engineLine[0]) { // an engine was selected from the combo box
904 snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
905 SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
906 ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
907 appData.firstProtocolVersion = PROTOVER;
908 ParseArgsFromString(buf);
910 ReplaceEngine(cps, i);
911 FloatToFront(&appData.recentEngineList, engineLine);
915 while(q = strchr(p, SLASH)) p = q+1;
916 if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
917 if(engineDir[0] != NULLCHAR)
918 appData.directory[i] = engineDir;
919 else if(p != engineName) { // derive directory from engine path, when not given
921 appData.directory[i] = strdup(engineName);
923 if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
924 } else appData.directory[i] = ".";
926 if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
927 snprintf(command, MSG_SIZ, "%s %s", p, params);
930 appData.chessProgram[i] = strdup(p);
931 appData.isUCI[i] = isUCI;
932 appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
933 appData.hasOwnBookUCI[i] = hasBook;
934 if(!nickName[0]) useNick = FALSE;
935 if(useNick) ASSIGN(appData.pgnName[i], nickName);
939 q = firstChessProgramNames;
940 if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
941 quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
942 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
943 quote, p, quote, appData.directory[i],
944 useNick ? " -fn \"" : "",
945 useNick ? nickName : "",
947 v1 ? " -firstProtocolVersion 1" : "",
948 hasBook ? "" : " -fNoOwnBookUCI",
949 isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
950 storeVariant ? " -variant " : "",
951 storeVariant ? VariantName(gameInfo.variant) : "");
952 firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
953 snprintf(firstChessProgramNames, len, "%s%s", q, buf);
955 FloatToFront(&appData.recentEngineList, buf);
957 ReplaceEngine(cps, i);
963 int matched, min, sec;
965 * Parse timeControl resource
967 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
968 appData.movesPerSession)) {
970 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
971 DisplayFatalError(buf, 0, 2);
975 * Parse searchTime resource
977 if (*appData.searchTime != NULLCHAR) {
978 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
980 searchTime = min * 60;
981 } else if (matched == 2) {
982 searchTime = min * 60 + sec;
985 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
986 DisplayFatalError(buf, 0, 2);
995 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
996 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
998 GetTimeMark(&programStartTime);
999 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1000 appData.seedBase = random() + (random()<<15);
1001 pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1003 ClearProgramStats();
1004 programStats.ok_to_send = 1;
1005 programStats.seen_stat = 0;
1008 * Initialize game list
1014 * Internet chess server status
1016 if (appData.icsActive) {
1017 appData.matchMode = FALSE;
1018 appData.matchGames = 0;
1020 appData.noChessProgram = !appData.zippyPlay;
1022 appData.zippyPlay = FALSE;
1023 appData.zippyTalk = FALSE;
1024 appData.noChessProgram = TRUE;
1026 if (*appData.icsHelper != NULLCHAR) {
1027 appData.useTelnet = TRUE;
1028 appData.telnetProgram = appData.icsHelper;
1031 appData.zippyTalk = appData.zippyPlay = FALSE;
1034 /* [AS] Initialize pv info list [HGM] and game state */
1038 for( i=0; i<=framePtr; i++ ) {
1039 pvInfoList[i].depth = -1;
1040 boards[i][EP_STATUS] = EP_NONE;
1041 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1047 /* [AS] Adjudication threshold */
1048 adjudicateLossThreshold = appData.adjudicateLossThreshold;
1050 InitEngine(&first, 0);
1051 InitEngine(&second, 1);
1054 pairing.which = "pairing"; // pairing engine
1055 pairing.pr = NoProc;
1057 pairing.program = appData.pairingEngine;
1058 pairing.host = "localhost";
1061 if (appData.icsActive) {
1062 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
1063 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1064 appData.clockMode = FALSE;
1065 first.sendTime = second.sendTime = 0;
1069 /* Override some settings from environment variables, for backward
1070 compatibility. Unfortunately it's not feasible to have the env
1071 vars just set defaults, at least in xboard. Ugh.
1073 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1078 if (!appData.icsActive) {
1082 /* Check for variants that are supported only in ICS mode,
1083 or not at all. Some that are accepted here nevertheless
1084 have bugs; see comments below.
1086 VariantClass variant = StringToVariant(appData.variant);
1088 case VariantBughouse: /* need four players and two boards */
1089 case VariantKriegspiel: /* need to hide pieces and move details */
1090 /* case VariantFischeRandom: (Fabien: moved below) */
1091 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1092 if( (len >= MSG_SIZ) && appData.debugMode )
1093 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1095 DisplayFatalError(buf, 0, 2);
1098 case VariantUnknown:
1099 case VariantLoadable:
1109 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1110 if( (len >= MSG_SIZ) && appData.debugMode )
1111 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1113 DisplayFatalError(buf, 0, 2);
1116 case VariantXiangqi: /* [HGM] repetition rules not implemented */
1117 case VariantFairy: /* [HGM] TestLegality definitely off! */
1118 case VariantGothic: /* [HGM] should work */
1119 case VariantCapablanca: /* [HGM] should work */
1120 case VariantCourier: /* [HGM] initial forced moves not implemented */
1121 case VariantShogi: /* [HGM] could still mate with pawn drop */
1122 case VariantKnightmate: /* [HGM] should work */
1123 case VariantCylinder: /* [HGM] untested */
1124 case VariantFalcon: /* [HGM] untested */
1125 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1126 offboard interposition not understood */
1127 case VariantNormal: /* definitely works! */
1128 case VariantWildCastle: /* pieces not automatically shuffled */
1129 case VariantNoCastle: /* pieces not automatically shuffled */
1130 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1131 case VariantLosers: /* should work except for win condition,
1132 and doesn't know captures are mandatory */
1133 case VariantSuicide: /* should work except for win condition,
1134 and doesn't know captures are mandatory */
1135 case VariantGiveaway: /* should work except for win condition,
1136 and doesn't know captures are mandatory */
1137 case VariantTwoKings: /* should work */
1138 case VariantAtomic: /* should work except for win condition */
1139 case Variant3Check: /* should work except for win condition */
1140 case VariantShatranj: /* should work except for all win conditions */
1141 case VariantMakruk: /* should work except for draw countdown */
1142 case VariantBerolina: /* might work if TestLegality is off */
1143 case VariantCapaRandom: /* should work */
1144 case VariantJanus: /* should work */
1145 case VariantSuper: /* experimental */
1146 case VariantGreat: /* experimental, requires legality testing to be off */
1147 case VariantSChess: /* S-Chess, should work */
1148 case VariantGrand: /* should work */
1149 case VariantSpartan: /* should work */
1157 NextIntegerFromString (char ** str, long * value)
1162 while( *s == ' ' || *s == '\t' ) {
1168 if( *s >= '0' && *s <= '9' ) {
1169 while( *s >= '0' && *s <= '9' ) {
1170 *value = *value * 10 + (*s - '0');
1183 NextTimeControlFromString (char ** str, long * value)
1186 int result = NextIntegerFromString( str, &temp );
1189 *value = temp * 60; /* Minutes */
1190 if( **str == ':' ) {
1192 result = NextIntegerFromString( str, &temp );
1193 *value += temp; /* Seconds */
1201 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1202 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1203 int result = -1, type = 0; long temp, temp2;
1205 if(**str != ':') return -1; // old params remain in force!
1207 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1208 if( NextIntegerFromString( str, &temp ) ) return -1;
1209 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1212 /* time only: incremental or sudden-death time control */
1213 if(**str == '+') { /* increment follows; read it */
1215 if(**str == '!') type = *(*str)++; // Bronstein TC
1216 if(result = NextIntegerFromString( str, &temp2)) return -1;
1217 *inc = temp2 * 1000;
1218 if(**str == '.') { // read fraction of increment
1219 char *start = ++(*str);
1220 if(result = NextIntegerFromString( str, &temp2)) return -1;
1222 while(start++ < *str) temp2 /= 10;
1226 *moves = 0; *tc = temp * 1000; *incType = type;
1230 (*str)++; /* classical time control */
1231 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1243 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1244 { /* [HGM] get time to add from the multi-session time-control string */
1245 int incType, moves=1; /* kludge to force reading of first session */
1246 long time, increment;
1249 if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1251 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1252 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1253 if(movenr == -1) return time; /* last move before new session */
1254 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1255 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1256 if(!moves) return increment; /* current session is incremental */
1257 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1258 } while(movenr >= -1); /* try again for next session */
1260 return 0; // no new time quota on this move
1264 ParseTimeControl (char *tc, float ti, int mps)
1268 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1271 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1272 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1273 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1277 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1279 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1282 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1284 snprintf(buf, MSG_SIZ, ":%s", mytc);
1286 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1288 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1293 /* Parse second time control */
1296 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1304 timeControl_2 = tc2 * 1000;
1314 timeControl = tc1 * 1000;
1317 timeIncrement = ti * 1000; /* convert to ms */
1318 movesPerSession = 0;
1321 movesPerSession = mps;
1329 if (appData.debugMode) {
1330 fprintf(debugFP, "%s\n", programVersion);
1332 ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1334 set_cont_sequence(appData.wrapContSeq);
1335 if (appData.matchGames > 0) {
1336 appData.matchMode = TRUE;
1337 } else if (appData.matchMode) {
1338 appData.matchGames = 1;
1340 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1341 appData.matchGames = appData.sameColorGames;
1342 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1343 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1344 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1347 if (appData.noChessProgram || first.protocolVersion == 1) {
1350 /* kludge: allow timeout for initial "feature" commands */
1352 DisplayMessage("", _("Starting chess program"));
1353 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1358 CalculateIndex (int index, int gameNr)
1359 { // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1361 if(index > 0) return index; // fixed nmber
1362 if(index == 0) return 1;
1363 res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1364 if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1369 LoadGameOrPosition (int gameNr)
1370 { // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1371 if (*appData.loadGameFile != NULLCHAR) {
1372 if (!LoadGameFromFile(appData.loadGameFile,
1373 CalculateIndex(appData.loadGameIndex, gameNr),
1374 appData.loadGameFile, FALSE)) {
1375 DisplayFatalError(_("Bad game file"), 0, 1);
1378 } else if (*appData.loadPositionFile != NULLCHAR) {
1379 if (!LoadPositionFromFile(appData.loadPositionFile,
1380 CalculateIndex(appData.loadPositionIndex, gameNr),
1381 appData.loadPositionFile)) {
1382 DisplayFatalError(_("Bad position file"), 0, 1);
1390 ReserveGame (int gameNr, char resChar)
1392 FILE *tf = fopen(appData.tourneyFile, "r+");
1393 char *p, *q, c, buf[MSG_SIZ];
1394 if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1395 safeStrCpy(buf, lastMsg, MSG_SIZ);
1396 DisplayMessage(_("Pick new game"), "");
1397 flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1398 ParseArgsFromFile(tf);
1399 p = q = appData.results;
1400 if(appData.debugMode) {
1401 char *r = appData.participants;
1402 fprintf(debugFP, "results = '%s'\n", p);
1403 while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1404 fprintf(debugFP, "\n");
1406 while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1408 q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1409 safeStrCpy(q, p, strlen(p) + 2);
1410 if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1411 if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1412 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1413 if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1416 fseek(tf, -(strlen(p)+4), SEEK_END);
1418 if(c != '"') // depending on DOS or Unix line endings we can be one off
1419 fseek(tf, -(strlen(p)+2), SEEK_END);
1420 else fseek(tf, -(strlen(p)+3), SEEK_END);
1421 fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1422 DisplayMessage(buf, "");
1423 free(p); appData.results = q;
1424 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1425 (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1426 int round = appData.defaultMatchGames * appData.tourneyType;
1427 if(gameNr < 0 || appData.tourneyType < 1 || // gauntlet engine can always stay loaded as first engine
1428 appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1429 UnloadEngine(&first); // next game belongs to other pairing;
1430 UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1432 if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1436 MatchEvent (int mode)
1437 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1439 if(matchMode) { // already in match mode: switch it off
1441 if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1444 // if(gameMode != BeginningOfGame) {
1445 // DisplayError(_("You can only start a match from the initial position."), 0);
1449 if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1450 /* Set up machine vs. machine match */
1452 NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1453 if(appData.tourneyFile[0]) {
1455 if(nextGame > appData.matchGames) {
1457 if(strchr(appData.results, '*') == NULL) {
1459 appData.tourneyCycles++;
1460 if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1462 NextTourneyGame(-1, &dummy);
1464 if(nextGame <= appData.matchGames) {
1465 DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1467 ScheduleDelayedEvent(NextMatchGame, 10000);
1472 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1473 DisplayError(buf, 0);
1474 appData.tourneyFile[0] = 0;
1478 if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically
1479 DisplayFatalError(_("Can't have a match with no chess programs"),
1484 matchGame = roundNr = 1;
1485 first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1489 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1492 InitBackEnd3 P((void))
1494 GameMode initialMode;
1498 InitChessProgram(&first, startedFromSetupPosition);
1500 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1501 free(programVersion);
1502 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1503 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1504 FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1507 if (appData.icsActive) {
1509 /* [DM] Make a console window if needed [HGM] merged ifs */
1515 if (*appData.icsCommPort != NULLCHAR)
1516 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1517 appData.icsCommPort);
1519 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1520 appData.icsHost, appData.icsPort);
1522 if( (len >= MSG_SIZ) && appData.debugMode )
1523 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1525 DisplayFatalError(buf, err, 1);
1530 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1532 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1533 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1534 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1535 } else if (appData.noChessProgram) {
1541 if (*appData.cmailGameName != NULLCHAR) {
1543 OpenLoopback(&cmailPR);
1545 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1549 DisplayMessage("", "");
1550 if (StrCaseCmp(appData.initialMode, "") == 0) {
1551 initialMode = BeginningOfGame;
1552 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1553 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1554 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1555 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1558 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1559 initialMode = TwoMachinesPlay;
1560 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1561 initialMode = AnalyzeFile;
1562 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1563 initialMode = AnalyzeMode;
1564 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1565 initialMode = MachinePlaysWhite;
1566 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1567 initialMode = MachinePlaysBlack;
1568 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1569 initialMode = EditGame;
1570 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1571 initialMode = EditPosition;
1572 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1573 initialMode = Training;
1575 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1576 if( (len >= MSG_SIZ) && appData.debugMode )
1577 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1579 DisplayFatalError(buf, 0, 2);
1583 if (appData.matchMode) {
1584 if(appData.tourneyFile[0]) { // start tourney from command line
1586 if(f = fopen(appData.tourneyFile, "r")) {
1587 ParseArgsFromFile(f); // make sure tourney parmeters re known
1589 appData.clockMode = TRUE;
1591 } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1594 } else if (*appData.cmailGameName != NULLCHAR) {
1595 /* Set up cmail mode */
1596 ReloadCmailMsgEvent(TRUE);
1598 /* Set up other modes */
1599 if (initialMode == AnalyzeFile) {
1600 if (*appData.loadGameFile == NULLCHAR) {
1601 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1605 if (*appData.loadGameFile != NULLCHAR) {
1606 (void) LoadGameFromFile(appData.loadGameFile,
1607 appData.loadGameIndex,
1608 appData.loadGameFile, TRUE);
1609 } else if (*appData.loadPositionFile != NULLCHAR) {
1610 (void) LoadPositionFromFile(appData.loadPositionFile,
1611 appData.loadPositionIndex,
1612 appData.loadPositionFile);
1613 /* [HGM] try to make self-starting even after FEN load */
1614 /* to allow automatic setup of fairy variants with wtm */
1615 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1616 gameMode = BeginningOfGame;
1617 setboardSpoiledMachineBlack = 1;
1619 /* [HGM] loadPos: make that every new game uses the setup */
1620 /* from file as long as we do not switch variant */
1621 if(!blackPlaysFirst) {
1622 startedFromPositionFile = TRUE;
1623 CopyBoard(filePosition, boards[0]);
1626 if (initialMode == AnalyzeMode) {
1627 if (appData.noChessProgram) {
1628 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1631 if (appData.icsActive) {
1632 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1636 } else if (initialMode == AnalyzeFile) {
1637 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1638 ShowThinkingEvent();
1640 AnalysisPeriodicEvent(1);
1641 } else if (initialMode == MachinePlaysWhite) {
1642 if (appData.noChessProgram) {
1643 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1647 if (appData.icsActive) {
1648 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1652 MachineWhiteEvent();
1653 } else if (initialMode == MachinePlaysBlack) {
1654 if (appData.noChessProgram) {
1655 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1659 if (appData.icsActive) {
1660 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1664 MachineBlackEvent();
1665 } else if (initialMode == TwoMachinesPlay) {
1666 if (appData.noChessProgram) {
1667 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1671 if (appData.icsActive) {
1672 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1677 } else if (initialMode == EditGame) {
1679 } else if (initialMode == EditPosition) {
1680 EditPositionEvent();
1681 } else if (initialMode == Training) {
1682 if (*appData.loadGameFile == NULLCHAR) {
1683 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1692 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1694 DisplayBook(current+1);
1696 MoveHistorySet( movelist, first, last, current, pvInfoList );
1698 EvalGraphSet( first, last, current, pvInfoList );
1700 MakeEngineOutputTitle();
1704 * Establish will establish a contact to a remote host.port.
1705 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1706 * used to talk to the host.
1707 * Returns 0 if okay, error code if not.
1714 if (*appData.icsCommPort != NULLCHAR) {
1715 /* Talk to the host through a serial comm port */
1716 return OpenCommPort(appData.icsCommPort, &icsPR);
1718 } else if (*appData.gateway != NULLCHAR) {
1719 if (*appData.remoteShell == NULLCHAR) {
1720 /* Use the rcmd protocol to run telnet program on a gateway host */
1721 snprintf(buf, sizeof(buf), "%s %s %s",
1722 appData.telnetProgram, appData.icsHost, appData.icsPort);
1723 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1726 /* Use the rsh program to run telnet program on a gateway host */
1727 if (*appData.remoteUser == NULLCHAR) {
1728 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1729 appData.gateway, appData.telnetProgram,
1730 appData.icsHost, appData.icsPort);
1732 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1733 appData.remoteShell, appData.gateway,
1734 appData.remoteUser, appData.telnetProgram,
1735 appData.icsHost, appData.icsPort);
1737 return StartChildProcess(buf, "", &icsPR);
1740 } else if (appData.useTelnet) {
1741 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1744 /* TCP socket interface differs somewhat between
1745 Unix and NT; handle details in the front end.
1747 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1752 EscapeExpand (char *p, char *q)
1753 { // [HGM] initstring: routine to shape up string arguments
1754 while(*p++ = *q++) if(p[-1] == '\\')
1756 case 'n': p[-1] = '\n'; break;
1757 case 'r': p[-1] = '\r'; break;
1758 case 't': p[-1] = '\t'; break;
1759 case '\\': p[-1] = '\\'; break;
1760 case 0: *p = 0; return;
1761 default: p[-1] = q[-1]; break;
1766 show_bytes (FILE *fp, char *buf, int count)
1769 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1770 fprintf(fp, "\\%03o", *buf & 0xff);
1779 /* Returns an errno value */
1781 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1783 char buf[8192], *p, *q, *buflim;
1784 int left, newcount, outcount;
1786 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1787 *appData.gateway != NULLCHAR) {
1788 if (appData.debugMode) {
1789 fprintf(debugFP, ">ICS: ");
1790 show_bytes(debugFP, message, count);
1791 fprintf(debugFP, "\n");
1793 return OutputToProcess(pr, message, count, outError);
1796 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1803 if (appData.debugMode) {
1804 fprintf(debugFP, ">ICS: ");
1805 show_bytes(debugFP, buf, newcount);
1806 fprintf(debugFP, "\n");
1808 outcount = OutputToProcess(pr, buf, newcount, outError);
1809 if (outcount < newcount) return -1; /* to be sure */
1816 } else if (((unsigned char) *p) == TN_IAC) {
1817 *q++ = (char) TN_IAC;
1824 if (appData.debugMode) {
1825 fprintf(debugFP, ">ICS: ");
1826 show_bytes(debugFP, buf, newcount);
1827 fprintf(debugFP, "\n");
1829 outcount = OutputToProcess(pr, buf, newcount, outError);
1830 if (outcount < newcount) return -1; /* to be sure */
1835 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1837 int outError, outCount;
1838 static int gotEof = 0;
1840 /* Pass data read from player on to ICS */
1843 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1844 if (outCount < count) {
1845 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1847 } else if (count < 0) {
1848 RemoveInputSource(isr);
1849 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1850 } else if (gotEof++ > 0) {
1851 RemoveInputSource(isr);
1852 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1858 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1859 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1860 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1861 SendToICS("date\n");
1862 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1865 /* added routine for printf style output to ics */
1867 ics_printf (char *format, ...)
1869 char buffer[MSG_SIZ];
1872 va_start(args, format);
1873 vsnprintf(buffer, sizeof(buffer), format, args);
1874 buffer[sizeof(buffer)-1] = '\0';
1882 int count, outCount, outError;
1884 if (icsPR == NoProc) return;
1887 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1888 if (outCount < count) {
1889 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1893 /* This is used for sending logon scripts to the ICS. Sending
1894 without a delay causes problems when using timestamp on ICC
1895 (at least on my machine). */
1897 SendToICSDelayed (char *s, long msdelay)
1899 int count, outCount, outError;
1901 if (icsPR == NoProc) return;
1904 if (appData.debugMode) {
1905 fprintf(debugFP, ">ICS: ");
1906 show_bytes(debugFP, s, count);
1907 fprintf(debugFP, "\n");
1909 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1911 if (outCount < count) {
1912 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1917 /* Remove all highlighting escape sequences in s
1918 Also deletes any suffix starting with '('
1921 StripHighlightAndTitle (char *s)
1923 static char retbuf[MSG_SIZ];
1926 while (*s != NULLCHAR) {
1927 while (*s == '\033') {
1928 while (*s != NULLCHAR && !isalpha(*s)) s++;
1929 if (*s != NULLCHAR) s++;
1931 while (*s != NULLCHAR && *s != '\033') {
1932 if (*s == '(' || *s == '[') {
1943 /* Remove all highlighting escape sequences in s */
1945 StripHighlight (char *s)
1947 static char retbuf[MSG_SIZ];
1950 while (*s != NULLCHAR) {
1951 while (*s == '\033') {
1952 while (*s != NULLCHAR && !isalpha(*s)) s++;
1953 if (*s != NULLCHAR) s++;
1955 while (*s != NULLCHAR && *s != '\033') {
1963 char *variantNames[] = VARIANT_NAMES;
1965 VariantName (VariantClass v)
1967 return variantNames[v];
1971 /* Identify a variant from the strings the chess servers use or the
1972 PGN Variant tag names we use. */
1974 StringToVariant (char *e)
1978 VariantClass v = VariantNormal;
1979 int i, found = FALSE;
1985 /* [HGM] skip over optional board-size prefixes */
1986 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1987 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1988 while( *e++ != '_');
1991 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1995 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1996 if (StrCaseStr(e, variantNames[i])) {
1997 v = (VariantClass) i;
2004 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2005 || StrCaseStr(e, "wild/fr")
2006 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2007 v = VariantFischeRandom;
2008 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2009 (i = 1, p = StrCaseStr(e, "w"))) {
2011 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2018 case 0: /* FICS only, actually */
2020 /* Castling legal even if K starts on d-file */
2021 v = VariantWildCastle;
2026 /* Castling illegal even if K & R happen to start in
2027 normal positions. */
2028 v = VariantNoCastle;
2041 /* Castling legal iff K & R start in normal positions */
2047 /* Special wilds for position setup; unclear what to do here */
2048 v = VariantLoadable;
2051 /* Bizarre ICC game */
2052 v = VariantTwoKings;
2055 v = VariantKriegspiel;
2061 v = VariantFischeRandom;
2064 v = VariantCrazyhouse;
2067 v = VariantBughouse;
2073 /* Not quite the same as FICS suicide! */
2074 v = VariantGiveaway;
2080 v = VariantShatranj;
2083 /* Temporary names for future ICC types. The name *will* change in
2084 the next xboard/WinBoard release after ICC defines it. */
2122 v = VariantCapablanca;
2125 v = VariantKnightmate;
2131 v = VariantCylinder;
2137 v = VariantCapaRandom;
2140 v = VariantBerolina;
2152 /* Found "wild" or "w" in the string but no number;
2153 must assume it's normal chess. */
2157 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2158 if( (len >= MSG_SIZ) && appData.debugMode )
2159 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2161 DisplayError(buf, 0);
2167 if (appData.debugMode) {
2168 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2169 e, wnum, VariantName(v));
2174 static int leftover_start = 0, leftover_len = 0;
2175 char star_match[STAR_MATCH_N][MSG_SIZ];
2177 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2178 advance *index beyond it, and set leftover_start to the new value of
2179 *index; else return FALSE. If pattern contains the character '*', it
2180 matches any sequence of characters not containing '\r', '\n', or the
2181 character following the '*' (if any), and the matched sequence(s) are
2182 copied into star_match.
2185 looking_at ( char *buf, int *index, char *pattern)
2187 char *bufp = &buf[*index], *patternp = pattern;
2189 char *matchp = star_match[0];
2192 if (*patternp == NULLCHAR) {
2193 *index = leftover_start = bufp - buf;
2197 if (*bufp == NULLCHAR) return FALSE;
2198 if (*patternp == '*') {
2199 if (*bufp == *(patternp + 1)) {
2201 matchp = star_match[++star_count];
2205 } else if (*bufp == '\n' || *bufp == '\r') {
2207 if (*patternp == NULLCHAR)
2212 *matchp++ = *bufp++;
2216 if (*patternp != *bufp) return FALSE;
2223 SendToPlayer (char *data, int length)
2225 int error, outCount;
2226 outCount = OutputToProcess(NoProc, data, length, &error);
2227 if (outCount < length) {
2228 DisplayFatalError(_("Error writing to display"), error, 1);
2233 PackHolding (char packed[], char *holding)
2243 switch (runlength) {
2254 sprintf(q, "%d", runlength);
2266 /* Telnet protocol requests from the front end */
2268 TelnetRequest (unsigned char ddww, unsigned char option)
2270 unsigned char msg[3];
2271 int outCount, outError;
2273 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2275 if (appData.debugMode) {
2276 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2292 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2301 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2304 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2309 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2311 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2318 if (!appData.icsActive) return;
2319 TelnetRequest(TN_DO, TN_ECHO);
2325 if (!appData.icsActive) return;
2326 TelnetRequest(TN_DONT, TN_ECHO);
2330 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2332 /* put the holdings sent to us by the server on the board holdings area */
2333 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2337 if(gameInfo.holdingsWidth < 2) return;
2338 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2339 return; // prevent overwriting by pre-board holdings
2341 if( (int)lowestPiece >= BlackPawn ) {
2344 holdingsStartRow = BOARD_HEIGHT-1;
2347 holdingsColumn = BOARD_WIDTH-1;
2348 countsColumn = BOARD_WIDTH-2;
2349 holdingsStartRow = 0;
2353 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2354 board[i][holdingsColumn] = EmptySquare;
2355 board[i][countsColumn] = (ChessSquare) 0;
2357 while( (p=*holdings++) != NULLCHAR ) {
2358 piece = CharToPiece( ToUpper(p) );
2359 if(piece == EmptySquare) continue;
2360 /*j = (int) piece - (int) WhitePawn;*/
2361 j = PieceToNumber(piece);
2362 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2363 if(j < 0) continue; /* should not happen */
2364 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2365 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2366 board[holdingsStartRow+j*direction][countsColumn]++;
2372 VariantSwitch (Board board, VariantClass newVariant)
2374 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2375 static Board oldBoard;
2377 startedFromPositionFile = FALSE;
2378 if(gameInfo.variant == newVariant) return;
2380 /* [HGM] This routine is called each time an assignment is made to
2381 * gameInfo.variant during a game, to make sure the board sizes
2382 * are set to match the new variant. If that means adding or deleting
2383 * holdings, we shift the playing board accordingly
2384 * This kludge is needed because in ICS observe mode, we get boards
2385 * of an ongoing game without knowing the variant, and learn about the
2386 * latter only later. This can be because of the move list we requested,
2387 * in which case the game history is refilled from the beginning anyway,
2388 * but also when receiving holdings of a crazyhouse game. In the latter
2389 * case we want to add those holdings to the already received position.
2393 if (appData.debugMode) {
2394 fprintf(debugFP, "Switch board from %s to %s\n",
2395 VariantName(gameInfo.variant), VariantName(newVariant));
2396 setbuf(debugFP, NULL);
2398 shuffleOpenings = 0; /* [HGM] shuffle */
2399 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2403 newWidth = 9; newHeight = 9;
2404 gameInfo.holdingsSize = 7;
2405 case VariantBughouse:
2406 case VariantCrazyhouse:
2407 newHoldingsWidth = 2; break;
2411 newHoldingsWidth = 2;
2412 gameInfo.holdingsSize = 8;
2415 case VariantCapablanca:
2416 case VariantCapaRandom:
2419 newHoldingsWidth = gameInfo.holdingsSize = 0;
2422 if(newWidth != gameInfo.boardWidth ||
2423 newHeight != gameInfo.boardHeight ||
2424 newHoldingsWidth != gameInfo.holdingsWidth ) {
2426 /* shift position to new playing area, if needed */
2427 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2428 for(i=0; i<BOARD_HEIGHT; i++)
2429 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2430 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2432 for(i=0; i<newHeight; i++) {
2433 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2434 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2436 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2437 for(i=0; i<BOARD_HEIGHT; i++)
2438 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2439 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2442 gameInfo.boardWidth = newWidth;
2443 gameInfo.boardHeight = newHeight;
2444 gameInfo.holdingsWidth = newHoldingsWidth;
2445 gameInfo.variant = newVariant;
2446 InitDrawingSizes(-2, 0);
2447 } else gameInfo.variant = newVariant;
2448 CopyBoard(oldBoard, board); // remember correctly formatted board
2449 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2450 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2453 static int loggedOn = FALSE;
2455 /*-- Game start info cache: --*/
2457 char gs_kind[MSG_SIZ];
2458 static char player1Name[128] = "";
2459 static char player2Name[128] = "";
2460 static char cont_seq[] = "\n\\ ";
2461 static int player1Rating = -1;
2462 static int player2Rating = -1;
2463 /*----------------------------*/
2465 ColorClass curColor = ColorNormal;
2466 int suppressKibitz = 0;
2469 Boolean soughtPending = FALSE;
2470 Boolean seekGraphUp;
2471 #define MAX_SEEK_ADS 200
2473 char *seekAdList[MAX_SEEK_ADS];
2474 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2475 float tcList[MAX_SEEK_ADS];
2476 char colorList[MAX_SEEK_ADS];
2477 int nrOfSeekAds = 0;
2478 int minRating = 1010, maxRating = 2800;
2479 int hMargin = 10, vMargin = 20, h, w;
2480 extern int squareSize, lineGap;
2485 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2486 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2487 if(r < minRating+100 && r >=0 ) r = minRating+100;
2488 if(r > maxRating) r = maxRating;
2489 if(tc < 1.) tc = 1.;
2490 if(tc > 95.) tc = 95.;
2491 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2492 y = ((double)r - minRating)/(maxRating - minRating)
2493 * (h-vMargin-squareSize/8-1) + vMargin;
2494 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2495 if(strstr(seekAdList[i], " u ")) color = 1;
2496 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2497 !strstr(seekAdList[i], "bullet") &&
2498 !strstr(seekAdList[i], "blitz") &&
2499 !strstr(seekAdList[i], "standard") ) color = 2;
2500 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2501 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2505 AddAd (char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2507 char buf[MSG_SIZ], *ext = "";
2508 VariantClass v = StringToVariant(type);
2509 if(strstr(type, "wild")) {
2510 ext = type + 4; // append wild number
2511 if(v == VariantFischeRandom) type = "chess960"; else
2512 if(v == VariantLoadable) type = "setup"; else
2513 type = VariantName(v);
2515 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2516 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2517 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2518 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2519 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2520 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2521 seekNrList[nrOfSeekAds] = nr;
2522 zList[nrOfSeekAds] = 0;
2523 seekAdList[nrOfSeekAds++] = StrSave(buf);
2524 if(plot) PlotSeekAd(nrOfSeekAds-1);
2529 EraseSeekDot (int i)
2531 int x = xList[i], y = yList[i], d=squareSize/4, k;
2532 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2533 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2534 // now replot every dot that overlapped
2535 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2536 int xx = xList[k], yy = yList[k];
2537 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2538 DrawSeekDot(xx, yy, colorList[k]);
2543 RemoveSeekAd (int nr)
2546 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2548 if(seekAdList[i]) free(seekAdList[i]);
2549 seekAdList[i] = seekAdList[--nrOfSeekAds];
2550 seekNrList[i] = seekNrList[nrOfSeekAds];
2551 ratingList[i] = ratingList[nrOfSeekAds];
2552 colorList[i] = colorList[nrOfSeekAds];
2553 tcList[i] = tcList[nrOfSeekAds];
2554 xList[i] = xList[nrOfSeekAds];
2555 yList[i] = yList[nrOfSeekAds];
2556 zList[i] = zList[nrOfSeekAds];
2557 seekAdList[nrOfSeekAds] = NULL;
2563 MatchSoughtLine (char *line)
2565 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2566 int nr, base, inc, u=0; char dummy;
2568 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2569 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2571 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2572 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2573 // match: compact and save the line
2574 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2584 if(!seekGraphUp) return FALSE;
2585 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2586 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2588 DrawSeekBackground(0, 0, w, h);
2589 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2590 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2591 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2592 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2594 DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2597 snprintf(buf, MSG_SIZ, "%d", i);
2598 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2601 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2602 for(i=1; i<100; i+=(i<10?1:5)) {
2603 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2604 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2605 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2607 snprintf(buf, MSG_SIZ, "%d", i);
2608 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2611 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2616 SeekGraphClick (ClickType click, int x, int y, int moving)
2618 static int lastDown = 0, displayed = 0, lastSecond;
2619 if(y < 0) return FALSE;
2620 if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2621 (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2622 if(!seekGraphUp) return FALSE;
2623 seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2624 DrawPosition(TRUE, NULL);
2627 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2628 if(click == Release || moving) return FALSE;
2630 soughtPending = TRUE;
2631 SendToICS(ics_prefix);
2632 SendToICS("sought\n"); // should this be "sought all"?
2633 } else { // issue challenge based on clicked ad
2634 int dist = 10000; int i, closest = 0, second = 0;
2635 for(i=0; i<nrOfSeekAds; i++) {
2636 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2637 if(d < dist) { dist = d; closest = i; }
2638 second += (d - zList[i] < 120); // count in-range ads
2639 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2643 second = (second > 1);
2644 if(displayed != closest || second != lastSecond) {
2645 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2646 lastSecond = second; displayed = closest;
2648 if(click == Press) {
2649 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2652 } // on press 'hit', only show info
2653 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2654 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2655 SendToICS(ics_prefix);
2657 return TRUE; // let incoming board of started game pop down the graph
2658 } else if(click == Release) { // release 'miss' is ignored
2659 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2660 if(moving == 2) { // right up-click
2661 nrOfSeekAds = 0; // refresh graph
2662 soughtPending = TRUE;
2663 SendToICS(ics_prefix);
2664 SendToICS("sought\n"); // should this be "sought all"?
2667 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2668 // press miss or release hit 'pop down' seek graph
2669 seekGraphUp = FALSE;
2670 DrawPosition(TRUE, NULL);
2676 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2678 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2679 #define STARTED_NONE 0
2680 #define STARTED_MOVES 1
2681 #define STARTED_BOARD 2
2682 #define STARTED_OBSERVE 3
2683 #define STARTED_HOLDINGS 4
2684 #define STARTED_CHATTER 5
2685 #define STARTED_COMMENT 6
2686 #define STARTED_MOVES_NOHIDE 7
2688 static int started = STARTED_NONE;
2689 static char parse[20000];
2690 static int parse_pos = 0;
2691 static char buf[BUF_SIZE + 1];
2692 static int firstTime = TRUE, intfSet = FALSE;
2693 static ColorClass prevColor = ColorNormal;
2694 static int savingComment = FALSE;
2695 static int cmatch = 0; // continuation sequence match
2702 int backup; /* [DM] For zippy color lines */
2704 char talker[MSG_SIZ]; // [HGM] chat
2707 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2709 if (appData.debugMode) {
2711 fprintf(debugFP, "<ICS: ");
2712 show_bytes(debugFP, data, count);
2713 fprintf(debugFP, "\n");
2717 if (appData.debugMode) { int f = forwardMostMove;
2718 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2719 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2720 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2723 /* If last read ended with a partial line that we couldn't parse,
2724 prepend it to the new read and try again. */
2725 if (leftover_len > 0) {
2726 for (i=0; i<leftover_len; i++)
2727 buf[i] = buf[leftover_start + i];
2730 /* copy new characters into the buffer */
2731 bp = buf + leftover_len;
2732 buf_len=leftover_len;
2733 for (i=0; i<count; i++)
2736 if (data[i] == '\r')
2739 // join lines split by ICS?
2740 if (!appData.noJoin)
2743 Joining just consists of finding matches against the
2744 continuation sequence, and discarding that sequence
2745 if found instead of copying it. So, until a match
2746 fails, there's nothing to do since it might be the
2747 complete sequence, and thus, something we don't want
2750 if (data[i] == cont_seq[cmatch])
2753 if (cmatch == strlen(cont_seq))
2755 cmatch = 0; // complete match. just reset the counter
2758 it's possible for the ICS to not include the space
2759 at the end of the last word, making our [correct]
2760 join operation fuse two separate words. the server
2761 does this when the space occurs at the width setting.
2763 if (!buf_len || buf[buf_len-1] != ' ')
2774 match failed, so we have to copy what matched before
2775 falling through and copying this character. In reality,
2776 this will only ever be just the newline character, but
2777 it doesn't hurt to be precise.
2779 strncpy(bp, cont_seq, cmatch);
2791 buf[buf_len] = NULLCHAR;
2792 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2797 while (i < buf_len) {
2798 /* Deal with part of the TELNET option negotiation
2799 protocol. We refuse to do anything beyond the
2800 defaults, except that we allow the WILL ECHO option,
2801 which ICS uses to turn off password echoing when we are
2802 directly connected to it. We reject this option
2803 if localLineEditing mode is on (always on in xboard)
2804 and we are talking to port 23, which might be a real
2805 telnet server that will try to keep WILL ECHO on permanently.
2807 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2808 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2809 unsigned char option;
2811 switch ((unsigned char) buf[++i]) {
2813 if (appData.debugMode)
2814 fprintf(debugFP, "\n<WILL ");
2815 switch (option = (unsigned char) buf[++i]) {
2817 if (appData.debugMode)
2818 fprintf(debugFP, "ECHO ");
2819 /* Reply only if this is a change, according
2820 to the protocol rules. */
2821 if (remoteEchoOption) break;
2822 if (appData.localLineEditing &&
2823 atoi(appData.icsPort) == TN_PORT) {
2824 TelnetRequest(TN_DONT, TN_ECHO);
2827 TelnetRequest(TN_DO, TN_ECHO);
2828 remoteEchoOption = TRUE;
2832 if (appData.debugMode)
2833 fprintf(debugFP, "%d ", option);
2834 /* Whatever this is, we don't want it. */
2835 TelnetRequest(TN_DONT, option);
2840 if (appData.debugMode)
2841 fprintf(debugFP, "\n<WONT ");
2842 switch (option = (unsigned char) buf[++i]) {
2844 if (appData.debugMode)
2845 fprintf(debugFP, "ECHO ");
2846 /* Reply only if this is a change, according
2847 to the protocol rules. */
2848 if (!remoteEchoOption) break;
2850 TelnetRequest(TN_DONT, TN_ECHO);
2851 remoteEchoOption = FALSE;
2854 if (appData.debugMode)
2855 fprintf(debugFP, "%d ", (unsigned char) option);
2856 /* Whatever this is, it must already be turned
2857 off, because we never agree to turn on
2858 anything non-default, so according to the
2859 protocol rules, we don't reply. */
2864 if (appData.debugMode)
2865 fprintf(debugFP, "\n<DO ");
2866 switch (option = (unsigned char) buf[++i]) {
2868 /* Whatever this is, we refuse to do it. */
2869 if (appData.debugMode)
2870 fprintf(debugFP, "%d ", option);
2871 TelnetRequest(TN_WONT, option);
2876 if (appData.debugMode)
2877 fprintf(debugFP, "\n<DONT ");
2878 switch (option = (unsigned char) buf[++i]) {
2880 if (appData.debugMode)
2881 fprintf(debugFP, "%d ", option);
2882 /* Whatever this is, we are already not doing
2883 it, because we never agree to do anything
2884 non-default, so according to the protocol
2885 rules, we don't reply. */
2890 if (appData.debugMode)
2891 fprintf(debugFP, "\n<IAC ");
2892 /* Doubled IAC; pass it through */
2896 if (appData.debugMode)
2897 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2898 /* Drop all other telnet commands on the floor */
2901 if (oldi > next_out)
2902 SendToPlayer(&buf[next_out], oldi - next_out);
2908 /* OK, this at least will *usually* work */
2909 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2913 if (loggedOn && !intfSet) {
2914 if (ics_type == ICS_ICC) {
2915 snprintf(str, MSG_SIZ,
2916 "/set-quietly interface %s\n/set-quietly style 12\n",
2918 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2919 strcat(str, "/set-2 51 1\n/set seek 1\n");
2920 } else if (ics_type == ICS_CHESSNET) {
2921 snprintf(str, MSG_SIZ, "/style 12\n");
2923 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2924 strcat(str, programVersion);
2925 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2926 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2927 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2929 strcat(str, "$iset nohighlight 1\n");
2931 strcat(str, "$iset lock 1\n$style 12\n");
2934 NotifyFrontendLogin();
2938 if (started == STARTED_COMMENT) {
2939 /* Accumulate characters in comment */
2940 parse[parse_pos++] = buf[i];
2941 if (buf[i] == '\n') {
2942 parse[parse_pos] = NULLCHAR;
2943 if(chattingPartner>=0) {
2945 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2946 OutputChatMessage(chattingPartner, mess);
2947 chattingPartner = -1;
2948 next_out = i+1; // [HGM] suppress printing in ICS window
2950 if(!suppressKibitz) // [HGM] kibitz
2951 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2952 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2953 int nrDigit = 0, nrAlph = 0, j;
2954 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2955 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2956 parse[parse_pos] = NULLCHAR;
2957 // try to be smart: if it does not look like search info, it should go to
2958 // ICS interaction window after all, not to engine-output window.
2959 for(j=0; j<parse_pos; j++) { // count letters and digits
2960 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2961 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2962 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2964 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2965 int depth=0; float score;
2966 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2967 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2968 pvInfoList[forwardMostMove-1].depth = depth;
2969 pvInfoList[forwardMostMove-1].score = 100*score;
2971 OutputKibitz(suppressKibitz, parse);
2974 if(gameMode == IcsObserving) // restore original ICS messages
2975 snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
2977 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2978 SendToPlayer(tmp, strlen(tmp));
2980 next_out = i+1; // [HGM] suppress printing in ICS window
2982 started = STARTED_NONE;
2984 /* Don't match patterns against characters in comment */
2989 if (started == STARTED_CHATTER) {
2990 if (buf[i] != '\n') {
2991 /* Don't match patterns against characters in chatter */
2995 started = STARTED_NONE;
2996 if(suppressKibitz) next_out = i+1;
2999 /* Kludge to deal with rcmd protocol */
3000 if (firstTime && looking_at(buf, &i, "\001*")) {
3001 DisplayFatalError(&buf[1], 0, 1);
3007 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3010 if (appData.debugMode)
3011 fprintf(debugFP, "ics_type %d\n", ics_type);
3014 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3015 ics_type = ICS_FICS;
3017 if (appData.debugMode)
3018 fprintf(debugFP, "ics_type %d\n", ics_type);
3021 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3022 ics_type = ICS_CHESSNET;
3024 if (appData.debugMode)
3025 fprintf(debugFP, "ics_type %d\n", ics_type);
3030 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3031 looking_at(buf, &i, "Logging you in as \"*\"") ||
3032 looking_at(buf, &i, "will be \"*\""))) {
3033 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3037 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3039 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3040 DisplayIcsInteractionTitle(buf);
3041 have_set_title = TRUE;
3044 /* skip finger notes */
3045 if (started == STARTED_NONE &&
3046 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3047 (buf[i] == '1' && buf[i+1] == '0')) &&
3048 buf[i+2] == ':' && buf[i+3] == ' ') {
3049 started = STARTED_CHATTER;
3055 // [HGM] seekgraph: recognize sought lines and end-of-sought message
3056 if(appData.seekGraph) {
3057 if(soughtPending && MatchSoughtLine(buf+i)) {
3058 i = strstr(buf+i, "rated") - buf;
3059 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3060 next_out = leftover_start = i;
3061 started = STARTED_CHATTER;
3062 suppressKibitz = TRUE;
3065 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3066 && looking_at(buf, &i, "* ads displayed")) {
3067 soughtPending = FALSE;
3072 if(appData.autoRefresh) {
3073 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3074 int s = (ics_type == ICS_ICC); // ICC format differs
3076 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3077 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3078 looking_at(buf, &i, "*% "); // eat prompt
3079 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3080 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3081 next_out = i; // suppress
3084 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3085 char *p = star_match[0];
3087 if(seekGraphUp) RemoveSeekAd(atoi(p));
3088 while(*p && *p++ != ' '); // next
3090 looking_at(buf, &i, "*% "); // eat prompt
3091 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3098 /* skip formula vars */
3099 if (started == STARTED_NONE &&
3100 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3101 started = STARTED_CHATTER;
3106 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3107 if (appData.autoKibitz && started == STARTED_NONE &&
3108 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3109 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3110 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3111 looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3112 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3113 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3114 suppressKibitz = TRUE;
3115 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3117 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3118 && (gameMode == IcsPlayingWhite)) ||
3119 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3120 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3121 started = STARTED_CHATTER; // own kibitz we simply discard
3123 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3124 parse_pos = 0; parse[0] = NULLCHAR;
3125 savingComment = TRUE;
3126 suppressKibitz = gameMode != IcsObserving ? 2 :
3127 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3131 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3132 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3133 && atoi(star_match[0])) {
3134 // suppress the acknowledgements of our own autoKibitz
3136 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3137 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3138 SendToPlayer(star_match[0], strlen(star_match[0]));
3139 if(looking_at(buf, &i, "*% ")) // eat prompt
3140 suppressKibitz = FALSE;
3144 } // [HGM] kibitz: end of patch
3146 if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3148 // [HGM] chat: intercept tells by users for which we have an open chat window
3150 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3151 looking_at(buf, &i, "* whispers:") ||
3152 looking_at(buf, &i, "* kibitzes:") ||
3153 looking_at(buf, &i, "* shouts:") ||
3154 looking_at(buf, &i, "* c-shouts:") ||
3155 looking_at(buf, &i, "--> * ") ||
3156 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3157 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3158 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3159 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3161 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3162 chattingPartner = -1;
3164 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3165 for(p=0; p<MAX_CHAT; p++) {
3166 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3167 talker[0] = '['; strcat(talker, "] ");
3168 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3169 chattingPartner = p; break;
3172 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3173 for(p=0; p<MAX_CHAT; p++) {
3174 if(!strcmp("kibitzes", chatPartner[p])) {
3175 talker[0] = '['; strcat(talker, "] ");
3176 chattingPartner = p; break;
3179 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3180 for(p=0; p<MAX_CHAT; p++) {
3181 if(!strcmp("whispers", chatPartner[p])) {
3182 talker[0] = '['; strcat(talker, "] ");
3183 chattingPartner = p; break;
3186 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3187 if(buf[i-8] == '-' && buf[i-3] == 't')
3188 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3189 if(!strcmp("c-shouts", chatPartner[p])) {
3190 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3191 chattingPartner = p; break;
3194 if(chattingPartner < 0)
3195 for(p=0; p<MAX_CHAT; p++) {
3196 if(!strcmp("shouts", chatPartner[p])) {
3197 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3198 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3199 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3200 chattingPartner = p; break;
3204 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3205 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3206 talker[0] = 0; Colorize(ColorTell, FALSE);
3207 chattingPartner = p; break;
3209 if(chattingPartner<0) i = oldi; else {
3210 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3211 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3212 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3213 started = STARTED_COMMENT;
3214 parse_pos = 0; parse[0] = NULLCHAR;
3215 savingComment = 3 + chattingPartner; // counts as TRUE
3216 suppressKibitz = TRUE;
3219 } // [HGM] chat: end of patch
3222 if (appData.zippyTalk || appData.zippyPlay) {
3223 /* [DM] Backup address for color zippy lines */
3225 if (loggedOn == TRUE)
3226 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3227 (appData.zippyPlay && ZippyMatch(buf, &backup)));
3229 } // [DM] 'else { ' deleted
3231 /* Regular tells and says */
3232 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3233 looking_at(buf, &i, "* (your partner) tells you: ") ||
3234 looking_at(buf, &i, "* says: ") ||
3235 /* Don't color "message" or "messages" output */
3236 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3237 looking_at(buf, &i, "*. * at *:*: ") ||
3238 looking_at(buf, &i, "--* (*:*): ") ||
3239 /* Message notifications (same color as tells) */
3240 looking_at(buf, &i, "* has left a message ") ||
3241 looking_at(buf, &i, "* just sent you a message:\n") ||
3242 /* Whispers and kibitzes */
3243 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3244 looking_at(buf, &i, "* kibitzes: ") ||
3246 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3248 if (tkind == 1 && strchr(star_match[0], ':')) {
3249 /* Avoid "tells you:" spoofs in channels */
3252 if (star_match[0][0] == NULLCHAR ||
3253 strchr(star_match[0], ' ') ||
3254 (tkind == 3 && strchr(star_match[1], ' '))) {
3255 /* Reject bogus matches */
3258 if (appData.colorize) {
3259 if (oldi > next_out) {
3260 SendToPlayer(&buf[next_out], oldi - next_out);
3265 Colorize(ColorTell, FALSE);
3266 curColor = ColorTell;
3269 Colorize(ColorKibitz, FALSE);
3270 curColor = ColorKibitz;
3273 p = strrchr(star_match[1], '(');
3280 Colorize(ColorChannel1, FALSE);
3281 curColor = ColorChannel1;
3283 Colorize(ColorChannel, FALSE);
3284 curColor = ColorChannel;
3288 curColor = ColorNormal;
3292 if (started == STARTED_NONE && appData.autoComment &&
3293 (gameMode == IcsObserving ||
3294 gameMode == IcsPlayingWhite ||
3295 gameMode == IcsPlayingBlack)) {
3296 parse_pos = i - oldi;
3297 memcpy(parse, &buf[oldi], parse_pos);
3298 parse[parse_pos] = NULLCHAR;
3299 started = STARTED_COMMENT;
3300 savingComment = TRUE;
3302 started = STARTED_CHATTER;
3303 savingComment = FALSE;
3310 if (looking_at(buf, &i, "* s-shouts: ") ||
3311 looking_at(buf, &i, "* c-shouts: ")) {
3312 if (appData.colorize) {
3313 if (oldi > next_out) {
3314 SendToPlayer(&buf[next_out], oldi - next_out);
3317 Colorize(ColorSShout, FALSE);
3318 curColor = ColorSShout;
3321 started = STARTED_CHATTER;
3325 if (looking_at(buf, &i, "--->")) {
3330 if (looking_at(buf, &i, "* shouts: ") ||
3331 looking_at(buf, &i, "--> ")) {
3332 if (appData.colorize) {
3333 if (oldi > next_out) {
3334 SendToPlayer(&buf[next_out], oldi - next_out);
3337 Colorize(ColorShout, FALSE);
3338 curColor = ColorShout;
3341 started = STARTED_CHATTER;
3345 if (looking_at( buf, &i, "Challenge:")) {
3346 if (appData.colorize) {
3347 if (oldi > next_out) {
3348 SendToPlayer(&buf[next_out], oldi - next_out);
3351 Colorize(ColorChallenge, FALSE);
3352 curColor = ColorChallenge;
3358 if (looking_at(buf, &i, "* offers you") ||
3359 looking_at(buf, &i, "* offers to be") ||
3360 looking_at(buf, &i, "* would like to") ||
3361 looking_at(buf, &i, "* requests to") ||
3362 looking_at(buf, &i, "Your opponent offers") ||
3363 looking_at(buf, &i, "Your opponent requests")) {
3365 if (appData.colorize) {
3366 if (oldi > next_out) {
3367 SendToPlayer(&buf[next_out], oldi - next_out);
3370 Colorize(ColorRequest, FALSE);
3371 curColor = ColorRequest;
3376 if (looking_at(buf, &i, "* (*) seeking")) {
3377 if (appData.colorize) {
3378 if (oldi > next_out) {
3379 SendToPlayer(&buf[next_out], oldi - next_out);
3382 Colorize(ColorSeek, FALSE);
3383 curColor = ColorSeek;
3388 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3390 if (looking_at(buf, &i, "\\ ")) {
3391 if (prevColor != ColorNormal) {
3392 if (oldi > next_out) {
3393 SendToPlayer(&buf[next_out], oldi - next_out);
3396 Colorize(prevColor, TRUE);
3397 curColor = prevColor;
3399 if (savingComment) {
3400 parse_pos = i - oldi;
3401 memcpy(parse, &buf[oldi], parse_pos);
3402 parse[parse_pos] = NULLCHAR;
3403 started = STARTED_COMMENT;
3404 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3405 chattingPartner = savingComment - 3; // kludge to remember the box
3407 started = STARTED_CHATTER;