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 */
254 extern int tinyLayout, smallLayout;
255 ChessProgramStats programStats;
256 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
258 static int exiting = 0; /* [HGM] moved to top */
259 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
260 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
261 Board partnerBoard; /* [HGM] bughouse: for peeking at partner game */
262 int partnerHighlight[2];
263 Boolean partnerBoardValid = 0;
264 char partnerStatus[MSG_SIZ];
266 Boolean originalFlip;
267 Boolean twoBoards = 0;
268 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
269 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
270 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
271 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
272 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
273 int opponentKibitzes;
274 int lastSavedGame; /* [HGM] save: ID of game */
275 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
276 extern int chatCount;
278 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
279 char lastMsg[MSG_SIZ];
280 ChessSquare pieceSweep = EmptySquare;
281 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
282 int promoDefaultAltered;
284 /* States for ics_getting_history */
286 #define H_REQUESTED 1
287 #define H_GOT_REQ_HEADER 2
288 #define H_GOT_UNREQ_HEADER 3
289 #define H_GETTING_MOVES 4
290 #define H_GOT_UNWANTED_HEADER 5
292 /* whosays values for GameEnds */
301 /* Maximum number of games in a cmail message */
302 #define CMAIL_MAX_GAMES 20
304 /* Different types of move when calling RegisterMove */
306 #define CMAIL_RESIGN 1
308 #define CMAIL_ACCEPT 3
310 /* Different types of result to remember for each game */
311 #define CMAIL_NOT_RESULT 0
312 #define CMAIL_OLD_RESULT 1
313 #define CMAIL_NEW_RESULT 2
315 /* Telnet protocol constants */
326 safeStrCpy( char *dst, const char *src, size_t count )
329 assert( dst != NULL );
330 assert( src != NULL );
333 for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
334 if( i == count && dst[count-1] != NULLCHAR)
336 dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
337 if(appData.debugMode)
338 fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
344 /* Some compiler can't cast u64 to double
345 * This function do the job for us:
347 * We use the highest bit for cast, this only
348 * works if the highest bit is not
349 * in use (This should not happen)
351 * We used this for all compiler
354 u64ToDouble(u64 value)
357 u64 tmp = value & u64Const(0x7fffffffffffffff);
358 r = (double)(s64)tmp;
359 if (value & u64Const(0x8000000000000000))
360 r += 9.2233720368547758080e18; /* 2^63 */
364 /* Fake up flags for now, as we aren't keeping track of castling
365 availability yet. [HGM] Change of logic: the flag now only
366 indicates the type of castlings allowed by the rule of the game.
367 The actual rights themselves are maintained in the array
368 castlingRights, as part of the game history, and are not probed
374 int flags = F_ALL_CASTLE_OK;
375 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
376 switch (gameInfo.variant) {
378 flags &= ~F_ALL_CASTLE_OK;
379 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
380 flags |= F_IGNORE_CHECK;
382 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
385 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
387 case VariantKriegspiel:
388 flags |= F_KRIEGSPIEL_CAPTURE;
390 case VariantCapaRandom:
391 case VariantFischeRandom:
392 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
393 case VariantNoCastle:
394 case VariantShatranj:
397 flags &= ~F_ALL_CASTLE_OK;
405 FILE *gameFileFP, *debugFP;
408 [AS] Note: sometimes, the sscanf() function is used to parse the input
409 into a fixed-size buffer. Because of this, we must be prepared to
410 receive strings as long as the size of the input buffer, which is currently
411 set to 4K for Windows and 8K for the rest.
412 So, we must either allocate sufficiently large buffers here, or
413 reduce the size of the input buffer in the input reading part.
416 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
417 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
418 char thinkOutput1[MSG_SIZ*10];
420 ChessProgramState first, second;
422 /* premove variables */
425 int premoveFromX = 0;
426 int premoveFromY = 0;
427 int premovePromoChar = 0;
429 Boolean alarmSounded;
430 /* end premove variables */
432 char *ics_prefix = "$";
433 int ics_type = ICS_GENERIC;
435 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
436 int pauseExamForwardMostMove = 0;
437 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
438 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
439 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
440 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
441 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
442 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
443 int whiteFlag = FALSE, blackFlag = FALSE;
444 int userOfferedDraw = FALSE;
445 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
446 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
447 int cmailMoveType[CMAIL_MAX_GAMES];
448 long ics_clock_paused = 0;
449 ProcRef icsPR = NoProc, cmailPR = NoProc;
450 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
451 GameMode gameMode = BeginningOfGame;
452 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
453 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
454 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
455 int hiddenThinkOutputState = 0; /* [AS] */
456 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
457 int adjudicateLossPlies = 6;
458 char white_holding[64], black_holding[64];
459 TimeMark lastNodeCountTime;
460 long lastNodeCount=0;
461 int shiftKey; // [HGM] set by mouse handler
463 int have_sent_ICS_logon = 0;
465 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
466 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
467 long timeControl_2; /* [AS] Allow separate time controls */
468 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
469 long timeRemaining[2][MAX_MOVES];
470 int matchGame = 0, nextGame = 0, roundNr = 0;
471 Boolean waitingForGame = FALSE;
472 TimeMark programStartTime, pauseStart;
473 char ics_handle[MSG_SIZ];
474 int have_set_title = 0;
476 /* animateTraining preserves the state of appData.animate
477 * when Training mode is activated. This allows the
478 * response to be animated when appData.animate == TRUE and
479 * appData.animateDragging == TRUE.
481 Boolean animateTraining;
487 Board boards[MAX_MOVES];
488 /* [HGM] Following 7 needed for accurate legality tests: */
489 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
490 signed char initialRights[BOARD_FILES];
491 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
492 int initialRulePlies, FENrulePlies;
493 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
496 int mute; // mute all sounds
498 // [HGM] vari: next 12 to save and restore variations
499 #define MAX_VARIATIONS 10
500 int framePtr = MAX_MOVES-1; // points to free stack entry
502 int savedFirst[MAX_VARIATIONS];
503 int savedLast[MAX_VARIATIONS];
504 int savedFramePtr[MAX_VARIATIONS];
505 char *savedDetails[MAX_VARIATIONS];
506 ChessMove savedResult[MAX_VARIATIONS];
508 void PushTail P((int firstMove, int lastMove));
509 Boolean PopTail P((Boolean annotate));
510 void PushInner P((int firstMove, int lastMove));
511 void PopInner P((Boolean annotate));
512 void CleanupTail P((void));
514 ChessSquare FIDEArray[2][BOARD_FILES] = {
515 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
516 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
517 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
518 BlackKing, BlackBishop, BlackKnight, BlackRook }
521 ChessSquare twoKingsArray[2][BOARD_FILES] = {
522 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
523 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
524 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
525 BlackKing, BlackKing, BlackKnight, BlackRook }
528 ChessSquare KnightmateArray[2][BOARD_FILES] = {
529 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
530 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
531 { BlackRook, BlackMan, BlackBishop, BlackQueen,
532 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
535 ChessSquare SpartanArray[2][BOARD_FILES] = {
536 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
537 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
538 { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
539 BlackDragon, BlackKing, BlackAngel, BlackAlfil }
542 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
543 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
544 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
545 { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
546 BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
549 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
550 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
551 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
552 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
553 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
556 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
557 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
558 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
559 { BlackRook, BlackKnight, BlackMan, BlackFerz,
560 BlackKing, BlackMan, BlackKnight, BlackRook }
564 #if (BOARD_FILES>=10)
565 ChessSquare ShogiArray[2][BOARD_FILES] = {
566 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
567 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
568 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
569 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
572 ChessSquare XiangqiArray[2][BOARD_FILES] = {
573 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
574 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
575 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
576 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
579 ChessSquare CapablancaArray[2][BOARD_FILES] = {
580 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
581 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
582 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
583 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
586 ChessSquare GreatArray[2][BOARD_FILES] = {
587 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
588 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
589 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
590 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
593 ChessSquare JanusArray[2][BOARD_FILES] = {
594 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
595 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
596 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
597 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
601 ChessSquare GothicArray[2][BOARD_FILES] = {
602 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
603 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
604 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
605 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
608 #define GothicArray CapablancaArray
612 ChessSquare FalconArray[2][BOARD_FILES] = {
613 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
614 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
615 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
616 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
619 #define FalconArray CapablancaArray
622 #else // !(BOARD_FILES>=10)
623 #define XiangqiPosition FIDEArray
624 #define CapablancaArray FIDEArray
625 #define GothicArray FIDEArray
626 #define GreatArray FIDEArray
627 #endif // !(BOARD_FILES>=10)
629 #if (BOARD_FILES>=12)
630 ChessSquare CourierArray[2][BOARD_FILES] = {
631 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
632 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
633 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
634 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
636 #else // !(BOARD_FILES>=12)
637 #define CourierArray CapablancaArray
638 #endif // !(BOARD_FILES>=12)
641 Board initialPosition;
644 /* Convert str to a rating. Checks for special cases of "----",
646 "++++", etc. Also strips ()'s */
648 string_to_rating(str)
651 while(*str && !isdigit(*str)) ++str;
653 return 0; /* One of the special "no rating" cases */
661 /* Init programStats */
662 programStats.movelist[0] = 0;
663 programStats.depth = 0;
664 programStats.nr_moves = 0;
665 programStats.moves_left = 0;
666 programStats.nodes = 0;
667 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
668 programStats.score = 0;
669 programStats.got_only_move = 0;
670 programStats.got_fail = 0;
671 programStats.line_is_book = 0;
676 { // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
677 if (appData.firstPlaysBlack) {
678 first.twoMachinesColor = "black\n";
679 second.twoMachinesColor = "white\n";
681 first.twoMachinesColor = "white\n";
682 second.twoMachinesColor = "black\n";
685 first.other = &second;
686 second.other = &first;
689 if(appData.timeOddsMode) {
690 norm = appData.timeOdds[0];
691 if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
693 first.timeOdds = appData.timeOdds[0]/norm;
694 second.timeOdds = appData.timeOdds[1]/norm;
697 if(programVersion) free(programVersion);
698 if (appData.noChessProgram) {
699 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
700 sprintf(programVersion, "%s", PACKAGE_STRING);
702 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
703 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
704 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
709 UnloadEngine(ChessProgramState *cps)
711 /* Kill off first chess program */
712 if (cps->isr != NULL)
713 RemoveInputSource(cps->isr);
716 if (cps->pr != NoProc) {
718 DoSleep( appData.delayBeforeQuit );
719 SendToProgram("quit\n", cps);
720 DoSleep( appData.delayAfterQuit );
721 DestroyChildProcess(cps->pr, cps->useSigterm);
724 if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
728 ClearOptions(ChessProgramState *cps)
731 cps->nrOptions = cps->comboCnt = 0;
732 for(i=0; i<MAX_OPTIONS; i++) {
733 cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
734 cps->option[i].textValue = 0;
738 char *engineNames[] = {
744 InitEngine(ChessProgramState *cps, int n)
745 { // [HGM] all engine initialiation put in a function that does one engine
749 cps->which = engineNames[n];
750 cps->maybeThinking = FALSE;
754 cps->sendDrawOffers = 1;
756 cps->program = appData.chessProgram[n];
757 cps->host = appData.host[n];
758 cps->dir = appData.directory[n];
759 cps->initString = appData.engInitString[n];
760 cps->computerString = appData.computerString[n];
761 cps->useSigint = TRUE;
762 cps->useSigterm = TRUE;
763 cps->reuse = appData.reuse[n];
764 cps->nps = appData.NPS[n]; // [HGM] nps: copy nodes per second
765 cps->useSetboard = FALSE;
767 cps->usePing = FALSE;
770 cps->usePlayother = FALSE;
771 cps->useColors = TRUE;
772 cps->useUsermove = FALSE;
773 cps->sendICS = FALSE;
774 cps->sendName = appData.icsActive;
775 cps->sdKludge = FALSE;
776 cps->stKludge = FALSE;
777 TidyProgramName(cps->program, cps->host, cps->tidy);
779 safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
780 cps->analysisSupport = 2; /* detect */
781 cps->analyzing = FALSE;
782 cps->initDone = FALSE;
784 /* New features added by Tord: */
785 cps->useFEN960 = FALSE;
786 cps->useOOCastle = TRUE;
787 /* End of new features added by Tord. */
788 cps->fenOverride = appData.fenOverride[n];
790 /* [HGM] time odds: set factor for each machine */
791 cps->timeOdds = appData.timeOdds[n];
793 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
794 cps->accumulateTC = appData.accumulateTC[n];
795 cps->maxNrOfSessions = 1;
800 cps->supportsNPS = UNKNOWN;
801 cps->memSize = FALSE;
802 cps->maxCores = FALSE;
803 cps->egtFormats[0] = NULLCHAR;
806 cps->optionSettings = appData.engOptions[n];
808 cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
809 cps->isUCI = appData.isUCI[n]; /* [AS] */
810 cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
812 if (appData.protocolVersion[n] > PROTOVER
813 || appData.protocolVersion[n] < 1)
818 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
819 appData.protocolVersion[n]);
820 if( (len > MSG_SIZ) && appData.debugMode )
821 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
823 DisplayFatalError(buf, 0, 2);
827 cps->protocolVersion = appData.protocolVersion[n];
830 InitEngineUCI( installDir, cps ); // [HGM] moved here from winboard.c, to make available in xboard
833 ChessProgramState *savCps;
839 if(WaitForEngine(savCps, LoadEngine)) return;
840 CommonEngineInit(); // recalculate time odds
841 if(gameInfo.variant != StringToVariant(appData.variant)) {
842 // we changed variant when loading the engine; this forces us to reset
843 Reset(TRUE, savCps != &first);
844 EditGameEvent(); // for consistency with other path, as Reset changes mode
846 InitChessProgram(savCps, FALSE);
847 SendToProgram("force\n", savCps);
848 DisplayMessage("", "");
849 if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
850 for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
856 ReplaceEngine(ChessProgramState *cps, int n)
860 appData.noChessProgram = FALSE;
861 appData.clockMode = TRUE;
863 if(n) return; // only startup first engine immediately; second can wait
864 savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
868 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
869 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
871 static char resetOptions[] =
872 "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
873 "-firstOptions \"\" -firstNPS -1 -fn \"\"";
876 Load(ChessProgramState *cps, int i)
878 char *p, *q, buf[MSG_SIZ], command[MSG_SIZ];
879 if(engineLine[0]) { // an engine was selected from the combo box
880 snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
881 SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
882 ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL;
883 ParseArgsFromString(buf);
885 ReplaceEngine(cps, i);
889 while(q = strchr(p, SLASH)) p = q+1;
890 if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
891 if(engineDir[0] != NULLCHAR)
892 appData.directory[i] = engineDir;
893 else if(p != engineName) { // derive directory from engine path, when not given
895 appData.directory[i] = strdup(engineName);
897 } else appData.directory[i] = ".";
899 snprintf(command, MSG_SIZ, "%s %s", p, params);
902 appData.chessProgram[i] = strdup(p);
903 appData.isUCI[i] = isUCI;
904 appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
905 appData.hasOwnBookUCI[i] = hasBook;
906 if(!nickName[0]) useNick = FALSE;
907 if(useNick) ASSIGN(appData.pgnName[i], nickName);
910 q = firstChessProgramNames;
911 if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
912 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "\"%s\" -fd \"%s\"%s%s%s%s%s%s%s%s\n", p, appData.directory[i],
913 useNick ? " -fn \"" : "",
914 useNick ? nickName : "",
916 v1 ? " -firstProtocolVersion 1" : "",
917 hasBook ? "" : " -fNoOwnBookUCI",
918 isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
919 storeVariant ? " -variant " : "",
920 storeVariant ? VariantName(gameInfo.variant) : "");
921 firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
922 snprintf(firstChessProgramNames, len, "%s%s", q, buf);
925 ReplaceEngine(cps, i);
931 int matched, min, sec;
933 * Parse timeControl resource
935 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
936 appData.movesPerSession)) {
938 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
939 DisplayFatalError(buf, 0, 2);
943 * Parse searchTime resource
945 if (*appData.searchTime != NULLCHAR) {
946 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
948 searchTime = min * 60;
949 } else if (matched == 2) {
950 searchTime = min * 60 + sec;
953 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
954 DisplayFatalError(buf, 0, 2);
963 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
964 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
966 GetTimeMark(&programStartTime);
967 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
968 pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
971 programStats.ok_to_send = 1;
972 programStats.seen_stat = 0;
975 * Initialize game list
981 * Internet chess server status
983 if (appData.icsActive) {
984 appData.matchMode = FALSE;
985 appData.matchGames = 0;
987 appData.noChessProgram = !appData.zippyPlay;
989 appData.zippyPlay = FALSE;
990 appData.zippyTalk = FALSE;
991 appData.noChessProgram = TRUE;
993 if (*appData.icsHelper != NULLCHAR) {
994 appData.useTelnet = TRUE;
995 appData.telnetProgram = appData.icsHelper;
998 appData.zippyTalk = appData.zippyPlay = FALSE;
1001 /* [AS] Initialize pv info list [HGM] and game state */
1005 for( i=0; i<=framePtr; i++ ) {
1006 pvInfoList[i].depth = -1;
1007 boards[i][EP_STATUS] = EP_NONE;
1008 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1014 /* [AS] Adjudication threshold */
1015 adjudicateLossThreshold = appData.adjudicateLossThreshold;
1017 InitEngine(&first, 0);
1018 InitEngine(&second, 1);
1021 if (appData.icsActive) {
1022 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
1023 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1024 appData.clockMode = FALSE;
1025 first.sendTime = second.sendTime = 0;
1029 /* Override some settings from environment variables, for backward
1030 compatibility. Unfortunately it's not feasible to have the env
1031 vars just set defaults, at least in xboard. Ugh.
1033 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1038 if (!appData.icsActive) {
1042 /* Check for variants that are supported only in ICS mode,
1043 or not at all. Some that are accepted here nevertheless
1044 have bugs; see comments below.
1046 VariantClass variant = StringToVariant(appData.variant);
1048 case VariantBughouse: /* need four players and two boards */
1049 case VariantKriegspiel: /* need to hide pieces and move details */
1050 /* case VariantFischeRandom: (Fabien: moved below) */
1051 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1052 if( (len > MSG_SIZ) && appData.debugMode )
1053 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1055 DisplayFatalError(buf, 0, 2);
1058 case VariantUnknown:
1059 case VariantLoadable:
1069 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1070 if( (len > MSG_SIZ) && appData.debugMode )
1071 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1073 DisplayFatalError(buf, 0, 2);
1076 case VariantXiangqi: /* [HGM] repetition rules not implemented */
1077 case VariantFairy: /* [HGM] TestLegality definitely off! */
1078 case VariantGothic: /* [HGM] should work */
1079 case VariantCapablanca: /* [HGM] should work */
1080 case VariantCourier: /* [HGM] initial forced moves not implemented */
1081 case VariantShogi: /* [HGM] could still mate with pawn drop */
1082 case VariantKnightmate: /* [HGM] should work */
1083 case VariantCylinder: /* [HGM] untested */
1084 case VariantFalcon: /* [HGM] untested */
1085 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1086 offboard interposition not understood */
1087 case VariantNormal: /* definitely works! */
1088 case VariantWildCastle: /* pieces not automatically shuffled */
1089 case VariantNoCastle: /* pieces not automatically shuffled */
1090 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1091 case VariantLosers: /* should work except for win condition,
1092 and doesn't know captures are mandatory */
1093 case VariantSuicide: /* should work except for win condition,
1094 and doesn't know captures are mandatory */
1095 case VariantGiveaway: /* should work except for win condition,
1096 and doesn't know captures are mandatory */
1097 case VariantTwoKings: /* should work */
1098 case VariantAtomic: /* should work except for win condition */
1099 case Variant3Check: /* should work except for win condition */
1100 case VariantShatranj: /* should work except for all win conditions */
1101 case VariantMakruk: /* should work except for daw countdown */
1102 case VariantBerolina: /* might work if TestLegality is off */
1103 case VariantCapaRandom: /* should work */
1104 case VariantJanus: /* should work */
1105 case VariantSuper: /* experimental */
1106 case VariantGreat: /* experimental, requires legality testing to be off */
1107 case VariantSChess: /* S-Chess, should work */
1108 case VariantSpartan: /* should work */
1115 int NextIntegerFromString( char ** str, long * value )
1120 while( *s == ' ' || *s == '\t' ) {
1126 if( *s >= '0' && *s <= '9' ) {
1127 while( *s >= '0' && *s <= '9' ) {
1128 *value = *value * 10 + (*s - '0');
1140 int NextTimeControlFromString( char ** str, long * value )
1143 int result = NextIntegerFromString( str, &temp );
1146 *value = temp * 60; /* Minutes */
1147 if( **str == ':' ) {
1149 result = NextIntegerFromString( str, &temp );
1150 *value += temp; /* Seconds */
1157 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1158 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1159 int result = -1, type = 0; long temp, temp2;
1161 if(**str != ':') return -1; // old params remain in force!
1163 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1164 if( NextIntegerFromString( str, &temp ) ) return -1;
1165 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1168 /* time only: incremental or sudden-death time control */
1169 if(**str == '+') { /* increment follows; read it */
1171 if(**str == '!') type = *(*str)++; // Bronstein TC
1172 if(result = NextIntegerFromString( str, &temp2)) return -1;
1173 *inc = temp2 * 1000;
1174 if(**str == '.') { // read fraction of increment
1175 char *start = ++(*str);
1176 if(result = NextIntegerFromString( str, &temp2)) return -1;
1178 while(start++ < *str) temp2 /= 10;
1182 *moves = 0; *tc = temp * 1000; *incType = type;
1186 (*str)++; /* classical time control */
1187 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1198 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1199 { /* [HGM] get time to add from the multi-session time-control string */
1200 int incType, moves=1; /* kludge to force reading of first session */
1201 long time, increment;
1204 if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1205 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1207 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1208 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1209 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1210 if(movenr == -1) return time; /* last move before new session */
1211 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1212 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1213 if(!moves) return increment; /* current session is incremental */
1214 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1215 } while(movenr >= -1); /* try again for next session */
1217 return 0; // no new time quota on this move
1221 ParseTimeControl(tc, ti, mps)
1228 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1231 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1232 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1233 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1237 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1239 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1242 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1244 snprintf(buf, MSG_SIZ, ":%s", mytc);
1246 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1248 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1253 /* Parse second time control */
1256 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1264 timeControl_2 = tc2 * 1000;
1274 timeControl = tc1 * 1000;
1277 timeIncrement = ti * 1000; /* convert to ms */
1278 movesPerSession = 0;
1281 movesPerSession = mps;
1289 if (appData.debugMode) {
1290 fprintf(debugFP, "%s\n", programVersion);
1293 set_cont_sequence(appData.wrapContSeq);
1294 if (appData.matchGames > 0) {
1295 appData.matchMode = TRUE;
1296 } else if (appData.matchMode) {
1297 appData.matchGames = 1;
1299 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1300 appData.matchGames = appData.sameColorGames;
1301 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1302 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1303 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1306 if (appData.noChessProgram || first.protocolVersion == 1) {
1309 /* kludge: allow timeout for initial "feature" commands */
1311 DisplayMessage("", _("Starting chess program"));
1312 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1317 CalculateIndex(int index, int gameNr)
1318 { // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1320 if(index > 0) return index; // fixed nmber
1321 if(index == 0) return 1;
1322 res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1323 if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1328 LoadGameOrPosition(int gameNr)
1329 { // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1330 if (*appData.loadGameFile != NULLCHAR) {
1331 if (!LoadGameFromFile(appData.loadGameFile,
1332 CalculateIndex(appData.loadGameIndex, gameNr),
1333 appData.loadGameFile, FALSE)) {
1334 DisplayFatalError(_("Bad game file"), 0, 1);
1337 } else if (*appData.loadPositionFile != NULLCHAR) {
1338 if (!LoadPositionFromFile(appData.loadPositionFile,
1339 CalculateIndex(appData.loadPositionIndex, gameNr),
1340 appData.loadPositionFile)) {
1341 DisplayFatalError(_("Bad position file"), 0, 1);
1349 ReserveGame(int gameNr, char resChar)
1351 FILE *tf = fopen(appData.tourneyFile, "r+");
1352 char *p, *q, c, buf[MSG_SIZ];
1353 if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1354 safeStrCpy(buf, lastMsg, MSG_SIZ);
1355 DisplayMessage(_("Pick new game"), "");
1356 flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1357 ParseArgsFromFile(tf);
1358 p = q = appData.results;
1359 if(appData.debugMode) {
1360 char *r = appData.participants;
1361 fprintf(debugFP, "results = '%s'\n", p);
1362 while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1363 fprintf(debugFP, "\n");
1365 while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1367 q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1368 safeStrCpy(q, p, strlen(p) + 2);
1369 if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1370 if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1371 if(nextGame <= appData.matchGames && resChar != ' ') { // already reserve next game, if tourney not yet done
1372 if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1375 fseek(tf, -(strlen(p)+4), SEEK_END);
1377 if(c != '"') // depending on DOS or Unix line endings we can be one off
1378 fseek(tf, -(strlen(p)+2), SEEK_END);
1379 else fseek(tf, -(strlen(p)+3), SEEK_END);
1380 fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1381 DisplayMessage(buf, "");
1382 free(p); appData.results = q;
1383 if(nextGame <= appData.matchGames && resChar != ' ' &&
1384 (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1385 UnloadEngine(&first); // next game belongs to other pairing;
1386 UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1391 MatchEvent(int mode)
1392 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1394 if(matchMode) { // already in match mode: switch it off
1396 appData.matchGames = appData.tourneyFile[0] ? nextGame: matchGame; // kludge to let match terminate after next game.
1397 ModeHighlight(); // kludgey way to remove checkmark...
1400 // if(gameMode != BeginningOfGame) {
1401 // DisplayError(_("You can only start a match from the initial position."), 0);
1405 appData.matchGames = appData.defaultMatchGames;
1406 /* Set up machine vs. machine match */
1408 NextTourneyGame(0, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1409 if(appData.tourneyFile[0]) {
1411 if(nextGame > appData.matchGames) {
1413 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1414 DisplayError(buf, 0);
1415 appData.tourneyFile[0] = 0;
1419 if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically
1420 DisplayFatalError(_("Can't have a match with no chess programs"),
1425 matchGame = roundNr = 1;
1426 first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
\r
1431 InitBackEnd3 P((void))
1433 GameMode initialMode;
1437 InitChessProgram(&first, startedFromSetupPosition);
1439 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1440 free(programVersion);
1441 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1442 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1445 if (appData.icsActive) {
1447 /* [DM] Make a console window if needed [HGM] merged ifs */
1453 if (*appData.icsCommPort != NULLCHAR)
1454 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1455 appData.icsCommPort);
1457 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1458 appData.icsHost, appData.icsPort);
1460 if( (len > MSG_SIZ) && appData.debugMode )
1461 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1463 DisplayFatalError(buf, err, 1);
1468 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1470 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1471 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1472 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1473 } else if (appData.noChessProgram) {
1479 if (*appData.cmailGameName != NULLCHAR) {
1481 OpenLoopback(&cmailPR);
1483 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1487 DisplayMessage("", "");
1488 if (StrCaseCmp(appData.initialMode, "") == 0) {
1489 initialMode = BeginningOfGame;
1490 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1491 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1492 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1493 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1496 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1497 initialMode = TwoMachinesPlay;
1498 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1499 initialMode = AnalyzeFile;
1500 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1501 initialMode = AnalyzeMode;
1502 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1503 initialMode = MachinePlaysWhite;
1504 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1505 initialMode = MachinePlaysBlack;
1506 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1507 initialMode = EditGame;
1508 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1509 initialMode = EditPosition;
1510 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1511 initialMode = Training;
1513 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1514 if( (len > MSG_SIZ) && appData.debugMode )
1515 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1517 DisplayFatalError(buf, 0, 2);
1521 if (appData.matchMode) {
1522 if(appData.tourneyFile[0]) { // start tourney from command line
1524 if(f = fopen(appData.tourneyFile, "r")) {
1525 ParseArgsFromFile(f); // make sure tourney parmeters re known
1527 } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1530 } else if (*appData.cmailGameName != NULLCHAR) {
1531 /* Set up cmail mode */
1532 ReloadCmailMsgEvent(TRUE);
1534 /* Set up other modes */
1535 if (initialMode == AnalyzeFile) {
1536 if (*appData.loadGameFile == NULLCHAR) {
1537 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1541 if (*appData.loadGameFile != NULLCHAR) {
1542 (void) LoadGameFromFile(appData.loadGameFile,
1543 appData.loadGameIndex,
1544 appData.loadGameFile, TRUE);
1545 } else if (*appData.loadPositionFile != NULLCHAR) {
1546 (void) LoadPositionFromFile(appData.loadPositionFile,
1547 appData.loadPositionIndex,
1548 appData.loadPositionFile);
1549 /* [HGM] try to make self-starting even after FEN load */
1550 /* to allow automatic setup of fairy variants with wtm */
1551 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1552 gameMode = BeginningOfGame;
1553 setboardSpoiledMachineBlack = 1;
1555 /* [HGM] loadPos: make that every new game uses the setup */
1556 /* from file as long as we do not switch variant */
1557 if(!blackPlaysFirst) {
1558 startedFromPositionFile = TRUE;
1559 CopyBoard(filePosition, boards[0]);
1562 if (initialMode == AnalyzeMode) {
1563 if (appData.noChessProgram) {
1564 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1567 if (appData.icsActive) {
1568 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1572 } else if (initialMode == AnalyzeFile) {
1573 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1574 ShowThinkingEvent();
1576 AnalysisPeriodicEvent(1);
1577 } else if (initialMode == MachinePlaysWhite) {
1578 if (appData.noChessProgram) {
1579 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1583 if (appData.icsActive) {
1584 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1588 MachineWhiteEvent();
1589 } else if (initialMode == MachinePlaysBlack) {
1590 if (appData.noChessProgram) {
1591 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1595 if (appData.icsActive) {
1596 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1600 MachineBlackEvent();
1601 } else if (initialMode == TwoMachinesPlay) {
1602 if (appData.noChessProgram) {
1603 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1607 if (appData.icsActive) {
1608 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1613 } else if (initialMode == EditGame) {
1615 } else if (initialMode == EditPosition) {
1616 EditPositionEvent();
1617 } else if (initialMode == Training) {
1618 if (*appData.loadGameFile == NULLCHAR) {
1619 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1628 * Establish will establish a contact to a remote host.port.
1629 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1630 * used to talk to the host.
1631 * Returns 0 if okay, error code if not.
1638 if (*appData.icsCommPort != NULLCHAR) {
1639 /* Talk to the host through a serial comm port */
1640 return OpenCommPort(appData.icsCommPort, &icsPR);
1642 } else if (*appData.gateway != NULLCHAR) {
1643 if (*appData.remoteShell == NULLCHAR) {
1644 /* Use the rcmd protocol to run telnet program on a gateway host */
1645 snprintf(buf, sizeof(buf), "%s %s %s",
1646 appData.telnetProgram, appData.icsHost, appData.icsPort);
1647 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1650 /* Use the rsh program to run telnet program on a gateway host */
1651 if (*appData.remoteUser == NULLCHAR) {
1652 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1653 appData.gateway, appData.telnetProgram,
1654 appData.icsHost, appData.icsPort);
1656 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1657 appData.remoteShell, appData.gateway,
1658 appData.remoteUser, appData.telnetProgram,
1659 appData.icsHost, appData.icsPort);
1661 return StartChildProcess(buf, "", &icsPR);
1664 } else if (appData.useTelnet) {
1665 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1668 /* TCP socket interface differs somewhat between
1669 Unix and NT; handle details in the front end.
1671 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1675 void EscapeExpand(char *p, char *q)
1676 { // [HGM] initstring: routine to shape up string arguments
1677 while(*p++ = *q++) if(p[-1] == '\\')
1679 case 'n': p[-1] = '\n'; break;
1680 case 'r': p[-1] = '\r'; break;
1681 case 't': p[-1] = '\t'; break;
1682 case '\\': p[-1] = '\\'; break;
1683 case 0: *p = 0; return;
1684 default: p[-1] = q[-1]; break;
1689 show_bytes(fp, buf, count)
1695 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1696 fprintf(fp, "\\%03o", *buf & 0xff);
1705 /* Returns an errno value */
1707 OutputMaybeTelnet(pr, message, count, outError)
1713 char buf[8192], *p, *q, *buflim;
1714 int left, newcount, outcount;
1716 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1717 *appData.gateway != NULLCHAR) {
1718 if (appData.debugMode) {
1719 fprintf(debugFP, ">ICS: ");
1720 show_bytes(debugFP, message, count);
1721 fprintf(debugFP, "\n");
1723 return OutputToProcess(pr, message, count, outError);
1726 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1733 if (appData.debugMode) {
1734 fprintf(debugFP, ">ICS: ");
1735 show_bytes(debugFP, buf, newcount);
1736 fprintf(debugFP, "\n");
1738 outcount = OutputToProcess(pr, buf, newcount, outError);
1739 if (outcount < newcount) return -1; /* to be sure */
1746 } else if (((unsigned char) *p) == TN_IAC) {
1747 *q++ = (char) TN_IAC;
1754 if (appData.debugMode) {
1755 fprintf(debugFP, ">ICS: ");
1756 show_bytes(debugFP, buf, newcount);
1757 fprintf(debugFP, "\n");
1759 outcount = OutputToProcess(pr, buf, newcount, outError);
1760 if (outcount < newcount) return -1; /* to be sure */
1765 read_from_player(isr, closure, message, count, error)
1772 int outError, outCount;
1773 static int gotEof = 0;
1775 /* Pass data read from player on to ICS */
1778 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1779 if (outCount < count) {
1780 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1782 } else if (count < 0) {
1783 RemoveInputSource(isr);
1784 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1785 } else if (gotEof++ > 0) {
1786 RemoveInputSource(isr);
1787 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1793 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1794 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1795 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1796 SendToICS("date\n");
1797 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1800 /* added routine for printf style output to ics */
1801 void ics_printf(char *format, ...)
1803 char buffer[MSG_SIZ];
1806 va_start(args, format);
1807 vsnprintf(buffer, sizeof(buffer), format, args);
1808 buffer[sizeof(buffer)-1] = '\0';
1817 int count, outCount, outError;
1819 if (icsPR == NULL) return;
1822 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1823 if (outCount < count) {
1824 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1828 /* This is used for sending logon scripts to the ICS. Sending
1829 without a delay causes problems when using timestamp on ICC
1830 (at least on my machine). */
1832 SendToICSDelayed(s,msdelay)
1836 int count, outCount, outError;
1838 if (icsPR == NULL) return;
1841 if (appData.debugMode) {
1842 fprintf(debugFP, ">ICS: ");
1843 show_bytes(debugFP, s, count);
1844 fprintf(debugFP, "\n");
1846 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1848 if (outCount < count) {
1849 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1854 /* Remove all highlighting escape sequences in s
1855 Also deletes any suffix starting with '('
1858 StripHighlightAndTitle(s)
1861 static char retbuf[MSG_SIZ];
1864 while (*s != NULLCHAR) {
1865 while (*s == '\033') {
1866 while (*s != NULLCHAR && !isalpha(*s)) s++;
1867 if (*s != NULLCHAR) s++;
1869 while (*s != NULLCHAR && *s != '\033') {
1870 if (*s == '(' || *s == '[') {
1881 /* Remove all highlighting escape sequences in s */
1886 static char retbuf[MSG_SIZ];
1889 while (*s != NULLCHAR) {
1890 while (*s == '\033') {
1891 while (*s != NULLCHAR && !isalpha(*s)) s++;
1892 if (*s != NULLCHAR) s++;
1894 while (*s != NULLCHAR && *s != '\033') {
1902 char *variantNames[] = VARIANT_NAMES;
1907 return variantNames[v];
1911 /* Identify a variant from the strings the chess servers use or the
1912 PGN Variant tag names we use. */
1919 VariantClass v = VariantNormal;
1920 int i, found = FALSE;
1926 /* [HGM] skip over optional board-size prefixes */
1927 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1928 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1929 while( *e++ != '_');
1932 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1936 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1937 if (StrCaseStr(e, variantNames[i])) {
1938 v = (VariantClass) i;
1945 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1946 || StrCaseStr(e, "wild/fr")
1947 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1948 v = VariantFischeRandom;
1949 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1950 (i = 1, p = StrCaseStr(e, "w"))) {
1952 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1959 case 0: /* FICS only, actually */
1961 /* Castling legal even if K starts on d-file */
1962 v = VariantWildCastle;
1967 /* Castling illegal even if K & R happen to start in
1968 normal positions. */
1969 v = VariantNoCastle;
1982 /* Castling legal iff K & R start in normal positions */
1988 /* Special wilds for position setup; unclear what to do here */
1989 v = VariantLoadable;
1992 /* Bizarre ICC game */
1993 v = VariantTwoKings;
1996 v = VariantKriegspiel;
2002 v = VariantFischeRandom;
2005 v = VariantCrazyhouse;
2008 v = VariantBughouse;
2014 /* Not quite the same as FICS suicide! */
2015 v = VariantGiveaway;
2021 v = VariantShatranj;
2024 /* Temporary names for future ICC types. The name *will* change in
2025 the next xboard/WinBoard release after ICC defines it. */
2063 v = VariantCapablanca;
2066 v = VariantKnightmate;
2072 v = VariantCylinder;
2078 v = VariantCapaRandom;
2081 v = VariantBerolina;
2093 /* Found "wild" or "w" in the string but no number;
2094 must assume it's normal chess. */
2098 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2099 if( (len > MSG_SIZ) && appData.debugMode )
2100 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2102 DisplayError(buf, 0);
2108 if (appData.debugMode) {
2109 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2110 e, wnum, VariantName(v));
2115 static int leftover_start = 0, leftover_len = 0;
2116 char star_match[STAR_MATCH_N][MSG_SIZ];
2118 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2119 advance *index beyond it, and set leftover_start to the new value of
2120 *index; else return FALSE. If pattern contains the character '*', it
2121 matches any sequence of characters not containing '\r', '\n', or the
2122 character following the '*' (if any), and the matched sequence(s) are
2123 copied into star_match.
2126 looking_at(buf, index, pattern)
2131 char *bufp = &buf[*index], *patternp = pattern;
2133 char *matchp = star_match[0];
2136 if (*patternp == NULLCHAR) {
2137 *index = leftover_start = bufp - buf;
2141 if (*bufp == NULLCHAR) return FALSE;
2142 if (*patternp == '*') {
2143 if (*bufp == *(patternp + 1)) {
2145 matchp = star_match[++star_count];
2149 } else if (*bufp == '\n' || *bufp == '\r') {
2151 if (*patternp == NULLCHAR)
2156 *matchp++ = *bufp++;
2160 if (*patternp != *bufp) return FALSE;
2167 SendToPlayer(data, length)
2171 int error, outCount;
2172 outCount = OutputToProcess(NoProc, data, length, &error);
2173 if (outCount < length) {
2174 DisplayFatalError(_("Error writing to display"), error, 1);
2179 PackHolding(packed, holding)
2191 switch (runlength) {
2202 sprintf(q, "%d", runlength);
2214 /* Telnet protocol requests from the front end */
2216 TelnetRequest(ddww, option)
2217 unsigned char ddww, option;
2219 unsigned char msg[3];
2220 int outCount, outError;
2222 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2224 if (appData.debugMode) {
2225 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2241 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2250 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2253 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2258 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2260 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2267 if (!appData.icsActive) return;
2268 TelnetRequest(TN_DO, TN_ECHO);
2274 if (!appData.icsActive) return;
2275 TelnetRequest(TN_DONT, TN_ECHO);
2279 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2281 /* put the holdings sent to us by the server on the board holdings area */
2282 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2286 if(gameInfo.holdingsWidth < 2) return;
2287 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2288 return; // prevent overwriting by pre-board holdings
2290 if( (int)lowestPiece >= BlackPawn ) {
2293 holdingsStartRow = BOARD_HEIGHT-1;
2296 holdingsColumn = BOARD_WIDTH-1;
2297 countsColumn = BOARD_WIDTH-2;
2298 holdingsStartRow = 0;
2302 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2303 board[i][holdingsColumn] = EmptySquare;
2304 board[i][countsColumn] = (ChessSquare) 0;
2306 while( (p=*holdings++) != NULLCHAR ) {
2307 piece = CharToPiece( ToUpper(p) );
2308 if(piece == EmptySquare) continue;
2309 /*j = (int) piece - (int) WhitePawn;*/
2310 j = PieceToNumber(piece);
2311 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2312 if(j < 0) continue; /* should not happen */
2313 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2314 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2315 board[holdingsStartRow+j*direction][countsColumn]++;
2321 VariantSwitch(Board board, VariantClass newVariant)
2323 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2324 static Board oldBoard;
2326 startedFromPositionFile = FALSE;
2327 if(gameInfo.variant == newVariant) return;
2329 /* [HGM] This routine is called each time an assignment is made to
2330 * gameInfo.variant during a game, to make sure the board sizes
2331 * are set to match the new variant. If that means adding or deleting
2332 * holdings, we shift the playing board accordingly
2333 * This kludge is needed because in ICS observe mode, we get boards
2334 * of an ongoing game without knowing the variant, and learn about the
2335 * latter only later. This can be because of the move list we requested,
2336 * in which case the game history is refilled from the beginning anyway,
2337 * but also when receiving holdings of a crazyhouse game. In the latter
2338 * case we want to add those holdings to the already received position.
2342 if (appData.debugMode) {
2343 fprintf(debugFP, "Switch board from %s to %s\n",
2344 VariantName(gameInfo.variant), VariantName(newVariant));
2345 setbuf(debugFP, NULL);
2347 shuffleOpenings = 0; /* [HGM] shuffle */
2348 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2352 newWidth = 9; newHeight = 9;
2353 gameInfo.holdingsSize = 7;
2354 case VariantBughouse:
2355 case VariantCrazyhouse:
2356 newHoldingsWidth = 2; break;
2360 newHoldingsWidth = 2;
2361 gameInfo.holdingsSize = 8;
2364 case VariantCapablanca:
2365 case VariantCapaRandom:
2368 newHoldingsWidth = gameInfo.holdingsSize = 0;
2371 if(newWidth != gameInfo.boardWidth ||
2372 newHeight != gameInfo.boardHeight ||
2373 newHoldingsWidth != gameInfo.holdingsWidth ) {
2375 /* shift position to new playing area, if needed */
2376 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2377 for(i=0; i<BOARD_HEIGHT; i++)
2378 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2379 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2381 for(i=0; i<newHeight; i++) {
2382 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2383 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2385 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2386 for(i=0; i<BOARD_HEIGHT; i++)
2387 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2388 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2391 gameInfo.boardWidth = newWidth;
2392 gameInfo.boardHeight = newHeight;
2393 gameInfo.holdingsWidth = newHoldingsWidth;
2394 gameInfo.variant = newVariant;
2395 InitDrawingSizes(-2, 0);
2396 } else gameInfo.variant = newVariant;
2397 CopyBoard(oldBoard, board); // remember correctly formatted board
2398 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2399 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2402 static int loggedOn = FALSE;
2404 /*-- Game start info cache: --*/
2406 char gs_kind[MSG_SIZ];
2407 static char player1Name[128] = "";
2408 static char player2Name[128] = "";
2409 static char cont_seq[] = "\n\\ ";
2410 static int player1Rating = -1;
2411 static int player2Rating = -1;
2412 /*----------------------------*/
2414 ColorClass curColor = ColorNormal;
2415 int suppressKibitz = 0;
2418 Boolean soughtPending = FALSE;
2419 Boolean seekGraphUp;
2420 #define MAX_SEEK_ADS 200
2422 char *seekAdList[MAX_SEEK_ADS];
2423 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2424 float tcList[MAX_SEEK_ADS];
2425 char colorList[MAX_SEEK_ADS];
2426 int nrOfSeekAds = 0;
2427 int minRating = 1010, maxRating = 2800;
2428 int hMargin = 10, vMargin = 20, h, w;
2429 extern int squareSize, lineGap;
2434 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2435 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2436 if(r < minRating+100 && r >=0 ) r = minRating+100;
2437 if(r > maxRating) r = maxRating;
2438 if(tc < 1.) tc = 1.;
2439 if(tc > 95.) tc = 95.;
2440 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2441 y = ((double)r - minRating)/(maxRating - minRating)
2442 * (h-vMargin-squareSize/8-1) + vMargin;
2443 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2444 if(strstr(seekAdList[i], " u ")) color = 1;
2445 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2446 !strstr(seekAdList[i], "bullet") &&
2447 !strstr(seekAdList[i], "blitz") &&
2448 !strstr(seekAdList[i], "standard") ) color = 2;
2449 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2450 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2454 AddAd(char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2456 char buf[MSG_SIZ], *ext = "";
2457 VariantClass v = StringToVariant(type);
2458 if(strstr(type, "wild")) {
2459 ext = type + 4; // append wild number
2460 if(v == VariantFischeRandom) type = "chess960"; else
2461 if(v == VariantLoadable) type = "setup"; else
2462 type = VariantName(v);
2464 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2465 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2466 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2467 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2468 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2469 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2470 seekNrList[nrOfSeekAds] = nr;
2471 zList[nrOfSeekAds] = 0;
2472 seekAdList[nrOfSeekAds++] = StrSave(buf);
2473 if(plot) PlotSeekAd(nrOfSeekAds-1);
2480 int x = xList[i], y = yList[i], d=squareSize/4, k;
2481 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2482 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2483 // now replot every dot that overlapped
2484 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2485 int xx = xList[k], yy = yList[k];
2486 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2487 DrawSeekDot(xx, yy, colorList[k]);
2492 RemoveSeekAd(int nr)
2495 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2497 if(seekAdList[i]) free(seekAdList[i]);
2498 seekAdList[i] = seekAdList[--nrOfSeekAds];
2499 seekNrList[i] = seekNrList[nrOfSeekAds];
2500 ratingList[i] = ratingList[nrOfSeekAds];
2501 colorList[i] = colorList[nrOfSeekAds];
2502 tcList[i] = tcList[nrOfSeekAds];
2503 xList[i] = xList[nrOfSeekAds];
2504 yList[i] = yList[nrOfSeekAds];
2505 zList[i] = zList[nrOfSeekAds];
2506 seekAdList[nrOfSeekAds] = NULL;
2512 MatchSoughtLine(char *line)
2514 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2515 int nr, base, inc, u=0; char dummy;
2517 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2518 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2520 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2521 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2522 // match: compact and save the line
2523 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2533 if(!seekGraphUp) return FALSE;
2534 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2535 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2537 DrawSeekBackground(0, 0, w, h);
2538 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2539 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2540 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2541 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2543 DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2546 snprintf(buf, MSG_SIZ, "%d", i);
2547 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2550 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2551 for(i=1; i<100; i+=(i<10?1:5)) {
2552 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2553 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2554 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2556 snprintf(buf, MSG_SIZ, "%d", i);
2557 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2560 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2564 int SeekGraphClick(ClickType click, int x, int y, int moving)
2566 static int lastDown = 0, displayed = 0, lastSecond;
2567 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2568 if(click == Release || moving) return FALSE;
2570 soughtPending = TRUE;
2571 SendToICS(ics_prefix);
2572 SendToICS("sought\n"); // should this be "sought all"?
2573 } else { // issue challenge based on clicked ad
2574 int dist = 10000; int i, closest = 0, second = 0;
2575 for(i=0; i<nrOfSeekAds; i++) {
2576 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2577 if(d < dist) { dist = d; closest = i; }
2578 second += (d - zList[i] < 120); // count in-range ads
2579 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2583 second = (second > 1);
2584 if(displayed != closest || second != lastSecond) {
2585 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2586 lastSecond = second; displayed = closest;
2588 if(click == Press) {
2589 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2592 } // on press 'hit', only show info
2593 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2594 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2595 SendToICS(ics_prefix);
2597 return TRUE; // let incoming board of started game pop down the graph
2598 } else if(click == Release) { // release 'miss' is ignored
2599 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2600 if(moving == 2) { // right up-click
2601 nrOfSeekAds = 0; // refresh graph
2602 soughtPending = TRUE;
2603 SendToICS(ics_prefix);
2604 SendToICS("sought\n"); // should this be "sought all"?
2607 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2608 // press miss or release hit 'pop down' seek graph
2609 seekGraphUp = FALSE;
2610 DrawPosition(TRUE, NULL);
2616 read_from_ics(isr, closure, data, count, error)
2623 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2624 #define STARTED_NONE 0
2625 #define STARTED_MOVES 1
2626 #define STARTED_BOARD 2
2627 #define STARTED_OBSERVE 3
2628 #define STARTED_HOLDINGS 4
2629 #define STARTED_CHATTER 5
2630 #define STARTED_COMMENT 6
2631 #define STARTED_MOVES_NOHIDE 7
2633 static int started = STARTED_NONE;
2634 static char parse[20000];
2635 static int parse_pos = 0;
2636 static char buf[BUF_SIZE + 1];
2637 static int firstTime = TRUE, intfSet = FALSE;
2638 static ColorClass prevColor = ColorNormal;
2639 static int savingComment = FALSE;
2640 static int cmatch = 0; // continuation sequence match
2647 int backup; /* [DM] For zippy color lines */
2649 char talker[MSG_SIZ]; // [HGM] chat
2652 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2654 if (appData.debugMode) {
2656 fprintf(debugFP, "<ICS: ");
2657 show_bytes(debugFP, data, count);
2658 fprintf(debugFP, "\n");
2662 if (appData.debugMode) { int f = forwardMostMove;
2663 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2664 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2665 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2668 /* If last read ended with a partial line that we couldn't parse,
2669 prepend it to the new read and try again. */
2670 if (leftover_len > 0) {
2671 for (i=0; i<leftover_len; i++)
2672 buf[i] = buf[leftover_start + i];
2675 /* copy new characters into the buffer */
2676 bp = buf + leftover_len;
2677 buf_len=leftover_len;
2678 for (i=0; i<count; i++)
2681 if (data[i] == '\r')
2684 // join lines split by ICS?
2685 if (!appData.noJoin)
2688 Joining just consists of finding matches against the
2689 continuation sequence, and discarding that sequence
2690 if found instead of copying it. So, until a match
2691 fails, there's nothing to do since it might be the
2692 complete sequence, and thus, something we don't want
2695 if (data[i] == cont_seq[cmatch])
2698 if (cmatch == strlen(cont_seq))
2700 cmatch = 0; // complete match. just reset the counter
2703 it's possible for the ICS to not include the space
2704 at the end of the last word, making our [correct]
2705 join operation fuse two separate words. the server
2706 does this when the space occurs at the width setting.
2708 if (!buf_len || buf[buf_len-1] != ' ')
2719 match failed, so we have to copy what matched before
2720 falling through and copying this character. In reality,
2721 this will only ever be just the newline character, but
2722 it doesn't hurt to be precise.
2724 strncpy(bp, cont_seq, cmatch);
2736 buf[buf_len] = NULLCHAR;
2737 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2742 while (i < buf_len) {
2743 /* Deal with part of the TELNET option negotiation
2744 protocol. We refuse to do anything beyond the
2745 defaults, except that we allow the WILL ECHO option,
2746 which ICS uses to turn off password echoing when we are
2747 directly connected to it. We reject this option
2748 if localLineEditing mode is on (always on in xboard)
2749 and we are talking to port 23, which might be a real
2750 telnet server that will try to keep WILL ECHO on permanently.
2752 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2753 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2754 unsigned char option;
2756 switch ((unsigned char) buf[++i]) {
2758 if (appData.debugMode)
2759 fprintf(debugFP, "\n<WILL ");
2760 switch (option = (unsigned char) buf[++i]) {
2762 if (appData.debugMode)
2763 fprintf(debugFP, "ECHO ");
2764 /* Reply only if this is a change, according
2765 to the protocol rules. */
2766 if (remoteEchoOption) break;
2767 if (appData.localLineEditing &&
2768 atoi(appData.icsPort) == TN_PORT) {
2769 TelnetRequest(TN_DONT, TN_ECHO);
2772 TelnetRequest(TN_DO, TN_ECHO);
2773 remoteEchoOption = TRUE;
2777 if (appData.debugMode)
2778 fprintf(debugFP, "%d ", option);
2779 /* Whatever this is, we don't want it. */
2780 TelnetRequest(TN_DONT, option);
2785 if (appData.debugMode)
2786 fprintf(debugFP, "\n<WONT ");
2787 switch (option = (unsigned char) buf[++i]) {
2789 if (appData.debugMode)
2790 fprintf(debugFP, "ECHO ");
2791 /* Reply only if this is a change, according
2792 to the protocol rules. */
2793 if (!remoteEchoOption) break;
2795 TelnetRequest(TN_DONT, TN_ECHO);
2796 remoteEchoOption = FALSE;
2799 if (appData.debugMode)
2800 fprintf(debugFP, "%d ", (unsigned char) option);
2801 /* Whatever this is, it must already be turned
2802 off, because we never agree to turn on
2803 anything non-default, so according to the
2804 protocol rules, we don't reply. */
2809 if (appData.debugMode)
2810 fprintf(debugFP, "\n<DO ");
2811 switch (option = (unsigned char) buf[++i]) {
2813 /* Whatever this is, we refuse to do it. */
2814 if (appData.debugMode)
2815 fprintf(debugFP, "%d ", option);
2816 TelnetRequest(TN_WONT, option);
2821 if (appData.debugMode)
2822 fprintf(debugFP, "\n<DONT ");
2823 switch (option = (unsigned char) buf[++i]) {
2825 if (appData.debugMode)
2826 fprintf(debugFP, "%d ", option);
2827 /* Whatever this is, we are already not doing
2828 it, because we never agree to do anything
2829 non-default, so according to the protocol
2830 rules, we don't reply. */
2835 if (appData.debugMode)
2836 fprintf(debugFP, "\n<IAC ");
2837 /* Doubled IAC; pass it through */
2841 if (appData.debugMode)
2842 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2843 /* Drop all other telnet commands on the floor */
2846 if (oldi > next_out)
2847 SendToPlayer(&buf[next_out], oldi - next_out);
2853 /* OK, this at least will *usually* work */
2854 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2858 if (loggedOn && !intfSet) {
2859 if (ics_type == ICS_ICC) {
2860 snprintf(str, MSG_SIZ,
2861 "/set-quietly interface %s\n/set-quietly style 12\n",
2863 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2864 strcat(str, "/set-2 51 1\n/set seek 1\n");
2865 } else if (ics_type == ICS_CHESSNET) {
2866 snprintf(str, MSG_SIZ, "/style 12\n");
2868 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2869 strcat(str, programVersion);
2870 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2871 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2872 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2874 strcat(str, "$iset nohighlight 1\n");
2876 strcat(str, "$iset lock 1\n$style 12\n");
2879 NotifyFrontendLogin();
2883 if (started == STARTED_COMMENT) {
2884 /* Accumulate characters in comment */
2885 parse[parse_pos++] = buf[i];
2886 if (buf[i] == '\n') {
2887 parse[parse_pos] = NULLCHAR;
2888 if(chattingPartner>=0) {
2890 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2891 OutputChatMessage(chattingPartner, mess);
2892 chattingPartner = -1;
2893 next_out = i+1; // [HGM] suppress printing in ICS window
2895 if(!suppressKibitz) // [HGM] kibitz
2896 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2897 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2898 int nrDigit = 0, nrAlph = 0, j;
2899 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2900 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2901 parse[parse_pos] = NULLCHAR;
2902 // try to be smart: if it does not look like search info, it should go to
2903 // ICS interaction window after all, not to engine-output window.
2904 for(j=0; j<parse_pos; j++) { // count letters and digits
2905 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2906 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2907 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2909 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2910 int depth=0; float score;
2911 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2912 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2913 pvInfoList[forwardMostMove-1].depth = depth;
2914 pvInfoList[forwardMostMove-1].score = 100*score;
2916 OutputKibitz(suppressKibitz, parse);
2919 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2920 SendToPlayer(tmp, strlen(tmp));
2922 next_out = i+1; // [HGM] suppress printing in ICS window
2924 started = STARTED_NONE;
2926 /* Don't match patterns against characters in comment */
2931 if (started == STARTED_CHATTER) {
2932 if (buf[i] != '\n') {
2933 /* Don't match patterns against characters in chatter */
2937 started = STARTED_NONE;
2938 if(suppressKibitz) next_out = i+1;
2941 /* Kludge to deal with rcmd protocol */
2942 if (firstTime && looking_at(buf, &i, "\001*")) {
2943 DisplayFatalError(&buf[1], 0, 1);
2949 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2952 if (appData.debugMode)
2953 fprintf(debugFP, "ics_type %d\n", ics_type);
2956 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2957 ics_type = ICS_FICS;
2959 if (appData.debugMode)
2960 fprintf(debugFP, "ics_type %d\n", ics_type);
2963 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2964 ics_type = ICS_CHESSNET;
2966 if (appData.debugMode)
2967 fprintf(debugFP, "ics_type %d\n", ics_type);
2972 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2973 looking_at(buf, &i, "Logging you in as \"*\"") ||
2974 looking_at(buf, &i, "will be \"*\""))) {
2975 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2979 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2981 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2982 DisplayIcsInteractionTitle(buf);
2983 have_set_title = TRUE;
2986 /* skip finger notes */
2987 if (started == STARTED_NONE &&
2988 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2989 (buf[i] == '1' && buf[i+1] == '0')) &&
2990 buf[i+2] == ':' && buf[i+3] == ' ') {
2991 started = STARTED_CHATTER;
2997 // [HGM] seekgraph: recognize sought lines and end-of-sought message
2998 if(appData.seekGraph) {
2999 if(soughtPending && MatchSoughtLine(buf+i)) {
3000 i = strstr(buf+i, "rated") - buf;
3001 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3002 next_out = leftover_start = i;
3003 started = STARTED_CHATTER;
3004 suppressKibitz = TRUE;
3007 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3008 && looking_at(buf, &i, "* ads displayed")) {
3009 soughtPending = FALSE;
3014 if(appData.autoRefresh) {
3015 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3016 int s = (ics_type == ICS_ICC); // ICC format differs
3018 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3019 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3020 looking_at(buf, &i, "*% "); // eat prompt
3021 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3022 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3023 next_out = i; // suppress
3026 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3027 char *p = star_match[0];
3029 if(seekGraphUp) RemoveSeekAd(atoi(p));
3030 while(*p && *p++ != ' '); // next
3032 looking_at(buf, &i, "*% "); // eat prompt
3033 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3040 /* skip formula vars */
3041 if (started == STARTED_NONE &&
3042 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3043 started = STARTED_CHATTER;
3048 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3049 if (appData.autoKibitz && started == STARTED_NONE &&
3050 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3051 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3052 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3053 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3054 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3055 suppressKibitz = TRUE;
3056 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3058 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3059 && (gameMode == IcsPlayingWhite)) ||
3060 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3061 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3062 started = STARTED_CHATTER; // own kibitz we simply discard
3064 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3065 parse_pos = 0; parse[0] = NULLCHAR;
3066 savingComment = TRUE;
3067 suppressKibitz = gameMode != IcsObserving ? 2 :
3068 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3072 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3073 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3074 && atoi(star_match[0])) {
3075 // suppress the acknowledgements of our own autoKibitz
3077 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3078 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3079 SendToPlayer(star_match[0], strlen(star_match[0]));
3080 if(looking_at(buf, &i, "*% ")) // eat prompt
3081 suppressKibitz = FALSE;
3085 } // [HGM] kibitz: end of patch
3087 // [HGM] chat: intercept tells by users for which we have an open chat window
3089 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3090 looking_at(buf, &i, "* whispers:") ||
3091 looking_at(buf, &i, "* kibitzes:") ||
3092 looking_at(buf, &i, "* shouts:") ||
3093 looking_at(buf, &i, "* c-shouts:") ||
3094 looking_at(buf, &i, "--> * ") ||
3095 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3096 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3097 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3098 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3100 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3101 chattingPartner = -1;
3103 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3104 for(p=0; p<MAX_CHAT; p++) {
3105 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3106 talker[0] = '['; strcat(talker, "] ");
3107 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3108 chattingPartner = p; break;
3111 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3112 for(p=0; p<MAX_CHAT; p++) {
3113 if(!strcmp("kibitzes", chatPartner[p])) {
3114 talker[0] = '['; strcat(talker, "] ");
3115 chattingPartner = p; break;
3118 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3119 for(p=0; p<MAX_CHAT; p++) {
3120 if(!strcmp("whispers", chatPartner[p])) {
3121 talker[0] = '['; strcat(talker, "] ");
3122 chattingPartner = p; break;
3125 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3126 if(buf[i-8] == '-' && buf[i-3] == 't')
3127 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3128 if(!strcmp("c-shouts", chatPartner[p])) {
3129 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3130 chattingPartner = p; break;
3133 if(chattingPartner < 0)
3134 for(p=0; p<MAX_CHAT; p++) {
3135 if(!strcmp("shouts", chatPartner[p])) {
3136 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3137 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3138 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3139 chattingPartner = p; break;
3143 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3144 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3145 talker[0] = 0; Colorize(ColorTell, FALSE);
3146 chattingPartner = p; break;
3148 if(chattingPartner<0) i = oldi; else {
3149 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3150 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3151 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3152 started = STARTED_COMMENT;
3153 parse_pos = 0; parse[0] = NULLCHAR;
3154 savingComment = 3 + chattingPartner; // counts as TRUE
3155 suppressKibitz = TRUE;
3158 } // [HGM] chat: end of patch
3161 if (appData.zippyTalk || appData.zippyPlay) {
3162 /* [DM] Backup address for color zippy lines */
3164 if (loggedOn == TRUE)
3165 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3166 (appData.zippyPlay && ZippyMatch(buf, &backup)));
3168 } // [DM] 'else { ' deleted
3170 /* Regular tells and says */
3171 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3172 looking_at(buf, &i, "* (your partner) tells you: ") ||
3173 looking_at(buf, &i, "* says: ") ||
3174 /* Don't color "message" or "messages" output */
3175 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3176 looking_at(buf, &i, "*. * at *:*: ") ||
3177 looking_at(buf, &i, "--* (*:*): ") ||
3178 /* Message notifications (same color as tells) */
3179 looking_at(buf, &i, "* has left a message ") ||
3180 looking_at(buf, &i, "* just sent you a message:\n") ||
3181 /* Whispers and kibitzes */
3182 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3183 looking_at(buf, &i, "* kibitzes: ") ||
3185 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3187 if (tkind == 1 && strchr(star_match[0], ':')) {
3188 /* Avoid "tells you:" spoofs in channels */
3191 if (star_match[0][0] == NULLCHAR ||
3192 strchr(star_match[0], ' ') ||
3193 (tkind == 3 && strchr(star_match[1], ' '))) {
3194 /* Reject bogus matches */
3197 if (appData.colorize) {
3198 if (oldi > next_out) {
3199 SendToPlayer(&buf[next_out], oldi - next_out);
3204 Colorize(ColorTell, FALSE);
3205 curColor = ColorTell;
3208 Colorize(ColorKibitz, FALSE);
3209 curColor = ColorKibitz;
3212 p = strrchr(star_match[1], '(');
3219 Colorize(ColorChannel1, FALSE);
3220 curColor = ColorChannel1;
3222 Colorize(ColorChannel, FALSE);
3223 curColor = ColorChannel;
3227 curColor = ColorNormal;
3231 if (started == STARTED_NONE && appData.autoComment &&
3232 (gameMode == IcsObserving ||
3233 gameMode == IcsPlayingWhite ||
3234 gameMode == IcsPlayingBlack)) {
3235 parse_pos = i - oldi;
3236 memcpy(parse, &buf[oldi], parse_pos);
3237 parse[parse_pos] = NULLCHAR;
3238 started = STARTED_COMMENT;
3239 savingComment = TRUE;
3241 started = STARTED_CHATTER;
3242 savingComment = FALSE;
3249 if (looking_at(buf, &i, "* s-shouts: ") ||
3250 looking_at(buf, &i, "* c-shouts: ")) {
3251 if (appData.colorize) {
3252 if (oldi > next_out) {
3253 SendToPlayer(&buf[next_out], oldi - next_out);
3256 Colorize(ColorSShout, FALSE);
3257 curColor = ColorSShout;
3260 started = STARTED_CHATTER;
3264 if (looking_at(buf, &i, "--->")) {
3269 if (looking_at(buf, &i, "* shouts: ") ||
3270 looking_at(buf, &i, "--> ")) {
3271 if (appData.colorize) {
3272 if (oldi > next_out) {
3273 SendToPlayer(&buf[next_out], oldi - next_out);
3276 Colorize(ColorShout, FALSE);
3277 curColor = ColorShout;
3280 started = STARTED_CHATTER;
3284 if (looking_at( buf, &i, "Challenge:")) {
3285 if (appData.colorize) {
3286 if (oldi > next_out) {
3287 SendToPlayer(&buf[next_out], oldi - next_out);
3290 Colorize(ColorChallenge, FALSE);
3291 curColor = ColorChallenge;
3297 if (looking_at(buf, &i, "* offers you") ||
3298 looking_at(buf, &i, "* offers to be") ||
3299 looking_at(buf, &i, "* would like to") ||
3300 looking_at(buf, &i, "* requests to") ||
3301 looking_at(buf, &i, "Your opponent offers") ||
3302 looking_at(buf, &i, "Your opponent requests")) {
3304 if (appData.colorize) {
3305 if (oldi > next_out) {
3306 SendToPlayer(&buf[next_out], oldi - next_out);
3309 Colorize(ColorRequest, FALSE);
3310 curColor = ColorRequest;
3315 if (looking_at(buf, &i, "* (*) seeking")) {
3316 if (appData.colorize) {
3317 if (oldi > next_out) {
3318 SendToPlayer(&buf[next_out], oldi - next_out);
3321 Colorize(ColorSeek, FALSE);
3322 curColor = ColorSeek;
3327 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3329 if (looking_at(buf, &i, "\\ ")) {
3330 if (prevColor != ColorNormal) {
3331 if (oldi > next_out) {
3332 SendToPlayer(&buf[next_out], oldi - next_out);
3335 Colorize(prevColor, TRUE);
3336 curColor = prevColor;
3338 if (savingComment) {
3339 parse_pos = i - oldi;
3340 memcpy(parse, &buf[oldi], parse_pos);
3341 parse[parse_pos] = NULLCHAR;
3342 started = STARTED_COMMENT;
3343 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3344 chattingPartner = savingComment - 3; // kludge to remember the box
3346 started = STARTED_CHATTER;
3351 if (looking_at(buf, &i, "Black Strength :") ||
3352 looking_at(buf, &i, "<<< style 10 board >>>") ||
3353 looking_at(buf, &i, "<10>") ||
3354 looking_at(buf, &i, "#@#")) {
3355 /* Wrong board style */
3357 SendToICS(ics_prefix);
3358 SendToICS("set style 12\n");
3359 SendToICS(ics_prefix);
3360 SendToICS("refresh\n");
3364 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3366 have_sent_ICS_logon = 1;
3370 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3371 (looking_at(buf, &i, "\n<12> ") ||
3372 looking_at(buf, &i, "<12> "))) {
3374 if (oldi > next_out) {
3375 SendToPlayer(&buf[next_out], oldi - next_out);
3378 started = STARTED_BOARD;
3383 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3384 looking_at(buf, &i, "<b1> ")) {
3385 if (oldi > next_out) {
3386 SendToPlayer(&buf[next_out], oldi - next_out);
3389 started = STARTED_HOLDINGS;
3394 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3396 /* Header for a move list -- first line */
3398 switch (ics_getting_history) {
3402 case BeginningOfGame:
3403 /* User typed "moves" or "oldmoves" while we
3404 were idle. Pretend we asked for these
3405 moves and soak them up so user can step
3406 through them and/or save them.
3409 gameMode = IcsObserving;
3412 ics_getting_history = H_GOT_UNREQ_HEADER;
3414 case EditGame: /*?*/
3415 case EditPosition: /*?*/
3416 /* Should above feature work in these modes too? */
3417 /* For now it doesn't */
3418 ics_getting_history = H_GOT_UNWANTED_HEADER;
3421 ics_getting_history = H_GOT_UNWANTED_HEADER;
3426 /* Is this the right one? */
3427 if (gameInfo.white && gameInfo.black &&
3428 strcmp(gameInfo.white, star_match[0]) == 0 &&
3429 strcmp(gameInfo.black, star_match[2]) == 0) {
3431 ics_getting_history = H_GOT_REQ_HEADER;
3434 case H_GOT_REQ_HEADER:
3435 case H_GOT_UNREQ_HEADER:
3436 case H_GOT_UNWANTED_HEADER:
3437 case H_GETTING_MOVES:
3438 /* Should not happen */
3439 DisplayError(_("Error gathering move list: two headers"), 0);
3440 ics_getting_history = H_FALSE;
3444 /* Save player ratings into gameInfo if needed */
3445 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3446 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3447 (gameInfo.whiteRating == -1 ||
3448 gameInfo.blackRating == -1)) {
3450 gameInfo.whiteRating = string_to_rating(star_match[1]);
3451 gameInfo.blackRating = string_to_rating(star_match[3]);
3452 if (appData.debugMode)
3453 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3454 gameInfo.whiteRating, gameInfo.blackRating);
3459 if (looking_at(buf, &i,
3460 "* * match, initial time: * minute*, increment: * second")) {
3461 /* Header for a move list -- second line */
3462 /* Initial board will follow if this is a wild game */
3463 if (gameInfo.event != NULL) free(gameInfo.event);
3464 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3465 gameInfo.event = StrSave(str);
3466 /* [HGM] we switched variant. Translate boards if needed. */
3467 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3471 if (looking_at(buf, &i, "Move ")) {
3472 /* Beginning of a move list */
3473 switch (ics_getting_history) {
3475 /* Normally should not happen */
3476 /* Maybe user hit reset while we were parsing */
3479 /* Happens if we are ignoring a move list that is not
3480 * the one we just requested. Common if the user
3481 * tries to observe two games without turning off
3484 case H_GETTING_MOVES:
3485 /* Should not happen */
3486 DisplayError(_("Error gathering move list: nested"), 0);
3487 ics_getting_history = H_FALSE;
3489 case H_GOT_REQ_HEADER:
3490 ics_getting_history = H_GETTING_MOVES;
3491 started = STARTED_MOVES;
3493 if (oldi > next_out) {
3494 SendToPlayer(&buf[next_out], oldi - next_out);
3497 case H_GOT_UNREQ_HEADER:
3498 ics_getting_history = H_GETTING_MOVES;
3499 started = STARTED_MOVES_NOHIDE;
3502 case H_GOT_UNWANTED_HEADER:
3503 ics_getting_history = H_FALSE;
3509 if (looking_at(buf, &i, "% ") ||
3510 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3511 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3512 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3513 soughtPending = FALSE;
3517 if(suppressKibitz) next_out = i;
3518 savingComment = FALSE;
3522 case STARTED_MOVES_NOHIDE:
3523 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3524 parse[parse_pos + i - oldi] = NULLCHAR;
3525 ParseGameHistory(parse);
3527 if (appData.zippyPlay && first.initDone) {
3528 FeedMovesToProgram(&first, forwardMostMove);
3529 if (gameMode == IcsPlayingWhite) {
3530 if (WhiteOnMove(forwardMostMove)) {
3531 if (first.sendTime) {
3532 if (first.useColors) {
3533 SendToProgram("black\n", &first);
3535 SendTimeRemaining(&first, TRUE);
3537 if (first.useColors) {
3538 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3540 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3541 first.maybeThinking = TRUE;
3543 if (first.usePlayother) {
3544 if (first.sendTime) {
3545 SendTimeRemaining(&first, TRUE);
3547 SendToProgram("playother\n", &first);
3553 } else if (gameMode == IcsPlayingBlack) {
3554 if (!WhiteOnMove(forwardMostMove)) {
3555 if (first.sendTime) {
3556 if (first.useColors) {
3557 SendToProgram("white\n", &first);
3559 SendTimeRemaining(&first, FALSE);
3561 if (first.useColors) {
3562 SendToProgram("black\n", &first);
3564 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3565 first.maybeThinking = TRUE;
3567 if (first.usePlayother) {
3568 if (first.sendTime) {
3569 SendTimeRemaining(&first, FALSE);
3571 SendToProgram("playother\n", &first);
3580 if (gameMode == IcsObserving && ics_gamenum == -1) {
3581 /* Moves came from oldmoves or moves command
3582 while we weren't doing anything else.
3584 currentMove = forwardMostMove;
3585 ClearHighlights();/*!!could figure this out*/
3586 flipView = appData.flipView;
3587 DrawPosition(TRUE, boards[currentMove]);
3588 DisplayBothClocks();
3589 snprintf(str, MSG_SIZ, "%s vs. %s",
3590 gameInfo.white, gameInfo.black);
3594 /* Moves were history of an active game */
3595 if (gameInfo.resultDetails != NULL) {
3596 free(gameInfo.resultDetails);
3597 gameInfo.resultDetails = NULL;
3600 HistorySet(parseList, backwardMostMove,
3601 forwardMostMove, currentMove-1);
3602 DisplayMove(currentMove - 1);
3603 if (started == STARTED_MOVES) next_out = i;
3604 started = STARTED_NONE;
3605 ics_getting_history = H_FALSE;
3608 case STARTED_OBSERVE:
3609 started = STARTED_NONE;
3610 SendToICS(ics_prefix);
3611 SendToICS("refresh\n");
3617 if(bookHit) { // [HGM] book: simulate book reply
3618 static char bookMove[MSG_SIZ]; // a bit generous?
3620 programStats.nodes = programStats.depth = programStats.time =
3621 programStats.score = programStats.got_only_move = 0;
3622 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3624 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3625 strcat(bookMove, bookHit);
3626 HandleMachineMove(bookMove, &first);
3631 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3632 started == STARTED_HOLDINGS ||
3633 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3634 /* Accumulate characters in move list or board */
3635 parse[parse_pos++] = buf[i];
3638 /* Start of game messages. Mostly we detect start of game
3639 when the first board image arrives. On some versions
3640 of the ICS, though, we need to do a "refresh" after starting
3641 to observe in order to get the current board right away. */
3642 if (looking_at(buf, &i, "Adding game * to observation list")) {
3643 started = STARTED_OBSERVE;
3647 /* Handle auto-observe */
3648 if (appData.autoObserve &&
3649 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3650 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3652 /* Choose the player that was highlighted, if any. */
3653 if (star_match[0][0] == '\033' ||
3654 star_match[1][0] != '\033') {
3655 player = star_match[0];
3657 player = star_match[2];
3659 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3660 ics_prefix, StripHighlightAndTitle(player));
3663 /* Save ratings from notify string */
3664 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3665 player1Rating = string_to_rating(star_match[1]);
3666 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3667 player2Rating = string_to_rating(star_match[3]);
3669 if (appData.debugMode)
3671 "Ratings from 'Game notification:' %s %d, %s %d\n",
3672 player1Name, player1Rating,
3673 player2Name, player2Rating);
3678 /* Deal with automatic examine mode after a game,
3679 and with IcsObserving -> IcsExamining transition */
3680 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3681 looking_at(buf, &i, "has made you an examiner of game *")) {
3683 int gamenum = atoi(star_match[0]);
3684 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3685 gamenum == ics_gamenum) {
3686 /* We were already playing or observing this game;
3687 no need to refetch history */
3688 gameMode = IcsExamining;
3690 pauseExamForwardMostMove = forwardMostMove;
3691 } else if (currentMove < forwardMostMove) {
3692 ForwardInner(forwardMostMove);
3695 /* I don't think this case really can happen */
3696 SendToICS(ics_prefix);
3697 SendToICS("refresh\n");
3702 /* Error messages */
3703 // if (ics_user_moved) {
3704 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3705 if (looking_at(buf, &i, "Illegal move") ||
3706 looking_at(buf, &i, "Not a legal move") ||
3707 looking_at(buf, &i, "Your king is in check") ||
3708 looking_at(buf, &i, "It isn't your turn") ||
3709 looking_at(buf, &i, "It is not your move")) {
3711 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3712 currentMove = forwardMostMove-1;
3713 DisplayMove(currentMove - 1); /* before DMError */
3714 DrawPosition(FALSE, boards[currentMove]);
3715 SwitchClocks(forwardMostMove-1); // [HGM] race
3716 DisplayBothClocks();
3718 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3724 if (looking_at(buf, &i, "still have time") ||
3725 looking_at(buf, &i, "not out of time") ||
3726 looking_at(buf, &i, "either player is out of time") ||
3727 looking_at(buf, &i, "has timeseal; checking")) {
3728 /* We must have called his flag a little too soon */
3729 whiteFlag = blackFlag = FALSE;
3733 if (looking_at(buf, &i, "added * seconds to") ||
3734 looking_at(buf, &i, "seconds were added to")) {
3735 /* Update the clocks */
3736 SendToICS(ics_prefix);
3737 SendToICS("refresh\n");
3741 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3742 ics_clock_paused = TRUE;
3747 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3748 ics_clock_paused = FALSE;
3753 /* Grab player ratings from the Creating: message.
3754 Note we have to check for the special case when
3755 the ICS inserts things like [white] or [black]. */
3756 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3757 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3759 0 player 1 name (not necessarily white)
3761 2 empty, white, or black (IGNORED)
3762 3 player 2 name (not necessarily black)
3765 The names/ratings are sorted out when the game
3766 actually starts (below).
3768 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3769 player1Rating = string_to_rating(star_match[1]);
3770 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3771 player2Rating = string_to_rating(star_match[4]);
3773 if (appData.debugMode)
3775 "Ratings from 'Creating:' %s %d, %s %d\n",
3776 player1Name, player1Rating,
3777 player2Name, player2Rating);
3782 /* Improved generic start/end-of-game messages */
3783 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3784 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3785 /* If tkind == 0: */
3786 /* star_match[0] is the game number */
3787 /* [1] is the white player's name */
3788 /* [2] is the black player's name */
3789 /* For end-of-game: */
3790 /* [3] is the reason for the game end */
3791 /* [4] is a PGN end game-token, preceded by " " */
3792 /* For start-of-game: */
3793 /* [3] begins with "Creating" or "Continuing" */
3794 /* [4] is " *" or empty (don't care). */
3795 int gamenum = atoi(star_match[0]);
3796 char *whitename, *blackname, *why, *endtoken;
3797 ChessMove endtype = EndOfFile;
3800 whitename = star_match[1];
3801 blackname = star_match[2];
3802 why = star_match[3];
3803 endtoken = star_match[4];
3805 whitename = star_match[1];
3806 blackname = star_match[3];
3807 why = star_match[5];
3808 endtoken = star_match[6];
3811 /* Game start messages */
3812 if (strncmp(why, "Creating ", 9) == 0 ||
3813 strncmp(why, "Continuing ", 11) == 0) {
3814 gs_gamenum = gamenum;
3815 safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3816 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3818 if (appData.zippyPlay) {
3819 ZippyGameStart(whitename, blackname);
3822 partnerBoardValid = FALSE; // [HGM] bughouse
3826 /* Game end messages */
3827 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3828 ics_gamenum != gamenum) {
3831 while (endtoken[0] == ' ') endtoken++;
3832 switch (endtoken[0]) {
3835 endtype = GameUnfinished;
3838 endtype = BlackWins;
3841 if (endtoken[1] == '/')
3842 endtype = GameIsDrawn;
3844 endtype = WhiteWins;
3847 GameEnds(endtype, why, GE_ICS);
3849 if (appData.zippyPlay && first.initDone) {
3850 ZippyGameEnd(endtype, why);
3851 if (first.pr == NULL) {
3852 /* Start the next process early so that we'll
3853 be ready for the next challenge */
3854 StartChessProgram(&first);
3856 /* Send "new" early, in case this command takes
3857 a long time to finish, so that we'll be ready
3858 for the next challenge. */
3859 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3863 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3867 if (looking_at(buf, &i, "Removing game * from observation") ||
3868 looking_at(buf, &i, "no longer observing game *") ||
3869 looking_at(buf, &i, "Game * (*) has no examiners")) {
3870 if (gameMode == IcsObserving &&
3871 atoi(star_match[0]) == ics_gamenum)
3873 /* icsEngineAnalyze */
3874 if (appData.icsEngineAnalyze) {