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));
238 extern void ConsoleCreate();
241 ChessProgramState *WhitePlayer();
242 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
243 int VerifyDisplayMode P(());
245 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
246 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
247 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
248 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
249 void ics_update_width P((int new_width));
250 extern char installDir[MSG_SIZ];
251 VariantClass startVariant; /* [HGM] nicks: initial variant */
253 extern int tinyLayout, smallLayout;
254 ChessProgramStats programStats;
255 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
257 static int exiting = 0; /* [HGM] moved to top */
258 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
259 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
260 Board partnerBoard; /* [HGM] bughouse: for peeking at partner game */
261 int partnerHighlight[2];
262 Boolean partnerBoardValid = 0;
263 char partnerStatus[MSG_SIZ];
265 Boolean originalFlip;
266 Boolean twoBoards = 0;
267 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
268 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
269 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
270 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
271 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
272 int opponentKibitzes;
273 int lastSavedGame; /* [HGM] save: ID of game */
274 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
275 extern int chatCount;
277 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
278 char lastMsg[MSG_SIZ];
279 ChessSquare pieceSweep = EmptySquare;
280 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
281 int promoDefaultAltered;
283 /* States for ics_getting_history */
285 #define H_REQUESTED 1
286 #define H_GOT_REQ_HEADER 2
287 #define H_GOT_UNREQ_HEADER 3
288 #define H_GETTING_MOVES 4
289 #define H_GOT_UNWANTED_HEADER 5
291 /* whosays values for GameEnds */
300 /* Maximum number of games in a cmail message */
301 #define CMAIL_MAX_GAMES 20
303 /* Different types of move when calling RegisterMove */
305 #define CMAIL_RESIGN 1
307 #define CMAIL_ACCEPT 3
309 /* Different types of result to remember for each game */
310 #define CMAIL_NOT_RESULT 0
311 #define CMAIL_OLD_RESULT 1
312 #define CMAIL_NEW_RESULT 2
314 /* Telnet protocol constants */
325 safeStrCpy( char *dst, const char *src, size_t count )
328 assert( dst != NULL );
329 assert( src != NULL );
332 for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
333 if( i == count && dst[count-1] != NULLCHAR)
335 dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
336 if(appData.debugMode)
337 fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
343 /* Some compiler can't cast u64 to double
344 * This function do the job for us:
346 * We use the highest bit for cast, this only
347 * works if the highest bit is not
348 * in use (This should not happen)
350 * We used this for all compiler
353 u64ToDouble(u64 value)
356 u64 tmp = value & u64Const(0x7fffffffffffffff);
357 r = (double)(s64)tmp;
358 if (value & u64Const(0x8000000000000000))
359 r += 9.2233720368547758080e18; /* 2^63 */
363 /* Fake up flags for now, as we aren't keeping track of castling
364 availability yet. [HGM] Change of logic: the flag now only
365 indicates the type of castlings allowed by the rule of the game.
366 The actual rights themselves are maintained in the array
367 castlingRights, as part of the game history, and are not probed
373 int flags = F_ALL_CASTLE_OK;
374 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
375 switch (gameInfo.variant) {
377 flags &= ~F_ALL_CASTLE_OK;
378 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
379 flags |= F_IGNORE_CHECK;
381 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
384 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
386 case VariantKriegspiel:
387 flags |= F_KRIEGSPIEL_CAPTURE;
389 case VariantCapaRandom:
390 case VariantFischeRandom:
391 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
392 case VariantNoCastle:
393 case VariantShatranj:
396 flags &= ~F_ALL_CASTLE_OK;
404 FILE *gameFileFP, *debugFP;
407 [AS] Note: sometimes, the sscanf() function is used to parse the input
408 into a fixed-size buffer. Because of this, we must be prepared to
409 receive strings as long as the size of the input buffer, which is currently
410 set to 4K for Windows and 8K for the rest.
411 So, we must either allocate sufficiently large buffers here, or
412 reduce the size of the input buffer in the input reading part.
415 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
416 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
417 char thinkOutput1[MSG_SIZ*10];
419 ChessProgramState first, second;
421 /* premove variables */
424 int premoveFromX = 0;
425 int premoveFromY = 0;
426 int premovePromoChar = 0;
428 Boolean alarmSounded;
429 /* end premove variables */
431 char *ics_prefix = "$";
432 int ics_type = ICS_GENERIC;
434 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
435 int pauseExamForwardMostMove = 0;
436 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
437 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
438 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
439 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
440 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
441 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
442 int whiteFlag = FALSE, blackFlag = FALSE;
443 int userOfferedDraw = FALSE;
444 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
445 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
446 int cmailMoveType[CMAIL_MAX_GAMES];
447 long ics_clock_paused = 0;
448 ProcRef icsPR = NoProc, cmailPR = NoProc;
449 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
450 GameMode gameMode = BeginningOfGame;
451 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
452 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
453 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
454 int hiddenThinkOutputState = 0; /* [AS] */
455 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
456 int adjudicateLossPlies = 6;
457 char white_holding[64], black_holding[64];
458 TimeMark lastNodeCountTime;
459 long lastNodeCount=0;
460 int shiftKey; // [HGM] set by mouse handler
462 int have_sent_ICS_logon = 0;
464 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
465 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
466 long timeControl_2; /* [AS] Allow separate time controls */
467 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
468 long timeRemaining[2][MAX_MOVES];
469 int matchGame = 0, nextGame = 0, roundNr = 0;
470 Boolean waitingForGame = FALSE;
471 TimeMark programStartTime, pauseStart;
472 char ics_handle[MSG_SIZ];
473 int have_set_title = 0;
475 /* animateTraining preserves the state of appData.animate
476 * when Training mode is activated. This allows the
477 * response to be animated when appData.animate == TRUE and
478 * appData.animateDragging == TRUE.
480 Boolean animateTraining;
486 Board boards[MAX_MOVES];
487 /* [HGM] Following 7 needed for accurate legality tests: */
488 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
489 signed char initialRights[BOARD_FILES];
490 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
491 int initialRulePlies, FENrulePlies;
492 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
495 int mute; // mute all sounds
497 // [HGM] vari: next 12 to save and restore variations
498 #define MAX_VARIATIONS 10
499 int framePtr = MAX_MOVES-1; // points to free stack entry
501 int savedFirst[MAX_VARIATIONS];
502 int savedLast[MAX_VARIATIONS];
503 int savedFramePtr[MAX_VARIATIONS];
504 char *savedDetails[MAX_VARIATIONS];
505 ChessMove savedResult[MAX_VARIATIONS];
507 void PushTail P((int firstMove, int lastMove));
508 Boolean PopTail P((Boolean annotate));
509 void PushInner P((int firstMove, int lastMove));
510 void PopInner P((Boolean annotate));
511 void CleanupTail P((void));
513 ChessSquare FIDEArray[2][BOARD_FILES] = {
514 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
515 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
516 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
517 BlackKing, BlackBishop, BlackKnight, BlackRook }
520 ChessSquare twoKingsArray[2][BOARD_FILES] = {
521 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
522 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
523 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
524 BlackKing, BlackKing, BlackKnight, BlackRook }
527 ChessSquare KnightmateArray[2][BOARD_FILES] = {
528 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
529 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
530 { BlackRook, BlackMan, BlackBishop, BlackQueen,
531 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
534 ChessSquare SpartanArray[2][BOARD_FILES] = {
535 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
536 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
537 { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
538 BlackDragon, BlackKing, BlackAngel, BlackAlfil }
541 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
542 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
543 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
544 { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
545 BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
548 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
549 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
550 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
551 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
552 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
555 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
556 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
557 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
558 { BlackRook, BlackKnight, BlackMan, BlackFerz,
559 BlackKing, BlackMan, BlackKnight, BlackRook }
563 #if (BOARD_FILES>=10)
564 ChessSquare ShogiArray[2][BOARD_FILES] = {
565 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
566 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
567 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
568 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
571 ChessSquare XiangqiArray[2][BOARD_FILES] = {
572 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
573 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
574 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
575 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
578 ChessSquare CapablancaArray[2][BOARD_FILES] = {
579 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
580 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
581 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
582 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
585 ChessSquare GreatArray[2][BOARD_FILES] = {
586 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
587 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
588 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
589 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
592 ChessSquare JanusArray[2][BOARD_FILES] = {
593 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
594 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
595 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
596 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
600 ChessSquare GothicArray[2][BOARD_FILES] = {
601 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
602 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
603 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
604 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
607 #define GothicArray CapablancaArray
611 ChessSquare FalconArray[2][BOARD_FILES] = {
612 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
613 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
614 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
615 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
618 #define FalconArray CapablancaArray
621 #else // !(BOARD_FILES>=10)
622 #define XiangqiPosition FIDEArray
623 #define CapablancaArray FIDEArray
624 #define GothicArray FIDEArray
625 #define GreatArray FIDEArray
626 #endif // !(BOARD_FILES>=10)
628 #if (BOARD_FILES>=12)
629 ChessSquare CourierArray[2][BOARD_FILES] = {
630 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
631 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
632 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
633 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
635 #else // !(BOARD_FILES>=12)
636 #define CourierArray CapablancaArray
637 #endif // !(BOARD_FILES>=12)
640 Board initialPosition;
643 /* Convert str to a rating. Checks for special cases of "----",
645 "++++", etc. Also strips ()'s */
647 string_to_rating(str)
650 while(*str && !isdigit(*str)) ++str;
652 return 0; /* One of the special "no rating" cases */
660 /* Init programStats */
661 programStats.movelist[0] = 0;
662 programStats.depth = 0;
663 programStats.nr_moves = 0;
664 programStats.moves_left = 0;
665 programStats.nodes = 0;
666 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
667 programStats.score = 0;
668 programStats.got_only_move = 0;
669 programStats.got_fail = 0;
670 programStats.line_is_book = 0;
675 { // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
676 if (appData.firstPlaysBlack) {
677 first.twoMachinesColor = "black\n";
678 second.twoMachinesColor = "white\n";
680 first.twoMachinesColor = "white\n";
681 second.twoMachinesColor = "black\n";
684 first.other = &second;
685 second.other = &first;
688 if(appData.timeOddsMode) {
689 norm = appData.timeOdds[0];
690 if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
692 first.timeOdds = appData.timeOdds[0]/norm;
693 second.timeOdds = appData.timeOdds[1]/norm;
696 if(programVersion) free(programVersion);
697 if (appData.noChessProgram) {
698 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
699 sprintf(programVersion, "%s", PACKAGE_STRING);
701 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
702 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
703 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
708 UnloadEngine(ChessProgramState *cps)
710 /* Kill off first chess program */
711 if (cps->isr != NULL)
712 RemoveInputSource(cps->isr);
715 if (cps->pr != NoProc) {
717 DoSleep( appData.delayBeforeQuit );
718 SendToProgram("quit\n", cps);
719 DoSleep( appData.delayAfterQuit );
720 DestroyChildProcess(cps->pr, cps->useSigterm);
723 if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
727 ClearOptions(ChessProgramState *cps)
730 cps->nrOptions = cps->comboCnt = 0;
731 for(i=0; i<MAX_OPTIONS; i++) {
732 cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
733 cps->option[i].textValue = 0;
737 char *engineNames[] = {
743 InitEngine(ChessProgramState *cps, int n)
744 { // [HGM] all engine initialiation put in a function that does one engine
748 cps->which = engineNames[n];
749 cps->maybeThinking = FALSE;
753 cps->sendDrawOffers = 1;
755 cps->program = appData.chessProgram[n];
756 cps->host = appData.host[n];
757 cps->dir = appData.directory[n];
758 cps->initString = appData.engInitString[n];
759 cps->computerString = appData.computerString[n];
760 cps->useSigint = TRUE;
761 cps->useSigterm = TRUE;
762 cps->reuse = appData.reuse[n];
763 cps->nps = appData.NPS[n]; // [HGM] nps: copy nodes per second
764 cps->useSetboard = FALSE;
766 cps->usePing = FALSE;
769 cps->usePlayother = FALSE;
770 cps->useColors = TRUE;
771 cps->useUsermove = FALSE;
772 cps->sendICS = FALSE;
773 cps->sendName = appData.icsActive;
774 cps->sdKludge = FALSE;
775 cps->stKludge = FALSE;
776 TidyProgramName(cps->program, cps->host, cps->tidy);
778 safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
779 cps->analysisSupport = 2; /* detect */
780 cps->analyzing = FALSE;
781 cps->initDone = FALSE;
783 /* New features added by Tord: */
784 cps->useFEN960 = FALSE;
785 cps->useOOCastle = TRUE;
786 /* End of new features added by Tord. */
787 cps->fenOverride = appData.fenOverride[n];
789 /* [HGM] time odds: set factor for each machine */
790 cps->timeOdds = appData.timeOdds[n];
792 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
793 cps->accumulateTC = appData.accumulateTC[n];
794 cps->maxNrOfSessions = 1;
798 cps->supportsNPS = UNKNOWN;
801 cps->optionSettings = appData.engOptions[n];
803 cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
804 cps->isUCI = appData.isUCI[n]; /* [AS] */
805 cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
807 if (appData.protocolVersion[n] > PROTOVER
808 || appData.protocolVersion[n] < 1)
813 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
814 appData.protocolVersion[n]);
815 if( (len > MSG_SIZ) && appData.debugMode )
816 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
818 DisplayFatalError(buf, 0, 2);
822 cps->protocolVersion = appData.protocolVersion[n];
825 InitEngineUCI( installDir, cps ); // [HGM] moved here from winboard.c, to make available in xboard
828 ChessProgramState *savCps;
834 if(WaitForEngine(savCps, LoadEngine)) return;
835 CommonEngineInit(); // recalculate time odds
836 if(gameInfo.variant != StringToVariant(appData.variant)) {
837 // we changed variant when loading the engine; this forces us to reset
838 Reset(TRUE, savCps != &first);
839 EditGameEvent(); // for consistency with other path, as Reset changes mode
841 InitChessProgram(savCps, FALSE);
842 SendToProgram("force\n", savCps);
843 DisplayMessage("", "");
844 if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
845 for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
851 ReplaceEngine(ChessProgramState *cps, int n)
855 appData.noChessProgram = FALSE;
856 appData.clockMode = TRUE;
858 if(n) return; // only startup first engine immediately; second can wait
859 savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
863 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
864 extern Boolean isUCI, hasBook, storeVariant, v1, addToList;
867 Load(ChessProgramState *cps, int i)
869 char *p, *q, buf[MSG_SIZ], command[MSG_SIZ];
870 if(engineLine[0]) { // an engine was selected from the combo box
871 snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
872 SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
873 ParseArgsFromString("-firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1");
874 ParseArgsFromString(buf);
876 ReplaceEngine(cps, i);
880 while(q = strchr(p, SLASH)) p = q+1;
881 if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
882 if(engineDir[0] != NULLCHAR)
883 appData.directory[i] = engineDir;
884 else if(p != engineName) { // derive directory from engine path, when not given
886 appData.directory[i] = strdup(engineName);
888 } else appData.directory[i] = ".";
890 snprintf(command, MSG_SIZ, "%s %s", p, params);
893 appData.chessProgram[i] = strdup(p);
894 appData.isUCI[i] = isUCI;
895 appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
896 appData.hasOwnBookUCI[i] = hasBook;
899 q = firstChessProgramNames;
900 if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
901 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "\"%s\" -fd \"%s\"%s%s%s%s%s\n", p, appData.directory[i],
902 v1 ? " -firstProtocolVersion 1" : "",
903 hasBook ? "" : " -fNoOwnBookUCI",
904 isUCI ? " -fUCI" : "",
905 storeVariant ? " -variant " : "",
906 storeVariant ? VariantName(gameInfo.variant) : "");
907 firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
908 snprintf(firstChessProgramNames, len, "%s%s", q, buf);
911 ReplaceEngine(cps, i);
917 int matched, min, sec;
919 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
920 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
922 GetTimeMark(&programStartTime);
923 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
924 pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
927 programStats.ok_to_send = 1;
928 programStats.seen_stat = 0;
931 * Initialize game list
937 * Internet chess server status
939 if (appData.icsActive) {
940 appData.matchMode = FALSE;
941 appData.matchGames = 0;
943 appData.noChessProgram = !appData.zippyPlay;
945 appData.zippyPlay = FALSE;
946 appData.zippyTalk = FALSE;
947 appData.noChessProgram = TRUE;
949 if (*appData.icsHelper != NULLCHAR) {
950 appData.useTelnet = TRUE;
951 appData.telnetProgram = appData.icsHelper;
954 appData.zippyTalk = appData.zippyPlay = FALSE;
957 /* [AS] Initialize pv info list [HGM] and game state */
961 for( i=0; i<=framePtr; i++ ) {
962 pvInfoList[i].depth = -1;
963 boards[i][EP_STATUS] = EP_NONE;
964 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
969 * Parse timeControl resource
971 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
972 appData.movesPerSession)) {
974 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
975 DisplayFatalError(buf, 0, 2);
979 * Parse searchTime resource
981 if (*appData.searchTime != NULLCHAR) {
982 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
984 searchTime = min * 60;
985 } else if (matched == 2) {
986 searchTime = min * 60 + sec;
989 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
990 DisplayFatalError(buf, 0, 2);
994 /* [AS] Adjudication threshold */
995 adjudicateLossThreshold = appData.adjudicateLossThreshold;
997 InitEngine(&first, 0);
998 InitEngine(&second, 1);
1001 if (appData.icsActive) {
1002 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
1003 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1004 appData.clockMode = FALSE;
1005 first.sendTime = second.sendTime = 0;
1009 /* Override some settings from environment variables, for backward
1010 compatibility. Unfortunately it's not feasible to have the env
1011 vars just set defaults, at least in xboard. Ugh.
1013 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1018 if (!appData.icsActive) {
1022 /* Check for variants that are supported only in ICS mode,
1023 or not at all. Some that are accepted here nevertheless
1024 have bugs; see comments below.
1026 VariantClass variant = StringToVariant(appData.variant);
1028 case VariantBughouse: /* need four players and two boards */
1029 case VariantKriegspiel: /* need to hide pieces and move details */
1030 /* case VariantFischeRandom: (Fabien: moved below) */
1031 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1032 if( (len > MSG_SIZ) && appData.debugMode )
1033 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1035 DisplayFatalError(buf, 0, 2);
1038 case VariantUnknown:
1039 case VariantLoadable:
1049 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1050 if( (len > MSG_SIZ) && appData.debugMode )
1051 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1053 DisplayFatalError(buf, 0, 2);
1056 case VariantXiangqi: /* [HGM] repetition rules not implemented */
1057 case VariantFairy: /* [HGM] TestLegality definitely off! */
1058 case VariantGothic: /* [HGM] should work */
1059 case VariantCapablanca: /* [HGM] should work */
1060 case VariantCourier: /* [HGM] initial forced moves not implemented */
1061 case VariantShogi: /* [HGM] could still mate with pawn drop */
1062 case VariantKnightmate: /* [HGM] should work */
1063 case VariantCylinder: /* [HGM] untested */
1064 case VariantFalcon: /* [HGM] untested */
1065 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1066 offboard interposition not understood */
1067 case VariantNormal: /* definitely works! */
1068 case VariantWildCastle: /* pieces not automatically shuffled */
1069 case VariantNoCastle: /* pieces not automatically shuffled */
1070 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1071 case VariantLosers: /* should work except for win condition,
1072 and doesn't know captures are mandatory */
1073 case VariantSuicide: /* should work except for win condition,
1074 and doesn't know captures are mandatory */
1075 case VariantGiveaway: /* should work except for win condition,
1076 and doesn't know captures are mandatory */
1077 case VariantTwoKings: /* should work */
1078 case VariantAtomic: /* should work except for win condition */
1079 case Variant3Check: /* should work except for win condition */
1080 case VariantShatranj: /* should work except for all win conditions */
1081 case VariantMakruk: /* should work except for daw countdown */
1082 case VariantBerolina: /* might work if TestLegality is off */
1083 case VariantCapaRandom: /* should work */
1084 case VariantJanus: /* should work */
1085 case VariantSuper: /* experimental */
1086 case VariantGreat: /* experimental, requires legality testing to be off */
1087 case VariantSChess: /* S-Chess, should work */
1088 case VariantSpartan: /* should work */
1095 int NextIntegerFromString( char ** str, long * value )
1100 while( *s == ' ' || *s == '\t' ) {
1106 if( *s >= '0' && *s <= '9' ) {
1107 while( *s >= '0' && *s <= '9' ) {
1108 *value = *value * 10 + (*s - '0');
1120 int NextTimeControlFromString( char ** str, long * value )
1123 int result = NextIntegerFromString( str, &temp );
1126 *value = temp * 60; /* Minutes */
1127 if( **str == ':' ) {
1129 result = NextIntegerFromString( str, &temp );
1130 *value += temp; /* Seconds */
1137 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1138 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1139 int result = -1, type = 0; long temp, temp2;
1141 if(**str != ':') return -1; // old params remain in force!
1143 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1144 if( NextIntegerFromString( str, &temp ) ) return -1;
1145 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1148 /* time only: incremental or sudden-death time control */
1149 if(**str == '+') { /* increment follows; read it */
1151 if(**str == '!') type = *(*str)++; // Bronstein TC
1152 if(result = NextIntegerFromString( str, &temp2)) return -1;
1153 *inc = temp2 * 1000;
1154 if(**str == '.') { // read fraction of increment
1155 char *start = ++(*str);
1156 if(result = NextIntegerFromString( str, &temp2)) return -1;
1158 while(start++ < *str) temp2 /= 10;
1162 *moves = 0; *tc = temp * 1000; *incType = type;
1166 (*str)++; /* classical time control */
1167 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1178 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1179 { /* [HGM] get time to add from the multi-session time-control string */
1180 int incType, moves=1; /* kludge to force reading of first session */
1181 long time, increment;
1184 if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1185 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1187 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1188 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1189 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1190 if(movenr == -1) return time; /* last move before new session */
1191 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1192 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1193 if(!moves) return increment; /* current session is incremental */
1194 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1195 } while(movenr >= -1); /* try again for next session */
1197 return 0; // no new time quota on this move
1201 ParseTimeControl(tc, ti, mps)
1208 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1211 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1212 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1213 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1217 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1219 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1222 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1224 snprintf(buf, MSG_SIZ, ":%s", mytc);
1226 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1228 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1233 /* Parse second time control */
1236 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1244 timeControl_2 = tc2 * 1000;
1254 timeControl = tc1 * 1000;
1257 timeIncrement = ti * 1000; /* convert to ms */
1258 movesPerSession = 0;
1261 movesPerSession = mps;
1269 if (appData.debugMode) {
1270 fprintf(debugFP, "%s\n", programVersion);
1273 set_cont_sequence(appData.wrapContSeq);
1274 if (appData.matchGames > 0) {
1275 appData.matchMode = TRUE;
1276 } else if (appData.matchMode) {
1277 appData.matchGames = 1;
1279 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1280 appData.matchGames = appData.sameColorGames;
1281 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1282 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1283 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1286 if (appData.noChessProgram || first.protocolVersion == 1) {
1289 /* kludge: allow timeout for initial "feature" commands */
1291 DisplayMessage("", _("Starting chess program"));
1292 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1297 CalculateIndex(int index, int gameNr)
1298 { // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1300 if(index > 0) return index; // fixed nmber
1301 if(index == 0) return 1;
1302 res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1303 if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1308 LoadGameOrPosition(int gameNr)
1309 { // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1310 if (*appData.loadGameFile != NULLCHAR) {
1311 if (!LoadGameFromFile(appData.loadGameFile,
1312 CalculateIndex(appData.loadGameIndex, gameNr),
1313 appData.loadGameFile, FALSE)) {
1314 DisplayFatalError(_("Bad game file"), 0, 1);
1317 } else if (*appData.loadPositionFile != NULLCHAR) {
1318 if (!LoadPositionFromFile(appData.loadPositionFile,
1319 CalculateIndex(appData.loadPositionIndex, gameNr),
1320 appData.loadPositionFile)) {
1321 DisplayFatalError(_("Bad position file"), 0, 1);
1329 ReserveGame(int gameNr, char resChar)
1331 FILE *tf = fopen(appData.tourneyFile, "r+");
1332 char *p, *q, c, buf[MSG_SIZ];
1333 if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1334 safeStrCpy(buf, lastMsg, MSG_SIZ);
1335 DisplayMessage(_("Pick new game"), "");
1336 flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1337 ParseArgsFromFile(tf);
1338 p = q = appData.results;
1339 if(appData.debugMode) {
1340 char *r = appData.participants;
1341 fprintf(debugFP, "results = '%s'\n", p);
1342 while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1343 fprintf(debugFP, "\n");
1345 while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1347 q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1348 safeStrCpy(q, p, strlen(p) + 2);
1349 if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1350 if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1351 if(nextGame <= appData.matchGames && resChar != ' ') { // already reserve next game, if tourney not yet done
1352 if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1355 fseek(tf, -(strlen(p)+4), SEEK_END);
1357 if(c != '"') // depending on DOS or Unix line endings we can be one off
1358 fseek(tf, -(strlen(p)+2), SEEK_END);
1359 else fseek(tf, -(strlen(p)+3), SEEK_END);
1360 fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1361 DisplayMessage(buf, "");
1362 free(p); appData.results = q;
1363 if(nextGame <= appData.matchGames && resChar != ' ' &&
1364 (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1365 UnloadEngine(&first); // next game belongs to other pairing;
1366 UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1371 MatchEvent(int mode)
1372 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1374 if(matchMode) { // already in match mode: switch it off
1375 appData.matchGames = matchGame; // kludge to let match terminate after next game.
1376 ModeHighlight(); // kludgey way to remove checkmark...
1379 if(gameMode != BeginningOfGame) {
1380 DisplayError(_("You can only start a match from the initial position."), 0);
1383 appData.matchGames = appData.defaultMatchGames;
1384 /* Set up machine vs. machine match */
1386 NextTourneyGame(0, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1387 if(appData.tourneyFile[0]) {
1389 if(nextGame > appData.matchGames) {
1391 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1392 DisplayError(buf, 0);
1393 appData.tourneyFile[0] = 0;
1397 if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically
1398 DisplayFatalError(_("Can't have a match with no chess programs"),
1403 matchGame = roundNr = 1;
1404 first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
\r
1409 InitBackEnd3 P((void))
1411 GameMode initialMode;
1415 InitChessProgram(&first, startedFromSetupPosition);
1417 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1418 free(programVersion);
1419 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1420 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1423 if (appData.icsActive) {
1425 /* [DM] Make a console window if needed [HGM] merged ifs */
1431 if (*appData.icsCommPort != NULLCHAR)
1432 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1433 appData.icsCommPort);
1435 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1436 appData.icsHost, appData.icsPort);
1438 if( (len > MSG_SIZ) && appData.debugMode )
1439 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1441 DisplayFatalError(buf, err, 1);
1446 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1448 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1449 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1450 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1451 } else if (appData.noChessProgram) {
1457 if (*appData.cmailGameName != NULLCHAR) {
1459 OpenLoopback(&cmailPR);
1461 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1465 DisplayMessage("", "");
1466 if (StrCaseCmp(appData.initialMode, "") == 0) {
1467 initialMode = BeginningOfGame;
1468 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1469 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1470 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1471 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1474 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1475 initialMode = TwoMachinesPlay;
1476 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1477 initialMode = AnalyzeFile;
1478 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1479 initialMode = AnalyzeMode;
1480 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1481 initialMode = MachinePlaysWhite;
1482 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1483 initialMode = MachinePlaysBlack;
1484 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1485 initialMode = EditGame;
1486 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1487 initialMode = EditPosition;
1488 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1489 initialMode = Training;
1491 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1492 if( (len > MSG_SIZ) && appData.debugMode )
1493 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1495 DisplayFatalError(buf, 0, 2);
1499 if (appData.matchMode) {
1500 if(appData.tourneyFile[0]) { // start tourney from command line
1502 if(f = fopen(appData.tourneyFile, "r")) {
1503 ParseArgsFromFile(f); // make sure tourney parmeters re known
1505 } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1508 } else if (*appData.cmailGameName != NULLCHAR) {
1509 /* Set up cmail mode */
1510 ReloadCmailMsgEvent(TRUE);
1512 /* Set up other modes */
1513 if (initialMode == AnalyzeFile) {
1514 if (*appData.loadGameFile == NULLCHAR) {
1515 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1519 if (*appData.loadGameFile != NULLCHAR) {
1520 (void) LoadGameFromFile(appData.loadGameFile,
1521 appData.loadGameIndex,
1522 appData.loadGameFile, TRUE);
1523 } else if (*appData.loadPositionFile != NULLCHAR) {
1524 (void) LoadPositionFromFile(appData.loadPositionFile,
1525 appData.loadPositionIndex,
1526 appData.loadPositionFile);
1527 /* [HGM] try to make self-starting even after FEN load */
1528 /* to allow automatic setup of fairy variants with wtm */
1529 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1530 gameMode = BeginningOfGame;
1531 setboardSpoiledMachineBlack = 1;
1533 /* [HGM] loadPos: make that every new game uses the setup */
1534 /* from file as long as we do not switch variant */
1535 if(!blackPlaysFirst) {
1536 startedFromPositionFile = TRUE;
1537 CopyBoard(filePosition, boards[0]);
1540 if (initialMode == AnalyzeMode) {
1541 if (appData.noChessProgram) {
1542 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1545 if (appData.icsActive) {
1546 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1550 } else if (initialMode == AnalyzeFile) {
1551 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1552 ShowThinkingEvent();
1554 AnalysisPeriodicEvent(1);
1555 } else if (initialMode == MachinePlaysWhite) {
1556 if (appData.noChessProgram) {
1557 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1561 if (appData.icsActive) {
1562 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1566 MachineWhiteEvent();
1567 } else if (initialMode == MachinePlaysBlack) {
1568 if (appData.noChessProgram) {
1569 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1573 if (appData.icsActive) {
1574 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1578 MachineBlackEvent();
1579 } else if (initialMode == TwoMachinesPlay) {
1580 if (appData.noChessProgram) {
1581 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1585 if (appData.icsActive) {
1586 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1591 } else if (initialMode == EditGame) {
1593 } else if (initialMode == EditPosition) {
1594 EditPositionEvent();
1595 } else if (initialMode == Training) {
1596 if (*appData.loadGameFile == NULLCHAR) {
1597 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1606 * Establish will establish a contact to a remote host.port.
1607 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1608 * used to talk to the host.
1609 * Returns 0 if okay, error code if not.
1616 if (*appData.icsCommPort != NULLCHAR) {
1617 /* Talk to the host through a serial comm port */
1618 return OpenCommPort(appData.icsCommPort, &icsPR);
1620 } else if (*appData.gateway != NULLCHAR) {
1621 if (*appData.remoteShell == NULLCHAR) {
1622 /* Use the rcmd protocol to run telnet program on a gateway host */
1623 snprintf(buf, sizeof(buf), "%s %s %s",
1624 appData.telnetProgram, appData.icsHost, appData.icsPort);
1625 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1628 /* Use the rsh program to run telnet program on a gateway host */
1629 if (*appData.remoteUser == NULLCHAR) {
1630 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1631 appData.gateway, appData.telnetProgram,
1632 appData.icsHost, appData.icsPort);
1634 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1635 appData.remoteShell, appData.gateway,
1636 appData.remoteUser, appData.telnetProgram,
1637 appData.icsHost, appData.icsPort);
1639 return StartChildProcess(buf, "", &icsPR);
1642 } else if (appData.useTelnet) {
1643 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1646 /* TCP socket interface differs somewhat between
1647 Unix and NT; handle details in the front end.
1649 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1653 void EscapeExpand(char *p, char *q)
1654 { // [HGM] initstring: routine to shape up string arguments
1655 while(*p++ = *q++) if(p[-1] == '\\')
1657 case 'n': p[-1] = '\n'; break;
1658 case 'r': p[-1] = '\r'; break;
1659 case 't': p[-1] = '\t'; break;
1660 case '\\': p[-1] = '\\'; break;
1661 case 0: *p = 0; return;
1662 default: p[-1] = q[-1]; break;
1667 show_bytes(fp, buf, count)
1673 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1674 fprintf(fp, "\\%03o", *buf & 0xff);
1683 /* Returns an errno value */
1685 OutputMaybeTelnet(pr, message, count, outError)
1691 char buf[8192], *p, *q, *buflim;
1692 int left, newcount, outcount;
1694 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1695 *appData.gateway != NULLCHAR) {
1696 if (appData.debugMode) {
1697 fprintf(debugFP, ">ICS: ");
1698 show_bytes(debugFP, message, count);
1699 fprintf(debugFP, "\n");
1701 return OutputToProcess(pr, message, count, outError);
1704 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1711 if (appData.debugMode) {
1712 fprintf(debugFP, ">ICS: ");
1713 show_bytes(debugFP, buf, newcount);
1714 fprintf(debugFP, "\n");
1716 outcount = OutputToProcess(pr, buf, newcount, outError);
1717 if (outcount < newcount) return -1; /* to be sure */
1724 } else if (((unsigned char) *p) == TN_IAC) {
1725 *q++ = (char) TN_IAC;
1732 if (appData.debugMode) {
1733 fprintf(debugFP, ">ICS: ");
1734 show_bytes(debugFP, buf, newcount);
1735 fprintf(debugFP, "\n");
1737 outcount = OutputToProcess(pr, buf, newcount, outError);
1738 if (outcount < newcount) return -1; /* to be sure */
1743 read_from_player(isr, closure, message, count, error)
1750 int outError, outCount;
1751 static int gotEof = 0;
1753 /* Pass data read from player on to ICS */
1756 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1757 if (outCount < count) {
1758 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1760 } else if (count < 0) {
1761 RemoveInputSource(isr);
1762 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1763 } else if (gotEof++ > 0) {
1764 RemoveInputSource(isr);
1765 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1771 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1772 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1773 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1774 SendToICS("date\n");
1775 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1778 /* added routine for printf style output to ics */
1779 void ics_printf(char *format, ...)
1781 char buffer[MSG_SIZ];
1784 va_start(args, format);
1785 vsnprintf(buffer, sizeof(buffer), format, args);
1786 buffer[sizeof(buffer)-1] = '\0';
1795 int count, outCount, outError;
1797 if (icsPR == NULL) return;
1800 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1801 if (outCount < count) {
1802 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1806 /* This is used for sending logon scripts to the ICS. Sending
1807 without a delay causes problems when using timestamp on ICC
1808 (at least on my machine). */
1810 SendToICSDelayed(s,msdelay)
1814 int count, outCount, outError;
1816 if (icsPR == NULL) return;
1819 if (appData.debugMode) {
1820 fprintf(debugFP, ">ICS: ");
1821 show_bytes(debugFP, s, count);
1822 fprintf(debugFP, "\n");
1824 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1826 if (outCount < count) {
1827 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1832 /* Remove all highlighting escape sequences in s
1833 Also deletes any suffix starting with '('
1836 StripHighlightAndTitle(s)
1839 static char retbuf[MSG_SIZ];
1842 while (*s != NULLCHAR) {
1843 while (*s == '\033') {
1844 while (*s != NULLCHAR && !isalpha(*s)) s++;
1845 if (*s != NULLCHAR) s++;
1847 while (*s != NULLCHAR && *s != '\033') {
1848 if (*s == '(' || *s == '[') {
1859 /* Remove all highlighting escape sequences in s */
1864 static char retbuf[MSG_SIZ];
1867 while (*s != NULLCHAR) {
1868 while (*s == '\033') {
1869 while (*s != NULLCHAR && !isalpha(*s)) s++;
1870 if (*s != NULLCHAR) s++;
1872 while (*s != NULLCHAR && *s != '\033') {
1880 char *variantNames[] = VARIANT_NAMES;
1885 return variantNames[v];
1889 /* Identify a variant from the strings the chess servers use or the
1890 PGN Variant tag names we use. */
1897 VariantClass v = VariantNormal;
1898 int i, found = FALSE;
1904 /* [HGM] skip over optional board-size prefixes */
1905 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1906 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1907 while( *e++ != '_');
1910 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1914 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1915 if (StrCaseStr(e, variantNames[i])) {
1916 v = (VariantClass) i;
1923 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1924 || StrCaseStr(e, "wild/fr")
1925 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1926 v = VariantFischeRandom;
1927 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1928 (i = 1, p = StrCaseStr(e, "w"))) {
1930 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1937 case 0: /* FICS only, actually */
1939 /* Castling legal even if K starts on d-file */
1940 v = VariantWildCastle;
1945 /* Castling illegal even if K & R happen to start in
1946 normal positions. */
1947 v = VariantNoCastle;
1960 /* Castling legal iff K & R start in normal positions */
1966 /* Special wilds for position setup; unclear what to do here */
1967 v = VariantLoadable;
1970 /* Bizarre ICC game */
1971 v = VariantTwoKings;
1974 v = VariantKriegspiel;
1980 v = VariantFischeRandom;
1983 v = VariantCrazyhouse;
1986 v = VariantBughouse;
1992 /* Not quite the same as FICS suicide! */
1993 v = VariantGiveaway;
1999 v = VariantShatranj;
2002 /* Temporary names for future ICC types. The name *will* change in
2003 the next xboard/WinBoard release after ICC defines it. */
2041 v = VariantCapablanca;
2044 v = VariantKnightmate;
2050 v = VariantCylinder;
2056 v = VariantCapaRandom;
2059 v = VariantBerolina;
2071 /* Found "wild" or "w" in the string but no number;
2072 must assume it's normal chess. */
2076 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2077 if( (len > MSG_SIZ) && appData.debugMode )
2078 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2080 DisplayError(buf, 0);
2086 if (appData.debugMode) {
2087 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2088 e, wnum, VariantName(v));
2093 static int leftover_start = 0, leftover_len = 0;
2094 char star_match[STAR_MATCH_N][MSG_SIZ];
2096 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2097 advance *index beyond it, and set leftover_start to the new value of
2098 *index; else return FALSE. If pattern contains the character '*', it
2099 matches any sequence of characters not containing '\r', '\n', or the
2100 character following the '*' (if any), and the matched sequence(s) are
2101 copied into star_match.
2104 looking_at(buf, index, pattern)
2109 char *bufp = &buf[*index], *patternp = pattern;
2111 char *matchp = star_match[0];
2114 if (*patternp == NULLCHAR) {
2115 *index = leftover_start = bufp - buf;
2119 if (*bufp == NULLCHAR) return FALSE;
2120 if (*patternp == '*') {
2121 if (*bufp == *(patternp + 1)) {
2123 matchp = star_match[++star_count];
2127 } else if (*bufp == '\n' || *bufp == '\r') {
2129 if (*patternp == NULLCHAR)
2134 *matchp++ = *bufp++;
2138 if (*patternp != *bufp) return FALSE;
2145 SendToPlayer(data, length)
2149 int error, outCount;
2150 outCount = OutputToProcess(NoProc, data, length, &error);
2151 if (outCount < length) {
2152 DisplayFatalError(_("Error writing to display"), error, 1);
2157 PackHolding(packed, holding)
2169 switch (runlength) {
2180 sprintf(q, "%d", runlength);
2192 /* Telnet protocol requests from the front end */
2194 TelnetRequest(ddww, option)
2195 unsigned char ddww, option;
2197 unsigned char msg[3];
2198 int outCount, outError;
2200 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2202 if (appData.debugMode) {
2203 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2219 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2228 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2231 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2236 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2238 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2245 if (!appData.icsActive) return;
2246 TelnetRequest(TN_DO, TN_ECHO);
2252 if (!appData.icsActive) return;
2253 TelnetRequest(TN_DONT, TN_ECHO);
2257 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2259 /* put the holdings sent to us by the server on the board holdings area */
2260 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2264 if(gameInfo.holdingsWidth < 2) return;
2265 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2266 return; // prevent overwriting by pre-board holdings
2268 if( (int)lowestPiece >= BlackPawn ) {
2271 holdingsStartRow = BOARD_HEIGHT-1;
2274 holdingsColumn = BOARD_WIDTH-1;
2275 countsColumn = BOARD_WIDTH-2;
2276 holdingsStartRow = 0;
2280 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2281 board[i][holdingsColumn] = EmptySquare;
2282 board[i][countsColumn] = (ChessSquare) 0;
2284 while( (p=*holdings++) != NULLCHAR ) {
2285 piece = CharToPiece( ToUpper(p) );
2286 if(piece == EmptySquare) continue;
2287 /*j = (int) piece - (int) WhitePawn;*/
2288 j = PieceToNumber(piece);
2289 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2290 if(j < 0) continue; /* should not happen */
2291 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2292 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2293 board[holdingsStartRow+j*direction][countsColumn]++;
2299 VariantSwitch(Board board, VariantClass newVariant)
2301 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2302 static Board oldBoard;
2304 startedFromPositionFile = FALSE;
2305 if(gameInfo.variant == newVariant) return;
2307 /* [HGM] This routine is called each time an assignment is made to
2308 * gameInfo.variant during a game, to make sure the board sizes
2309 * are set to match the new variant. If that means adding or deleting
2310 * holdings, we shift the playing board accordingly
2311 * This kludge is needed because in ICS observe mode, we get boards
2312 * of an ongoing game without knowing the variant, and learn about the
2313 * latter only later. This can be because of the move list we requested,
2314 * in which case the game history is refilled from the beginning anyway,
2315 * but also when receiving holdings of a crazyhouse game. In the latter
2316 * case we want to add those holdings to the already received position.
2320 if (appData.debugMode) {
2321 fprintf(debugFP, "Switch board from %s to %s\n",
2322 VariantName(gameInfo.variant), VariantName(newVariant));
2323 setbuf(debugFP, NULL);
2325 shuffleOpenings = 0; /* [HGM] shuffle */
2326 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2330 newWidth = 9; newHeight = 9;
2331 gameInfo.holdingsSize = 7;
2332 case VariantBughouse:
2333 case VariantCrazyhouse:
2334 newHoldingsWidth = 2; break;
2338 newHoldingsWidth = 2;
2339 gameInfo.holdingsSize = 8;
2342 case VariantCapablanca:
2343 case VariantCapaRandom:
2346 newHoldingsWidth = gameInfo.holdingsSize = 0;
2349 if(newWidth != gameInfo.boardWidth ||
2350 newHeight != gameInfo.boardHeight ||
2351 newHoldingsWidth != gameInfo.holdingsWidth ) {
2353 /* shift position to new playing area, if needed */
2354 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2355 for(i=0; i<BOARD_HEIGHT; i++)
2356 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2357 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2359 for(i=0; i<newHeight; i++) {
2360 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2361 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2363 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2364 for(i=0; i<BOARD_HEIGHT; i++)
2365 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2366 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2369 gameInfo.boardWidth = newWidth;
2370 gameInfo.boardHeight = newHeight;
2371 gameInfo.holdingsWidth = newHoldingsWidth;
2372 gameInfo.variant = newVariant;
2373 InitDrawingSizes(-2, 0);
2374 } else gameInfo.variant = newVariant;
2375 CopyBoard(oldBoard, board); // remember correctly formatted board
2376 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2377 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2380 static int loggedOn = FALSE;
2382 /*-- Game start info cache: --*/
2384 char gs_kind[MSG_SIZ];
2385 static char player1Name[128] = "";
2386 static char player2Name[128] = "";
2387 static char cont_seq[] = "\n\\ ";
2388 static int player1Rating = -1;
2389 static int player2Rating = -1;
2390 /*----------------------------*/
2392 ColorClass curColor = ColorNormal;
2393 int suppressKibitz = 0;
2396 Boolean soughtPending = FALSE;
2397 Boolean seekGraphUp;
2398 #define MAX_SEEK_ADS 200
2400 char *seekAdList[MAX_SEEK_ADS];
2401 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2402 float tcList[MAX_SEEK_ADS];
2403 char colorList[MAX_SEEK_ADS];
2404 int nrOfSeekAds = 0;
2405 int minRating = 1010, maxRating = 2800;
2406 int hMargin = 10, vMargin = 20, h, w;
2407 extern int squareSize, lineGap;
2412 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2413 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2414 if(r < minRating+100 && r >=0 ) r = minRating+100;
2415 if(r > maxRating) r = maxRating;
2416 if(tc < 1.) tc = 1.;
2417 if(tc > 95.) tc = 95.;
2418 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2419 y = ((double)r - minRating)/(maxRating - minRating)
2420 * (h-vMargin-squareSize/8-1) + vMargin;
2421 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2422 if(strstr(seekAdList[i], " u ")) color = 1;
2423 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2424 !strstr(seekAdList[i], "bullet") &&
2425 !strstr(seekAdList[i], "blitz") &&
2426 !strstr(seekAdList[i], "standard") ) color = 2;
2427 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2428 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2432 AddAd(char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2434 char buf[MSG_SIZ], *ext = "";
2435 VariantClass v = StringToVariant(type);
2436 if(strstr(type, "wild")) {
2437 ext = type + 4; // append wild number
2438 if(v == VariantFischeRandom) type = "chess960"; else
2439 if(v == VariantLoadable) type = "setup"; else
2440 type = VariantName(v);
2442 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2443 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2444 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2445 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2446 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2447 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2448 seekNrList[nrOfSeekAds] = nr;
2449 zList[nrOfSeekAds] = 0;
2450 seekAdList[nrOfSeekAds++] = StrSave(buf);
2451 if(plot) PlotSeekAd(nrOfSeekAds-1);
2458 int x = xList[i], y = yList[i], d=squareSize/4, k;
2459 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2460 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2461 // now replot every dot that overlapped
2462 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2463 int xx = xList[k], yy = yList[k];
2464 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2465 DrawSeekDot(xx, yy, colorList[k]);
2470 RemoveSeekAd(int nr)
2473 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2475 if(seekAdList[i]) free(seekAdList[i]);
2476 seekAdList[i] = seekAdList[--nrOfSeekAds];
2477 seekNrList[i] = seekNrList[nrOfSeekAds];
2478 ratingList[i] = ratingList[nrOfSeekAds];
2479 colorList[i] = colorList[nrOfSeekAds];
2480 tcList[i] = tcList[nrOfSeekAds];
2481 xList[i] = xList[nrOfSeekAds];
2482 yList[i] = yList[nrOfSeekAds];
2483 zList[i] = zList[nrOfSeekAds];
2484 seekAdList[nrOfSeekAds] = NULL;
2490 MatchSoughtLine(char *line)
2492 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2493 int nr, base, inc, u=0; char dummy;
2495 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2496 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2498 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2499 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2500 // match: compact and save the line
2501 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2511 if(!seekGraphUp) return FALSE;
2512 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2513 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2515 DrawSeekBackground(0, 0, w, h);
2516 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2517 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2518 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2519 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2521 DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2524 snprintf(buf, MSG_SIZ, "%d", i);
2525 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2528 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2529 for(i=1; i<100; i+=(i<10?1:5)) {
2530 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2531 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2532 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2534 snprintf(buf, MSG_SIZ, "%d", i);
2535 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2538 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2542 int SeekGraphClick(ClickType click, int x, int y, int moving)
2544 static int lastDown = 0, displayed = 0, lastSecond;
2545 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2546 if(click == Release || moving) return FALSE;
2548 soughtPending = TRUE;
2549 SendToICS(ics_prefix);
2550 SendToICS("sought\n"); // should this be "sought all"?
2551 } else { // issue challenge based on clicked ad
2552 int dist = 10000; int i, closest = 0, second = 0;
2553 for(i=0; i<nrOfSeekAds; i++) {
2554 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2555 if(d < dist) { dist = d; closest = i; }
2556 second += (d - zList[i] < 120); // count in-range ads
2557 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2561 second = (second > 1);
2562 if(displayed != closest || second != lastSecond) {
2563 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2564 lastSecond = second; displayed = closest;
2566 if(click == Press) {
2567 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2570 } // on press 'hit', only show info
2571 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2572 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2573 SendToICS(ics_prefix);
2575 return TRUE; // let incoming board of started game pop down the graph
2576 } else if(click == Release) { // release 'miss' is ignored
2577 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2578 if(moving == 2) { // right up-click
2579 nrOfSeekAds = 0; // refresh graph
2580 soughtPending = TRUE;
2581 SendToICS(ics_prefix);
2582 SendToICS("sought\n"); // should this be "sought all"?
2585 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2586 // press miss or release hit 'pop down' seek graph
2587 seekGraphUp = FALSE;
2588 DrawPosition(TRUE, NULL);
2594 read_from_ics(isr, closure, data, count, error)
2601 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2602 #define STARTED_NONE 0
2603 #define STARTED_MOVES 1
2604 #define STARTED_BOARD 2
2605 #define STARTED_OBSERVE 3
2606 #define STARTED_HOLDINGS 4
2607 #define STARTED_CHATTER 5
2608 #define STARTED_COMMENT 6
2609 #define STARTED_MOVES_NOHIDE 7
2611 static int started = STARTED_NONE;
2612 static char parse[20000];
2613 static int parse_pos = 0;
2614 static char buf[BUF_SIZE + 1];
2615 static int firstTime = TRUE, intfSet = FALSE;
2616 static ColorClass prevColor = ColorNormal;
2617 static int savingComment = FALSE;
2618 static int cmatch = 0; // continuation sequence match
2625 int backup; /* [DM] For zippy color lines */
2627 char talker[MSG_SIZ]; // [HGM] chat
2630 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2632 if (appData.debugMode) {
2634 fprintf(debugFP, "<ICS: ");
2635 show_bytes(debugFP, data, count);
2636 fprintf(debugFP, "\n");
2640 if (appData.debugMode) { int f = forwardMostMove;
2641 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2642 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2643 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2646 /* If last read ended with a partial line that we couldn't parse,
2647 prepend it to the new read and try again. */
2648 if (leftover_len > 0) {
2649 for (i=0; i<leftover_len; i++)
2650 buf[i] = buf[leftover_start + i];
2653 /* copy new characters into the buffer */
2654 bp = buf + leftover_len;
2655 buf_len=leftover_len;
2656 for (i=0; i<count; i++)
2659 if (data[i] == '\r')
2662 // join lines split by ICS?
2663 if (!appData.noJoin)
2666 Joining just consists of finding matches against the
2667 continuation sequence, and discarding that sequence
2668 if found instead of copying it. So, until a match
2669 fails, there's nothing to do since it might be the
2670 complete sequence, and thus, something we don't want
2673 if (data[i] == cont_seq[cmatch])
2676 if (cmatch == strlen(cont_seq))
2678 cmatch = 0; // complete match. just reset the counter
2681 it's possible for the ICS to not include the space
2682 at the end of the last word, making our [correct]
2683 join operation fuse two separate words. the server
2684 does this when the space occurs at the width setting.
2686 if (!buf_len || buf[buf_len-1] != ' ')
2697 match failed, so we have to copy what matched before
2698 falling through and copying this character. In reality,
2699 this will only ever be just the newline character, but
2700 it doesn't hurt to be precise.
2702 strncpy(bp, cont_seq, cmatch);
2714 buf[buf_len] = NULLCHAR;
2715 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2720 while (i < buf_len) {
2721 /* Deal with part of the TELNET option negotiation
2722 protocol. We refuse to do anything beyond the
2723 defaults, except that we allow the WILL ECHO option,
2724 which ICS uses to turn off password echoing when we are
2725 directly connected to it. We reject this option
2726 if localLineEditing mode is on (always on in xboard)
2727 and we are talking to port 23, which might be a real
2728 telnet server that will try to keep WILL ECHO on permanently.
2730 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2731 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2732 unsigned char option;
2734 switch ((unsigned char) buf[++i]) {
2736 if (appData.debugMode)
2737 fprintf(debugFP, "\n<WILL ");
2738 switch (option = (unsigned char) buf[++i]) {
2740 if (appData.debugMode)
2741 fprintf(debugFP, "ECHO ");
2742 /* Reply only if this is a change, according
2743 to the protocol rules. */
2744 if (remoteEchoOption) break;
2745 if (appData.localLineEditing &&
2746 atoi(appData.icsPort) == TN_PORT) {
2747 TelnetRequest(TN_DONT, TN_ECHO);
2750 TelnetRequest(TN_DO, TN_ECHO);
2751 remoteEchoOption = TRUE;
2755 if (appData.debugMode)
2756 fprintf(debugFP, "%d ", option);
2757 /* Whatever this is, we don't want it. */
2758 TelnetRequest(TN_DONT, option);
2763 if (appData.debugMode)
2764 fprintf(debugFP, "\n<WONT ");
2765 switch (option = (unsigned char) buf[++i]) {
2767 if (appData.debugMode)
2768 fprintf(debugFP, "ECHO ");
2769 /* Reply only if this is a change, according
2770 to the protocol rules. */
2771 if (!remoteEchoOption) break;
2773 TelnetRequest(TN_DONT, TN_ECHO);
2774 remoteEchoOption = FALSE;
2777 if (appData.debugMode)
2778 fprintf(debugFP, "%d ", (unsigned char) option);
2779 /* Whatever this is, it must already be turned
2780 off, because we never agree to turn on
2781 anything non-default, so according to the
2782 protocol rules, we don't reply. */
2787 if (appData.debugMode)
2788 fprintf(debugFP, "\n<DO ");
2789 switch (option = (unsigned char) buf[++i]) {
2791 /* Whatever this is, we refuse to do it. */
2792 if (appData.debugMode)
2793 fprintf(debugFP, "%d ", option);
2794 TelnetRequest(TN_WONT, option);
2799 if (appData.debugMode)
2800 fprintf(debugFP, "\n<DONT ");
2801 switch (option = (unsigned char) buf[++i]) {
2803 if (appData.debugMode)
2804 fprintf(debugFP, "%d ", option);
2805 /* Whatever this is, we are already not doing
2806 it, because we never agree to do anything
2807 non-default, so according to the protocol
2808 rules, we don't reply. */
2813 if (appData.debugMode)
2814 fprintf(debugFP, "\n<IAC ");
2815 /* Doubled IAC; pass it through */
2819 if (appData.debugMode)
2820 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2821 /* Drop all other telnet commands on the floor */
2824 if (oldi > next_out)
2825 SendToPlayer(&buf[next_out], oldi - next_out);
2831 /* OK, this at least will *usually* work */
2832 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2836 if (loggedOn && !intfSet) {
2837 if (ics_type == ICS_ICC) {
2838 snprintf(str, MSG_SIZ,
2839 "/set-quietly interface %s\n/set-quietly style 12\n",
2841 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2842 strcat(str, "/set-2 51 1\n/set seek 1\n");
2843 } else if (ics_type == ICS_CHESSNET) {
2844 snprintf(str, MSG_SIZ, "/style 12\n");
2846 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2847 strcat(str, programVersion);
2848 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2849 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2850 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2852 strcat(str, "$iset nohighlight 1\n");
2854 strcat(str, "$iset lock 1\n$style 12\n");
2857 NotifyFrontendLogin();
2861 if (started == STARTED_COMMENT) {
2862 /* Accumulate characters in comment */
2863 parse[parse_pos++] = buf[i];
2864 if (buf[i] == '\n') {
2865 parse[parse_pos] = NULLCHAR;
2866 if(chattingPartner>=0) {
2868 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2869 OutputChatMessage(chattingPartner, mess);
2870 chattingPartner = -1;
2871 next_out = i+1; // [HGM] suppress printing in ICS window
2873 if(!suppressKibitz) // [HGM] kibitz
2874 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2875 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2876 int nrDigit = 0, nrAlph = 0, j;
2877 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2878 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2879 parse[parse_pos] = NULLCHAR;
2880 // try to be smart: if it does not look like search info, it should go to
2881 // ICS interaction window after all, not to engine-output window.
2882 for(j=0; j<parse_pos; j++) { // count letters and digits
2883 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2884 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2885 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2887 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2888 int depth=0; float score;
2889 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2890 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2891 pvInfoList[forwardMostMove-1].depth = depth;
2892 pvInfoList[forwardMostMove-1].score = 100*score;
2894 OutputKibitz(suppressKibitz, parse);
2897 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2898 SendToPlayer(tmp, strlen(tmp));
2900 next_out = i+1; // [HGM] suppress printing in ICS window
2902 started = STARTED_NONE;
2904 /* Don't match patterns against characters in comment */
2909 if (started == STARTED_CHATTER) {
2910 if (buf[i] != '\n') {
2911 /* Don't match patterns against characters in chatter */
2915 started = STARTED_NONE;
2916 if(suppressKibitz) next_out = i+1;
2919 /* Kludge to deal with rcmd protocol */
2920 if (firstTime && looking_at(buf, &i, "\001*")) {
2921 DisplayFatalError(&buf[1], 0, 1);
2927 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2930 if (appData.debugMode)
2931 fprintf(debugFP, "ics_type %d\n", ics_type);
2934 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2935 ics_type = ICS_FICS;
2937 if (appData.debugMode)
2938 fprintf(debugFP, "ics_type %d\n", ics_type);
2941 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2942 ics_type = ICS_CHESSNET;
2944 if (appData.debugMode)
2945 fprintf(debugFP, "ics_type %d\n", ics_type);
2950 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2951 looking_at(buf, &i, "Logging you in as \"*\"") ||
2952 looking_at(buf, &i, "will be \"*\""))) {
2953 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2957 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2959 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2960 DisplayIcsInteractionTitle(buf);
2961 have_set_title = TRUE;
2964 /* skip finger notes */
2965 if (started == STARTED_NONE &&
2966 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2967 (buf[i] == '1' && buf[i+1] == '0')) &&
2968 buf[i+2] == ':' && buf[i+3] == ' ') {
2969 started = STARTED_CHATTER;
2975 // [HGM] seekgraph: recognize sought lines and end-of-sought message
2976 if(appData.seekGraph) {
2977 if(soughtPending && MatchSoughtLine(buf+i)) {
2978 i = strstr(buf+i, "rated") - buf;
2979 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2980 next_out = leftover_start = i;
2981 started = STARTED_CHATTER;
2982 suppressKibitz = TRUE;
2985 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2986 && looking_at(buf, &i, "* ads displayed")) {
2987 soughtPending = FALSE;
2992 if(appData.autoRefresh) {
2993 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2994 int s = (ics_type == ICS_ICC); // ICC format differs
2996 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
2997 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2998 looking_at(buf, &i, "*% "); // eat prompt
2999 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3000 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3001 next_out = i; // suppress
3004 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3005 char *p = star_match[0];
3007 if(seekGraphUp) RemoveSeekAd(atoi(p));
3008 while(*p && *p++ != ' '); // next
3010 looking_at(buf, &i, "*% "); // eat prompt
3011 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3018 /* skip formula vars */
3019 if (started == STARTED_NONE &&
3020 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3021 started = STARTED_CHATTER;
3026 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3027 if (appData.autoKibitz && started == STARTED_NONE &&
3028 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3029 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3030 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3031 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3032 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3033 suppressKibitz = TRUE;
3034 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3036 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3037 && (gameMode == IcsPlayingWhite)) ||
3038 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3039 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3040 started = STARTED_CHATTER; // own kibitz we simply discard
3042 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3043 parse_pos = 0; parse[0] = NULLCHAR;
3044 savingComment = TRUE;
3045 suppressKibitz = gameMode != IcsObserving ? 2 :
3046 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3050 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3051 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3052 && atoi(star_match[0])) {
3053 // suppress the acknowledgements of our own autoKibitz
3055 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3056 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3057 SendToPlayer(star_match[0], strlen(star_match[0]));
3058 if(looking_at(buf, &i, "*% ")) // eat prompt
3059 suppressKibitz = FALSE;
3063 } // [HGM] kibitz: end of patch
3065 // [HGM] chat: intercept tells by users for which we have an open chat window
3067 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3068 looking_at(buf, &i, "* whispers:") ||
3069 looking_at(buf, &i, "* kibitzes:") ||
3070 looking_at(buf, &i, "* shouts:") ||
3071 looking_at(buf, &i, "* c-shouts:") ||
3072 looking_at(buf, &i, "--> * ") ||
3073 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3074 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3075 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3076 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3078 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3079 chattingPartner = -1;
3081 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3082 for(p=0; p<MAX_CHAT; p++) {
3083 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3084 talker[0] = '['; strcat(talker, "] ");
3085 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3086 chattingPartner = p; break;
3089 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3090 for(p=0; p<MAX_CHAT; p++) {
3091 if(!strcmp("kibitzes", chatPartner[p])) {
3092 talker[0] = '['; strcat(talker, "] ");
3093 chattingPartner = p; break;
3096 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3097 for(p=0; p<MAX_CHAT; p++) {
3098 if(!strcmp("whispers", chatPartner[p])) {
3099 talker[0] = '['; strcat(talker, "] ");
3100 chattingPartner = p; break;
3103 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3104 if(buf[i-8] == '-' && buf[i-3] == 't')
3105 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3106 if(!strcmp("c-shouts", chatPartner[p])) {
3107 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3108 chattingPartner = p; break;
3111 if(chattingPartner < 0)
3112 for(p=0; p<MAX_CHAT; p++) {
3113 if(!strcmp("shouts", chatPartner[p])) {
3114 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3115 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3116 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3117 chattingPartner = p; break;
3121 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3122 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3123 talker[0] = 0; Colorize(ColorTell, FALSE);
3124 chattingPartner = p; break;
3126 if(chattingPartner<0) i = oldi; else {
3127 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3128 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3129 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3130 started = STARTED_COMMENT;
3131 parse_pos = 0; parse[0] = NULLCHAR;
3132 savingComment = 3 + chattingPartner; // counts as TRUE
3133 suppressKibitz = TRUE;
3136 } // [HGM] chat: end of patch
3139 if (appData.zippyTalk || appData.zippyPlay) {
3140 /* [DM] Backup address for color zippy lines */
3142 if (loggedOn == TRUE)
3143 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3144 (appData.zippyPlay && ZippyMatch(buf, &backup)));
3146 } // [DM] 'else { ' deleted
3148 /* Regular tells and says */
3149 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3150 looking_at(buf, &i, "* (your partner) tells you: ") ||
3151 looking_at(buf, &i, "* says: ") ||
3152 /* Don't color "message" or "messages" output */
3153 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3154 looking_at(buf, &i, "*. * at *:*: ") ||
3155 looking_at(buf, &i, "--* (*:*): ") ||
3156 /* Message notifications (same color as tells) */
3157 looking_at(buf, &i, "* has left a message ") ||
3158 looking_at(buf, &i, "* just sent you a message:\n") ||
3159 /* Whispers and kibitzes */
3160 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3161 looking_at(buf, &i, "* kibitzes: ") ||
3163 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3165 if (tkind == 1 && strchr(star_match[0], ':')) {
3166 /* Avoid "tells you:" spoofs in channels */
3169 if (star_match[0][0] == NULLCHAR ||
3170 strchr(star_match[0], ' ') ||
3171 (tkind == 3 && strchr(star_match[1], ' '))) {
3172 /* Reject bogus matches */
3175 if (appData.colorize) {
3176 if (oldi > next_out) {
3177 SendToPlayer(&buf[next_out], oldi - next_out);
3182 Colorize(ColorTell, FALSE);
3183 curColor = ColorTell;
3186 Colorize(ColorKibitz, FALSE);
3187 curColor = ColorKibitz;
3190 p = strrchr(star_match[1], '(');
3197 Colorize(ColorChannel1, FALSE);
3198 curColor = ColorChannel1;
3200 Colorize(ColorChannel, FALSE);
3201 curColor = ColorChannel;
3205 curColor = ColorNormal;
3209 if (started == STARTED_NONE && appData.autoComment &&
3210 (gameMode == IcsObserving ||
3211 gameMode == IcsPlayingWhite ||
3212 gameMode == IcsPlayingBlack)) {
3213 parse_pos = i - oldi;
3214 memcpy(parse, &buf[oldi], parse_pos);
3215 parse[parse_pos] = NULLCHAR;
3216 started = STARTED_COMMENT;
3217 savingComment = TRUE;
3219 started = STARTED_CHATTER;
3220 savingComment = FALSE;
3227 if (looking_at(buf, &i, "* s-shouts: ") ||
3228 looking_at(buf, &i, "* c-shouts: ")) {
3229 if (appData.colorize) {
3230 if (oldi > next_out) {
3231 SendToPlayer(&buf[next_out], oldi - next_out);
3234 Colorize(ColorSShout, FALSE);
3235 curColor = ColorSShout;
3238 started = STARTED_CHATTER;
3242 if (looking_at(buf, &i, "--->")) {
3247 if (looking_at(buf, &i, "* shouts: ") ||
3248 looking_at(buf, &i, "--> ")) {
3249 if (appData.colorize) {
3250 if (oldi > next_out) {
3251 SendToPlayer(&buf[next_out], oldi - next_out);
3254 Colorize(ColorShout, FALSE);
3255 curColor = ColorShout;
3258 started = STARTED_CHATTER;
3262 if (looking_at( buf, &i, "Challenge:")) {
3263 if (appData.colorize) {
3264 if (oldi > next_out) {
3265 SendToPlayer(&buf[next_out], oldi - next_out);
3268 Colorize(ColorChallenge, FALSE);
3269 curColor = ColorChallenge;
3275 if (looking_at(buf, &i, "* offers you") ||
3276 looking_at(buf, &i, "* offers to be") ||
3277 looking_at(buf, &i, "* would like to") ||
3278 looking_at(buf, &i, "* requests to") ||
3279 looking_at(buf, &i, "Your opponent offers") ||
3280 looking_at(buf, &i, "Your opponent requests")) {
3282 if (appData.colorize) {
3283 if (oldi > next_out) {
3284 SendToPlayer(&buf[next_out], oldi - next_out);
3287 Colorize(ColorRequest, FALSE);
3288 curColor = ColorRequest;
3293 if (looking_at(buf, &i, "* (*) seeking")) {
3294 if (appData.colorize) {
3295 if (oldi > next_out) {
3296 SendToPlayer(&buf[next_out], oldi - next_out);
3299 Colorize(ColorSeek, FALSE);
3300 curColor = ColorSeek;
3305 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3307 if (looking_at(buf, &i, "\\ ")) {
3308 if (prevColor != ColorNormal) {
3309 if (oldi > next_out) {
3310 SendToPlayer(&buf[next_out], oldi - next_out);
3313 Colorize(prevColor, TRUE);
3314 curColor = prevColor;
3316 if (savingComment) {
3317 parse_pos = i - oldi;
3318 memcpy(parse, &buf[oldi], parse_pos);
3319 parse[parse_pos] = NULLCHAR;
3320 started = STARTED_COMMENT;
3321 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3322 chattingPartner = savingComment - 3; // kludge to remember the box
3324 started = STARTED_CHATTER;
3329 if (looking_at(buf, &i, "Black Strength :") ||
3330 looking_at(buf, &i, "<<< style 10 board >>>") ||
3331 looking_at(buf, &i, "<10>") ||
3332 looking_at(buf, &i, "#@#")) {
3333 /* Wrong board style */
3335 SendToICS(ics_prefix);
3336 SendToICS("set style 12\n");
3337 SendToICS(ics_prefix);
3338 SendToICS("refresh\n");
3342 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3344 have_sent_ICS_logon = 1;
3348 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3349 (looking_at(buf, &i, "\n<12> ") ||
3350 looking_at(buf, &i, "<12> "))) {
3352 if (oldi > next_out) {
3353 SendToPlayer(&buf[next_out], oldi - next_out);
3356 started = STARTED_BOARD;
3361 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3362 looking_at(buf, &i, "<b1> ")) {
3363 if (oldi > next_out) {
3364 SendToPlayer(&buf[next_out], oldi - next_out);
3367 started = STARTED_HOLDINGS;
3372 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3374 /* Header for a move list -- first line */
3376 switch (ics_getting_history) {
3380 case BeginningOfGame:
3381 /* User typed "moves" or "oldmoves" while we
3382 were idle. Pretend we asked for these
3383 moves and soak them up so user can step
3384 through them and/or save them.
3387 gameMode = IcsObserving;
3390 ics_getting_history = H_GOT_UNREQ_HEADER;
3392 case EditGame: /*?*/
3393 case EditPosition: /*?*/
3394 /* Should above feature work in these modes too? */
3395 /* For now it doesn't */
3396 ics_getting_history = H_GOT_UNWANTED_HEADER;
3399 ics_getting_history = H_GOT_UNWANTED_HEADER;
3404 /* Is this the right one? */
3405 if (gameInfo.white && gameInfo.black &&
3406 strcmp(gameInfo.white, star_match[0]) == 0 &&
3407 strcmp(gameInfo.black, star_match[2]) == 0) {
3409 ics_getting_history = H_GOT_REQ_HEADER;