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 CleanupTail P((void));
511 ChessSquare FIDEArray[2][BOARD_FILES] = {
512 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
513 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
514 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
515 BlackKing, BlackBishop, BlackKnight, BlackRook }
518 ChessSquare twoKingsArray[2][BOARD_FILES] = {
519 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
520 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
521 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
522 BlackKing, BlackKing, BlackKnight, BlackRook }
525 ChessSquare KnightmateArray[2][BOARD_FILES] = {
526 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
527 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
528 { BlackRook, BlackMan, BlackBishop, BlackQueen,
529 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
532 ChessSquare SpartanArray[2][BOARD_FILES] = {
533 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
534 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
535 { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
536 BlackDragon, BlackKing, BlackAngel, BlackAlfil }
539 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
540 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
541 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
542 { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
543 BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
546 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
547 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
548 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
549 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
550 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
553 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
554 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
555 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
556 { BlackRook, BlackKnight, BlackMan, BlackFerz,
557 BlackKing, BlackMan, BlackKnight, BlackRook }
561 #if (BOARD_FILES>=10)
562 ChessSquare ShogiArray[2][BOARD_FILES] = {
563 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
564 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
565 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
566 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
569 ChessSquare XiangqiArray[2][BOARD_FILES] = {
570 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
571 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
572 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
573 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
576 ChessSquare CapablancaArray[2][BOARD_FILES] = {
577 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
578 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
579 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
580 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
583 ChessSquare GreatArray[2][BOARD_FILES] = {
584 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
585 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
586 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
587 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
590 ChessSquare JanusArray[2][BOARD_FILES] = {
591 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
592 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
593 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
594 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
598 ChessSquare GothicArray[2][BOARD_FILES] = {
599 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
600 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
601 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
602 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
605 #define GothicArray CapablancaArray
609 ChessSquare FalconArray[2][BOARD_FILES] = {
610 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
611 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
612 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
613 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
616 #define FalconArray CapablancaArray
619 #else // !(BOARD_FILES>=10)
620 #define XiangqiPosition FIDEArray
621 #define CapablancaArray FIDEArray
622 #define GothicArray FIDEArray
623 #define GreatArray FIDEArray
624 #endif // !(BOARD_FILES>=10)
626 #if (BOARD_FILES>=12)
627 ChessSquare CourierArray[2][BOARD_FILES] = {
628 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
629 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
630 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
631 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
633 #else // !(BOARD_FILES>=12)
634 #define CourierArray CapablancaArray
635 #endif // !(BOARD_FILES>=12)
638 Board initialPosition;
641 /* Convert str to a rating. Checks for special cases of "----",
643 "++++", etc. Also strips ()'s */
645 string_to_rating(str)
648 while(*str && !isdigit(*str)) ++str;
650 return 0; /* One of the special "no rating" cases */
658 /* Init programStats */
659 programStats.movelist[0] = 0;
660 programStats.depth = 0;
661 programStats.nr_moves = 0;
662 programStats.moves_left = 0;
663 programStats.nodes = 0;
664 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
665 programStats.score = 0;
666 programStats.got_only_move = 0;
667 programStats.got_fail = 0;
668 programStats.line_is_book = 0;
673 { // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
674 if (appData.firstPlaysBlack) {
675 first.twoMachinesColor = "black\n";
676 second.twoMachinesColor = "white\n";
678 first.twoMachinesColor = "white\n";
679 second.twoMachinesColor = "black\n";
682 first.other = &second;
683 second.other = &first;
686 if(appData.timeOddsMode) {
687 norm = appData.timeOdds[0];
688 if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
690 first.timeOdds = appData.timeOdds[0]/norm;
691 second.timeOdds = appData.timeOdds[1]/norm;
694 if(programVersion) free(programVersion);
695 if (appData.noChessProgram) {
696 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
697 sprintf(programVersion, "%s", PACKAGE_STRING);
699 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
700 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
701 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
706 UnloadEngine(ChessProgramState *cps)
708 /* Kill off first chess program */
709 if (cps->isr != NULL)
710 RemoveInputSource(cps->isr);
713 if (cps->pr != NoProc) {
715 DoSleep( appData.delayBeforeQuit );
716 SendToProgram("quit\n", cps);
717 DoSleep( appData.delayAfterQuit );
718 DestroyChildProcess(cps->pr, cps->useSigterm);
721 if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
725 ClearOptions(ChessProgramState *cps)
728 cps->nrOptions = cps->comboCnt = 0;
729 for(i=0; i<MAX_OPTIONS; i++) {
730 cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
731 cps->option[i].textValue = 0;
735 char *engineNames[] = {
741 InitEngine(ChessProgramState *cps, int n)
742 { // [HGM] all engine initialiation put in a function that does one engine
746 cps->which = engineNames[n];
747 cps->maybeThinking = FALSE;
751 cps->sendDrawOffers = 1;
753 cps->program = appData.chessProgram[n];
754 cps->host = appData.host[n];
755 cps->dir = appData.directory[n];
756 cps->initString = appData.engInitString[n];
757 cps->computerString = appData.computerString[n];
758 cps->useSigint = TRUE;
759 cps->useSigterm = TRUE;
760 cps->reuse = appData.reuse[n];
761 cps->nps = appData.NPS[n]; // [HGM] nps: copy nodes per second
762 cps->useSetboard = FALSE;
764 cps->usePing = FALSE;
767 cps->usePlayother = FALSE;
768 cps->useColors = TRUE;
769 cps->useUsermove = FALSE;
770 cps->sendICS = FALSE;
771 cps->sendName = appData.icsActive;
772 cps->sdKludge = FALSE;
773 cps->stKludge = FALSE;
774 TidyProgramName(cps->program, cps->host, cps->tidy);
776 safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
777 cps->analysisSupport = 2; /* detect */
778 cps->analyzing = FALSE;
779 cps->initDone = FALSE;
781 /* New features added by Tord: */
782 cps->useFEN960 = FALSE;
783 cps->useOOCastle = TRUE;
784 /* End of new features added by Tord. */
785 cps->fenOverride = appData.fenOverride[n];
787 /* [HGM] time odds: set factor for each machine */
788 cps->timeOdds = appData.timeOdds[n];
790 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
791 cps->accumulateTC = appData.accumulateTC[n];
792 cps->maxNrOfSessions = 1;
796 cps->supportsNPS = UNKNOWN;
799 cps->optionSettings = appData.engOptions[n];
801 cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
802 cps->isUCI = appData.isUCI[n]; /* [AS] */
803 cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
805 if (appData.protocolVersion[n] > PROTOVER
806 || appData.protocolVersion[n] < 1)
811 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
812 appData.protocolVersion[n]);
813 if( (len > MSG_SIZ) && appData.debugMode )
814 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
816 DisplayFatalError(buf, 0, 2);
820 cps->protocolVersion = appData.protocolVersion[n];
823 InitEngineUCI( installDir, cps ); // [HGM] moved here from winboard.c, to make available in xboard
826 ChessProgramState *savCps;
832 if(WaitForEngine(savCps, LoadEngine)) return;
833 CommonEngineInit(); // recalculate time odds
834 if(gameInfo.variant != StringToVariant(appData.variant)) {
835 // we changed variant when loading the engine; this forces us to reset
836 Reset(TRUE, savCps != &first);
837 EditGameEvent(); // for consistency with other path, as Reset changes mode
839 InitChessProgram(savCps, FALSE);
840 SendToProgram("force\n", savCps);
841 DisplayMessage("", "");
842 if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
843 for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
849 ReplaceEngine(ChessProgramState *cps, int n)
853 appData.noChessProgram = FALSE;
854 appData.clockMode = TRUE;
856 if(n) return; // only startup first engine immediately; second can wait
857 savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
861 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
862 extern Boolean isUCI, hasBook, storeVariant, v1, addToList;
865 Load(ChessProgramState *cps, int i)
867 char *p, *q, buf[MSG_SIZ], command[MSG_SIZ];
868 if(engineLine[0]) { // an engine was selected from the combo box
869 snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
870 SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
871 ParseArgsFromString("-firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1");
872 ParseArgsFromString(buf);
874 ReplaceEngine(cps, i);
878 while(q = strchr(p, SLASH)) p = q+1;
879 if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
880 if(engineDir[0] != NULLCHAR)
881 appData.directory[i] = engineDir;
882 else if(p != engineName) { // derive directory from engine path, when not given
884 appData.directory[i] = strdup(engineName);
886 } else appData.directory[i] = ".";
888 snprintf(command, MSG_SIZ, "%s %s", p, params);
891 appData.chessProgram[i] = strdup(p);
892 appData.isUCI[i] = isUCI;
893 appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
894 appData.hasOwnBookUCI[i] = hasBook;
897 q = firstChessProgramNames;
898 if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
899 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "\"%s\" -fd \"%s\"%s%s%s%s%s\n", p, appData.directory[i],
900 v1 ? " -firstProtocolVersion 1" : "",
901 hasBook ? "" : " -fNoOwnBookUCI",
902 isUCI ? " -fUCI" : "",
903 storeVariant ? " -variant " : "",
904 storeVariant ? VariantName(gameInfo.variant) : "");
905 firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
906 snprintf(firstChessProgramNames, len, "%s%s", q, buf);
909 ReplaceEngine(cps, i);
915 int matched, min, sec;
917 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
918 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
920 GetTimeMark(&programStartTime);
921 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
922 pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
925 programStats.ok_to_send = 1;
926 programStats.seen_stat = 0;
929 * Initialize game list
935 * Internet chess server status
937 if (appData.icsActive) {
938 appData.matchMode = FALSE;
939 appData.matchGames = 0;
941 appData.noChessProgram = !appData.zippyPlay;
943 appData.zippyPlay = FALSE;
944 appData.zippyTalk = FALSE;
945 appData.noChessProgram = TRUE;
947 if (*appData.icsHelper != NULLCHAR) {
948 appData.useTelnet = TRUE;
949 appData.telnetProgram = appData.icsHelper;
952 appData.zippyTalk = appData.zippyPlay = FALSE;
955 /* [AS] Initialize pv info list [HGM] and game state */
959 for( i=0; i<=framePtr; i++ ) {
960 pvInfoList[i].depth = -1;
961 boards[i][EP_STATUS] = EP_NONE;
962 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
967 * Parse timeControl resource
969 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
970 appData.movesPerSession)) {
972 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
973 DisplayFatalError(buf, 0, 2);
977 * Parse searchTime resource
979 if (*appData.searchTime != NULLCHAR) {
980 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
982 searchTime = min * 60;
983 } else if (matched == 2) {
984 searchTime = min * 60 + sec;
987 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
988 DisplayFatalError(buf, 0, 2);
992 /* [AS] Adjudication threshold */
993 adjudicateLossThreshold = appData.adjudicateLossThreshold;
995 InitEngine(&first, 0);
996 InitEngine(&second, 1);
999 if (appData.icsActive) {
1000 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
1001 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1002 appData.clockMode = FALSE;
1003 first.sendTime = second.sendTime = 0;
1007 /* Override some settings from environment variables, for backward
1008 compatibility. Unfortunately it's not feasible to have the env
1009 vars just set defaults, at least in xboard. Ugh.
1011 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1016 if (!appData.icsActive) {
1020 /* Check for variants that are supported only in ICS mode,
1021 or not at all. Some that are accepted here nevertheless
1022 have bugs; see comments below.
1024 VariantClass variant = StringToVariant(appData.variant);
1026 case VariantBughouse: /* need four players and two boards */
1027 case VariantKriegspiel: /* need to hide pieces and move details */
1028 /* case VariantFischeRandom: (Fabien: moved below) */
1029 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1030 if( (len > MSG_SIZ) && appData.debugMode )
1031 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1033 DisplayFatalError(buf, 0, 2);
1036 case VariantUnknown:
1037 case VariantLoadable:
1047 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1048 if( (len > MSG_SIZ) && appData.debugMode )
1049 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1051 DisplayFatalError(buf, 0, 2);
1054 case VariantXiangqi: /* [HGM] repetition rules not implemented */
1055 case VariantFairy: /* [HGM] TestLegality definitely off! */
1056 case VariantGothic: /* [HGM] should work */
1057 case VariantCapablanca: /* [HGM] should work */
1058 case VariantCourier: /* [HGM] initial forced moves not implemented */
1059 case VariantShogi: /* [HGM] could still mate with pawn drop */
1060 case VariantKnightmate: /* [HGM] should work */
1061 case VariantCylinder: /* [HGM] untested */
1062 case VariantFalcon: /* [HGM] untested */
1063 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1064 offboard interposition not understood */
1065 case VariantNormal: /* definitely works! */
1066 case VariantWildCastle: /* pieces not automatically shuffled */
1067 case VariantNoCastle: /* pieces not automatically shuffled */
1068 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1069 case VariantLosers: /* should work except for win condition,
1070 and doesn't know captures are mandatory */
1071 case VariantSuicide: /* should work except for win condition,
1072 and doesn't know captures are mandatory */
1073 case VariantGiveaway: /* should work except for win condition,
1074 and doesn't know captures are mandatory */
1075 case VariantTwoKings: /* should work */
1076 case VariantAtomic: /* should work except for win condition */
1077 case Variant3Check: /* should work except for win condition */
1078 case VariantShatranj: /* should work except for all win conditions */
1079 case VariantMakruk: /* should work except for daw countdown */
1080 case VariantBerolina: /* might work if TestLegality is off */
1081 case VariantCapaRandom: /* should work */
1082 case VariantJanus: /* should work */
1083 case VariantSuper: /* experimental */
1084 case VariantGreat: /* experimental, requires legality testing to be off */
1085 case VariantSChess: /* S-Chess, should work */
1086 case VariantSpartan: /* should work */
1093 int NextIntegerFromString( char ** str, long * value )
1098 while( *s == ' ' || *s == '\t' ) {
1104 if( *s >= '0' && *s <= '9' ) {
1105 while( *s >= '0' && *s <= '9' ) {
1106 *value = *value * 10 + (*s - '0');
1118 int NextTimeControlFromString( char ** str, long * value )
1121 int result = NextIntegerFromString( str, &temp );
1124 *value = temp * 60; /* Minutes */
1125 if( **str == ':' ) {
1127 result = NextIntegerFromString( str, &temp );
1128 *value += temp; /* Seconds */
1135 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1136 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1137 int result = -1, type = 0; long temp, temp2;
1139 if(**str != ':') return -1; // old params remain in force!
1141 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1142 if( NextIntegerFromString( str, &temp ) ) return -1;
1143 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1146 /* time only: incremental or sudden-death time control */
1147 if(**str == '+') { /* increment follows; read it */
1149 if(**str == '!') type = *(*str)++; // Bronstein TC
1150 if(result = NextIntegerFromString( str, &temp2)) return -1;
1151 *inc = temp2 * 1000;
1152 if(**str == '.') { // read fraction of increment
1153 char *start = ++(*str);
1154 if(result = NextIntegerFromString( str, &temp2)) return -1;
1156 while(start++ < *str) temp2 /= 10;
1160 *moves = 0; *tc = temp * 1000; *incType = type;
1164 (*str)++; /* classical time control */
1165 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1176 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1177 { /* [HGM] get time to add from the multi-session time-control string */
1178 int incType, moves=1; /* kludge to force reading of first session */
1179 long time, increment;
1182 if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1183 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1185 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1186 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1187 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1188 if(movenr == -1) return time; /* last move before new session */
1189 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1190 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1191 if(!moves) return increment; /* current session is incremental */
1192 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1193 } while(movenr >= -1); /* try again for next session */
1195 return 0; // no new time quota on this move
1199 ParseTimeControl(tc, ti, mps)
1206 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1209 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1210 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1211 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1215 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1217 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1220 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1222 snprintf(buf, MSG_SIZ, ":%s", mytc);
1224 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1226 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1231 /* Parse second time control */
1234 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1242 timeControl_2 = tc2 * 1000;
1252 timeControl = tc1 * 1000;
1255 timeIncrement = ti * 1000; /* convert to ms */
1256 movesPerSession = 0;
1259 movesPerSession = mps;
1267 if (appData.debugMode) {
1268 fprintf(debugFP, "%s\n", programVersion);
1271 set_cont_sequence(appData.wrapContSeq);
1272 if (appData.matchGames > 0) {
1273 appData.matchMode = TRUE;
1274 } else if (appData.matchMode) {
1275 appData.matchGames = 1;
1277 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1278 appData.matchGames = appData.sameColorGames;
1279 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1280 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1281 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1284 if (appData.noChessProgram || first.protocolVersion == 1) {
1287 /* kludge: allow timeout for initial "feature" commands */
1289 DisplayMessage("", _("Starting chess program"));
1290 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1295 CalculateIndex(int index, int gameNr)
1296 { // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1298 if(index > 0) return index; // fixed nmber
1299 if(index == 0) return 1;
1300 res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1301 if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1306 LoadGameOrPosition(int gameNr)
1307 { // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1308 if (*appData.loadGameFile != NULLCHAR) {
1309 if (!LoadGameFromFile(appData.loadGameFile,
1310 CalculateIndex(appData.loadGameIndex, gameNr),
1311 appData.loadGameFile, FALSE)) {
1312 DisplayFatalError(_("Bad game file"), 0, 1);
1315 } else if (*appData.loadPositionFile != NULLCHAR) {
1316 if (!LoadPositionFromFile(appData.loadPositionFile,
1317 CalculateIndex(appData.loadPositionIndex, gameNr),
1318 appData.loadPositionFile)) {
1319 DisplayFatalError(_("Bad position file"), 0, 1);
1327 ReserveGame(int gameNr, char resChar)
1329 FILE *tf = fopen(appData.tourneyFile, "r+");
1330 char *p, *q, c, buf[MSG_SIZ];
1331 if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1332 safeStrCpy(buf, lastMsg, MSG_SIZ);
1333 DisplayMessage(_("Pick new game"), "");
1334 flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1335 ParseArgsFromFile(tf);
1336 p = q = appData.results;
1337 if(appData.debugMode) {
1338 char *r = appData.participants;
1339 fprintf(debugFP, "results = '%s'\n", p);
1340 while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1341 fprintf(debugFP, "\n");
1343 while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1345 q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1346 safeStrCpy(q, p, strlen(p) + 2);
1347 if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1348 if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1349 if(nextGame <= appData.matchGames && resChar != ' ') { // already reserve next game, if tourney not yet done
1350 if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1353 fseek(tf, -(strlen(p)+4), SEEK_END);
1355 if(c != '"') // depending on DOS or Unix line endings we can be one off
1356 fseek(tf, -(strlen(p)+2), SEEK_END);
1357 else fseek(tf, -(strlen(p)+3), SEEK_END);
1358 fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1359 DisplayMessage(buf, "");
1360 free(p); appData.results = q;
1361 if(nextGame <= appData.matchGames && resChar != ' ' &&
1362 (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1363 UnloadEngine(&first); // next game belongs to other pairing;
1364 UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1369 MatchEvent(int mode)
1370 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1372 if(matchMode) { // already in match mode: switch it off
1373 appData.matchGames = matchGame; // kludge to let match terminate after next game.
1374 ModeHighlight(); // kludgey way to remove checkmark...
1377 if(gameMode != BeginningOfGame) {
1378 DisplayError(_("You can only start a match from the initial position."), 0);
1381 appData.matchGames = appData.defaultMatchGames;
1382 /* Set up machine vs. machine match */
1384 NextTourneyGame(0, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1385 if(appData.tourneyFile[0]) {
1387 if(nextGame > appData.matchGames) {
1389 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1390 DisplayError(buf, 0);
1391 appData.tourneyFile[0] = 0;
1395 if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically
1396 DisplayFatalError(_("Can't have a match with no chess programs"),
1401 matchGame = roundNr = 1;
1402 first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
\r
1407 InitBackEnd3 P((void))
1409 GameMode initialMode;
1413 InitChessProgram(&first, startedFromSetupPosition);
1415 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1416 free(programVersion);
1417 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1418 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1421 if (appData.icsActive) {
1423 /* [DM] Make a console window if needed [HGM] merged ifs */
1429 if (*appData.icsCommPort != NULLCHAR)
1430 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1431 appData.icsCommPort);
1433 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1434 appData.icsHost, appData.icsPort);
1436 if( (len > MSG_SIZ) && appData.debugMode )
1437 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1439 DisplayFatalError(buf, err, 1);
1444 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1446 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1447 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1448 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1449 } else if (appData.noChessProgram) {
1455 if (*appData.cmailGameName != NULLCHAR) {
1457 OpenLoopback(&cmailPR);
1459 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1463 DisplayMessage("", "");
1464 if (StrCaseCmp(appData.initialMode, "") == 0) {
1465 initialMode = BeginningOfGame;
1466 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1467 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1468 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1469 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1472 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1473 initialMode = TwoMachinesPlay;
1474 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1475 initialMode = AnalyzeFile;
1476 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1477 initialMode = AnalyzeMode;
1478 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1479 initialMode = MachinePlaysWhite;
1480 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1481 initialMode = MachinePlaysBlack;
1482 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1483 initialMode = EditGame;
1484 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1485 initialMode = EditPosition;
1486 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1487 initialMode = Training;
1489 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1490 if( (len > MSG_SIZ) && appData.debugMode )
1491 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1493 DisplayFatalError(buf, 0, 2);
1497 if (appData.matchMode) {
1498 if(appData.tourneyFile[0]) { // start tourney from command line
1500 if(f = fopen(appData.tourneyFile, "r")) {
1501 ParseArgsFromFile(f); // make sure tourney parmeters re known
1503 } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1506 } else if (*appData.cmailGameName != NULLCHAR) {
1507 /* Set up cmail mode */
1508 ReloadCmailMsgEvent(TRUE);
1510 /* Set up other modes */
1511 if (initialMode == AnalyzeFile) {
1512 if (*appData.loadGameFile == NULLCHAR) {
1513 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1517 if (*appData.loadGameFile != NULLCHAR) {
1518 (void) LoadGameFromFile(appData.loadGameFile,
1519 appData.loadGameIndex,
1520 appData.loadGameFile, TRUE);
1521 } else if (*appData.loadPositionFile != NULLCHAR) {
1522 (void) LoadPositionFromFile(appData.loadPositionFile,
1523 appData.loadPositionIndex,
1524 appData.loadPositionFile);
1525 /* [HGM] try to make self-starting even after FEN load */
1526 /* to allow automatic setup of fairy variants with wtm */
1527 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1528 gameMode = BeginningOfGame;
1529 setboardSpoiledMachineBlack = 1;
1531 /* [HGM] loadPos: make that every new game uses the setup */
1532 /* from file as long as we do not switch variant */
1533 if(!blackPlaysFirst) {
1534 startedFromPositionFile = TRUE;
1535 CopyBoard(filePosition, boards[0]);
1538 if (initialMode == AnalyzeMode) {
1539 if (appData.noChessProgram) {
1540 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1543 if (appData.icsActive) {
1544 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1548 } else if (initialMode == AnalyzeFile) {
1549 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1550 ShowThinkingEvent();
1552 AnalysisPeriodicEvent(1);
1553 } else if (initialMode == MachinePlaysWhite) {
1554 if (appData.noChessProgram) {
1555 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1559 if (appData.icsActive) {
1560 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1564 MachineWhiteEvent();
1565 } else if (initialMode == MachinePlaysBlack) {
1566 if (appData.noChessProgram) {
1567 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1571 if (appData.icsActive) {
1572 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1576 MachineBlackEvent();
1577 } else if (initialMode == TwoMachinesPlay) {
1578 if (appData.noChessProgram) {
1579 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1583 if (appData.icsActive) {
1584 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1589 } else if (initialMode == EditGame) {
1591 } else if (initialMode == EditPosition) {
1592 EditPositionEvent();
1593 } else if (initialMode == Training) {
1594 if (*appData.loadGameFile == NULLCHAR) {
1595 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1604 * Establish will establish a contact to a remote host.port.
1605 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1606 * used to talk to the host.
1607 * Returns 0 if okay, error code if not.
1614 if (*appData.icsCommPort != NULLCHAR) {
1615 /* Talk to the host through a serial comm port */
1616 return OpenCommPort(appData.icsCommPort, &icsPR);
1618 } else if (*appData.gateway != NULLCHAR) {
1619 if (*appData.remoteShell == NULLCHAR) {
1620 /* Use the rcmd protocol to run telnet program on a gateway host */
1621 snprintf(buf, sizeof(buf), "%s %s %s",
1622 appData.telnetProgram, appData.icsHost, appData.icsPort);
1623 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1626 /* Use the rsh program to run telnet program on a gateway host */
1627 if (*appData.remoteUser == NULLCHAR) {
1628 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1629 appData.gateway, appData.telnetProgram,
1630 appData.icsHost, appData.icsPort);
1632 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1633 appData.remoteShell, appData.gateway,
1634 appData.remoteUser, appData.telnetProgram,
1635 appData.icsHost, appData.icsPort);
1637 return StartChildProcess(buf, "", &icsPR);
1640 } else if (appData.useTelnet) {
1641 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1644 /* TCP socket interface differs somewhat between
1645 Unix and NT; handle details in the front end.
1647 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1651 void EscapeExpand(char *p, char *q)
1652 { // [HGM] initstring: routine to shape up string arguments
1653 while(*p++ = *q++) if(p[-1] == '\\')
1655 case 'n': p[-1] = '\n'; break;
1656 case 'r': p[-1] = '\r'; break;
1657 case 't': p[-1] = '\t'; break;
1658 case '\\': p[-1] = '\\'; break;
1659 case 0: *p = 0; return;
1660 default: p[-1] = q[-1]; break;
1665 show_bytes(fp, buf, count)
1671 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1672 fprintf(fp, "\\%03o", *buf & 0xff);
1681 /* Returns an errno value */
1683 OutputMaybeTelnet(pr, message, count, outError)
1689 char buf[8192], *p, *q, *buflim;
1690 int left, newcount, outcount;
1692 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1693 *appData.gateway != NULLCHAR) {
1694 if (appData.debugMode) {
1695 fprintf(debugFP, ">ICS: ");
1696 show_bytes(debugFP, message, count);
1697 fprintf(debugFP, "\n");
1699 return OutputToProcess(pr, message, count, outError);
1702 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1709 if (appData.debugMode) {
1710 fprintf(debugFP, ">ICS: ");
1711 show_bytes(debugFP, buf, newcount);
1712 fprintf(debugFP, "\n");
1714 outcount = OutputToProcess(pr, buf, newcount, outError);
1715 if (outcount < newcount) return -1; /* to be sure */
1722 } else if (((unsigned char) *p) == TN_IAC) {
1723 *q++ = (char) TN_IAC;
1730 if (appData.debugMode) {
1731 fprintf(debugFP, ">ICS: ");
1732 show_bytes(debugFP, buf, newcount);
1733 fprintf(debugFP, "\n");
1735 outcount = OutputToProcess(pr, buf, newcount, outError);
1736 if (outcount < newcount) return -1; /* to be sure */
1741 read_from_player(isr, closure, message, count, error)
1748 int outError, outCount;
1749 static int gotEof = 0;
1751 /* Pass data read from player on to ICS */
1754 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1755 if (outCount < count) {
1756 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1758 } else if (count < 0) {
1759 RemoveInputSource(isr);
1760 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1761 } else if (gotEof++ > 0) {
1762 RemoveInputSource(isr);
1763 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1769 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1770 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1771 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1772 SendToICS("date\n");
1773 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1776 /* added routine for printf style output to ics */
1777 void ics_printf(char *format, ...)
1779 char buffer[MSG_SIZ];
1782 va_start(args, format);
1783 vsnprintf(buffer, sizeof(buffer), format, args);
1784 buffer[sizeof(buffer)-1] = '\0';
1793 int count, outCount, outError;
1795 if (icsPR == NULL) return;
1798 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1799 if (outCount < count) {
1800 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1804 /* This is used for sending logon scripts to the ICS. Sending
1805 without a delay causes problems when using timestamp on ICC
1806 (at least on my machine). */
1808 SendToICSDelayed(s,msdelay)
1812 int count, outCount, outError;
1814 if (icsPR == NULL) return;
1817 if (appData.debugMode) {
1818 fprintf(debugFP, ">ICS: ");
1819 show_bytes(debugFP, s, count);
1820 fprintf(debugFP, "\n");
1822 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1824 if (outCount < count) {
1825 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1830 /* Remove all highlighting escape sequences in s
1831 Also deletes any suffix starting with '('
1834 StripHighlightAndTitle(s)
1837 static char retbuf[MSG_SIZ];
1840 while (*s != NULLCHAR) {
1841 while (*s == '\033') {
1842 while (*s != NULLCHAR && !isalpha(*s)) s++;
1843 if (*s != NULLCHAR) s++;
1845 while (*s != NULLCHAR && *s != '\033') {
1846 if (*s == '(' || *s == '[') {
1857 /* Remove all highlighting escape sequences in s */
1862 static char retbuf[MSG_SIZ];
1865 while (*s != NULLCHAR) {
1866 while (*s == '\033') {
1867 while (*s != NULLCHAR && !isalpha(*s)) s++;
1868 if (*s != NULLCHAR) s++;
1870 while (*s != NULLCHAR && *s != '\033') {
1878 char *variantNames[] = VARIANT_NAMES;
1883 return variantNames[v];
1887 /* Identify a variant from the strings the chess servers use or the
1888 PGN Variant tag names we use. */
1895 VariantClass v = VariantNormal;
1896 int i, found = FALSE;
1902 /* [HGM] skip over optional board-size prefixes */
1903 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1904 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1905 while( *e++ != '_');
1908 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1912 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1913 if (StrCaseStr(e, variantNames[i])) {
1914 v = (VariantClass) i;
1921 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1922 || StrCaseStr(e, "wild/fr")
1923 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1924 v = VariantFischeRandom;
1925 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1926 (i = 1, p = StrCaseStr(e, "w"))) {
1928 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1935 case 0: /* FICS only, actually */
1937 /* Castling legal even if K starts on d-file */
1938 v = VariantWildCastle;
1943 /* Castling illegal even if K & R happen to start in
1944 normal positions. */
1945 v = VariantNoCastle;
1958 /* Castling legal iff K & R start in normal positions */
1964 /* Special wilds for position setup; unclear what to do here */
1965 v = VariantLoadable;
1968 /* Bizarre ICC game */
1969 v = VariantTwoKings;
1972 v = VariantKriegspiel;
1978 v = VariantFischeRandom;
1981 v = VariantCrazyhouse;
1984 v = VariantBughouse;
1990 /* Not quite the same as FICS suicide! */
1991 v = VariantGiveaway;
1997 v = VariantShatranj;
2000 /* Temporary names for future ICC types. The name *will* change in
2001 the next xboard/WinBoard release after ICC defines it. */
2039 v = VariantCapablanca;
2042 v = VariantKnightmate;
2048 v = VariantCylinder;
2054 v = VariantCapaRandom;
2057 v = VariantBerolina;
2069 /* Found "wild" or "w" in the string but no number;
2070 must assume it's normal chess. */
2074 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2075 if( (len > MSG_SIZ) && appData.debugMode )
2076 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2078 DisplayError(buf, 0);
2084 if (appData.debugMode) {
2085 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2086 e, wnum, VariantName(v));
2091 static int leftover_start = 0, leftover_len = 0;
2092 char star_match[STAR_MATCH_N][MSG_SIZ];
2094 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2095 advance *index beyond it, and set leftover_start to the new value of
2096 *index; else return FALSE. If pattern contains the character '*', it
2097 matches any sequence of characters not containing '\r', '\n', or the
2098 character following the '*' (if any), and the matched sequence(s) are
2099 copied into star_match.
2102 looking_at(buf, index, pattern)
2107 char *bufp = &buf[*index], *patternp = pattern;
2109 char *matchp = star_match[0];
2112 if (*patternp == NULLCHAR) {
2113 *index = leftover_start = bufp - buf;
2117 if (*bufp == NULLCHAR) return FALSE;
2118 if (*patternp == '*') {
2119 if (*bufp == *(patternp + 1)) {
2121 matchp = star_match[++star_count];
2125 } else if (*bufp == '\n' || *bufp == '\r') {
2127 if (*patternp == NULLCHAR)
2132 *matchp++ = *bufp++;
2136 if (*patternp != *bufp) return FALSE;
2143 SendToPlayer(data, length)
2147 int error, outCount;
2148 outCount = OutputToProcess(NoProc, data, length, &error);
2149 if (outCount < length) {
2150 DisplayFatalError(_("Error writing to display"), error, 1);
2155 PackHolding(packed, holding)
2167 switch (runlength) {
2178 sprintf(q, "%d", runlength);
2190 /* Telnet protocol requests from the front end */
2192 TelnetRequest(ddww, option)
2193 unsigned char ddww, option;
2195 unsigned char msg[3];
2196 int outCount, outError;
2198 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2200 if (appData.debugMode) {
2201 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2217 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2226 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2229 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2234 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2236 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2243 if (!appData.icsActive) return;
2244 TelnetRequest(TN_DO, TN_ECHO);
2250 if (!appData.icsActive) return;
2251 TelnetRequest(TN_DONT, TN_ECHO);
2255 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2257 /* put the holdings sent to us by the server on the board holdings area */
2258 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2262 if(gameInfo.holdingsWidth < 2) return;
2263 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2264 return; // prevent overwriting by pre-board holdings
2266 if( (int)lowestPiece >= BlackPawn ) {
2269 holdingsStartRow = BOARD_HEIGHT-1;
2272 holdingsColumn = BOARD_WIDTH-1;
2273 countsColumn = BOARD_WIDTH-2;
2274 holdingsStartRow = 0;
2278 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2279 board[i][holdingsColumn] = EmptySquare;
2280 board[i][countsColumn] = (ChessSquare) 0;
2282 while( (p=*holdings++) != NULLCHAR ) {
2283 piece = CharToPiece( ToUpper(p) );
2284 if(piece == EmptySquare) continue;
2285 /*j = (int) piece - (int) WhitePawn;*/
2286 j = PieceToNumber(piece);
2287 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2288 if(j < 0) continue; /* should not happen */
2289 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2290 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2291 board[holdingsStartRow+j*direction][countsColumn]++;
2297 VariantSwitch(Board board, VariantClass newVariant)
2299 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2300 static Board oldBoard;
2302 startedFromPositionFile = FALSE;
2303 if(gameInfo.variant == newVariant) return;
2305 /* [HGM] This routine is called each time an assignment is made to
2306 * gameInfo.variant during a game, to make sure the board sizes
2307 * are set to match the new variant. If that means adding or deleting
2308 * holdings, we shift the playing board accordingly
2309 * This kludge is needed because in ICS observe mode, we get boards
2310 * of an ongoing game without knowing the variant, and learn about the
2311 * latter only later. This can be because of the move list we requested,
2312 * in which case the game history is refilled from the beginning anyway,
2313 * but also when receiving holdings of a crazyhouse game. In the latter
2314 * case we want to add those holdings to the already received position.
2318 if (appData.debugMode) {
2319 fprintf(debugFP, "Switch board from %s to %s\n",
2320 VariantName(gameInfo.variant), VariantName(newVariant));
2321 setbuf(debugFP, NULL);
2323 shuffleOpenings = 0; /* [HGM] shuffle */
2324 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2328 newWidth = 9; newHeight = 9;
2329 gameInfo.holdingsSize = 7;
2330 case VariantBughouse:
2331 case VariantCrazyhouse:
2332 newHoldingsWidth = 2; break;
2336 newHoldingsWidth = 2;
2337 gameInfo.holdingsSize = 8;
2340 case VariantCapablanca:
2341 case VariantCapaRandom:
2344 newHoldingsWidth = gameInfo.holdingsSize = 0;
2347 if(newWidth != gameInfo.boardWidth ||
2348 newHeight != gameInfo.boardHeight ||
2349 newHoldingsWidth != gameInfo.holdingsWidth ) {
2351 /* shift position to new playing area, if needed */
2352 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2353 for(i=0; i<BOARD_HEIGHT; i++)
2354 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2355 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2357 for(i=0; i<newHeight; i++) {
2358 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2359 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2361 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2362 for(i=0; i<BOARD_HEIGHT; i++)
2363 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2364 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2367 gameInfo.boardWidth = newWidth;
2368 gameInfo.boardHeight = newHeight;
2369 gameInfo.holdingsWidth = newHoldingsWidth;
2370 gameInfo.variant = newVariant;
2371 InitDrawingSizes(-2, 0);
2372 } else gameInfo.variant = newVariant;
2373 CopyBoard(oldBoard, board); // remember correctly formatted board
2374 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2375 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2378 static int loggedOn = FALSE;
2380 /*-- Game start info cache: --*/
2382 char gs_kind[MSG_SIZ];
2383 static char player1Name[128] = "";
2384 static char player2Name[128] = "";
2385 static char cont_seq[] = "\n\\ ";
2386 static int player1Rating = -1;
2387 static int player2Rating = -1;
2388 /*----------------------------*/
2390 ColorClass curColor = ColorNormal;
2391 int suppressKibitz = 0;
2394 Boolean soughtPending = FALSE;
2395 Boolean seekGraphUp;
2396 #define MAX_SEEK_ADS 200
2398 char *seekAdList[MAX_SEEK_ADS];
2399 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2400 float tcList[MAX_SEEK_ADS];
2401 char colorList[MAX_SEEK_ADS];
2402 int nrOfSeekAds = 0;
2403 int minRating = 1010, maxRating = 2800;
2404 int hMargin = 10, vMargin = 20, h, w;
2405 extern int squareSize, lineGap;
2410 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2411 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2412 if(r < minRating+100 && r >=0 ) r = minRating+100;
2413 if(r > maxRating) r = maxRating;
2414 if(tc < 1.) tc = 1.;
2415 if(tc > 95.) tc = 95.;
2416 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2417 y = ((double)r - minRating)/(maxRating - minRating)
2418 * (h-vMargin-squareSize/8-1) + vMargin;
2419 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2420 if(strstr(seekAdList[i], " u ")) color = 1;
2421 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2422 !strstr(seekAdList[i], "bullet") &&
2423 !strstr(seekAdList[i], "blitz") &&
2424 !strstr(seekAdList[i], "standard") ) color = 2;
2425 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2426 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2430 AddAd(char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2432 char buf[MSG_SIZ], *ext = "";
2433 VariantClass v = StringToVariant(type);
2434 if(strstr(type, "wild")) {
2435 ext = type + 4; // append wild number
2436 if(v == VariantFischeRandom) type = "chess960"; else
2437 if(v == VariantLoadable) type = "setup"; else
2438 type = VariantName(v);
2440 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2441 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2442 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2443 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2444 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2445 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2446 seekNrList[nrOfSeekAds] = nr;
2447 zList[nrOfSeekAds] = 0;
2448 seekAdList[nrOfSeekAds++] = StrSave(buf);
2449 if(plot) PlotSeekAd(nrOfSeekAds-1);
2456 int x = xList[i], y = yList[i], d=squareSize/4, k;
2457 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2458 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2459 // now replot every dot that overlapped
2460 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2461 int xx = xList[k], yy = yList[k];
2462 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2463 DrawSeekDot(xx, yy, colorList[k]);
2468 RemoveSeekAd(int nr)
2471 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2473 if(seekAdList[i]) free(seekAdList[i]);
2474 seekAdList[i] = seekAdList[--nrOfSeekAds];
2475 seekNrList[i] = seekNrList[nrOfSeekAds];
2476 ratingList[i] = ratingList[nrOfSeekAds];
2477 colorList[i] = colorList[nrOfSeekAds];
2478 tcList[i] = tcList[nrOfSeekAds];
2479 xList[i] = xList[nrOfSeekAds];
2480 yList[i] = yList[nrOfSeekAds];
2481 zList[i] = zList[nrOfSeekAds];
2482 seekAdList[nrOfSeekAds] = NULL;
2488 MatchSoughtLine(char *line)
2490 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2491 int nr, base, inc, u=0; char dummy;
2493 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2494 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2496 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2497 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2498 // match: compact and save the line
2499 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2509 if(!seekGraphUp) return FALSE;
2510 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2511 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2513 DrawSeekBackground(0, 0, w, h);
2514 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2515 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2516 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2517 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2519 DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2522 snprintf(buf, MSG_SIZ, "%d", i);
2523 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2526 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2527 for(i=1; i<100; i+=(i<10?1:5)) {
2528 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2529 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2530 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2532 snprintf(buf, MSG_SIZ, "%d", i);
2533 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2536 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2540 int SeekGraphClick(ClickType click, int x, int y, int moving)
2542 static int lastDown = 0, displayed = 0, lastSecond;
2543 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2544 if(click == Release || moving) return FALSE;
2546 soughtPending = TRUE;
2547 SendToICS(ics_prefix);
2548 SendToICS("sought\n"); // should this be "sought all"?
2549 } else { // issue challenge based on clicked ad
2550 int dist = 10000; int i, closest = 0, second = 0;
2551 for(i=0; i<nrOfSeekAds; i++) {
2552 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2553 if(d < dist) { dist = d; closest = i; }
2554 second += (d - zList[i] < 120); // count in-range ads
2555 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2559 second = (second > 1);
2560 if(displayed != closest || second != lastSecond) {
2561 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2562 lastSecond = second; displayed = closest;
2564 if(click == Press) {
2565 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2568 } // on press 'hit', only show info
2569 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2570 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2571 SendToICS(ics_prefix);
2573 return TRUE; // let incoming board of started game pop down the graph
2574 } else if(click == Release) { // release 'miss' is ignored
2575 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2576 if(moving == 2) { // right up-click
2577 nrOfSeekAds = 0; // refresh graph
2578 soughtPending = TRUE;
2579 SendToICS(ics_prefix);
2580 SendToICS("sought\n"); // should this be "sought all"?
2583 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2584 // press miss or release hit 'pop down' seek graph
2585 seekGraphUp = FALSE;
2586 DrawPosition(TRUE, NULL);
2592 read_from_ics(isr, closure, data, count, error)
2599 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2600 #define STARTED_NONE 0
2601 #define STARTED_MOVES 1
2602 #define STARTED_BOARD 2
2603 #define STARTED_OBSERVE 3
2604 #define STARTED_HOLDINGS 4
2605 #define STARTED_CHATTER 5
2606 #define STARTED_COMMENT 6
2607 #define STARTED_MOVES_NOHIDE 7
2609 static int started = STARTED_NONE;
2610 static char parse[20000];
2611 static int parse_pos = 0;
2612 static char buf[BUF_SIZE + 1];
2613 static int firstTime = TRUE, intfSet = FALSE;
2614 static ColorClass prevColor = ColorNormal;
2615 static int savingComment = FALSE;
2616 static int cmatch = 0; // continuation sequence match
2623 int backup; /* [DM] For zippy color lines */
2625 char talker[MSG_SIZ]; // [HGM] chat
2628 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2630 if (appData.debugMode) {
2632 fprintf(debugFP, "<ICS: ");
2633 show_bytes(debugFP, data, count);
2634 fprintf(debugFP, "\n");
2638 if (appData.debugMode) { int f = forwardMostMove;
2639 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2640 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2641 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2644 /* If last read ended with a partial line that we couldn't parse,
2645 prepend it to the new read and try again. */
2646 if (leftover_len > 0) {
2647 for (i=0; i<leftover_len; i++)
2648 buf[i] = buf[leftover_start + i];
2651 /* copy new characters into the buffer */
2652 bp = buf + leftover_len;
2653 buf_len=leftover_len;
2654 for (i=0; i<count; i++)
2657 if (data[i] == '\r')
2660 // join lines split by ICS?
2661 if (!appData.noJoin)
2664 Joining just consists of finding matches against the
2665 continuation sequence, and discarding that sequence
2666 if found instead of copying it. So, until a match
2667 fails, there's nothing to do since it might be the
2668 complete sequence, and thus, something we don't want
2671 if (data[i] == cont_seq[cmatch])
2674 if (cmatch == strlen(cont_seq))
2676 cmatch = 0; // complete match. just reset the counter
2679 it's possible for the ICS to not include the space
2680 at the end of the last word, making our [correct]
2681 join operation fuse two separate words. the server
2682 does this when the space occurs at the width setting.
2684 if (!buf_len || buf[buf_len-1] != ' ')
2695 match failed, so we have to copy what matched before
2696 falling through and copying this character. In reality,
2697 this will only ever be just the newline character, but
2698 it doesn't hurt to be precise.
2700 strncpy(bp, cont_seq, cmatch);
2712 buf[buf_len] = NULLCHAR;
2713 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2718 while (i < buf_len) {
2719 /* Deal with part of the TELNET option negotiation
2720 protocol. We refuse to do anything beyond the
2721 defaults, except that we allow the WILL ECHO option,
2722 which ICS uses to turn off password echoing when we are
2723 directly connected to it. We reject this option
2724 if localLineEditing mode is on (always on in xboard)
2725 and we are talking to port 23, which might be a real
2726 telnet server that will try to keep WILL ECHO on permanently.
2728 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2729 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2730 unsigned char option;
2732 switch ((unsigned char) buf[++i]) {
2734 if (appData.debugMode)
2735 fprintf(debugFP, "\n<WILL ");
2736 switch (option = (unsigned char) buf[++i]) {
2738 if (appData.debugMode)
2739 fprintf(debugFP, "ECHO ");
2740 /* Reply only if this is a change, according
2741 to the protocol rules. */
2742 if (remoteEchoOption) break;
2743 if (appData.localLineEditing &&
2744 atoi(appData.icsPort) == TN_PORT) {
2745 TelnetRequest(TN_DONT, TN_ECHO);
2748 TelnetRequest(TN_DO, TN_ECHO);
2749 remoteEchoOption = TRUE;
2753 if (appData.debugMode)
2754 fprintf(debugFP, "%d ", option);
2755 /* Whatever this is, we don't want it. */
2756 TelnetRequest(TN_DONT, option);
2761 if (appData.debugMode)
2762 fprintf(debugFP, "\n<WONT ");
2763 switch (option = (unsigned char) buf[++i]) {
2765 if (appData.debugMode)
2766 fprintf(debugFP, "ECHO ");
2767 /* Reply only if this is a change, according
2768 to the protocol rules. */
2769 if (!remoteEchoOption) break;
2771 TelnetRequest(TN_DONT, TN_ECHO);
2772 remoteEchoOption = FALSE;
2775 if (appData.debugMode)
2776 fprintf(debugFP, "%d ", (unsigned char) option);
2777 /* Whatever this is, it must already be turned
2778 off, because we never agree to turn on
2779 anything non-default, so according to the
2780 protocol rules, we don't reply. */
2785 if (appData.debugMode)
2786 fprintf(debugFP, "\n<DO ");
2787 switch (option = (unsigned char) buf[++i]) {
2789 /* Whatever this is, we refuse to do it. */
2790 if (appData.debugMode)
2791 fprintf(debugFP, "%d ", option);
2792 TelnetRequest(TN_WONT, option);
2797 if (appData.debugMode)
2798 fprintf(debugFP, "\n<DONT ");
2799 switch (option = (unsigned char) buf[++i]) {
2801 if (appData.debugMode)
2802 fprintf(debugFP, "%d ", option);
2803 /* Whatever this is, we are already not doing
2804 it, because we never agree to do anything
2805 non-default, so according to the protocol
2806 rules, we don't reply. */
2811 if (appData.debugMode)
2812 fprintf(debugFP, "\n<IAC ");
2813 /* Doubled IAC; pass it through */
2817 if (appData.debugMode)
2818 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2819 /* Drop all other telnet commands on the floor */
2822 if (oldi > next_out)
2823 SendToPlayer(&buf[next_out], oldi - next_out);
2829 /* OK, this at least will *usually* work */
2830 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2834 if (loggedOn && !intfSet) {
2835 if (ics_type == ICS_ICC) {
2836 snprintf(str, MSG_SIZ,
2837 "/set-quietly interface %s\n/set-quietly style 12\n",
2839 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2840 strcat(str, "/set-2 51 1\n/set seek 1\n");
2841 } else if (ics_type == ICS_CHESSNET) {
2842 snprintf(str, MSG_SIZ, "/style 12\n");
2844 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2845 strcat(str, programVersion);
2846 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2847 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2848 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2850 strcat(str, "$iset nohighlight 1\n");
2852 strcat(str, "$iset lock 1\n$style 12\n");
2855 NotifyFrontendLogin();
2859 if (started == STARTED_COMMENT) {
2860 /* Accumulate characters in comment */
2861 parse[parse_pos++] = buf[i];
2862 if (buf[i] == '\n') {
2863 parse[parse_pos] = NULLCHAR;
2864 if(chattingPartner>=0) {
2866 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2867 OutputChatMessage(chattingPartner, mess);
2868 chattingPartner = -1;
2869 next_out = i+1; // [HGM] suppress printing in ICS window
2871 if(!suppressKibitz) // [HGM] kibitz
2872 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2873 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2874 int nrDigit = 0, nrAlph = 0, j;
2875 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2876 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2877 parse[parse_pos] = NULLCHAR;
2878 // try to be smart: if it does not look like search info, it should go to
2879 // ICS interaction window after all, not to engine-output window.
2880 for(j=0; j<parse_pos; j++) { // count letters and digits
2881 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2882 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2883 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2885 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2886 int depth=0; float score;
2887 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2888 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2889 pvInfoList[forwardMostMove-1].depth = depth;
2890 pvInfoList[forwardMostMove-1].score = 100*score;
2892 OutputKibitz(suppressKibitz, parse);
2895 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2896 SendToPlayer(tmp, strlen(tmp));
2898 next_out = i+1; // [HGM] suppress printing in ICS window
2900 started = STARTED_NONE;
2902 /* Don't match patterns against characters in comment */
2907 if (started == STARTED_CHATTER) {
2908 if (buf[i] != '\n') {
2909 /* Don't match patterns against characters in chatter */
2913 started = STARTED_NONE;
2914 if(suppressKibitz) next_out = i+1;
2917 /* Kludge to deal with rcmd protocol */
2918 if (firstTime && looking_at(buf, &i, "\001*")) {
2919 DisplayFatalError(&buf[1], 0, 1);
2925 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2928 if (appData.debugMode)
2929 fprintf(debugFP, "ics_type %d\n", ics_type);
2932 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2933 ics_type = ICS_FICS;
2935 if (appData.debugMode)
2936 fprintf(debugFP, "ics_type %d\n", ics_type);
2939 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2940 ics_type = ICS_CHESSNET;
2942 if (appData.debugMode)
2943 fprintf(debugFP, "ics_type %d\n", ics_type);
2948 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2949 looking_at(buf, &i, "Logging you in as \"*\"") ||
2950 looking_at(buf, &i, "will be \"*\""))) {
2951 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2955 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2957 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2958 DisplayIcsInteractionTitle(buf);
2959 have_set_title = TRUE;
2962 /* skip finger notes */
2963 if (started == STARTED_NONE &&
2964 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2965 (buf[i] == '1' && buf[i+1] == '0')) &&
2966 buf[i+2] == ':' && buf[i+3] == ' ') {
2967 started = STARTED_CHATTER;
2973 // [HGM] seekgraph: recognize sought lines and end-of-sought message
2974 if(appData.seekGraph) {
2975 if(soughtPending && MatchSoughtLine(buf+i)) {
2976 i = strstr(buf+i, "rated") - buf;
2977 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2978 next_out = leftover_start = i;
2979 started = STARTED_CHATTER;
2980 suppressKibitz = TRUE;
2983 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2984 && looking_at(buf, &i, "* ads displayed")) {
2985 soughtPending = FALSE;
2990 if(appData.autoRefresh) {
2991 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2992 int s = (ics_type == ICS_ICC); // ICC format differs
2994 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
2995 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
2996 looking_at(buf, &i, "*% "); // eat prompt
2997 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
2998 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2999 next_out = i; // suppress
3002 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3003 char *p = star_match[0];
3005 if(seekGraphUp) RemoveSeekAd(atoi(p));
3006 while(*p && *p++ != ' '); // next
3008 looking_at(buf, &i, "*% "); // eat prompt
3009 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3016 /* skip formula vars */
3017 if (started == STARTED_NONE &&
3018 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3019 started = STARTED_CHATTER;
3024 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3025 if (appData.autoKibitz && started == STARTED_NONE &&
3026 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3027 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3028 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3029 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3030 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3031 suppressKibitz = TRUE;
3032 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3034 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3035 && (gameMode == IcsPlayingWhite)) ||
3036 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3037 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3038 started = STARTED_CHATTER; // own kibitz we simply discard
3040 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3041 parse_pos = 0; parse[0] = NULLCHAR;
3042 savingComment = TRUE;
3043 suppressKibitz = gameMode != IcsObserving ? 2 :
3044 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3048 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3049 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3050 && atoi(star_match[0])) {
3051 // suppress the acknowledgements of our own autoKibitz
3053 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3054 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3055 SendToPlayer(star_match[0], strlen(star_match[0]));
3056 if(looking_at(buf, &i, "*% ")) // eat prompt
3057 suppressKibitz = FALSE;
3061 } // [HGM] kibitz: end of patch
3063 // [HGM] chat: intercept tells by users for which we have an open chat window
3065 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3066 looking_at(buf, &i, "* whispers:") ||
3067 looking_at(buf, &i, "* kibitzes:") ||
3068 looking_at(buf, &i, "* shouts:") ||
3069 looking_at(buf, &i, "* c-shouts:") ||
3070 looking_at(buf, &i, "--> * ") ||
3071 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3072 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3073 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3074 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3076 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3077 chattingPartner = -1;
3079 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3080 for(p=0; p<MAX_CHAT; p++) {
3081 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3082 talker[0] = '['; strcat(talker, "] ");
3083 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3084 chattingPartner = p; break;
3087 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3088 for(p=0; p<MAX_CHAT; p++) {
3089 if(!strcmp("kibitzes", chatPartner[p])) {
3090 talker[0] = '['; strcat(talker, "] ");
3091 chattingPartner = p; break;
3094 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3095 for(p=0; p<MAX_CHAT; p++) {
3096 if(!strcmp("whispers", chatPartner[p])) {
3097 talker[0] = '['; strcat(talker, "] ");
3098 chattingPartner = p; break;
3101 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3102 if(buf[i-8] == '-' && buf[i-3] == 't')
3103 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3104 if(!strcmp("c-shouts", chatPartner[p])) {
3105 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3106 chattingPartner = p; break;
3109 if(chattingPartner < 0)
3110 for(p=0; p<MAX_CHAT; p++) {
3111 if(!strcmp("shouts", chatPartner[p])) {
3112 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3113 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3114 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3115 chattingPartner = p; break;
3119 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3120 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3121 talker[0] = 0; Colorize(ColorTell, FALSE);
3122 chattingPartner = p; break;
3124 if(chattingPartner<0) i = oldi; else {
3125 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3126 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3127 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3128 started = STARTED_COMMENT;
3129 parse_pos = 0; parse[0] = NULLCHAR;
3130 savingComment = 3 + chattingPartner; // counts as TRUE
3131 suppressKibitz = TRUE;
3134 } // [HGM] chat: end of patch
3137 if (appData.zippyTalk || appData.zippyPlay) {
3138 /* [DM] Backup address for color zippy lines */
3140 if (loggedOn == TRUE)
3141 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3142 (appData.zippyPlay && ZippyMatch(buf, &backup)));
3144 } // [DM] 'else { ' deleted
3146 /* Regular tells and says */
3147 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3148 looking_at(buf, &i, "* (your partner) tells you: ") ||
3149 looking_at(buf, &i, "* says: ") ||
3150 /* Don't color "message" or "messages" output */
3151 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3152 looking_at(buf, &i, "*. * at *:*: ") ||
3153 looking_at(buf, &i, "--* (*:*): ") ||
3154 /* Message notifications (same color as tells) */
3155 looking_at(buf, &i, "* has left a message ") ||
3156 looking_at(buf, &i, "* just sent you a message:\n") ||
3157 /* Whispers and kibitzes */
3158 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3159 looking_at(buf, &i, "* kibitzes: ") ||
3161 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3163 if (tkind == 1 && strchr(star_match[0], ':')) {
3164 /* Avoid "tells you:" spoofs in channels */
3167 if (star_match[0][0] == NULLCHAR ||
3168 strchr(star_match[0], ' ') ||
3169 (tkind == 3 && strchr(star_match[1], ' '))) {
3170 /* Reject bogus matches */
3173 if (appData.colorize) {
3174 if (oldi > next_out) {
3175 SendToPlayer(&buf[next_out], oldi - next_out);
3180 Colorize(ColorTell, FALSE);
3181 curColor = ColorTell;
3184 Colorize(ColorKibitz, FALSE);
3185 curColor = ColorKibitz;
3188 p = strrchr(star_match[1], '(');
3195 Colorize(ColorChannel1, FALSE);
3196 curColor = ColorChannel1;
3198 Colorize(ColorChannel, FALSE);
3199 curColor = ColorChannel;
3203 curColor = ColorNormal;
3207 if (started == STARTED_NONE && appData.autoComment &&
3208 (gameMode == IcsObserving ||
3209 gameMode == IcsPlayingWhite ||
3210 gameMode == IcsPlayingBlack)) {
3211 parse_pos = i - oldi;
3212 memcpy(parse, &buf[oldi], parse_pos);
3213 parse[parse_pos] = NULLCHAR;
3214 started = STARTED_COMMENT;
3215 savingComment = TRUE;
3217 started = STARTED_CHATTER;
3218 savingComment = FALSE;
3225 if (looking_at(buf, &i, "* s-shouts: ") ||
3226 looking_at(buf, &i, "* c-shouts: ")) {
3227 if (appData.colorize) {
3228 if (oldi > next_out) {
3229 SendToPlayer(&buf[next_out], oldi - next_out);
3232 Colorize(ColorSShout, FALSE);
3233 curColor = ColorSShout;
3236 started = STARTED_CHATTER;
3240 if (looking_at(buf, &i, "--->")) {
3245 if (looking_at(buf, &i, "* shouts: ") ||
3246 looking_at(buf, &i, "--> ")) {
3247 if (appData.colorize) {
3248 if (oldi > next_out) {
3249 SendToPlayer(&buf[next_out], oldi - next_out);
3252 Colorize(ColorShout, FALSE);
3253 curColor = ColorShout;
3256 started = STARTED_CHATTER;
3260 if (looking_at( buf, &i, "Challenge:")) {
3261 if (appData.colorize) {
3262 if (oldi > next_out) {
3263 SendToPlayer(&buf[next_out], oldi - next_out);
3266 Colorize(ColorChallenge, FALSE);
3267 curColor = ColorChallenge;
3273 if (looking_at(buf, &i, "* offers you") ||
3274 looking_at(buf, &i, "* offers to be") ||
3275 looking_at(buf, &i, "* would like to") ||
3276 looking_at(buf, &i, "* requests to") ||
3277 looking_at(buf, &i, "Your opponent offers") ||
3278 looking_at(buf, &i, "Your opponent requests")) {
3280 if (appData.colorize) {
3281 if (oldi > next_out) {
3282 SendToPlayer(&buf[next_out], oldi - next_out);
3285 Colorize(ColorRequest, FALSE);
3286 curColor = ColorRequest;
3291 if (looking_at(buf, &i, "* (*) seeking")) {
3292 if (appData.colorize) {
3293 if (oldi > next_out) {
3294 SendToPlayer(&buf[next_out], oldi - next_out);
3297 Colorize(ColorSeek, FALSE);
3298 curColor = ColorSeek;
3303 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3305 if (looking_at(buf, &i, "\\ ")) {
3306 if (prevColor != ColorNormal) {
3307 if (oldi > next_out) {
3308 SendToPlayer(&buf[next_out], oldi - next_out);
3311 Colorize(prevColor, TRUE);
3312 curColor = prevColor;
3314 if (savingComment) {
3315 parse_pos = i - oldi;
3316 memcpy(parse, &buf[oldi], parse_pos);
3317 parse[parse_pos] = NULLCHAR;
3318 started = STARTED_COMMENT;
3319 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3320 chattingPartner = savingComment - 3; // kludge to remember the box
3322 started = STARTED_CHATTER;
3327 if (looking_at(buf, &i, "Black Strength :") ||
3328 looking_at(buf, &i, "<<< style 10 board >>>") ||
3329 looking_at(buf, &i, "<10>") ||
3330 looking_at(buf, &i, "#@#")) {
3331 /* Wrong board style */
3333 SendToICS(ics_prefix);
3334 SendToICS("set style 12\n");
3335 SendToICS(ics_prefix);
3336 SendToICS("refresh\n");
3340 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3342 have_sent_ICS_logon = 1;
3346 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3347 (looking_at(buf, &i, "\n<12> ") ||
3348 looking_at(buf, &i, "<12> "))) {
3350 if (oldi > next_out) {
3351 SendToPlayer(&buf[next_out], oldi - next_out);
3354 started = STARTED_BOARD;
3359 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3360 looking_at(buf, &i, "<b1> ")) {
3361 if (oldi > next_out) {
3362 SendToPlayer(&buf[next_out], oldi - next_out);
3365 started = STARTED_HOLDINGS;
3370 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3372 /* Header for a move list -- first line */
3374 switch (ics_getting_history) {
3378 case BeginningOfGame:
3379 /* User typed "moves" or "oldmoves" while we
3380 were idle. Pretend we asked for these
3381 moves and soak them up so user can step
3382 through them and/or save them.
3385 gameMode = IcsObserving;
3388 ics_getting_history = H_GOT_UNREQ_HEADER;
3390 case EditGame: /*?*/
3391 case EditPosition: /*?*/
3392 /* Should above feature work in these modes too? */
3393 /* For now it doesn't */
3394 ics_getting_history = H_GOT_UNWANTED_HEADER;
3397 ics_getting_history = H_GOT_UNWANTED_HEADER;
3402 /* Is this the right one? */
3403 if (gameInfo.white && gameInfo.black &&
3404 strcmp(gameInfo.white, star_match[0]) == 0 &&
3405 strcmp(gameInfo.black, star_match[2]) == 0) {
3407 ics_getting_history = H_GOT_REQ_HEADER;
3410 case H_GOT_REQ_HEADER:
3411 case H_GOT_UNREQ_HEADER:
3412 case H_GOT_UNWANTED_HEADER:
3413 case H_GETTING_MOVES:
3414 /* Should not happen */
3415 DisplayError(_("Error gathering move list: two headers"), 0);
3416 ics_getting_history = H_FALSE;
3420 /* Save player ratings into gameInfo if needed */
3421 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3422 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3423 (gameInfo.whiteRating == -1 ||
3424 gameInfo.blackRating == -1)) {
3426 gameInfo.whiteRating = string_to_rating(star_match[1]);
3427 gameInfo.blackRating = string_to_rating(star_match[3]);
3428 if (appData.debugMode)
3429 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3430 gameInfo.whiteRating, gameInfo.blackRating);
3435 if (looking_at(buf, &i,
3436 "* * match, initial time: * minute*, increment: * second")) {
3437 /* Header for a move list -- second line */
3438 /* Initial board will follow if this is a wild game */
3439 if (gameInfo.event != NULL) free(gameInfo.event);
3440 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3441 gameInfo.event = StrSave(str);
3442 /* [HGM] we switched variant. Translate boards if needed. */
3443 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3447 if (looking_at(buf, &i, "Move ")) {
3448 /* Beginning of a move list */
3449 switch (ics_getting_history) {
3451 /* Normally should not happen */
3452 /* Maybe user hit reset while we were parsing */
3455 /* Happens if we are ignoring a move list that is not
3456 * the one we just requested. Common if the user
3457 * tries to observe two games without turning off
3460 case H_GETTING_MOVES:
3461 /* Should not happen */
3462 DisplayError(_("Error gathering move list: nested"), 0);
3463 ics_getting_history = H_FALSE;
3465 case H_GOT_REQ_HEADER:
3466 ics_getting_history = H_GETTING_MOVES;
3467 started = STARTED_MOVES;
3469 if (oldi > next_out) {
3470 SendToPlayer(&buf[next_out], oldi - next_out);
3473 case H_GOT_UNREQ_HEADER:
3474 ics_getting_history = H_GETTING_MOVES;
3475 started = STARTED_MOVES_NOHIDE;
3478 case H_GOT_UNWANTED_HEADER:
3479 ics_getting_history = H_FALSE;
3485 if (looking_at(buf, &i, "% ") ||
3486 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3487 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3488 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3489 soughtPending = FALSE;
3493 if(suppressKibitz) next_out = i;
3494 savingComment = FALSE;
3498 case STARTED_MOVES_NOHIDE:
3499 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3500 parse[parse_pos + i - oldi] = NULLCHAR;
3501 ParseGameHistory(parse);
3503 if (appData.zippyPlay && first.initDone) {
3504 FeedMovesToProgram(&first, forwardMostMove);
3505 if (gameMode == IcsPlayingWhite) {
3506 if (WhiteOnMove(forwardMostMove)) {
3507 if (first.sendTime) {
3508 if (first.useColors) {
3509 SendToProgram("black\n", &first);
3511 SendTimeRemaining(&first, TRUE);
3513 if (first.useColors) {
3514 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3516 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3517 first.maybeThinking = TRUE;
3519 if (first.usePlayother) {
3520 if (first.sendTime) {
3521 SendTimeRemaining(&first, TRUE);
3523 SendToProgram("playother\n", &first);
3529 } else if (gameMode == IcsPlayingBlack) {
3530 if (!WhiteOnMove(forwardMostMove)) {
3531 if (first.sendTime) {
3532 if (first.useColors) {
3533 SendToProgram("white\n", &first);
3535 SendTimeRemaining(&first, FALSE);
3537 if (first.useColors) {
3538 SendToProgram("black\n", &first);
3540 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3541 first.maybeThinking = TRUE;
3543 if (first.usePlayother) {
3544 if (first.sendTime) {
3545 SendTimeRemaining(&first, FALSE);
3547 SendToProgram("playother\n", &first);
3556 if (gameMode == IcsObserving && ics_gamenum == -1) {
3557 /* Moves came from oldmoves or moves command
3558 while we weren't doing anything else.
3560 currentMove = forwardMostMove;
3561 ClearHighlights();/*!!could figure this out*/
3562 flipView = appData.flipView;
3563 DrawPosition(TRUE, boards[currentMove]);
3564 DisplayBothClocks();
3565 snprintf(str, MSG_SIZ, "%s vs. %s",
3566 gameInfo.white, gameInfo.black);
3570 /* Moves were history of an active game */
3571 if (gameInfo.resultDetails != NULL) {
3572 free(gameInfo.resultDetails);
3573 gameInfo.resultDetails = NULL;
3576 HistorySet(parseList, backwardMostMove,
3577 forwardMostMove, currentMove-1);
3578 DisplayMove(currentMove - 1);
3579 if (started == STARTED_MOVES) next_out = i;
3580 started = STARTED_NONE;
3581 ics_getting_history = H_FALSE;
3584 case STARTED_OBSERVE:
3585 started = STARTED_NONE;
3586 SendToICS(ics_prefix);
3587 SendToICS("refresh\n");
3593 if(bookHit) { // [HGM] book: simulate book reply
3594 static char bookMove[MSG_SIZ]; // a bit generous?
3596 programStats.nodes = programStats.depth = programStats.time =
3597 programStats.score = programStats.got_only_move = 0;
3598 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3600 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3601 strcat(bookMove, bookHit);
3602 HandleMachineMove(bookMove, &first);
3607 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3608 started == STARTED_HOLDINGS ||
3609 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3610 /* Accumulate characters in move list or board */
3611 parse[parse_pos++] = buf[i];
3614 /* Start of game messages. Mostly we detect start of game
3615 when the first board image arrives. On some versions
3616 of the ICS, though, we need to do a "refresh" after starting
3617 to observe in order to get the current board right away. */
3618 if (looking_at(buf, &i, "Adding game * to observation list")) {
3619 started = STARTED_OBSERVE;
3623 /* Handle auto-observe */
3624 if (appData.autoObserve &&
3625 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3626 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3628 /* Choose the player that was highlighted, if any. */
3629 if (star_match[0][0] == '\033' ||
3630 star_match[1][0] != '\033') {
3631 player = star_match[0];
3633 player = star_match[2];
3635 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3636 ics_prefix, StripHighlightAndTitle(player));
3639 /* Save ratings from notify string */
3640 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3641 player1Rating = string_to_rating(star_match[1]);
3642 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3643 player2Rating = string_to_rating(star_match[3]);
3645 if (appData.debugMode)
3647 "Ratings from 'Game notification:' %s %d, %s %d\n",
3648 player1Name, player1Rating,
3649 player2Name, player2Rating);
3654 /* Deal with automatic examine mode after a game,
3655 and with IcsObserving -> IcsExamining transition */
3656 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3657 looking_at(buf, &i, "has made you an examiner of game *")) {
3659 int gamenum = atoi(star_match[0]);
3660 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3661 gamenum == ics_gamenum) {
3662 /* We were already playing or observing this game;
3663 no need to refetch history */
3664 gameMode = IcsExamining;
3666 pauseExamForwardMostMove = forwardMostMove;
3667 } else if (currentMove < forwardMostMove) {
3668 ForwardInner(forwardMostMove);
3671 /* I don't think this case really can happen */
3672 SendToICS(ics_prefix);
3673 SendToICS("refresh\n");
3678 /* Error messages */
3679 // if (ics_user_moved) {
3680 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3681 if (looking_at(buf, &i, "Illegal move") ||
3682 looking_at(buf, &i, "Not a legal move") ||
3683 looking_at(buf, &i, "Your king is in check") ||
3684 looking_at(buf, &i, "It isn't your turn") ||
3685 looking_at(buf, &i, "It is not your move")) {
3687 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3688 currentMove = forwardMostMove-1;
3689 DisplayMove(currentMove - 1); /* before DMError */
3690 DrawPosition(FALSE, boards[currentMove]);
3691 SwitchClocks(forwardMostMove-1); // [HGM] race
3692 DisplayBothClocks();
3694 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3700 if (looking_at(buf, &i, "still have time") ||
3701 looking_at(buf, &i, "not out of time") ||
3702 looking_at(buf, &i, "either player is out of time") ||
3703 looking_at(buf, &i, "has timeseal; checking")) {
3704 /* We must have called his flag a little too soon */
3705 whiteFlag = blackFlag = FALSE;
3709 if (looking_at(buf, &i, "added * seconds to") ||
3710 looking_at(buf, &i, "seconds were added to")) {
3711 /* Update the clocks */
3712 SendToICS(ics_prefix);
3713 SendToICS("refresh\n");
3717 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3718 ics_clock_paused = TRUE;
3723 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3724 ics_clock_paused = FALSE;
3729 /* Grab player ratings from the Creating: message.
3730 Note we have to check for the special case when
3731 the ICS inserts things like [white] or [black]. */
3732 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3733 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3735 0 player 1 name (not necessarily white)
3737 2 empty, white, or black (IGNORED)
3738 3 player 2 name (not necessarily black)
3741 The names/ratings are sorted out when the game
3742 actually starts (below).
3744 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3745 player1Rating = string_to_rating(star_match[1]);
3746 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3747 player2Rating = string_to_rating(star_match[4]);
3749 if (appData.debugMode)
3751 "Ratings from 'Creating:' %s %d, %s %d\n",
3752 player1Name, player1Rating,
3753 player2Name, player2Rating);
3758 /* Improved generic start/end-of-game messages */
3759 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3760 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3761 /* If tkind == 0: */
3762 /* star_match[0] is the game number */
3763 /* [1] is the white player's name */
3764 /* [2] is the black player's name */
3765 /* For end-of-game: */
3766 /* [3] is the reason for the game end */
3767 /* [4] is a PGN end game-token, preceded by " " */
3768 /* For start-of-game: */
3769 /* [3] begins with "Creating" or "Continuing" */
3770 /* [4] is " *" or empty (don't care). */
3771 int gamenum = atoi(star_match[0]);
3772 char *whitename, *blackname, *why, *endtoken;
3773 ChessMove endtype = EndOfFile;
3776 whitename = star_match[1];
3777 blackname = star_match[2];
3778 why = star_match[3];
3779 endtoken = star_match[4];
3781 whitename = star_match[1];
3782 blackname = star_match[3];
3783 why = star_match[5];
3784 endtoken = star_match[6];
3787 /* Game start messages */
3788 if (strncmp(why, "Creating ", 9) == 0 ||
3789 strncmp(why, "Continuing ", 11) == 0) {
3790 gs_gamenum = gamenum;
3791 safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3792 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3794 if (appData.zippyPlay) {
3795 ZippyGameStart(whitename, blackname);
3798 partnerBoardValid = FALSE; // [HGM] bughouse
3802 /* Game end messages */
3803 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3804 ics_gamenum != gamenum) {
3807 while (endtoken[0] == ' ') endtoken++;
3808 switch (endtoken[0]) {
3811 endtype = GameUnfinished;
3814 endtype = BlackWins;
3817 if (endtoken[1] == '/')
3818 endtype = GameIsDrawn;
3820 endtype = WhiteWins;
3823 GameEnds(endtype, why, GE_ICS);
3825 if (appData.zippyPlay && first.initDone) {
3826 ZippyGameEnd(endtype, why);
3827 if (first.pr == NULL) {
3828 /* Start the next process early so that we'll
3829 be ready for the next challenge */
3830 StartChessProgram(&first);
3832 /* Send "new" early, in case this command takes
3833 a long time to finish, so that we'll be ready
3834 for the next challenge. */
3835 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3839 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3843 if (looking_at(buf, &i, "Removing game * from observation") ||
3844 looking_at(buf, &i, "no longer observing game *") ||
3845 looking_at(buf, &i, "Game * (*) has no examiners")) {
3846 if (gameMode == IcsObserving &&
3847 atoi(star_match[0]) == ics_gamenum)
3849 /* icsEngineAnalyze */
3850 if (appData.icsEngineAnalyze) {
3857 ics_user_moved = FALSE;
3862 if (looking_at(buf, &i, "no longer examining game *")) {
3863 if (gameMode == IcsExamining &&
3864 atoi(star_match[0]) == ics_gamenum)
3868 ics_user_moved = FALSE;
3873 /* Advance leftover_start past any newlines we find,
3874 so only partial lines can get reparsed */
3875 if (looking_at(buf, &i, "\n")) {
3876 prevColor = curColor;
3877 if (curColor != ColorNormal) {