2 * backend.c -- Common back end for X and Windows NT versions of
4 * Copyright 1991 by Digital Equipment Corporation, Maynard,
7 * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8 * 2007, 2008, 2009, 2010, 2011 Free Software Foundation, Inc.
10 * Enhancements Copyright 2005 Alessandro Scotti
12 * The following terms apply to Digital Equipment Corporation's copyright
14 * ------------------------------------------------------------------------
17 * Permission to use, copy, modify, and distribute this software and its
18 * documentation for any purpose and without fee is hereby granted,
19 * provided that the above copyright notice appear in all copies and that
20 * both that copyright notice and this permission notice appear in
21 * supporting documentation, and that the name of Digital not be
22 * used in advertising or publicity pertaining to distribution of the
23 * software without specific, written prior permission.
25 * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27 * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
32 * ------------------------------------------------------------------------
34 * The following terms apply to the enhanced version of XBoard
35 * distributed by the Free Software Foundation:
36 * ------------------------------------------------------------------------
38 * GNU XBoard is free software: you can redistribute it and/or modify
39 * it under the terms of the GNU General Public License as published by
40 * the Free Software Foundation, either version 3 of the License, or (at
41 * your option) any later version.
43 * GNU XBoard is distributed in the hope that it will be useful, but
44 * WITHOUT ANY WARRANTY; without even the implied warranty of
45 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46 * General Public License for more details.
48 * You should have received a copy of the GNU General Public License
49 * along with this program. If not, see http://www.gnu.org/licenses/. *
51 *------------------------------------------------------------------------
52 ** See the file ChangeLog for a revision history. */
54 /* [AS] Also useful here for debugging */
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
60 int flock(int f, int code);
66 #define DoSleep( n ) if( (n) >= 0) sleep(n)
77 #include <sys/types.h>
86 #else /* not STDC_HEADERS */
89 # else /* not HAVE_STRING_H */
91 # endif /* not HAVE_STRING_H */
92 #endif /* not STDC_HEADERS */
95 # include <sys/fcntl.h>
96 #else /* not HAVE_SYS_FCNTL_H */
99 # endif /* HAVE_FCNTL_H */
100 #endif /* not HAVE_SYS_FCNTL_H */
102 #if TIME_WITH_SYS_TIME
103 # include <sys/time.h>
107 # include <sys/time.h>
113 #if defined(_amigados) && !defined(__GNUC__)
118 extern int gettimeofday(struct timeval *, struct timezone *);
126 #include "frontend.h"
133 #include "backendz.h"
137 # define _(s) gettext (s)
138 # define N_(s) gettext_noop (s)
139 # define T_(s) gettext(s)
152 /* A point in time */
154 long sec; /* Assuming this is >= 32 bits */
155 int ms; /* Assuming this is >= 16 bits */
158 int establish P((void));
159 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
160 char *buf, int count, int error));
161 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
162 char *buf, int count, int error));
163 void ics_printf P((char *format, ...));
164 void SendToICS P((char *s));
165 void SendToICSDelayed P((char *s, long msdelay));
166 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
167 void HandleMachineMove P((char *message, ChessProgramState *cps));
168 int AutoPlayOneMove P((void));
169 int LoadGameOneMove P((ChessMove readAhead));
170 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
171 int LoadPositionFromFile P((char *filename, int n, char *title));
172 int SavePositionToFile P((char *filename));
173 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
175 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
176 void ShowMove P((int fromX, int fromY, int toX, int toY));
177 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
178 /*char*/int promoChar));
179 void BackwardInner P((int target));
180 void ForwardInner P((int target));
181 int Adjudicate P((ChessProgramState *cps));
182 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
183 void EditPositionDone P((Boolean fakeRights));
184 void PrintOpponents P((FILE *fp));
185 void PrintPosition P((FILE *fp, int move));
186 void StartChessProgram P((ChessProgramState *cps));
187 void SendToProgram P((char *message, ChessProgramState *cps));
188 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
189 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
190 char *buf, int count, int error));
191 void SendTimeControl P((ChessProgramState *cps,
192 int mps, long tc, int inc, int sd, int st));
193 char *TimeControlTagValue P((void));
194 void Attention P((ChessProgramState *cps));
195 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
196 int ResurrectChessProgram P((void));
197 void DisplayComment P((int moveNumber, char *text));
198 void DisplayMove P((int moveNumber));
200 void ParseGameHistory P((char *game));
201 void ParseBoard12 P((char *string));
202 void KeepAlive P((void));
203 void StartClocks P((void));
204 void SwitchClocks P((int nr));
205 void StopClocks P((void));
206 void ResetClocks P((void));
207 char *PGNDate P((void));
208 void SetGameInfo P((void));
209 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
210 int RegisterMove P((void));
211 void MakeRegisteredMove P((void));
212 void TruncateGame P((void));
213 int looking_at P((char *, int *, char *));
214 void CopyPlayerNameIntoFileName P((char **, char *));
215 char *SavePart P((char *));
216 int SaveGameOldStyle P((FILE *));
217 int SaveGamePGN P((FILE *));
218 void GetTimeMark P((TimeMark *));
219 long SubtractTimeMarks P((TimeMark *, TimeMark *));
220 int CheckFlags P((void));
221 long NextTickLength P((long));
222 void CheckTimeControl P((void));
223 void show_bytes P((FILE *, char *, int));
224 int string_to_rating P((char *str));
225 void ParseFeatures P((char* args, ChessProgramState *cps));
226 void InitBackEnd3 P((void));
227 void FeatureDone P((ChessProgramState* cps, int val));
228 void InitChessProgram P((ChessProgramState *cps, int setup));
229 void OutputKibitz(int window, char *text);
230 int PerpetualChase(int first, int last);
231 int EngineOutputIsUp();
232 void InitDrawingSizes(int x, int y);
233 void NextMatchGame P((void));
234 int NextTourneyGame P((int nr, int *swap));
235 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
236 FILE *WriteTourneyFile P((char *results));
239 extern void ConsoleCreate();
242 ChessProgramState *WhitePlayer();
243 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
244 int VerifyDisplayMode P(());
246 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
247 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
248 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
249 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
250 void ics_update_width P((int new_width));
251 extern char installDir[MSG_SIZ];
252 VariantClass startVariant; /* [HGM] nicks: initial variant */
255 extern int tinyLayout, smallLayout;
256 ChessProgramStats programStats;
257 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
259 static int exiting = 0; /* [HGM] moved to top */
260 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
261 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
262 Board partnerBoard; /* [HGM] bughouse: for peeking at partner game */
263 int partnerHighlight[2];
264 Boolean partnerBoardValid = 0;
265 char partnerStatus[MSG_SIZ];
267 Boolean originalFlip;
268 Boolean twoBoards = 0;
269 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
270 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
271 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
272 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
273 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
274 int opponentKibitzes;
275 int lastSavedGame; /* [HGM] save: ID of game */
276 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
277 extern int chatCount;
279 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
280 char lastMsg[MSG_SIZ];
281 ChessSquare pieceSweep = EmptySquare;
282 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
283 int promoDefaultAltered;
285 /* States for ics_getting_history */
287 #define H_REQUESTED 1
288 #define H_GOT_REQ_HEADER 2
289 #define H_GOT_UNREQ_HEADER 3
290 #define H_GETTING_MOVES 4
291 #define H_GOT_UNWANTED_HEADER 5
293 /* whosays values for GameEnds */
302 /* Maximum number of games in a cmail message */
303 #define CMAIL_MAX_GAMES 20
305 /* Different types of move when calling RegisterMove */
307 #define CMAIL_RESIGN 1
309 #define CMAIL_ACCEPT 3
311 /* Different types of result to remember for each game */
312 #define CMAIL_NOT_RESULT 0
313 #define CMAIL_OLD_RESULT 1
314 #define CMAIL_NEW_RESULT 2
316 /* Telnet protocol constants */
327 safeStrCpy( char *dst, const char *src, size_t count )
330 assert( dst != NULL );
331 assert( src != NULL );
334 for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
335 if( i == count && dst[count-1] != NULLCHAR)
337 dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
338 if(appData.debugMode)
339 fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
345 /* Some compiler can't cast u64 to double
346 * This function do the job for us:
348 * We use the highest bit for cast, this only
349 * works if the highest bit is not
350 * in use (This should not happen)
352 * We used this for all compiler
355 u64ToDouble(u64 value)
358 u64 tmp = value & u64Const(0x7fffffffffffffff);
359 r = (double)(s64)tmp;
360 if (value & u64Const(0x8000000000000000))
361 r += 9.2233720368547758080e18; /* 2^63 */
365 /* Fake up flags for now, as we aren't keeping track of castling
366 availability yet. [HGM] Change of logic: the flag now only
367 indicates the type of castlings allowed by the rule of the game.
368 The actual rights themselves are maintained in the array
369 castlingRights, as part of the game history, and are not probed
375 int flags = F_ALL_CASTLE_OK;
376 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
377 switch (gameInfo.variant) {
379 flags &= ~F_ALL_CASTLE_OK;
380 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
381 flags |= F_IGNORE_CHECK;
383 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
386 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
388 case VariantKriegspiel:
389 flags |= F_KRIEGSPIEL_CAPTURE;
391 case VariantCapaRandom:
392 case VariantFischeRandom:
393 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
394 case VariantNoCastle:
395 case VariantShatranj:
398 flags &= ~F_ALL_CASTLE_OK;
406 FILE *gameFileFP, *debugFP;
409 [AS] Note: sometimes, the sscanf() function is used to parse the input
410 into a fixed-size buffer. Because of this, we must be prepared to
411 receive strings as long as the size of the input buffer, which is currently
412 set to 4K for Windows and 8K for the rest.
413 So, we must either allocate sufficiently large buffers here, or
414 reduce the size of the input buffer in the input reading part.
417 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
418 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
419 char thinkOutput1[MSG_SIZ*10];
421 ChessProgramState first, second, pairing;
423 /* premove variables */
426 int premoveFromX = 0;
427 int premoveFromY = 0;
428 int premovePromoChar = 0;
430 Boolean alarmSounded;
431 /* end premove variables */
433 char *ics_prefix = "$";
434 int ics_type = ICS_GENERIC;
436 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
437 int pauseExamForwardMostMove = 0;
438 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
439 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
440 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
441 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
442 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
443 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
444 int whiteFlag = FALSE, blackFlag = FALSE;
445 int userOfferedDraw = FALSE;
446 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
447 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
448 int cmailMoveType[CMAIL_MAX_GAMES];
449 long ics_clock_paused = 0;
450 ProcRef icsPR = NoProc, cmailPR = NoProc;
451 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
452 GameMode gameMode = BeginningOfGame;
453 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
454 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
455 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
456 int hiddenThinkOutputState = 0; /* [AS] */
457 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
458 int adjudicateLossPlies = 6;
459 char white_holding[64], black_holding[64];
460 TimeMark lastNodeCountTime;
461 long lastNodeCount=0;
462 int shiftKey; // [HGM] set by mouse handler
464 int have_sent_ICS_logon = 0;
466 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
467 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
468 long timeControl_2; /* [AS] Allow separate time controls */
469 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
470 long timeRemaining[2][MAX_MOVES];
471 int matchGame = 0, nextGame = 0, roundNr = 0;
472 Boolean waitingForGame = FALSE;
473 TimeMark programStartTime, pauseStart;
474 char ics_handle[MSG_SIZ];
475 int have_set_title = 0;
477 /* animateTraining preserves the state of appData.animate
478 * when Training mode is activated. This allows the
479 * response to be animated when appData.animate == TRUE and
480 * appData.animateDragging == TRUE.
482 Boolean animateTraining;
488 Board boards[MAX_MOVES];
489 /* [HGM] Following 7 needed for accurate legality tests: */
490 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
491 signed char initialRights[BOARD_FILES];
492 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
493 int initialRulePlies, FENrulePlies;
494 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
497 int mute; // mute all sounds
499 // [HGM] vari: next 12 to save and restore variations
500 #define MAX_VARIATIONS 10
501 int framePtr = MAX_MOVES-1; // points to free stack entry
503 int savedFirst[MAX_VARIATIONS];
504 int savedLast[MAX_VARIATIONS];
505 int savedFramePtr[MAX_VARIATIONS];
506 char *savedDetails[MAX_VARIATIONS];
507 ChessMove savedResult[MAX_VARIATIONS];
509 void PushTail P((int firstMove, int lastMove));
510 Boolean PopTail P((Boolean annotate));
511 void PushInner P((int firstMove, int lastMove));
512 void PopInner P((Boolean annotate));
513 void CleanupTail P((void));
515 ChessSquare FIDEArray[2][BOARD_FILES] = {
516 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
517 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
518 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
519 BlackKing, BlackBishop, BlackKnight, BlackRook }
522 ChessSquare twoKingsArray[2][BOARD_FILES] = {
523 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
524 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
525 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
526 BlackKing, BlackKing, BlackKnight, BlackRook }
529 ChessSquare KnightmateArray[2][BOARD_FILES] = {
530 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
531 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
532 { BlackRook, BlackMan, BlackBishop, BlackQueen,
533 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
536 ChessSquare SpartanArray[2][BOARD_FILES] = {
537 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
538 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
539 { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
540 BlackDragon, BlackKing, BlackAngel, BlackAlfil }
543 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
544 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
545 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
546 { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
547 BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
550 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
551 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
552 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
553 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
554 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
557 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
558 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
559 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
560 { BlackRook, BlackKnight, BlackMan, BlackFerz,
561 BlackKing, BlackMan, BlackKnight, BlackRook }
565 #if (BOARD_FILES>=10)
566 ChessSquare ShogiArray[2][BOARD_FILES] = {
567 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
568 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
569 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
570 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
573 ChessSquare XiangqiArray[2][BOARD_FILES] = {
574 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
575 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
576 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
577 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
580 ChessSquare CapablancaArray[2][BOARD_FILES] = {
581 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
582 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
583 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
584 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
587 ChessSquare GreatArray[2][BOARD_FILES] = {
588 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
589 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
590 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
591 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
594 ChessSquare JanusArray[2][BOARD_FILES] = {
595 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
596 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
597 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
598 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
602 ChessSquare GothicArray[2][BOARD_FILES] = {
603 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
604 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
605 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
606 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
609 #define GothicArray CapablancaArray
613 ChessSquare FalconArray[2][BOARD_FILES] = {
614 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
615 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
616 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
617 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
620 #define FalconArray CapablancaArray
623 #else // !(BOARD_FILES>=10)
624 #define XiangqiPosition FIDEArray
625 #define CapablancaArray FIDEArray
626 #define GothicArray FIDEArray
627 #define GreatArray FIDEArray
628 #endif // !(BOARD_FILES>=10)
630 #if (BOARD_FILES>=12)
631 ChessSquare CourierArray[2][BOARD_FILES] = {
632 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
633 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
634 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
635 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
637 #else // !(BOARD_FILES>=12)
638 #define CourierArray CapablancaArray
639 #endif // !(BOARD_FILES>=12)
642 Board initialPosition;
645 /* Convert str to a rating. Checks for special cases of "----",
647 "++++", etc. Also strips ()'s */
649 string_to_rating(str)
652 while(*str && !isdigit(*str)) ++str;
654 return 0; /* One of the special "no rating" cases */
662 /* Init programStats */
663 programStats.movelist[0] = 0;
664 programStats.depth = 0;
665 programStats.nr_moves = 0;
666 programStats.moves_left = 0;
667 programStats.nodes = 0;
668 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
669 programStats.score = 0;
670 programStats.got_only_move = 0;
671 programStats.got_fail = 0;
672 programStats.line_is_book = 0;
677 { // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
678 if (appData.firstPlaysBlack) {
679 first.twoMachinesColor = "black\n";
680 second.twoMachinesColor = "white\n";
682 first.twoMachinesColor = "white\n";
683 second.twoMachinesColor = "black\n";
686 first.other = &second;
687 second.other = &first;
690 if(appData.timeOddsMode) {
691 norm = appData.timeOdds[0];
692 if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
694 first.timeOdds = appData.timeOdds[0]/norm;
695 second.timeOdds = appData.timeOdds[1]/norm;
698 if(programVersion) free(programVersion);
699 if (appData.noChessProgram) {
700 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
701 sprintf(programVersion, "%s", PACKAGE_STRING);
703 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
704 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
705 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
710 UnloadEngine(ChessProgramState *cps)
712 /* Kill off first chess program */
713 if (cps->isr != NULL)
714 RemoveInputSource(cps->isr);
717 if (cps->pr != NoProc) {
719 DoSleep( appData.delayBeforeQuit );
720 SendToProgram("quit\n", cps);
721 DoSleep( appData.delayAfterQuit );
722 DestroyChildProcess(cps->pr, cps->useSigterm);
725 if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
729 ClearOptions(ChessProgramState *cps)
732 cps->nrOptions = cps->comboCnt = 0;
733 for(i=0; i<MAX_OPTIONS; i++) {
734 cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
735 cps->option[i].textValue = 0;
739 char *engineNames[] = {
745 InitEngine(ChessProgramState *cps, int n)
746 { // [HGM] all engine initialiation put in a function that does one engine
750 cps->which = engineNames[n];
751 cps->maybeThinking = FALSE;
755 cps->sendDrawOffers = 1;
757 cps->program = appData.chessProgram[n];
758 cps->host = appData.host[n];
759 cps->dir = appData.directory[n];
760 cps->initString = appData.engInitString[n];
761 cps->computerString = appData.computerString[n];
762 cps->useSigint = TRUE;
763 cps->useSigterm = TRUE;
764 cps->reuse = appData.reuse[n];
765 cps->nps = appData.NPS[n]; // [HGM] nps: copy nodes per second
766 cps->useSetboard = FALSE;
768 cps->usePing = FALSE;
771 cps->usePlayother = FALSE;
772 cps->useColors = TRUE;
773 cps->useUsermove = FALSE;
774 cps->sendICS = FALSE;
775 cps->sendName = appData.icsActive;
776 cps->sdKludge = FALSE;
777 cps->stKludge = FALSE;
778 TidyProgramName(cps->program, cps->host, cps->tidy);
780 safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
781 cps->analysisSupport = 2; /* detect */
782 cps->analyzing = FALSE;
783 cps->initDone = FALSE;
785 /* New features added by Tord: */
786 cps->useFEN960 = FALSE;
787 cps->useOOCastle = TRUE;
788 /* End of new features added by Tord. */
789 cps->fenOverride = appData.fenOverride[n];
791 /* [HGM] time odds: set factor for each machine */
792 cps->timeOdds = appData.timeOdds[n];
794 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
795 cps->accumulateTC = appData.accumulateTC[n];
796 cps->maxNrOfSessions = 1;
801 cps->supportsNPS = UNKNOWN;
802 cps->memSize = FALSE;
803 cps->maxCores = FALSE;
804 cps->egtFormats[0] = NULLCHAR;
807 cps->optionSettings = appData.engOptions[n];
809 cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
810 cps->isUCI = appData.isUCI[n]; /* [AS] */
811 cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
813 if (appData.protocolVersion[n] > PROTOVER
814 || appData.protocolVersion[n] < 1)
819 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
820 appData.protocolVersion[n]);
821 if( (len > MSG_SIZ) && appData.debugMode )
822 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
824 DisplayFatalError(buf, 0, 2);
828 cps->protocolVersion = appData.protocolVersion[n];
831 InitEngineUCI( installDir, cps ); // [HGM] moved here from winboard.c, to make available in xboard
834 ChessProgramState *savCps;
840 if(WaitForEngine(savCps, LoadEngine)) return;
841 CommonEngineInit(); // recalculate time odds
842 if(gameInfo.variant != StringToVariant(appData.variant)) {
843 // we changed variant when loading the engine; this forces us to reset
844 Reset(TRUE, savCps != &first);
845 EditGameEvent(); // for consistency with other path, as Reset changes mode
847 InitChessProgram(savCps, FALSE);
848 SendToProgram("force\n", savCps);
849 DisplayMessage("", "");
850 if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
851 for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
857 ReplaceEngine(ChessProgramState *cps, int n)
861 appData.noChessProgram = FALSE;
862 appData.clockMode = TRUE;
865 if(n) return; // only startup first engine immediately; second can wait
866 savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
870 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
871 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
873 static char resetOptions[] =
874 "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
875 "-firstOptions \"\" -firstNPS -1 -fn \"\"";
878 Load(ChessProgramState *cps, int i)
880 char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
881 if(engineLine[0]) { // an engine was selected from the combo box
882 snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
883 SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
884 ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL;
885 ParseArgsFromString(buf);
887 ReplaceEngine(cps, i);
891 while(q = strchr(p, SLASH)) p = q+1;
892 if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
893 if(engineDir[0] != NULLCHAR)
894 appData.directory[i] = engineDir;
895 else if(p != engineName) { // derive directory from engine path, when not given
897 appData.directory[i] = strdup(engineName);
899 } else appData.directory[i] = ".";
900 if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
902 snprintf(command, MSG_SIZ, "%s %s", p, params);
905 appData.chessProgram[i] = strdup(p);
906 appData.isUCI[i] = isUCI;
907 appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
908 appData.hasOwnBookUCI[i] = hasBook;
909 if(!nickName[0]) useNick = FALSE;
910 if(useNick) ASSIGN(appData.pgnName[i], nickName);
914 q = firstChessProgramNames;
915 if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
916 quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
917 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
918 quote, p, quote, appData.directory[i],
919 useNick ? " -fn \"" : "",
920 useNick ? nickName : "",
922 v1 ? " -firstProtocolVersion 1" : "",
923 hasBook ? "" : " -fNoOwnBookUCI",
924 isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
925 storeVariant ? " -variant " : "",
926 storeVariant ? VariantName(gameInfo.variant) : "");
927 firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
928 snprintf(firstChessProgramNames, len, "%s%s", q, buf);
931 ReplaceEngine(cps, i);
937 int matched, min, sec;
939 * Parse timeControl resource
941 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
942 appData.movesPerSession)) {
944 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
945 DisplayFatalError(buf, 0, 2);
949 * Parse searchTime resource
951 if (*appData.searchTime != NULLCHAR) {
952 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
954 searchTime = min * 60;
955 } else if (matched == 2) {
956 searchTime = min * 60 + sec;
959 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
960 DisplayFatalError(buf, 0, 2);
969 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
970 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
972 GetTimeMark(&programStartTime);
973 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
974 pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
977 programStats.ok_to_send = 1;
978 programStats.seen_stat = 0;
981 * Initialize game list
987 * Internet chess server status
989 if (appData.icsActive) {
990 appData.matchMode = FALSE;
991 appData.matchGames = 0;
993 appData.noChessProgram = !appData.zippyPlay;
995 appData.zippyPlay = FALSE;
996 appData.zippyTalk = FALSE;
997 appData.noChessProgram = TRUE;
999 if (*appData.icsHelper != NULLCHAR) {
1000 appData.useTelnet = TRUE;
1001 appData.telnetProgram = appData.icsHelper;
1004 appData.zippyTalk = appData.zippyPlay = FALSE;
1007 /* [AS] Initialize pv info list [HGM] and game state */
1011 for( i=0; i<=framePtr; i++ ) {
1012 pvInfoList[i].depth = -1;
1013 boards[i][EP_STATUS] = EP_NONE;
1014 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1020 /* [AS] Adjudication threshold */
1021 adjudicateLossThreshold = appData.adjudicateLossThreshold;
1023 InitEngine(&first, 0);
1024 InitEngine(&second, 1);
1027 pairing.which = "pairing"; // pairing engine
1028 pairing.pr = NoProc;
1030 pairing.program = appData.pairingEngine;
1031 pairing.host = "localhost";
1034 if (appData.icsActive) {
1035 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
1036 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1037 appData.clockMode = FALSE;
1038 first.sendTime = second.sendTime = 0;
1042 /* Override some settings from environment variables, for backward
1043 compatibility. Unfortunately it's not feasible to have the env
1044 vars just set defaults, at least in xboard. Ugh.
1046 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1051 if (!appData.icsActive) {
1055 /* Check for variants that are supported only in ICS mode,
1056 or not at all. Some that are accepted here nevertheless
1057 have bugs; see comments below.
1059 VariantClass variant = StringToVariant(appData.variant);
1061 case VariantBughouse: /* need four players and two boards */
1062 case VariantKriegspiel: /* need to hide pieces and move details */
1063 /* case VariantFischeRandom: (Fabien: moved below) */
1064 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1065 if( (len > MSG_SIZ) && appData.debugMode )
1066 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1068 DisplayFatalError(buf, 0, 2);
1071 case VariantUnknown:
1072 case VariantLoadable:
1082 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1083 if( (len > MSG_SIZ) && appData.debugMode )
1084 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1086 DisplayFatalError(buf, 0, 2);
1089 case VariantXiangqi: /* [HGM] repetition rules not implemented */
1090 case VariantFairy: /* [HGM] TestLegality definitely off! */
1091 case VariantGothic: /* [HGM] should work */
1092 case VariantCapablanca: /* [HGM] should work */
1093 case VariantCourier: /* [HGM] initial forced moves not implemented */
1094 case VariantShogi: /* [HGM] could still mate with pawn drop */
1095 case VariantKnightmate: /* [HGM] should work */
1096 case VariantCylinder: /* [HGM] untested */
1097 case VariantFalcon: /* [HGM] untested */
1098 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1099 offboard interposition not understood */
1100 case VariantNormal: /* definitely works! */
1101 case VariantWildCastle: /* pieces not automatically shuffled */
1102 case VariantNoCastle: /* pieces not automatically shuffled */
1103 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1104 case VariantLosers: /* should work except for win condition,
1105 and doesn't know captures are mandatory */
1106 case VariantSuicide: /* should work except for win condition,
1107 and doesn't know captures are mandatory */
1108 case VariantGiveaway: /* should work except for win condition,
1109 and doesn't know captures are mandatory */
1110 case VariantTwoKings: /* should work */
1111 case VariantAtomic: /* should work except for win condition */
1112 case Variant3Check: /* should work except for win condition */
1113 case VariantShatranj: /* should work except for all win conditions */
1114 case VariantMakruk: /* should work except for daw countdown */
1115 case VariantBerolina: /* might work if TestLegality is off */
1116 case VariantCapaRandom: /* should work */
1117 case VariantJanus: /* should work */
1118 case VariantSuper: /* experimental */
1119 case VariantGreat: /* experimental, requires legality testing to be off */
1120 case VariantSChess: /* S-Chess, should work */
1121 case VariantSpartan: /* should work */
1128 int NextIntegerFromString( char ** str, long * value )
1133 while( *s == ' ' || *s == '\t' ) {
1139 if( *s >= '0' && *s <= '9' ) {
1140 while( *s >= '0' && *s <= '9' ) {
1141 *value = *value * 10 + (*s - '0');
1153 int NextTimeControlFromString( char ** str, long * value )
1156 int result = NextIntegerFromString( str, &temp );
1159 *value = temp * 60; /* Minutes */
1160 if( **str == ':' ) {
1162 result = NextIntegerFromString( str, &temp );
1163 *value += temp; /* Seconds */
1170 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1171 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1172 int result = -1, type = 0; long temp, temp2;
1174 if(**str != ':') return -1; // old params remain in force!
1176 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1177 if( NextIntegerFromString( str, &temp ) ) return -1;
1178 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1181 /* time only: incremental or sudden-death time control */
1182 if(**str == '+') { /* increment follows; read it */
1184 if(**str == '!') type = *(*str)++; // Bronstein TC
1185 if(result = NextIntegerFromString( str, &temp2)) return -1;
1186 *inc = temp2 * 1000;
1187 if(**str == '.') { // read fraction of increment
1188 char *start = ++(*str);
1189 if(result = NextIntegerFromString( str, &temp2)) return -1;
1191 while(start++ < *str) temp2 /= 10;
1195 *moves = 0; *tc = temp * 1000; *incType = type;
1199 (*str)++; /* classical time control */
1200 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1211 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1212 { /* [HGM] get time to add from the multi-session time-control string */
1213 int incType, moves=1; /* kludge to force reading of first session */
1214 long time, increment;
1217 if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1218 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1220 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1221 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1222 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1223 if(movenr == -1) return time; /* last move before new session */
1224 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1225 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1226 if(!moves) return increment; /* current session is incremental */
1227 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1228 } while(movenr >= -1); /* try again for next session */
1230 return 0; // no new time quota on this move
1234 ParseTimeControl(tc, ti, mps)
1241 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1244 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1245 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1246 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1250 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1252 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1255 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1257 snprintf(buf, MSG_SIZ, ":%s", mytc);
1259 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1261 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1266 /* Parse second time control */
1269 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1277 timeControl_2 = tc2 * 1000;
1287 timeControl = tc1 * 1000;
1290 timeIncrement = ti * 1000; /* convert to ms */
1291 movesPerSession = 0;
1294 movesPerSession = mps;
1302 if (appData.debugMode) {
1303 fprintf(debugFP, "%s\n", programVersion);
1306 set_cont_sequence(appData.wrapContSeq);
1307 if (appData.matchGames > 0) {
1308 appData.matchMode = TRUE;
1309 } else if (appData.matchMode) {
1310 appData.matchGames = 1;
1312 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1313 appData.matchGames = appData.sameColorGames;
1314 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1315 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1316 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1319 if (appData.noChessProgram || first.protocolVersion == 1) {
1322 /* kludge: allow timeout for initial "feature" commands */
1324 DisplayMessage("", _("Starting chess program"));
1325 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1330 CalculateIndex(int index, int gameNr)
1331 { // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1333 if(index > 0) return index; // fixed nmber
1334 if(index == 0) return 1;
1335 res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1336 if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1341 LoadGameOrPosition(int gameNr)
1342 { // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1343 if (*appData.loadGameFile != NULLCHAR) {
1344 if (!LoadGameFromFile(appData.loadGameFile,
1345 CalculateIndex(appData.loadGameIndex, gameNr),
1346 appData.loadGameFile, FALSE)) {
1347 DisplayFatalError(_("Bad game file"), 0, 1);
1350 } else if (*appData.loadPositionFile != NULLCHAR) {
1351 if (!LoadPositionFromFile(appData.loadPositionFile,
1352 CalculateIndex(appData.loadPositionIndex, gameNr),
1353 appData.loadPositionFile)) {
1354 DisplayFatalError(_("Bad position file"), 0, 1);
1362 ReserveGame(int gameNr, char resChar)
1364 FILE *tf = fopen(appData.tourneyFile, "r+");
1365 char *p, *q, c, buf[MSG_SIZ];
1366 if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1367 safeStrCpy(buf, lastMsg, MSG_SIZ);
1368 DisplayMessage(_("Pick new game"), "");
1369 flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1370 ParseArgsFromFile(tf);
1371 p = q = appData.results;
1372 if(appData.debugMode) {
1373 char *r = appData.participants;
1374 fprintf(debugFP, "results = '%s'\n", p);
1375 while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1376 fprintf(debugFP, "\n");
1378 while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1380 q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1381 safeStrCpy(q, p, strlen(p) + 2);
1382 if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1383 if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1384 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1385 if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1388 fseek(tf, -(strlen(p)+4), SEEK_END);
1390 if(c != '"') // depending on DOS or Unix line endings we can be one off
1391 fseek(tf, -(strlen(p)+2), SEEK_END);
1392 else fseek(tf, -(strlen(p)+3), SEEK_END);
1393 fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1394 DisplayMessage(buf, "");
1395 free(p); appData.results = q;
1396 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1397 (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1398 UnloadEngine(&first); // next game belongs to other pairing;
1399 UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1404 MatchEvent(int mode)
1405 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1407 if(matchMode) { // already in match mode: switch it off
1409 if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1412 // if(gameMode != BeginningOfGame) {
1413 // DisplayError(_("You can only start a match from the initial position."), 0);
1417 if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1418 /* Set up machine vs. machine match */
1420 NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1421 if(appData.tourneyFile[0]) {
1423 if(nextGame > appData.matchGames) {
1425 if(strchr(appData.results, '*') == NULL) {
1427 appData.tourneyCycles++;
1428 if(f = WriteTourneyFile(appData.results)) { // make a tourney file with increased number of cycles
1430 NextTourneyGame(-1, &dummy);
1432 if(nextGame <= appData.matchGames) {
1433 DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1435 ScheduleDelayedEvent(NextMatchGame, 10000);
1440 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1441 DisplayError(buf, 0);
1442 appData.tourneyFile[0] = 0;
1446 if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically
1447 DisplayFatalError(_("Can't have a match with no chess programs"),
1452 matchGame = roundNr = 1;
1453 first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
\r
1458 InitBackEnd3 P((void))
1460 GameMode initialMode;
1464 InitChessProgram(&first, startedFromSetupPosition);
1466 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1467 free(programVersion);
1468 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1469 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1472 if (appData.icsActive) {
1474 /* [DM] Make a console window if needed [HGM] merged ifs */
1480 if (*appData.icsCommPort != NULLCHAR)
1481 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1482 appData.icsCommPort);
1484 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1485 appData.icsHost, appData.icsPort);
1487 if( (len > MSG_SIZ) && appData.debugMode )
1488 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1490 DisplayFatalError(buf, err, 1);
1495 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1497 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1498 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1499 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1500 } else if (appData.noChessProgram) {
1506 if (*appData.cmailGameName != NULLCHAR) {
1508 OpenLoopback(&cmailPR);
1510 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1514 DisplayMessage("", "");
1515 if (StrCaseCmp(appData.initialMode, "") == 0) {
1516 initialMode = BeginningOfGame;
1517 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1518 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1519 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1520 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1523 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1524 initialMode = TwoMachinesPlay;
1525 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1526 initialMode = AnalyzeFile;
1527 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1528 initialMode = AnalyzeMode;
1529 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1530 initialMode = MachinePlaysWhite;
1531 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1532 initialMode = MachinePlaysBlack;
1533 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1534 initialMode = EditGame;
1535 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1536 initialMode = EditPosition;
1537 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1538 initialMode = Training;
1540 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1541 if( (len > MSG_SIZ) && appData.debugMode )
1542 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1544 DisplayFatalError(buf, 0, 2);
1548 if (appData.matchMode) {
1549 if(appData.tourneyFile[0]) { // start tourney from command line
1551 if(f = fopen(appData.tourneyFile, "r")) {
1552 ParseArgsFromFile(f); // make sure tourney parmeters re known
1554 appData.clockMode = TRUE;
1556 } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1559 } else if (*appData.cmailGameName != NULLCHAR) {
1560 /* Set up cmail mode */
1561 ReloadCmailMsgEvent(TRUE);
1563 /* Set up other modes */
1564 if (initialMode == AnalyzeFile) {
1565 if (*appData.loadGameFile == NULLCHAR) {
1566 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1570 if (*appData.loadGameFile != NULLCHAR) {
1571 (void) LoadGameFromFile(appData.loadGameFile,
1572 appData.loadGameIndex,
1573 appData.loadGameFile, TRUE);
1574 } else if (*appData.loadPositionFile != NULLCHAR) {
1575 (void) LoadPositionFromFile(appData.loadPositionFile,
1576 appData.loadPositionIndex,
1577 appData.loadPositionFile);
1578 /* [HGM] try to make self-starting even after FEN load */
1579 /* to allow automatic setup of fairy variants with wtm */
1580 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1581 gameMode = BeginningOfGame;
1582 setboardSpoiledMachineBlack = 1;
1584 /* [HGM] loadPos: make that every new game uses the setup */
1585 /* from file as long as we do not switch variant */
1586 if(!blackPlaysFirst) {
1587 startedFromPositionFile = TRUE;
1588 CopyBoard(filePosition, boards[0]);
1591 if (initialMode == AnalyzeMode) {
1592 if (appData.noChessProgram) {
1593 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1596 if (appData.icsActive) {
1597 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1601 } else if (initialMode == AnalyzeFile) {
1602 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1603 ShowThinkingEvent();
1605 AnalysisPeriodicEvent(1);
1606 } else if (initialMode == MachinePlaysWhite) {
1607 if (appData.noChessProgram) {
1608 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1612 if (appData.icsActive) {
1613 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1617 MachineWhiteEvent();
1618 } else if (initialMode == MachinePlaysBlack) {
1619 if (appData.noChessProgram) {
1620 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1624 if (appData.icsActive) {
1625 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1629 MachineBlackEvent();
1630 } else if (initialMode == TwoMachinesPlay) {
1631 if (appData.noChessProgram) {
1632 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1636 if (appData.icsActive) {
1637 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1642 } else if (initialMode == EditGame) {
1644 } else if (initialMode == EditPosition) {
1645 EditPositionEvent();
1646 } else if (initialMode == Training) {
1647 if (*appData.loadGameFile == NULLCHAR) {
1648 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1657 * Establish will establish a contact to a remote host.port.
1658 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1659 * used to talk to the host.
1660 * Returns 0 if okay, error code if not.
1667 if (*appData.icsCommPort != NULLCHAR) {
1668 /* Talk to the host through a serial comm port */
1669 return OpenCommPort(appData.icsCommPort, &icsPR);
1671 } else if (*appData.gateway != NULLCHAR) {
1672 if (*appData.remoteShell == NULLCHAR) {
1673 /* Use the rcmd protocol to run telnet program on a gateway host */
1674 snprintf(buf, sizeof(buf), "%s %s %s",
1675 appData.telnetProgram, appData.icsHost, appData.icsPort);
1676 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1679 /* Use the rsh program to run telnet program on a gateway host */
1680 if (*appData.remoteUser == NULLCHAR) {
1681 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1682 appData.gateway, appData.telnetProgram,
1683 appData.icsHost, appData.icsPort);
1685 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1686 appData.remoteShell, appData.gateway,
1687 appData.remoteUser, appData.telnetProgram,
1688 appData.icsHost, appData.icsPort);
1690 return StartChildProcess(buf, "", &icsPR);
1693 } else if (appData.useTelnet) {
1694 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1697 /* TCP socket interface differs somewhat between
1698 Unix and NT; handle details in the front end.
1700 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1704 void EscapeExpand(char *p, char *q)
1705 { // [HGM] initstring: routine to shape up string arguments
1706 while(*p++ = *q++) if(p[-1] == '\\')
1708 case 'n': p[-1] = '\n'; break;
1709 case 'r': p[-1] = '\r'; break;
1710 case 't': p[-1] = '\t'; break;
1711 case '\\': p[-1] = '\\'; break;
1712 case 0: *p = 0; return;
1713 default: p[-1] = q[-1]; break;
1718 show_bytes(fp, buf, count)
1724 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1725 fprintf(fp, "\\%03o", *buf & 0xff);
1734 /* Returns an errno value */
1736 OutputMaybeTelnet(pr, message, count, outError)
1742 char buf[8192], *p, *q, *buflim;
1743 int left, newcount, outcount;
1745 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1746 *appData.gateway != NULLCHAR) {
1747 if (appData.debugMode) {
1748 fprintf(debugFP, ">ICS: ");
1749 show_bytes(debugFP, message, count);
1750 fprintf(debugFP, "\n");
1752 return OutputToProcess(pr, message, count, outError);
1755 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1762 if (appData.debugMode) {
1763 fprintf(debugFP, ">ICS: ");
1764 show_bytes(debugFP, buf, newcount);
1765 fprintf(debugFP, "\n");
1767 outcount = OutputToProcess(pr, buf, newcount, outError);
1768 if (outcount < newcount) return -1; /* to be sure */
1775 } else if (((unsigned char) *p) == TN_IAC) {
1776 *q++ = (char) TN_IAC;
1783 if (appData.debugMode) {
1784 fprintf(debugFP, ">ICS: ");
1785 show_bytes(debugFP, buf, newcount);
1786 fprintf(debugFP, "\n");
1788 outcount = OutputToProcess(pr, buf, newcount, outError);
1789 if (outcount < newcount) return -1; /* to be sure */
1794 read_from_player(isr, closure, message, count, error)
1801 int outError, outCount;
1802 static int gotEof = 0;
1804 /* Pass data read from player on to ICS */
1807 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1808 if (outCount < count) {
1809 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1811 } else if (count < 0) {
1812 RemoveInputSource(isr);
1813 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1814 } else if (gotEof++ > 0) {
1815 RemoveInputSource(isr);
1816 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1822 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1823 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1824 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1825 SendToICS("date\n");
1826 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1829 /* added routine for printf style output to ics */
1830 void ics_printf(char *format, ...)
1832 char buffer[MSG_SIZ];
1835 va_start(args, format);
1836 vsnprintf(buffer, sizeof(buffer), format, args);
1837 buffer[sizeof(buffer)-1] = '\0';
1846 int count, outCount, outError;
1848 if (icsPR == NULL) return;
1851 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1852 if (outCount < count) {
1853 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1857 /* This is used for sending logon scripts to the ICS. Sending
1858 without a delay causes problems when using timestamp on ICC
1859 (at least on my machine). */
1861 SendToICSDelayed(s,msdelay)
1865 int count, outCount, outError;
1867 if (icsPR == NULL) return;
1870 if (appData.debugMode) {
1871 fprintf(debugFP, ">ICS: ");
1872 show_bytes(debugFP, s, count);
1873 fprintf(debugFP, "\n");
1875 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1877 if (outCount < count) {
1878 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1883 /* Remove all highlighting escape sequences in s
1884 Also deletes any suffix starting with '('
1887 StripHighlightAndTitle(s)
1890 static char retbuf[MSG_SIZ];
1893 while (*s != NULLCHAR) {
1894 while (*s == '\033') {
1895 while (*s != NULLCHAR && !isalpha(*s)) s++;
1896 if (*s != NULLCHAR) s++;
1898 while (*s != NULLCHAR && *s != '\033') {
1899 if (*s == '(' || *s == '[') {
1910 /* Remove all highlighting escape sequences in s */
1915 static char retbuf[MSG_SIZ];
1918 while (*s != NULLCHAR) {
1919 while (*s == '\033') {
1920 while (*s != NULLCHAR && !isalpha(*s)) s++;
1921 if (*s != NULLCHAR) s++;
1923 while (*s != NULLCHAR && *s != '\033') {
1931 char *variantNames[] = VARIANT_NAMES;
1936 return variantNames[v];
1940 /* Identify a variant from the strings the chess servers use or the
1941 PGN Variant tag names we use. */
1948 VariantClass v = VariantNormal;
1949 int i, found = FALSE;
1955 /* [HGM] skip over optional board-size prefixes */
1956 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1957 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1958 while( *e++ != '_');
1961 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1965 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1966 if (StrCaseStr(e, variantNames[i])) {
1967 v = (VariantClass) i;
1974 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1975 || StrCaseStr(e, "wild/fr")
1976 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1977 v = VariantFischeRandom;
1978 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1979 (i = 1, p = StrCaseStr(e, "w"))) {
1981 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1988 case 0: /* FICS only, actually */
1990 /* Castling legal even if K starts on d-file */
1991 v = VariantWildCastle;
1996 /* Castling illegal even if K & R happen to start in
1997 normal positions. */
1998 v = VariantNoCastle;
2011 /* Castling legal iff K & R start in normal positions */
2017 /* Special wilds for position setup; unclear what to do here */
2018 v = VariantLoadable;
2021 /* Bizarre ICC game */
2022 v = VariantTwoKings;
2025 v = VariantKriegspiel;
2031 v = VariantFischeRandom;
2034 v = VariantCrazyhouse;
2037 v = VariantBughouse;
2043 /* Not quite the same as FICS suicide! */
2044 v = VariantGiveaway;
2050 v = VariantShatranj;
2053 /* Temporary names for future ICC types. The name *will* change in
2054 the next xboard/WinBoard release after ICC defines it. */
2092 v = VariantCapablanca;
2095 v = VariantKnightmate;
2101 v = VariantCylinder;
2107 v = VariantCapaRandom;
2110 v = VariantBerolina;
2122 /* Found "wild" or "w" in the string but no number;
2123 must assume it's normal chess. */
2127 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2128 if( (len > MSG_SIZ) && appData.debugMode )
2129 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2131 DisplayError(buf, 0);
2137 if (appData.debugMode) {
2138 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2139 e, wnum, VariantName(v));
2144 static int leftover_start = 0, leftover_len = 0;
2145 char star_match[STAR_MATCH_N][MSG_SIZ];
2147 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2148 advance *index beyond it, and set leftover_start to the new value of
2149 *index; else return FALSE. If pattern contains the character '*', it
2150 matches any sequence of characters not containing '\r', '\n', or the
2151 character following the '*' (if any), and the matched sequence(s) are
2152 copied into star_match.
2155 looking_at(buf, index, pattern)
2160 char *bufp = &buf[*index], *patternp = pattern;
2162 char *matchp = star_match[0];
2165 if (*patternp == NULLCHAR) {
2166 *index = leftover_start = bufp - buf;
2170 if (*bufp == NULLCHAR) return FALSE;
2171 if (*patternp == '*') {
2172 if (*bufp == *(patternp + 1)) {
2174 matchp = star_match[++star_count];
2178 } else if (*bufp == '\n' || *bufp == '\r') {
2180 if (*patternp == NULLCHAR)
2185 *matchp++ = *bufp++;
2189 if (*patternp != *bufp) return FALSE;
2196 SendToPlayer(data, length)
2200 int error, outCount;
2201 outCount = OutputToProcess(NoProc, data, length, &error);
2202 if (outCount < length) {
2203 DisplayFatalError(_("Error writing to display"), error, 1);
2208 PackHolding(packed, holding)
2220 switch (runlength) {
2231 sprintf(q, "%d", runlength);
2243 /* Telnet protocol requests from the front end */
2245 TelnetRequest(ddww, option)
2246 unsigned char ddww, option;
2248 unsigned char msg[3];
2249 int outCount, outError;
2251 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2253 if (appData.debugMode) {
2254 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2270 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2279 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2282 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2287 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2289 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2296 if (!appData.icsActive) return;
2297 TelnetRequest(TN_DO, TN_ECHO);
2303 if (!appData.icsActive) return;
2304 TelnetRequest(TN_DONT, TN_ECHO);
2308 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2310 /* put the holdings sent to us by the server on the board holdings area */
2311 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2315 if(gameInfo.holdingsWidth < 2) return;
2316 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2317 return; // prevent overwriting by pre-board holdings
2319 if( (int)lowestPiece >= BlackPawn ) {
2322 holdingsStartRow = BOARD_HEIGHT-1;
2325 holdingsColumn = BOARD_WIDTH-1;
2326 countsColumn = BOARD_WIDTH-2;
2327 holdingsStartRow = 0;
2331 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2332 board[i][holdingsColumn] = EmptySquare;
2333 board[i][countsColumn] = (ChessSquare) 0;
2335 while( (p=*holdings++) != NULLCHAR ) {
2336 piece = CharToPiece( ToUpper(p) );
2337 if(piece == EmptySquare) continue;
2338 /*j = (int) piece - (int) WhitePawn;*/
2339 j = PieceToNumber(piece);
2340 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2341 if(j < 0) continue; /* should not happen */
2342 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2343 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2344 board[holdingsStartRow+j*direction][countsColumn]++;
2350 VariantSwitch(Board board, VariantClass newVariant)
2352 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2353 static Board oldBoard;
2355 startedFromPositionFile = FALSE;
2356 if(gameInfo.variant == newVariant) return;
2358 /* [HGM] This routine is called each time an assignment is made to
2359 * gameInfo.variant during a game, to make sure the board sizes
2360 * are set to match the new variant. If that means adding or deleting
2361 * holdings, we shift the playing board accordingly
2362 * This kludge is needed because in ICS observe mode, we get boards
2363 * of an ongoing game without knowing the variant, and learn about the
2364 * latter only later. This can be because of the move list we requested,
2365 * in which case the game history is refilled from the beginning anyway,
2366 * but also when receiving holdings of a crazyhouse game. In the latter
2367 * case we want to add those holdings to the already received position.
2371 if (appData.debugMode) {
2372 fprintf(debugFP, "Switch board from %s to %s\n",
2373 VariantName(gameInfo.variant), VariantName(newVariant));
2374 setbuf(debugFP, NULL);
2376 shuffleOpenings = 0; /* [HGM] shuffle */
2377 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2381 newWidth = 9; newHeight = 9;
2382 gameInfo.holdingsSize = 7;
2383 case VariantBughouse:
2384 case VariantCrazyhouse:
2385 newHoldingsWidth = 2; break;
2389 newHoldingsWidth = 2;
2390 gameInfo.holdingsSize = 8;
2393 case VariantCapablanca:
2394 case VariantCapaRandom:
2397 newHoldingsWidth = gameInfo.holdingsSize = 0;
2400 if(newWidth != gameInfo.boardWidth ||
2401 newHeight != gameInfo.boardHeight ||
2402 newHoldingsWidth != gameInfo.holdingsWidth ) {
2404 /* shift position to new playing area, if needed */
2405 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2406 for(i=0; i<BOARD_HEIGHT; i++)
2407 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2408 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2410 for(i=0; i<newHeight; i++) {
2411 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2412 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2414 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2415 for(i=0; i<BOARD_HEIGHT; i++)
2416 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2417 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2420 gameInfo.boardWidth = newWidth;
2421 gameInfo.boardHeight = newHeight;
2422 gameInfo.holdingsWidth = newHoldingsWidth;
2423 gameInfo.variant = newVariant;
2424 InitDrawingSizes(-2, 0);
2425 } else gameInfo.variant = newVariant;
2426 CopyBoard(oldBoard, board); // remember correctly formatted board
2427 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2428 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2431 static int loggedOn = FALSE;
2433 /*-- Game start info cache: --*/
2435 char gs_kind[MSG_SIZ];
2436 static char player1Name[128] = "";
2437 static char player2Name[128] = "";
2438 static char cont_seq[] = "\n\\ ";
2439 static int player1Rating = -1;
2440 static int player2Rating = -1;
2441 /*----------------------------*/
2443 ColorClass curColor = ColorNormal;
2444 int suppressKibitz = 0;
2447 Boolean soughtPending = FALSE;
2448 Boolean seekGraphUp;
2449 #define MAX_SEEK_ADS 200
2451 char *seekAdList[MAX_SEEK_ADS];
2452 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2453 float tcList[MAX_SEEK_ADS];
2454 char colorList[MAX_SEEK_ADS];
2455 int nrOfSeekAds = 0;
2456 int minRating = 1010, maxRating = 2800;
2457 int hMargin = 10, vMargin = 20, h, w;
2458 extern int squareSize, lineGap;
2463 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2464 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2465 if(r < minRating+100 && r >=0 ) r = minRating+100;
2466 if(r > maxRating) r = maxRating;
2467 if(tc < 1.) tc = 1.;
2468 if(tc > 95.) tc = 95.;
2469 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2470 y = ((double)r - minRating)/(maxRating - minRating)
2471 * (h-vMargin-squareSize/8-1) + vMargin;
2472 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2473 if(strstr(seekAdList[i], " u ")) color = 1;
2474 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2475 !strstr(seekAdList[i], "bullet") &&
2476 !strstr(seekAdList[i], "blitz") &&
2477 !strstr(seekAdList[i], "standard") ) color = 2;
2478 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2479 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2483 AddAd(char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2485 char buf[MSG_SIZ], *ext = "";
2486 VariantClass v = StringToVariant(type);
2487 if(strstr(type, "wild")) {
2488 ext = type + 4; // append wild number
2489 if(v == VariantFischeRandom) type = "chess960"; else
2490 if(v == VariantLoadable) type = "setup"; else
2491 type = VariantName(v);
2493 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2494 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2495 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2496 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2497 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2498 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2499 seekNrList[nrOfSeekAds] = nr;
2500 zList[nrOfSeekAds] = 0;
2501 seekAdList[nrOfSeekAds++] = StrSave(buf);
2502 if(plot) PlotSeekAd(nrOfSeekAds-1);
2509 int x = xList[i], y = yList[i], d=squareSize/4, k;
2510 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2511 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2512 // now replot every dot that overlapped
2513 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2514 int xx = xList[k], yy = yList[k];
2515 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2516 DrawSeekDot(xx, yy, colorList[k]);
2521 RemoveSeekAd(int nr)
2524 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2526 if(seekAdList[i]) free(seekAdList[i]);
2527 seekAdList[i] = seekAdList[--nrOfSeekAds];
2528 seekNrList[i] = seekNrList[nrOfSeekAds];
2529 ratingList[i] = ratingList[nrOfSeekAds];
2530 colorList[i] = colorList[nrOfSeekAds];
2531 tcList[i] = tcList[nrOfSeekAds];
2532 xList[i] = xList[nrOfSeekAds];
2533 yList[i] = yList[nrOfSeekAds];
2534 zList[i] = zList[nrOfSeekAds];
2535 seekAdList[nrOfSeekAds] = NULL;
2541 MatchSoughtLine(char *line)
2543 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2544 int nr, base, inc, u=0; char dummy;
2546 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2547 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2549 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2550 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2551 // match: compact and save the line
2552 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2562 if(!seekGraphUp) return FALSE;
2563 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2564 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2566 DrawSeekBackground(0, 0, w, h);
2567 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2568 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2569 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2570 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2572 DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2575 snprintf(buf, MSG_SIZ, "%d", i);
2576 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2579 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2580 for(i=1; i<100; i+=(i<10?1:5)) {
2581 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2582 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2583 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2585 snprintf(buf, MSG_SIZ, "%d", i);
2586 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2589 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2593 int SeekGraphClick(ClickType click, int x, int y, int moving)
2595 static int lastDown = 0, displayed = 0, lastSecond;
2596 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2597 if(click == Release || moving) return FALSE;
2599 soughtPending = TRUE;
2600 SendToICS(ics_prefix);
2601 SendToICS("sought\n"); // should this be "sought all"?
2602 } else { // issue challenge based on clicked ad
2603 int dist = 10000; int i, closest = 0, second = 0;
2604 for(i=0; i<nrOfSeekAds; i++) {
2605 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2606 if(d < dist) { dist = d; closest = i; }
2607 second += (d - zList[i] < 120); // count in-range ads
2608 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2612 second = (second > 1);
2613 if(displayed != closest || second != lastSecond) {
2614 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2615 lastSecond = second; displayed = closest;
2617 if(click == Press) {
2618 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2621 } // on press 'hit', only show info
2622 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2623 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2624 SendToICS(ics_prefix);
2626 return TRUE; // let incoming board of started game pop down the graph
2627 } else if(click == Release) { // release 'miss' is ignored
2628 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2629 if(moving == 2) { // right up-click
2630 nrOfSeekAds = 0; // refresh graph
2631 soughtPending = TRUE;
2632 SendToICS(ics_prefix);
2633 SendToICS("sought\n"); // should this be "sought all"?
2636 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2637 // press miss or release hit 'pop down' seek graph
2638 seekGraphUp = FALSE;
2639 DrawPosition(TRUE, NULL);
2645 read_from_ics(isr, closure, data, count, error)
2652 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2653 #define STARTED_NONE 0
2654 #define STARTED_MOVES 1
2655 #define STARTED_BOARD 2
2656 #define STARTED_OBSERVE 3
2657 #define STARTED_HOLDINGS 4
2658 #define STARTED_CHATTER 5
2659 #define STARTED_COMMENT 6
2660 #define STARTED_MOVES_NOHIDE 7
2662 static int started = STARTED_NONE;
2663 static char parse[20000];
2664 static int parse_pos = 0;
2665 static char buf[BUF_SIZE + 1];
2666 static int firstTime = TRUE, intfSet = FALSE;
2667 static ColorClass prevColor = ColorNormal;
2668 static int savingComment = FALSE;
2669 static int cmatch = 0; // continuation sequence match
2676 int backup; /* [DM] For zippy color lines */
2678 char talker[MSG_SIZ]; // [HGM] chat
2681 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2683 if (appData.debugMode) {
2685 fprintf(debugFP, "<ICS: ");
2686 show_bytes(debugFP, data, count);
2687 fprintf(debugFP, "\n");
2691 if (appData.debugMode) { int f = forwardMostMove;
2692 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2693 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2694 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2697 /* If last read ended with a partial line that we couldn't parse,
2698 prepend it to the new read and try again. */
2699 if (leftover_len > 0) {
2700 for (i=0; i<leftover_len; i++)
2701 buf[i] = buf[leftover_start + i];
2704 /* copy new characters into the buffer */
2705 bp = buf + leftover_len;
2706 buf_len=leftover_len;
2707 for (i=0; i<count; i++)
2710 if (data[i] == '\r')
2713 // join lines split by ICS?
2714 if (!appData.noJoin)
2717 Joining just consists of finding matches against the
2718 continuation sequence, and discarding that sequence
2719 if found instead of copying it. So, until a match
2720 fails, there's nothing to do since it might be the
2721 complete sequence, and thus, something we don't want
2724 if (data[i] == cont_seq[cmatch])
2727 if (cmatch == strlen(cont_seq))
2729 cmatch = 0; // complete match. just reset the counter
2732 it's possible for the ICS to not include the space
2733 at the end of the last word, making our [correct]
2734 join operation fuse two separate words. the server
2735 does this when the space occurs at the width setting.
2737 if (!buf_len || buf[buf_len-1] != ' ')
2748 match failed, so we have to copy what matched before
2749 falling through and copying this character. In reality,
2750 this will only ever be just the newline character, but
2751 it doesn't hurt to be precise.
2753 strncpy(bp, cont_seq, cmatch);
2765 buf[buf_len] = NULLCHAR;
2766 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2771 while (i < buf_len) {
2772 /* Deal with part of the TELNET option negotiation
2773 protocol. We refuse to do anything beyond the
2774 defaults, except that we allow the WILL ECHO option,
2775 which ICS uses to turn off password echoing when we are
2776 directly connected to it. We reject this option
2777 if localLineEditing mode is on (always on in xboard)
2778 and we are talking to port 23, which might be a real
2779 telnet server that will try to keep WILL ECHO on permanently.
2781 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2782 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2783 unsigned char option;
2785 switch ((unsigned char) buf[++i]) {
2787 if (appData.debugMode)
2788 fprintf(debugFP, "\n<WILL ");
2789 switch (option = (unsigned char) buf[++i]) {
2791 if (appData.debugMode)
2792 fprintf(debugFP, "ECHO ");
2793 /* Reply only if this is a change, according
2794 to the protocol rules. */
2795 if (remoteEchoOption) break;
2796 if (appData.localLineEditing &&
2797 atoi(appData.icsPort) == TN_PORT) {
2798 TelnetRequest(TN_DONT, TN_ECHO);
2801 TelnetRequest(TN_DO, TN_ECHO);
2802 remoteEchoOption = TRUE;
2806 if (appData.debugMode)
2807 fprintf(debugFP, "%d ", option);
2808 /* Whatever this is, we don't want it. */
2809 TelnetRequest(TN_DONT, option);
2814 if (appData.debugMode)
2815 fprintf(debugFP, "\n<WONT ");
2816 switch (option = (unsigned char) buf[++i]) {
2818 if (appData.debugMode)
2819 fprintf(debugFP, "ECHO ");
2820 /* Reply only if this is a change, according
2821 to the protocol rules. */
2822 if (!remoteEchoOption) break;
2824 TelnetRequest(TN_DONT, TN_ECHO);
2825 remoteEchoOption = FALSE;
2828 if (appData.debugMode)
2829 fprintf(debugFP, "%d ", (unsigned char) option);
2830 /* Whatever this is, it must already be turned
2831 off, because we never agree to turn on
2832 anything non-default, so according to the
2833 protocol rules, we don't reply. */
2838 if (appData.debugMode)
2839 fprintf(debugFP, "\n<DO ");
2840 switch (option = (unsigned char) buf[++i]) {
2842 /* Whatever this is, we refuse to do it. */
2843 if (appData.debugMode)
2844 fprintf(debugFP, "%d ", option);
2845 TelnetRequest(TN_WONT, option);
2850 if (appData.debugMode)
2851 fprintf(debugFP, "\n<DONT ");
2852 switch (option = (unsigned char) buf[++i]) {
2854 if (appData.debugMode)
2855 fprintf(debugFP, "%d ", option);
2856 /* Whatever this is, we are already not doing
2857 it, because we never agree to do anything
2858 non-default, so according to the protocol
2859 rules, we don't reply. */
2864 if (appData.debugMode)
2865 fprintf(debugFP, "\n<IAC ");
2866 /* Doubled IAC; pass it through */
2870 if (appData.debugMode)
2871 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2872 /* Drop all other telnet commands on the floor */
2875 if (oldi > next_out)
2876 SendToPlayer(&buf[next_out], oldi - next_out);
2882 /* OK, this at least will *usually* work */
2883 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2887 if (loggedOn && !intfSet) {
2888 if (ics_type == ICS_ICC) {
2889 snprintf(str, MSG_SIZ,
2890 "/set-quietly interface %s\n/set-quietly style 12\n",
2892 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2893 strcat(str, "/set-2 51 1\n/set seek 1\n");
2894 } else if (ics_type == ICS_CHESSNET) {
2895 snprintf(str, MSG_SIZ, "/style 12\n");
2897 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2898 strcat(str, programVersion);
2899 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2900 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2901 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2903 strcat(str, "$iset nohighlight 1\n");
2905 strcat(str, "$iset lock 1\n$style 12\n");
2908 NotifyFrontendLogin();
2912 if (started == STARTED_COMMENT) {
2913 /* Accumulate characters in comment */
2914 parse[parse_pos++] = buf[i];
2915 if (buf[i] == '\n') {
2916 parse[parse_pos] = NULLCHAR;
2917 if(chattingPartner>=0) {
2919 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2920 OutputChatMessage(chattingPartner, mess);
2921 chattingPartner = -1;
2922 next_out = i+1; // [HGM] suppress printing in ICS window
2924 if(!suppressKibitz) // [HGM] kibitz
2925 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2926 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2927 int nrDigit = 0, nrAlph = 0, j;
2928 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2929 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2930 parse[parse_pos] = NULLCHAR;
2931 // try to be smart: if it does not look like search info, it should go to
2932 // ICS interaction window after all, not to engine-output window.
2933 for(j=0; j<parse_pos; j++) { // count letters and digits
2934 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2935 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2936 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2938 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2939 int depth=0; float score;
2940 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2941 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2942 pvInfoList[forwardMostMove-1].depth = depth;
2943 pvInfoList[forwardMostMove-1].score = 100*score;
2945 OutputKibitz(suppressKibitz, parse);
2948 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2949 SendToPlayer(tmp, strlen(tmp));
2951 next_out = i+1; // [HGM] suppress printing in ICS window
2953 started = STARTED_NONE;
2955 /* Don't match patterns against characters in comment */
2960 if (started == STARTED_CHATTER) {
2961 if (buf[i] != '\n') {
2962 /* Don't match patterns against characters in chatter */
2966 started = STARTED_NONE;
2967 if(suppressKibitz) next_out = i+1;
2970 /* Kludge to deal with rcmd protocol */
2971 if (firstTime && looking_at(buf, &i, "\001*")) {
2972 DisplayFatalError(&buf[1], 0, 1);
2978 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2981 if (appData.debugMode)
2982 fprintf(debugFP, "ics_type %d\n", ics_type);
2985 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2986 ics_type = ICS_FICS;
2988 if (appData.debugMode)
2989 fprintf(debugFP, "ics_type %d\n", ics_type);
2992 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2993 ics_type = ICS_CHESSNET;
2995 if (appData.debugMode)
2996 fprintf(debugFP, "ics_type %d\n", ics_type);
3001 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3002 looking_at(buf, &i, "Logging you in as \"*\"") ||
3003 looking_at(buf, &i, "will be \"*\""))) {
3004 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3008 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3010 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3011 DisplayIcsInteractionTitle(buf);
3012 have_set_title = TRUE;
3015 /* skip finger notes */
3016 if (started == STARTED_NONE &&
3017 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3018 (buf[i] == '1' && buf[i+1] == '0')) &&
3019 buf[i+2] == ':' && buf[i+3] == ' ') {
3020 started = STARTED_CHATTER;
3026 // [HGM] seekgraph: recognize sought lines and end-of-sought message
3027 if(appData.seekGraph) {
3028 if(soughtPending && MatchSoughtLine(buf+i)) {
3029 i = strstr(buf+i, "rated") - buf;
3030 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3031 next_out = leftover_start = i;
3032 started = STARTED_CHATTER;
3033 suppressKibitz = TRUE;
3036 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3037 && looking_at(buf, &i, "* ads displayed")) {
3038 soughtPending = FALSE;
3043 if(appData.autoRefresh) {
3044 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3045 int s = (ics_type == ICS_ICC); // ICC format differs
3047 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3048 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3049 looking_at(buf, &i, "*% "); // eat prompt
3050 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3051 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3052 next_out = i; // suppress
3055 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3056 char *p = star_match[0];
3058 if(seekGraphUp) RemoveSeekAd(atoi(p));
3059 while(*p && *p++ != ' '); // next
3061 looking_at(buf, &i, "*% "); // eat prompt
3062 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3069 /* skip formula vars */
3070 if (started == STARTED_NONE &&
3071 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3072 started = STARTED_CHATTER;
3077 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3078 if (appData.autoKibitz && started == STARTED_NONE &&
3079 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3080 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3081 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3082 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3083 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3084 suppressKibitz = TRUE;
3085 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3087 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3088 && (gameMode == IcsPlayingWhite)) ||
3089 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3090 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3091 started = STARTED_CHATTER; // own kibitz we simply discard
3093 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3094 parse_pos = 0; parse[0] = NULLCHAR;
3095 savingComment = TRUE;
3096 suppressKibitz = gameMode != IcsObserving ? 2 :
3097 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3101 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3102 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3103 && atoi(star_match[0])) {
3104 // suppress the acknowledgements of our own autoKibitz
3106 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3107 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3108 SendToPlayer(star_match[0], strlen(star_match[0]));
3109 if(looking_at(buf, &i, "*% ")) // eat prompt
3110 suppressKibitz = FALSE;
3114 } // [HGM] kibitz: end of patch
3116 // [HGM] chat: intercept tells by users for which we have an open chat window
3118 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3119 looking_at(buf, &i, "* whispers:") ||
3120 looking_at(buf, &i, "* kibitzes:") ||
3121 looking_at(buf, &i, "* shouts:") ||
3122 looking_at(buf, &i, "* c-shouts:") ||
3123 looking_at(buf, &i, "--> * ") ||
3124 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3125 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3126 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3127 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3129 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3130 chattingPartner = -1;
3132 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3133 for(p=0; p<MAX_CHAT; p++) {
3134 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3135 talker[0] = '['; strcat(talker, "] ");
3136 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3137 chattingPartner = p; break;
3140 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3141 for(p=0; p<MAX_CHAT; p++) {
3142 if(!strcmp("kibitzes", chatPartner[p])) {
3143 talker[0] = '['; strcat(talker, "] ");
3144 chattingPartner = p; break;
3147 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3148 for(p=0; p<MAX_CHAT; p++) {
3149 if(!strcmp("whispers", chatPartner[p])) {
3150 talker[0] = '['; strcat(talker, "] ");
3151 chattingPartner = p; break;
3154 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3155 if(buf[i-8] == '-' && buf[i-3] == 't')
3156 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3157 if(!strcmp("c-shouts", chatPartner[p])) {
3158 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3159 chattingPartner = p; break;
3162 if(chattingPartner < 0)
3163 for(p=0; p<MAX_CHAT; p++) {
3164 if(!strcmp("shouts", chatPartner[p])) {
3165 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3166 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3167 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3168 chattingPartner = p; break;
3172 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3173 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3174 talker[0] = 0; Colorize(ColorTell, FALSE);
3175 chattingPartner = p; break;
3177 if(chattingPartner<0) i = oldi; else {
3178 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3179 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3180 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3181 started = STARTED_COMMENT;
3182 parse_pos = 0; parse[0] = NULLCHAR;
3183 savingComment = 3 + chattingPartner; // counts as TRUE
3184 suppressKibitz = TRUE;
3187 } // [HGM] chat: end of patch
3190 if (appData.zippyTalk || appData.zippyPlay) {
3191 /* [DM] Backup address for color zippy lines */
3193 if (loggedOn == TRUE)
3194 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3195 (appData.zippyPlay && ZippyMatch(buf, &backup)));
3197 } // [DM] 'else { ' deleted
3199 /* Regular tells and says */
3200 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3201 looking_at(buf, &i, "* (your partner) tells you: ") ||
3202 looking_at(buf, &i, "* says: ") ||
3203 /* Don't color "message" or "messages" output */
3204 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3205 looking_at(buf, &i, "*. * at *:*: ") ||
3206 looking_at(buf, &i, "--* (*:*): ") ||
3207 /* Message notifications (same color as tells) */
3208 looking_at(buf, &i, "* has left a message ") ||
3209 looking_at(buf, &i, "* just sent you a message:\n") ||
3210 /* Whispers and kibitzes */
3211 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3212 looking_at(buf, &i, "* kibitzes: ") ||
3214 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3216 if (tkind == 1 && strchr(star_match[0], ':')) {
3217 /* Avoid "tells you:" spoofs in channels */
3220 if (star_match[0][0] == NULLCHAR ||
3221 strchr(star_match[0], ' ') ||
3222 (tkind == 3 && strchr(star_match[1], ' '))) {
3223 /* Reject bogus matches */
3226 if (appData.colorize) {
3227 if (oldi > next_out) {
3228 SendToPlayer(&buf[next_out], oldi - next_out);
3233 Colorize(ColorTell, FALSE);
3234 curColor = ColorTell;
3237 Colorize(ColorKibitz, FALSE);
3238 curColor = ColorKibitz;
3241 p = strrchr(star_match[1], '(');
3248 Colorize(ColorChannel1, FALSE);
3249 curColor = ColorChannel1;
3251 Colorize(ColorChannel, FALSE);
3252 curColor = ColorChannel;
3256 curColor = ColorNormal;
3260 if (started == STARTED_NONE && appData.autoComment &&
3261 (gameMode == IcsObserving ||
3262 gameMode == IcsPlayingWhite ||
3263 gameMode == IcsPlayingBlack)) {
3264 parse_pos = i - oldi;
3265 memcpy(parse, &buf[oldi], parse_pos);
3266 parse[parse_pos] = NULLCHAR;
3267 started = STARTED_COMMENT;
3268 savingComment = TRUE;
3270 started = STARTED_CHATTER;
3271 savingComment = FALSE;
3278 if (looking_at(buf, &i, "* s-shouts: ") ||
3279 looking_at(buf, &i, "* c-shouts: ")) {
3280 if (appData.colorize) {
3281 if (oldi > next_out) {
3282 SendToPlayer(&buf[next_out], oldi - next_out);
3285 Colorize(ColorSShout, FALSE);
3286 curColor = ColorSShout;
3289 started = STARTED_CHATTER;
3293 if (looking_at(buf, &i, "--->")) {
3298 if (looking_at(buf, &i, "* shouts: ") ||
3299 looking_at(buf, &i, "--> ")) {
3300 if (appData.colorize) {
3301 if (oldi > next_out) {
3302 SendToPlayer(&buf[next_out], oldi - next_out);
3305 Colorize(ColorShout, FALSE);
3306 curColor = ColorShout;
3309 started = STARTED_CHATTER;
3313 if (looking_at( buf, &i, "Challenge:")) {
3314 if (appData.colorize) {
3315 if (oldi > next_out) {
3316 SendToPlayer(&buf[next_out], oldi - next_out);
3319 Colorize(ColorChallenge, FALSE);
3320 curColor = ColorChallenge;
3326 if (looking_at(buf, &i, "* offers you") ||
3327 looking_at(buf, &i, "* offers to be") ||
3328 looking_at(buf, &i, "* would like to") ||
3329 looking_at(buf, &i, "* requests to") ||
3330 looking_at(buf, &i, "Your opponent offers") ||
3331 looking_at(buf, &i, "Your opponent requests")) {
3333 if (appData.colorize) {
3334 if (oldi > next_out) {
3335 SendToPlayer(&buf[next_out], oldi - next_out);
3338 Colorize(ColorRequest, FALSE);
3339 curColor = ColorRequest;
3344 if (looking_at(buf, &i, "* (*) seeking")) {
3345 if (appData.colorize) {
3346 if (oldi > next_out) {
3347 SendToPlayer(&buf[next_out], oldi - next_out);
3350 Colorize(ColorSeek, FALSE);
3351 curColor = ColorSeek;
3356 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3358 if (looking_at(buf, &i, "\\ ")) {
3359 if (prevColor != ColorNormal) {
3360 if (oldi > next_out) {
3361 SendToPlayer(&buf[next_out], oldi - next_out);
3364 Colorize(prevColor, TRUE);
3365 curColor = prevColor;
3367 if (savingComment) {
3368 parse_pos = i - oldi;
3369 memcpy(parse, &buf[oldi], parse_pos);
3370 parse[parse_pos] = NULLCHAR;
3371 started = STARTED_COMMENT;
3372 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3373 chattingPartner = savingComment - 3; // kludge to remember the box
3375 started = STARTED_CHATTER;
3380 if (looking_at(buf, &i, "Black Strength :") ||
3381 looking_at(buf, &i, "<<< style 10 board >>>") ||
3382 looking_at(buf, &i, "<10>") ||
3383 looking_at(buf, &i, "#@#")) {
3384 /* Wrong board style */
3386 SendToICS(ics_prefix);
3387 SendToICS("set style 12\n");
3388 SendToICS(ics_prefix);
3389 SendToICS("refresh\n");
3393 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3395 have_sent_ICS_logon = 1;
3399 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3400 (looking_at(buf, &i, "\n<12> ") ||
3401 looking_at(buf, &i, "<12> "))) {
3403 if (oldi > next_out) {
3404 SendToPlayer(&buf[next_out], oldi - next_out);
3407 started = STARTED_BOARD;
3412 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3413 looking_at(buf, &i, "<b1> ")) {
3414 if (oldi > next_out) {