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;
864 if(n) return; // only startup first engine immediately; second can wait
865 savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
869 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
870 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
872 static char resetOptions[] =
873 "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
874 "-firstOptions \"\" -firstNPS -1 -fn \"\"";
877 Load(ChessProgramState *cps, int i)
879 char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
880 if(engineLine[0]) { // an engine was selected from the combo box
881 snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
882 SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
883 ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL;
884 ParseArgsFromString(buf);
886 ReplaceEngine(cps, i);
890 while(q = strchr(p, SLASH)) p = q+1;
891 if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
892 if(engineDir[0] != NULLCHAR)
893 appData.directory[i] = engineDir;
894 else if(p != engineName) { // derive directory from engine path, when not given
896 appData.directory[i] = strdup(engineName);
898 } else appData.directory[i] = ".";
899 if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
901 snprintf(command, MSG_SIZ, "%s %s", p, params);
904 appData.chessProgram[i] = strdup(p);
905 appData.isUCI[i] = isUCI;
906 appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
907 appData.hasOwnBookUCI[i] = hasBook;
908 if(!nickName[0]) useNick = FALSE;
909 if(useNick) ASSIGN(appData.pgnName[i], nickName);
913 q = firstChessProgramNames;
914 if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
915 quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
916 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
917 quote, p, quote, appData.directory[i],
918 useNick ? " -fn \"" : "",
919 useNick ? nickName : "",
921 v1 ? " -firstProtocolVersion 1" : "",
922 hasBook ? "" : " -fNoOwnBookUCI",
923 isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
924 storeVariant ? " -variant " : "",
925 storeVariant ? VariantName(gameInfo.variant) : "");
926 firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
927 snprintf(firstChessProgramNames, len, "%s%s", q, buf);
930 ReplaceEngine(cps, i);
936 int matched, min, sec;
938 * Parse timeControl resource
940 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
941 appData.movesPerSession)) {
943 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
944 DisplayFatalError(buf, 0, 2);
948 * Parse searchTime resource
950 if (*appData.searchTime != NULLCHAR) {
951 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
953 searchTime = min * 60;
954 } else if (matched == 2) {
955 searchTime = min * 60 + sec;
958 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
959 DisplayFatalError(buf, 0, 2);
968 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
969 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
971 GetTimeMark(&programStartTime);
972 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
973 pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
976 programStats.ok_to_send = 1;
977 programStats.seen_stat = 0;
980 * Initialize game list
986 * Internet chess server status
988 if (appData.icsActive) {
989 appData.matchMode = FALSE;
990 appData.matchGames = 0;
992 appData.noChessProgram = !appData.zippyPlay;
994 appData.zippyPlay = FALSE;
995 appData.zippyTalk = FALSE;
996 appData.noChessProgram = TRUE;
998 if (*appData.icsHelper != NULLCHAR) {
999 appData.useTelnet = TRUE;
1000 appData.telnetProgram = appData.icsHelper;
1003 appData.zippyTalk = appData.zippyPlay = FALSE;
1006 /* [AS] Initialize pv info list [HGM] and game state */
1010 for( i=0; i<=framePtr; i++ ) {
1011 pvInfoList[i].depth = -1;
1012 boards[i][EP_STATUS] = EP_NONE;
1013 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1019 /* [AS] Adjudication threshold */
1020 adjudicateLossThreshold = appData.adjudicateLossThreshold;
1022 InitEngine(&first, 0);
1023 InitEngine(&second, 1);
1026 pairing.which = "pairing"; // pairing engine
1027 pairing.pr = NoProc;
1029 pairing.program = appData.pairingEngine;
1030 pairing.host = "localhost";
1033 if (appData.icsActive) {
1034 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
1035 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1036 appData.clockMode = FALSE;
1037 first.sendTime = second.sendTime = 0;
1041 /* Override some settings from environment variables, for backward
1042 compatibility. Unfortunately it's not feasible to have the env
1043 vars just set defaults, at least in xboard. Ugh.
1045 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1050 if (!appData.icsActive) {
1054 /* Check for variants that are supported only in ICS mode,
1055 or not at all. Some that are accepted here nevertheless
1056 have bugs; see comments below.
1058 VariantClass variant = StringToVariant(appData.variant);
1060 case VariantBughouse: /* need four players and two boards */
1061 case VariantKriegspiel: /* need to hide pieces and move details */
1062 /* case VariantFischeRandom: (Fabien: moved below) */
1063 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1064 if( (len > MSG_SIZ) && appData.debugMode )
1065 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1067 DisplayFatalError(buf, 0, 2);
1070 case VariantUnknown:
1071 case VariantLoadable:
1081 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1082 if( (len > MSG_SIZ) && appData.debugMode )
1083 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1085 DisplayFatalError(buf, 0, 2);
1088 case VariantXiangqi: /* [HGM] repetition rules not implemented */
1089 case VariantFairy: /* [HGM] TestLegality definitely off! */
1090 case VariantGothic: /* [HGM] should work */
1091 case VariantCapablanca: /* [HGM] should work */
1092 case VariantCourier: /* [HGM] initial forced moves not implemented */
1093 case VariantShogi: /* [HGM] could still mate with pawn drop */
1094 case VariantKnightmate: /* [HGM] should work */
1095 case VariantCylinder: /* [HGM] untested */
1096 case VariantFalcon: /* [HGM] untested */
1097 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1098 offboard interposition not understood */
1099 case VariantNormal: /* definitely works! */
1100 case VariantWildCastle: /* pieces not automatically shuffled */
1101 case VariantNoCastle: /* pieces not automatically shuffled */
1102 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1103 case VariantLosers: /* should work except for win condition,
1104 and doesn't know captures are mandatory */
1105 case VariantSuicide: /* should work except for win condition,
1106 and doesn't know captures are mandatory */
1107 case VariantGiveaway: /* should work except for win condition,
1108 and doesn't know captures are mandatory */
1109 case VariantTwoKings: /* should work */
1110 case VariantAtomic: /* should work except for win condition */
1111 case Variant3Check: /* should work except for win condition */
1112 case VariantShatranj: /* should work except for all win conditions */
1113 case VariantMakruk: /* should work except for daw countdown */
1114 case VariantBerolina: /* might work if TestLegality is off */
1115 case VariantCapaRandom: /* should work */
1116 case VariantJanus: /* should work */
1117 case VariantSuper: /* experimental */
1118 case VariantGreat: /* experimental, requires legality testing to be off */
1119 case VariantSChess: /* S-Chess, should work */
1120 case VariantSpartan: /* should work */
1127 int NextIntegerFromString( char ** str, long * value )
1132 while( *s == ' ' || *s == '\t' ) {
1138 if( *s >= '0' && *s <= '9' ) {
1139 while( *s >= '0' && *s <= '9' ) {
1140 *value = *value * 10 + (*s - '0');
1152 int NextTimeControlFromString( char ** str, long * value )
1155 int result = NextIntegerFromString( str, &temp );
1158 *value = temp * 60; /* Minutes */
1159 if( **str == ':' ) {
1161 result = NextIntegerFromString( str, &temp );
1162 *value += temp; /* Seconds */
1169 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1170 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1171 int result = -1, type = 0; long temp, temp2;
1173 if(**str != ':') return -1; // old params remain in force!
1175 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1176 if( NextIntegerFromString( str, &temp ) ) return -1;
1177 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1180 /* time only: incremental or sudden-death time control */
1181 if(**str == '+') { /* increment follows; read it */
1183 if(**str == '!') type = *(*str)++; // Bronstein TC
1184 if(result = NextIntegerFromString( str, &temp2)) return -1;
1185 *inc = temp2 * 1000;
1186 if(**str == '.') { // read fraction of increment
1187 char *start = ++(*str);
1188 if(result = NextIntegerFromString( str, &temp2)) return -1;
1190 while(start++ < *str) temp2 /= 10;
1194 *moves = 0; *tc = temp * 1000; *incType = type;
1198 (*str)++; /* classical time control */
1199 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1210 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1211 { /* [HGM] get time to add from the multi-session time-control string */
1212 int incType, moves=1; /* kludge to force reading of first session */
1213 long time, increment;
1216 if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1217 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1219 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1220 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1221 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1222 if(movenr == -1) return time; /* last move before new session */
1223 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1224 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1225 if(!moves) return increment; /* current session is incremental */
1226 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1227 } while(movenr >= -1); /* try again for next session */
1229 return 0; // no new time quota on this move
1233 ParseTimeControl(tc, ti, mps)
1240 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1243 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1244 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1245 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1249 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1251 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1254 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1256 snprintf(buf, MSG_SIZ, ":%s", mytc);
1258 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1260 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1265 /* Parse second time control */
1268 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1276 timeControl_2 = tc2 * 1000;
1286 timeControl = tc1 * 1000;
1289 timeIncrement = ti * 1000; /* convert to ms */
1290 movesPerSession = 0;
1293 movesPerSession = mps;
1301 if (appData.debugMode) {
1302 fprintf(debugFP, "%s\n", programVersion);
1305 set_cont_sequence(appData.wrapContSeq);
1306 if (appData.matchGames > 0) {
1307 appData.matchMode = TRUE;
1308 } else if (appData.matchMode) {
1309 appData.matchGames = 1;
1311 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1312 appData.matchGames = appData.sameColorGames;
1313 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1314 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1315 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1318 if (appData.noChessProgram || first.protocolVersion == 1) {
1321 /* kludge: allow timeout for initial "feature" commands */
1323 DisplayMessage("", _("Starting chess program"));
1324 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1329 CalculateIndex(int index, int gameNr)
1330 { // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1332 if(index > 0) return index; // fixed nmber
1333 if(index == 0) return 1;
1334 res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1335 if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1340 LoadGameOrPosition(int gameNr)
1341 { // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1342 if (*appData.loadGameFile != NULLCHAR) {
1343 if (!LoadGameFromFile(appData.loadGameFile,
1344 CalculateIndex(appData.loadGameIndex, gameNr),
1345 appData.loadGameFile, FALSE)) {
1346 DisplayFatalError(_("Bad game file"), 0, 1);
1349 } else if (*appData.loadPositionFile != NULLCHAR) {
1350 if (!LoadPositionFromFile(appData.loadPositionFile,
1351 CalculateIndex(appData.loadPositionIndex, gameNr),
1352 appData.loadPositionFile)) {
1353 DisplayFatalError(_("Bad position file"), 0, 1);
1361 ReserveGame(int gameNr, char resChar)
1363 FILE *tf = fopen(appData.tourneyFile, "r+");
1364 char *p, *q, c, buf[MSG_SIZ];
1365 if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1366 safeStrCpy(buf, lastMsg, MSG_SIZ);
1367 DisplayMessage(_("Pick new game"), "");
1368 flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1369 ParseArgsFromFile(tf);
1370 p = q = appData.results;
1371 if(appData.debugMode) {
1372 char *r = appData.participants;
1373 fprintf(debugFP, "results = '%s'\n", p);
1374 while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1375 fprintf(debugFP, "\n");
1377 while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1379 q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1380 safeStrCpy(q, p, strlen(p) + 2);
1381 if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1382 if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1383 if(nextGame <= appData.matchGames && resChar != ' ') { // already reserve next game, if tourney not yet done
1384 if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1387 fseek(tf, -(strlen(p)+4), SEEK_END);
1389 if(c != '"') // depending on DOS or Unix line endings we can be one off
1390 fseek(tf, -(strlen(p)+2), SEEK_END);
1391 else fseek(tf, -(strlen(p)+3), SEEK_END);
1392 fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1393 DisplayMessage(buf, "");
1394 free(p); appData.results = q;
1395 if(nextGame <= appData.matchGames && resChar != ' ' &&
1396 (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1397 UnloadEngine(&first); // next game belongs to other pairing;
1398 UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1403 MatchEvent(int mode)
1404 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1406 if(matchMode) { // already in match mode: switch it off
1408 appData.matchGames = appData.tourneyFile[0] ? nextGame: matchGame; // kludge to let match terminate after next game.
1409 ModeHighlight(); // kludgey way to remove checkmark...
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 } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1557 } else if (*appData.cmailGameName != NULLCHAR) {
1558 /* Set up cmail mode */
1559 ReloadCmailMsgEvent(TRUE);
1561 /* Set up other modes */
1562 if (initialMode == AnalyzeFile) {
1563 if (*appData.loadGameFile == NULLCHAR) {
1564 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1568 if (*appData.loadGameFile != NULLCHAR) {
1569 (void) LoadGameFromFile(appData.loadGameFile,
1570 appData.loadGameIndex,
1571 appData.loadGameFile, TRUE);
1572 } else if (*appData.loadPositionFile != NULLCHAR) {
1573 (void) LoadPositionFromFile(appData.loadPositionFile,
1574 appData.loadPositionIndex,
1575 appData.loadPositionFile);
1576 /* [HGM] try to make self-starting even after FEN load */
1577 /* to allow automatic setup of fairy variants with wtm */
1578 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1579 gameMode = BeginningOfGame;
1580 setboardSpoiledMachineBlack = 1;
1582 /* [HGM] loadPos: make that every new game uses the setup */
1583 /* from file as long as we do not switch variant */
1584 if(!blackPlaysFirst) {
1585 startedFromPositionFile = TRUE;
1586 CopyBoard(filePosition, boards[0]);
1589 if (initialMode == AnalyzeMode) {
1590 if (appData.noChessProgram) {
1591 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1594 if (appData.icsActive) {
1595 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1599 } else if (initialMode == AnalyzeFile) {
1600 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1601 ShowThinkingEvent();
1603 AnalysisPeriodicEvent(1);
1604 } else if (initialMode == MachinePlaysWhite) {
1605 if (appData.noChessProgram) {
1606 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1610 if (appData.icsActive) {
1611 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1615 MachineWhiteEvent();
1616 } else if (initialMode == MachinePlaysBlack) {
1617 if (appData.noChessProgram) {
1618 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1622 if (appData.icsActive) {
1623 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1627 MachineBlackEvent();
1628 } else if (initialMode == TwoMachinesPlay) {
1629 if (appData.noChessProgram) {
1630 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1634 if (appData.icsActive) {
1635 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1640 } else if (initialMode == EditGame) {
1642 } else if (initialMode == EditPosition) {
1643 EditPositionEvent();
1644 } else if (initialMode == Training) {
1645 if (*appData.loadGameFile == NULLCHAR) {
1646 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1655 * Establish will establish a contact to a remote host.port.
1656 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1657 * used to talk to the host.
1658 * Returns 0 if okay, error code if not.
1665 if (*appData.icsCommPort != NULLCHAR) {
1666 /* Talk to the host through a serial comm port */
1667 return OpenCommPort(appData.icsCommPort, &icsPR);
1669 } else if (*appData.gateway != NULLCHAR) {
1670 if (*appData.remoteShell == NULLCHAR) {
1671 /* Use the rcmd protocol to run telnet program on a gateway host */
1672 snprintf(buf, sizeof(buf), "%s %s %s",
1673 appData.telnetProgram, appData.icsHost, appData.icsPort);
1674 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1677 /* Use the rsh program to run telnet program on a gateway host */
1678 if (*appData.remoteUser == NULLCHAR) {
1679 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1680 appData.gateway, appData.telnetProgram,
1681 appData.icsHost, appData.icsPort);
1683 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1684 appData.remoteShell, appData.gateway,
1685 appData.remoteUser, appData.telnetProgram,
1686 appData.icsHost, appData.icsPort);
1688 return StartChildProcess(buf, "", &icsPR);
1691 } else if (appData.useTelnet) {
1692 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1695 /* TCP socket interface differs somewhat between
1696 Unix and NT; handle details in the front end.
1698 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1702 void EscapeExpand(char *p, char *q)
1703 { // [HGM] initstring: routine to shape up string arguments
1704 while(*p++ = *q++) if(p[-1] == '\\')
1706 case 'n': p[-1] = '\n'; break;
1707 case 'r': p[-1] = '\r'; break;
1708 case 't': p[-1] = '\t'; break;
1709 case '\\': p[-1] = '\\'; break;
1710 case 0: *p = 0; return;
1711 default: p[-1] = q[-1]; break;
1716 show_bytes(fp, buf, count)
1722 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1723 fprintf(fp, "\\%03o", *buf & 0xff);
1732 /* Returns an errno value */
1734 OutputMaybeTelnet(pr, message, count, outError)
1740 char buf[8192], *p, *q, *buflim;
1741 int left, newcount, outcount;
1743 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1744 *appData.gateway != NULLCHAR) {
1745 if (appData.debugMode) {
1746 fprintf(debugFP, ">ICS: ");
1747 show_bytes(debugFP, message, count);
1748 fprintf(debugFP, "\n");
1750 return OutputToProcess(pr, message, count, outError);
1753 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1760 if (appData.debugMode) {
1761 fprintf(debugFP, ">ICS: ");
1762 show_bytes(debugFP, buf, newcount);
1763 fprintf(debugFP, "\n");
1765 outcount = OutputToProcess(pr, buf, newcount, outError);
1766 if (outcount < newcount) return -1; /* to be sure */
1773 } else if (((unsigned char) *p) == TN_IAC) {
1774 *q++ = (char) TN_IAC;
1781 if (appData.debugMode) {
1782 fprintf(debugFP, ">ICS: ");
1783 show_bytes(debugFP, buf, newcount);
1784 fprintf(debugFP, "\n");
1786 outcount = OutputToProcess(pr, buf, newcount, outError);
1787 if (outcount < newcount) return -1; /* to be sure */
1792 read_from_player(isr, closure, message, count, error)
1799 int outError, outCount;
1800 static int gotEof = 0;
1802 /* Pass data read from player on to ICS */
1805 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1806 if (outCount < count) {
1807 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1809 } else if (count < 0) {
1810 RemoveInputSource(isr);
1811 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1812 } else if (gotEof++ > 0) {
1813 RemoveInputSource(isr);
1814 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1820 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1821 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1822 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1823 SendToICS("date\n");
1824 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1827 /* added routine for printf style output to ics */
1828 void ics_printf(char *format, ...)
1830 char buffer[MSG_SIZ];
1833 va_start(args, format);
1834 vsnprintf(buffer, sizeof(buffer), format, args);
1835 buffer[sizeof(buffer)-1] = '\0';
1844 int count, outCount, outError;
1846 if (icsPR == NULL) return;
1849 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1850 if (outCount < count) {
1851 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1855 /* This is used for sending logon scripts to the ICS. Sending
1856 without a delay causes problems when using timestamp on ICC
1857 (at least on my machine). */
1859 SendToICSDelayed(s,msdelay)
1863 int count, outCount, outError;
1865 if (icsPR == NULL) return;
1868 if (appData.debugMode) {
1869 fprintf(debugFP, ">ICS: ");
1870 show_bytes(debugFP, s, count);
1871 fprintf(debugFP, "\n");
1873 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1875 if (outCount < count) {
1876 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1881 /* Remove all highlighting escape sequences in s
1882 Also deletes any suffix starting with '('
1885 StripHighlightAndTitle(s)
1888 static char retbuf[MSG_SIZ];
1891 while (*s != NULLCHAR) {
1892 while (*s == '\033') {
1893 while (*s != NULLCHAR && !isalpha(*s)) s++;
1894 if (*s != NULLCHAR) s++;
1896 while (*s != NULLCHAR && *s != '\033') {
1897 if (*s == '(' || *s == '[') {
1908 /* Remove all highlighting escape sequences in s */
1913 static char retbuf[MSG_SIZ];
1916 while (*s != NULLCHAR) {
1917 while (*s == '\033') {
1918 while (*s != NULLCHAR && !isalpha(*s)) s++;
1919 if (*s != NULLCHAR) s++;
1921 while (*s != NULLCHAR && *s != '\033') {
1929 char *variantNames[] = VARIANT_NAMES;
1934 return variantNames[v];
1938 /* Identify a variant from the strings the chess servers use or the
1939 PGN Variant tag names we use. */
1946 VariantClass v = VariantNormal;
1947 int i, found = FALSE;
1953 /* [HGM] skip over optional board-size prefixes */
1954 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1955 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1956 while( *e++ != '_');
1959 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1963 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1964 if (StrCaseStr(e, variantNames[i])) {
1965 v = (VariantClass) i;
1972 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1973 || StrCaseStr(e, "wild/fr")
1974 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1975 v = VariantFischeRandom;
1976 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1977 (i = 1, p = StrCaseStr(e, "w"))) {
1979 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1986 case 0: /* FICS only, actually */
1988 /* Castling legal even if K starts on d-file */
1989 v = VariantWildCastle;
1994 /* Castling illegal even if K & R happen to start in
1995 normal positions. */
1996 v = VariantNoCastle;
2009 /* Castling legal iff K & R start in normal positions */
2015 /* Special wilds for position setup; unclear what to do here */
2016 v = VariantLoadable;
2019 /* Bizarre ICC game */
2020 v = VariantTwoKings;
2023 v = VariantKriegspiel;
2029 v = VariantFischeRandom;
2032 v = VariantCrazyhouse;
2035 v = VariantBughouse;
2041 /* Not quite the same as FICS suicide! */
2042 v = VariantGiveaway;
2048 v = VariantShatranj;
2051 /* Temporary names for future ICC types. The name *will* change in
2052 the next xboard/WinBoard release after ICC defines it. */
2090 v = VariantCapablanca;
2093 v = VariantKnightmate;
2099 v = VariantCylinder;
2105 v = VariantCapaRandom;
2108 v = VariantBerolina;
2120 /* Found "wild" or "w" in the string but no number;
2121 must assume it's normal chess. */
2125 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2126 if( (len > MSG_SIZ) && appData.debugMode )
2127 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2129 DisplayError(buf, 0);
2135 if (appData.debugMode) {
2136 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2137 e, wnum, VariantName(v));
2142 static int leftover_start = 0, leftover_len = 0;
2143 char star_match[STAR_MATCH_N][MSG_SIZ];
2145 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2146 advance *index beyond it, and set leftover_start to the new value of
2147 *index; else return FALSE. If pattern contains the character '*', it
2148 matches any sequence of characters not containing '\r', '\n', or the
2149 character following the '*' (if any), and the matched sequence(s) are
2150 copied into star_match.
2153 looking_at(buf, index, pattern)
2158 char *bufp = &buf[*index], *patternp = pattern;
2160 char *matchp = star_match[0];
2163 if (*patternp == NULLCHAR) {
2164 *index = leftover_start = bufp - buf;
2168 if (*bufp == NULLCHAR) return FALSE;
2169 if (*patternp == '*') {
2170 if (*bufp == *(patternp + 1)) {
2172 matchp = star_match[++star_count];
2176 } else if (*bufp == '\n' || *bufp == '\r') {
2178 if (*patternp == NULLCHAR)
2183 *matchp++ = *bufp++;
2187 if (*patternp != *bufp) return FALSE;
2194 SendToPlayer(data, length)
2198 int error, outCount;
2199 outCount = OutputToProcess(NoProc, data, length, &error);
2200 if (outCount < length) {
2201 DisplayFatalError(_("Error writing to display"), error, 1);
2206 PackHolding(packed, holding)
2218 switch (runlength) {
2229 sprintf(q, "%d", runlength);
2241 /* Telnet protocol requests from the front end */
2243 TelnetRequest(ddww, option)
2244 unsigned char ddww, option;
2246 unsigned char msg[3];
2247 int outCount, outError;
2249 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2251 if (appData.debugMode) {
2252 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2268 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2277 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2280 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2285 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2287 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2294 if (!appData.icsActive) return;
2295 TelnetRequest(TN_DO, TN_ECHO);
2301 if (!appData.icsActive) return;
2302 TelnetRequest(TN_DONT, TN_ECHO);
2306 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2308 /* put the holdings sent to us by the server on the board holdings area */
2309 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2313 if(gameInfo.holdingsWidth < 2) return;
2314 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2315 return; // prevent overwriting by pre-board holdings
2317 if( (int)lowestPiece >= BlackPawn ) {
2320 holdingsStartRow = BOARD_HEIGHT-1;
2323 holdingsColumn = BOARD_WIDTH-1;
2324 countsColumn = BOARD_WIDTH-2;
2325 holdingsStartRow = 0;
2329 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2330 board[i][holdingsColumn] = EmptySquare;
2331 board[i][countsColumn] = (ChessSquare) 0;
2333 while( (p=*holdings++) != NULLCHAR ) {
2334 piece = CharToPiece( ToUpper(p) );
2335 if(piece == EmptySquare) continue;
2336 /*j = (int) piece - (int) WhitePawn;*/
2337 j = PieceToNumber(piece);
2338 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2339 if(j < 0) continue; /* should not happen */
2340 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2341 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2342 board[holdingsStartRow+j*direction][countsColumn]++;
2348 VariantSwitch(Board board, VariantClass newVariant)
2350 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2351 static Board oldBoard;
2353 startedFromPositionFile = FALSE;
2354 if(gameInfo.variant == newVariant) return;
2356 /* [HGM] This routine is called each time an assignment is made to
2357 * gameInfo.variant during a game, to make sure the board sizes
2358 * are set to match the new variant. If that means adding or deleting
2359 * holdings, we shift the playing board accordingly
2360 * This kludge is needed because in ICS observe mode, we get boards
2361 * of an ongoing game without knowing the variant, and learn about the
2362 * latter only later. This can be because of the move list we requested,
2363 * in which case the game history is refilled from the beginning anyway,
2364 * but also when receiving holdings of a crazyhouse game. In the latter
2365 * case we want to add those holdings to the already received position.
2369 if (appData.debugMode) {
2370 fprintf(debugFP, "Switch board from %s to %s\n",
2371 VariantName(gameInfo.variant), VariantName(newVariant));
2372 setbuf(debugFP, NULL);
2374 shuffleOpenings = 0; /* [HGM] shuffle */
2375 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2379 newWidth = 9; newHeight = 9;
2380 gameInfo.holdingsSize = 7;
2381 case VariantBughouse:
2382 case VariantCrazyhouse:
2383 newHoldingsWidth = 2; break;
2387 newHoldingsWidth = 2;
2388 gameInfo.holdingsSize = 8;
2391 case VariantCapablanca:
2392 case VariantCapaRandom:
2395 newHoldingsWidth = gameInfo.holdingsSize = 0;
2398 if(newWidth != gameInfo.boardWidth ||
2399 newHeight != gameInfo.boardHeight ||
2400 newHoldingsWidth != gameInfo.holdingsWidth ) {
2402 /* shift position to new playing area, if needed */
2403 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2404 for(i=0; i<BOARD_HEIGHT; i++)
2405 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2406 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2408 for(i=0; i<newHeight; i++) {
2409 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2410 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2412 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2413 for(i=0; i<BOARD_HEIGHT; i++)
2414 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2415 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2418 gameInfo.boardWidth = newWidth;
2419 gameInfo.boardHeight = newHeight;
2420 gameInfo.holdingsWidth = newHoldingsWidth;
2421 gameInfo.variant = newVariant;
2422 InitDrawingSizes(-2, 0);
2423 } else gameInfo.variant = newVariant;
2424 CopyBoard(oldBoard, board); // remember correctly formatted board
2425 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2426 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2429 static int loggedOn = FALSE;
2431 /*-- Game start info cache: --*/
2433 char gs_kind[MSG_SIZ];
2434 static char player1Name[128] = "";
2435 static char player2Name[128] = "";
2436 static char cont_seq[] = "\n\\ ";
2437 static int player1Rating = -1;
2438 static int player2Rating = -1;
2439 /*----------------------------*/
2441 ColorClass curColor = ColorNormal;
2442 int suppressKibitz = 0;
2445 Boolean soughtPending = FALSE;
2446 Boolean seekGraphUp;
2447 #define MAX_SEEK_ADS 200
2449 char *seekAdList[MAX_SEEK_ADS];
2450 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2451 float tcList[MAX_SEEK_ADS];
2452 char colorList[MAX_SEEK_ADS];
2453 int nrOfSeekAds = 0;
2454 int minRating = 1010, maxRating = 2800;
2455 int hMargin = 10, vMargin = 20, h, w;
2456 extern int squareSize, lineGap;
2461 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2462 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2463 if(r < minRating+100 && r >=0 ) r = minRating+100;
2464 if(r > maxRating) r = maxRating;
2465 if(tc < 1.) tc = 1.;
2466 if(tc > 95.) tc = 95.;
2467 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2468 y = ((double)r - minRating)/(maxRating - minRating)
2469 * (h-vMargin-squareSize/8-1) + vMargin;
2470 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2471 if(strstr(seekAdList[i], " u ")) color = 1;
2472 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2473 !strstr(seekAdList[i], "bullet") &&
2474 !strstr(seekAdList[i], "blitz") &&
2475 !strstr(seekAdList[i], "standard") ) color = 2;
2476 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2477 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2481 AddAd(char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2483 char buf[MSG_SIZ], *ext = "";
2484 VariantClass v = StringToVariant(type);
2485 if(strstr(type, "wild")) {
2486 ext = type + 4; // append wild number
2487 if(v == VariantFischeRandom) type = "chess960"; else
2488 if(v == VariantLoadable) type = "setup"; else
2489 type = VariantName(v);
2491 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2492 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2493 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2494 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2495 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2496 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2497 seekNrList[nrOfSeekAds] = nr;
2498 zList[nrOfSeekAds] = 0;
2499 seekAdList[nrOfSeekAds++] = StrSave(buf);
2500 if(plot) PlotSeekAd(nrOfSeekAds-1);
2507 int x = xList[i], y = yList[i], d=squareSize/4, k;
2508 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2509 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2510 // now replot every dot that overlapped
2511 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2512 int xx = xList[k], yy = yList[k];
2513 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2514 DrawSeekDot(xx, yy, colorList[k]);
2519 RemoveSeekAd(int nr)
2522 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2524 if(seekAdList[i]) free(seekAdList[i]);
2525 seekAdList[i] = seekAdList[--nrOfSeekAds];
2526 seekNrList[i] = seekNrList[nrOfSeekAds];
2527 ratingList[i] = ratingList[nrOfSeekAds];
2528 colorList[i] = colorList[nrOfSeekAds];
2529 tcList[i] = tcList[nrOfSeekAds];
2530 xList[i] = xList[nrOfSeekAds];
2531 yList[i] = yList[nrOfSeekAds];
2532 zList[i] = zList[nrOfSeekAds];
2533 seekAdList[nrOfSeekAds] = NULL;
2539 MatchSoughtLine(char *line)
2541 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2542 int nr, base, inc, u=0; char dummy;
2544 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2545 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2547 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2548 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2549 // match: compact and save the line
2550 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2560 if(!seekGraphUp) return FALSE;
2561 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2562 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2564 DrawSeekBackground(0, 0, w, h);
2565 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2566 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2567 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2568 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2570 DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2573 snprintf(buf, MSG_SIZ, "%d", i);
2574 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2577 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2578 for(i=1; i<100; i+=(i<10?1:5)) {
2579 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2580 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2581 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2583 snprintf(buf, MSG_SIZ, "%d", i);
2584 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2587 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2591 int SeekGraphClick(ClickType click, int x, int y, int moving)
2593 static int lastDown = 0, displayed = 0, lastSecond;
2594 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2595 if(click == Release || moving) return FALSE;
2597 soughtPending = TRUE;
2598 SendToICS(ics_prefix);
2599 SendToICS("sought\n"); // should this be "sought all"?
2600 } else { // issue challenge based on clicked ad
2601 int dist = 10000; int i, closest = 0, second = 0;
2602 for(i=0; i<nrOfSeekAds; i++) {
2603 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2604 if(d < dist) { dist = d; closest = i; }
2605 second += (d - zList[i] < 120); // count in-range ads
2606 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2610 second = (second > 1);
2611 if(displayed != closest || second != lastSecond) {
2612 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2613 lastSecond = second; displayed = closest;
2615 if(click == Press) {
2616 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2619 } // on press 'hit', only show info
2620 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2621 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2622 SendToICS(ics_prefix);
2624 return TRUE; // let incoming board of started game pop down the graph
2625 } else if(click == Release) { // release 'miss' is ignored
2626 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2627 if(moving == 2) { // right up-click
2628 nrOfSeekAds = 0; // refresh graph
2629 soughtPending = TRUE;
2630 SendToICS(ics_prefix);
2631 SendToICS("sought\n"); // should this be "sought all"?
2634 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2635 // press miss or release hit 'pop down' seek graph
2636 seekGraphUp = FALSE;
2637 DrawPosition(TRUE, NULL);
2643 read_from_ics(isr, closure, data, count, error)
2650 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2651 #define STARTED_NONE 0
2652 #define STARTED_MOVES 1
2653 #define STARTED_BOARD 2
2654 #define STARTED_OBSERVE 3
2655 #define STARTED_HOLDINGS 4
2656 #define STARTED_CHATTER 5
2657 #define STARTED_COMMENT 6
2658 #define STARTED_MOVES_NOHIDE 7
2660 static int started = STARTED_NONE;
2661 static char parse[20000];
2662 static int parse_pos = 0;
2663 static char buf[BUF_SIZE + 1];
2664 static int firstTime = TRUE, intfSet = FALSE;
2665 static ColorClass prevColor = ColorNormal;
2666 static int savingComment = FALSE;
2667 static int cmatch = 0; // continuation sequence match
2674 int backup; /* [DM] For zippy color lines */
2676 char talker[MSG_SIZ]; // [HGM] chat
2679 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2681 if (appData.debugMode) {
2683 fprintf(debugFP, "<ICS: ");
2684 show_bytes(debugFP, data, count);
2685 fprintf(debugFP, "\n");
2689 if (appData.debugMode) { int f = forwardMostMove;
2690 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2691 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2692 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2695 /* If last read ended with a partial line that we couldn't parse,
2696 prepend it to the new read and try again. */
2697 if (leftover_len > 0) {
2698 for (i=0; i<leftover_len; i++)
2699 buf[i] = buf[leftover_start + i];
2702 /* copy new characters into the buffer */
2703 bp = buf + leftover_len;
2704 buf_len=leftover_len;
2705 for (i=0; i<count; i++)
2708 if (data[i] == '\r')
2711 // join lines split by ICS?
2712 if (!appData.noJoin)
2715 Joining just consists of finding matches against the
2716 continuation sequence, and discarding that sequence
2717 if found instead of copying it. So, until a match
2718 fails, there's nothing to do since it might be the
2719 complete sequence, and thus, something we don't want
2722 if (data[i] == cont_seq[cmatch])
2725 if (cmatch == strlen(cont_seq))
2727 cmatch = 0; // complete match. just reset the counter
2730 it's possible for the ICS to not include the space
2731 at the end of the last word, making our [correct]
2732 join operation fuse two separate words. the server
2733 does this when the space occurs at the width setting.
2735 if (!buf_len || buf[buf_len-1] != ' ')
2746 match failed, so we have to copy what matched before
2747 falling through and copying this character. In reality,
2748 this will only ever be just the newline character, but
2749 it doesn't hurt to be precise.
2751 strncpy(bp, cont_seq, cmatch);
2763 buf[buf_len] = NULLCHAR;
2764 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2769 while (i < buf_len) {
2770 /* Deal with part of the TELNET option negotiation
2771 protocol. We refuse to do anything beyond the
2772 defaults, except that we allow the WILL ECHO option,
2773 which ICS uses to turn off password echoing when we are
2774 directly connected to it. We reject this option
2775 if localLineEditing mode is on (always on in xboard)
2776 and we are talking to port 23, which might be a real
2777 telnet server that will try to keep WILL ECHO on permanently.
2779 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2780 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2781 unsigned char option;
2783 switch ((unsigned char) buf[++i]) {
2785 if (appData.debugMode)
2786 fprintf(debugFP, "\n<WILL ");
2787 switch (option = (unsigned char) buf[++i]) {
2789 if (appData.debugMode)
2790 fprintf(debugFP, "ECHO ");
2791 /* Reply only if this is a change, according
2792 to the protocol rules. */
2793 if (remoteEchoOption) break;
2794 if (appData.localLineEditing &&
2795 atoi(appData.icsPort) == TN_PORT) {
2796 TelnetRequest(TN_DONT, TN_ECHO);
2799 TelnetRequest(TN_DO, TN_ECHO);
2800 remoteEchoOption = TRUE;
2804 if (appData.debugMode)
2805 fprintf(debugFP, "%d ", option);
2806 /* Whatever this is, we don't want it. */
2807 TelnetRequest(TN_DONT, option);
2812 if (appData.debugMode)
2813 fprintf(debugFP, "\n<WONT ");
2814 switch (option = (unsigned char) buf[++i]) {
2816 if (appData.debugMode)
2817 fprintf(debugFP, "ECHO ");
2818 /* Reply only if this is a change, according
2819 to the protocol rules. */
2820 if (!remoteEchoOption) break;
2822 TelnetRequest(TN_DONT, TN_ECHO);
2823 remoteEchoOption = FALSE;
2826 if (appData.debugMode)
2827 fprintf(debugFP, "%d ", (unsigned char) option);
2828 /* Whatever this is, it must already be turned
2829 off, because we never agree to turn on
2830 anything non-default, so according to the
2831 protocol rules, we don't reply. */
2836 if (appData.debugMode)
2837 fprintf(debugFP, "\n<DO ");
2838 switch (option = (unsigned char) buf[++i]) {
2840 /* Whatever this is, we refuse to do it. */
2841 if (appData.debugMode)
2842 fprintf(debugFP, "%d ", option);
2843 TelnetRequest(TN_WONT, option);
2848 if (appData.debugMode)
2849 fprintf(debugFP, "\n<DONT ");
2850 switch (option = (unsigned char) buf[++i]) {
2852 if (appData.debugMode)
2853 fprintf(debugFP, "%d ", option);
2854 /* Whatever this is, we are already not doing
2855 it, because we never agree to do anything
2856 non-default, so according to the protocol
2857 rules, we don't reply. */
2862 if (appData.debugMode)
2863 fprintf(debugFP, "\n<IAC ");
2864 /* Doubled IAC; pass it through */
2868 if (appData.debugMode)
2869 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2870 /* Drop all other telnet commands on the floor */
2873 if (oldi > next_out)
2874 SendToPlayer(&buf[next_out], oldi - next_out);
2880 /* OK, this at least will *usually* work */
2881 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2885 if (loggedOn && !intfSet) {
2886 if (ics_type == ICS_ICC) {
2887 snprintf(str, MSG_SIZ,
2888 "/set-quietly interface %s\n/set-quietly style 12\n",
2890 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2891 strcat(str, "/set-2 51 1\n/set seek 1\n");
2892 } else if (ics_type == ICS_CHESSNET) {
2893 snprintf(str, MSG_SIZ, "/style 12\n");
2895 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2896 strcat(str, programVersion);
2897 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2898 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2899 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2901 strcat(str, "$iset nohighlight 1\n");
2903 strcat(str, "$iset lock 1\n$style 12\n");
2906 NotifyFrontendLogin();
2910 if (started == STARTED_COMMENT) {
2911 /* Accumulate characters in comment */
2912 parse[parse_pos++] = buf[i];
2913 if (buf[i] == '\n') {
2914 parse[parse_pos] = NULLCHAR;
2915 if(chattingPartner>=0) {
2917 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2918 OutputChatMessage(chattingPartner, mess);
2919 chattingPartner = -1;
2920 next_out = i+1; // [HGM] suppress printing in ICS window
2922 if(!suppressKibitz) // [HGM] kibitz
2923 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2924 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2925 int nrDigit = 0, nrAlph = 0, j;
2926 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2927 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2928 parse[parse_pos] = NULLCHAR;
2929 // try to be smart: if it does not look like search info, it should go to
2930 // ICS interaction window after all, not to engine-output window.
2931 for(j=0; j<parse_pos; j++) { // count letters and digits
2932 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2933 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2934 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2936 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2937 int depth=0; float score;
2938 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2939 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2940 pvInfoList[forwardMostMove-1].depth = depth;
2941 pvInfoList[forwardMostMove-1].score = 100*score;
2943 OutputKibitz(suppressKibitz, parse);
2946 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2947 SendToPlayer(tmp, strlen(tmp));
2949 next_out = i+1; // [HGM] suppress printing in ICS window
2951 started = STARTED_NONE;
2953 /* Don't match patterns against characters in comment */
2958 if (started == STARTED_CHATTER) {
2959 if (buf[i] != '\n') {
2960 /* Don't match patterns against characters in chatter */
2964 started = STARTED_NONE;
2965 if(suppressKibitz) next_out = i+1;
2968 /* Kludge to deal with rcmd protocol */
2969 if (firstTime && looking_at(buf, &i, "\001*")) {
2970 DisplayFatalError(&buf[1], 0, 1);
2976 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2979 if (appData.debugMode)
2980 fprintf(debugFP, "ics_type %d\n", ics_type);
2983 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2984 ics_type = ICS_FICS;
2986 if (appData.debugMode)
2987 fprintf(debugFP, "ics_type %d\n", ics_type);
2990 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2991 ics_type = ICS_CHESSNET;
2993 if (appData.debugMode)
2994 fprintf(debugFP, "ics_type %d\n", ics_type);
2999 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3000 looking_at(buf, &i, "Logging you in as \"*\"") ||
3001 looking_at(buf, &i, "will be \"*\""))) {
3002 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3006 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3008 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3009 DisplayIcsInteractionTitle(buf);
3010 have_set_title = TRUE;
3013 /* skip finger notes */
3014 if (started == STARTED_NONE &&
3015 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3016 (buf[i] == '1' && buf[i+1] == '0')) &&
3017 buf[i+2] == ':' && buf[i+3] == ' ') {
3018 started = STARTED_CHATTER;
3024 // [HGM] seekgraph: recognize sought lines and end-of-sought message
3025 if(appData.seekGraph) {
3026 if(soughtPending && MatchSoughtLine(buf+i)) {
3027 i = strstr(buf+i, "rated") - buf;
3028 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3029 next_out = leftover_start = i;
3030 started = STARTED_CHATTER;
3031 suppressKibitz = TRUE;
3034 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3035 && looking_at(buf, &i, "* ads displayed")) {
3036 soughtPending = FALSE;
3041 if(appData.autoRefresh) {
3042 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3043 int s = (ics_type == ICS_ICC); // ICC format differs
3045 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3046 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3047 looking_at(buf, &i, "*% "); // eat prompt
3048 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3049 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3050 next_out = i; // suppress
3053 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3054 char *p = star_match[0];
3056 if(seekGraphUp) RemoveSeekAd(atoi(p));
3057 while(*p && *p++ != ' '); // next
3059 looking_at(buf, &i, "*% "); // eat prompt
3060 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3067 /* skip formula vars */
3068 if (started == STARTED_NONE &&
3069 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3070 started = STARTED_CHATTER;
3075 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3076 if (appData.autoKibitz && started == STARTED_NONE &&
3077 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3078 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3079 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3080 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3081 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3082 suppressKibitz = TRUE;
3083 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3085 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3086 && (gameMode == IcsPlayingWhite)) ||
3087 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3088 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3089 started = STARTED_CHATTER; // own kibitz we simply discard
3091 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3092 parse_pos = 0; parse[0] = NULLCHAR;
3093 savingComment = TRUE;
3094 suppressKibitz = gameMode != IcsObserving ? 2 :
3095 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3099 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3100 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3101 && atoi(star_match[0])) {
3102 // suppress the acknowledgements of our own autoKibitz
3104 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3105 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3106 SendToPlayer(star_match[0], strlen(star_match[0]));
3107 if(looking_at(buf, &i, "*% ")) // eat prompt
3108 suppressKibitz = FALSE;
3112 } // [HGM] kibitz: end of patch
3114 // [HGM] chat: intercept tells by users for which we have an open chat window
3116 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3117 looking_at(buf, &i, "* whispers:") ||
3118 looking_at(buf, &i, "* kibitzes:") ||
3119 looking_at(buf, &i, "* shouts:") ||
3120 looking_at(buf, &i, "* c-shouts:") ||
3121 looking_at(buf, &i, "--> * ") ||
3122 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3123 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3124 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3125 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3127 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3128 chattingPartner = -1;
3130 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3131 for(p=0; p<MAX_CHAT; p++) {
3132 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3133 talker[0] = '['; strcat(talker, "] ");
3134 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3135 chattingPartner = p; break;
3138 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3139 for(p=0; p<MAX_CHAT; p++) {
3140 if(!strcmp("kibitzes", chatPartner[p])) {
3141 talker[0] = '['; strcat(talker, "] ");
3142 chattingPartner = p; break;
3145 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3146 for(p=0; p<MAX_CHAT; p++) {
3147 if(!strcmp("whispers", chatPartner[p])) {
3148 talker[0] = '['; strcat(talker, "] ");
3149 chattingPartner = p; break;
3152 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3153 if(buf[i-8] == '-' && buf[i-3] == 't')
3154 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3155 if(!strcmp("c-shouts", chatPartner[p])) {
3156 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3157 chattingPartner = p; break;
3160 if(chattingPartner < 0)
3161 for(p=0; p<MAX_CHAT; p++) {
3162 if(!strcmp("shouts", chatPartner[p])) {
3163 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3164 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3165 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3166 chattingPartner = p; break;
3170 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3171 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3172 talker[0] = 0; Colorize(ColorTell, FALSE);
3173 chattingPartner = p; break;
3175 if(chattingPartner<0) i = oldi; else {
3176 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3177 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3178 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3179 started = STARTED_COMMENT;
3180 parse_pos = 0; parse[0] = NULLCHAR;
3181 savingComment = 3 + chattingPartner; // counts as TRUE
3182 suppressKibitz = TRUE;
3185 } // [HGM] chat: end of patch
3188 if (appData.zippyTalk || appData.zippyPlay) {
3189 /* [DM] Backup address for color zippy lines */
3191 if (loggedOn == TRUE)
3192 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3193 (appData.zippyPlay && ZippyMatch(buf, &backup)));
3195 } // [DM] 'else { ' deleted
3197 /* Regular tells and says */
3198 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3199 looking_at(buf, &i, "* (your partner) tells you: ") ||
3200 looking_at(buf, &i, "* says: ") ||
3201 /* Don't color "message" or "messages" output */
3202 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3203 looking_at(buf, &i, "*. * at *:*: ") ||
3204 looking_at(buf, &i, "--* (*:*): ") ||
3205 /* Message notifications (same color as tells) */
3206 looking_at(buf, &i, "* has left a message ") ||
3207 looking_at(buf, &i, "* just sent you a message:\n") ||
3208 /* Whispers and kibitzes */
3209 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3210 looking_at(buf, &i, "* kibitzes: ") ||
3212 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3214 if (tkind == 1 && strchr(star_match[0], ':')) {
3215 /* Avoid "tells you:" spoofs in channels */
3218 if (star_match[0][0] == NULLCHAR ||
3219 strchr(star_match[0], ' ') ||
3220 (tkind == 3 && strchr(star_match[1], ' '))) {
3221 /* Reject bogus matches */
3224 if (appData.colorize) {
3225 if (oldi > next_out) {
3226 SendToPlayer(&buf[next_out], oldi - next_out);
3231 Colorize(ColorTell, FALSE);
3232 curColor = ColorTell;
3235 Colorize(ColorKibitz, FALSE);
3236 curColor = ColorKibitz;
3239 p = strrchr(star_match[1], '(');
3246 Colorize(ColorChannel1, FALSE);
3247 curColor = ColorChannel1;
3249 Colorize(ColorChannel, FALSE);
3250 curColor = ColorChannel;
3254 curColor = ColorNormal;
3258 if (started == STARTED_NONE && appData.autoComment &&
3259 (gameMode == IcsObserving ||
3260 gameMode == IcsPlayingWhite ||
3261 gameMode == IcsPlayingBlack)) {
3262 parse_pos = i - oldi;
3263 memcpy(parse, &buf[oldi], parse_pos);
3264 parse[parse_pos] = NULLCHAR;
3265 started = STARTED_COMMENT;
3266 savingComment = TRUE;
3268 started = STARTED_CHATTER;
3269 savingComment = FALSE;
3276 if (looking_at(buf, &i, "* s-shouts: ") ||
3277 looking_at(buf, &i, "* c-shouts: ")) {
3278 if (appData.colorize) {
3279 if (oldi > next_out) {
3280 SendToPlayer(&buf[next_out], oldi - next_out);
3283 Colorize(ColorSShout, FALSE);
3284 curColor = ColorSShout;
3287 started = STARTED_CHATTER;
3291 if (looking_at(buf, &i, "--->")) {
3296 if (looking_at(buf, &i, "* shouts: ") ||
3297 looking_at(buf, &i, "--> ")) {
3298 if (appData.colorize) {
3299 if (oldi > next_out) {
3300 SendToPlayer(&buf[next_out], oldi - next_out);
3303 Colorize(ColorShout, FALSE);
3304 curColor = ColorShout;
3307 started = STARTED_CHATTER;
3311 if (looking_at( buf, &i, "Challenge:")) {
3312 if (appData.colorize) {
3313 if (oldi > next_out) {
3314 SendToPlayer(&buf[next_out], oldi - next_out);
3317 Colorize(ColorChallenge, FALSE);
3318 curColor = ColorChallenge;
3324 if (looking_at(buf, &i, "* offers you") ||
3325 looking_at(buf, &i, "* offers to be") ||
3326 looking_at(buf, &i, "* would like to") ||
3327 looking_at(buf, &i, "* requests to") ||
3328 looking_at(buf, &i, "Your opponent offers") ||
3329 looking_at(buf, &i, "Your opponent requests")) {
3331 if (appData.colorize) {
3332 if (oldi > next_out) {
3333 SendToPlayer(&buf[next_out], oldi - next_out);
3336 Colorize(ColorRequest, FALSE);
3337 curColor = ColorRequest;
3342 if (looking_at(buf, &i, "* (*) seeking")) {
3343 if (appData.colorize) {
3344 if (oldi > next_out) {
3345 SendToPlayer(&buf[next_out], oldi - next_out);
3348 Colorize(ColorSeek, FALSE);
3349 curColor = ColorSeek;
3354 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3356 if (looking_at(buf, &i, "\\ ")) {
3357 if (prevColor != ColorNormal) {
3358 if (oldi > next_out) {
3359 SendToPlayer(&buf[next_out], oldi - next_out);
3362 Colorize(prevColor, TRUE);
3363 curColor = prevColor;
3365 if (savingComment) {
3366 parse_pos = i - oldi;
3367 memcpy(parse, &buf[oldi], parse_pos);
3368 parse[parse_pos] = NULLCHAR;
3369 started = STARTED_COMMENT;
3370 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3371 chattingPartner = savingComment - 3; // kludge to remember the box
3373 started = STARTED_CHATTER;
3378 if (looking_at(buf, &i, "Black Strength :") ||
3379 looking_at(buf, &i, "<<< style 10 board >>>") ||
3380 looking_at(buf, &i, "<10>") ||
3381 looking_at(buf, &i, "#@#")) {
3382 /* Wrong board style */
3384 SendToICS(ics_prefix);
3385 SendToICS("set style 12\n");
3386 SendToICS(ics_prefix);
3387 SendToICS("refresh\n");
3391 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3393 have_sent_ICS_logon = 1;
3397 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3398 (looking_at(buf, &i, "\n<12> ") ||
3399 looking_at(buf, &i, "<12> "))) {
3401 if (oldi > next_out) {
3402 SendToPlayer(&buf[next_out], oldi - next_out);
3405 started = STARTED_BOARD;
3410 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3411 looking_at(buf, &i, "<b1> ")) {
3412 if (oldi > next_out) {
3413 SendToPlayer(&buf[next_out], oldi - next_out);