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;
799 cps->supportsNPS = UNKNOWN;
802 cps->optionSettings = appData.engOptions[n];
804 cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
805 cps->isUCI = appData.isUCI[n]; /* [AS] */
806 cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
808 if (appData.protocolVersion[n] > PROTOVER
809 || appData.protocolVersion[n] < 1)
814 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
815 appData.protocolVersion[n]);
816 if( (len > MSG_SIZ) && appData.debugMode )
817 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
819 DisplayFatalError(buf, 0, 2);
823 cps->protocolVersion = appData.protocolVersion[n];
826 InitEngineUCI( installDir, cps ); // [HGM] moved here from winboard.c, to make available in xboard
829 ChessProgramState *savCps;
835 if(WaitForEngine(savCps, LoadEngine)) return;
836 CommonEngineInit(); // recalculate time odds
837 if(gameInfo.variant != StringToVariant(appData.variant)) {
838 // we changed variant when loading the engine; this forces us to reset
839 Reset(TRUE, savCps != &first);
840 EditGameEvent(); // for consistency with other path, as Reset changes mode
842 InitChessProgram(savCps, FALSE);
843 SendToProgram("force\n", savCps);
844 DisplayMessage("", "");
845 if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
846 for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
852 ReplaceEngine(ChessProgramState *cps, int n)
856 appData.noChessProgram = FALSE;
857 appData.clockMode = TRUE;
859 if(n) return; // only startup first engine immediately; second can wait
860 savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
864 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
865 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
868 Load(ChessProgramState *cps, int i)
870 char *p, *q, buf[MSG_SIZ], command[MSG_SIZ];
871 if(engineLine[0]) { // an engine was selected from the combo box
872 snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
873 SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
874 ParseArgsFromString("-firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 -fn \"\"");
875 ParseArgsFromString(buf);
877 ReplaceEngine(cps, i);
881 while(q = strchr(p, SLASH)) p = q+1;
882 if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
883 if(engineDir[0] != NULLCHAR)
884 appData.directory[i] = engineDir;
885 else if(p != engineName) { // derive directory from engine path, when not given
887 appData.directory[i] = strdup(engineName);
889 } else appData.directory[i] = ".";
891 snprintf(command, MSG_SIZ, "%s %s", p, params);
894 appData.chessProgram[i] = strdup(p);
895 appData.isUCI[i] = isUCI;
896 appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
897 appData.hasOwnBookUCI[i] = hasBook;
898 if(!nickName[0]) useNick = FALSE;
899 if(useNick) ASSIGN(appData.pgnName[i], nickName);
902 q = firstChessProgramNames;
903 if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
904 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "\"%s\" -fd \"%s\"%s%s%s%s%s%s%s%s\n", p, appData.directory[i],
905 useNick ? " -fn \"" : "",
906 useNick ? nickName : "",
908 v1 ? " -firstProtocolVersion 1" : "",
909 hasBook ? "" : " -fNoOwnBookUCI",
910 isUCI ? " -fUCI" : "",
911 storeVariant ? " -variant " : "",
912 storeVariant ? VariantName(gameInfo.variant) : "");
913 firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
914 snprintf(firstChessProgramNames, len, "%s%s", q, buf);
917 ReplaceEngine(cps, i);
923 int matched, min, sec;
925 * Parse timeControl resource
927 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
928 appData.movesPerSession)) {
930 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
931 DisplayFatalError(buf, 0, 2);
935 * Parse searchTime resource
937 if (*appData.searchTime != NULLCHAR) {
938 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
940 searchTime = min * 60;
941 } else if (matched == 2) {
942 searchTime = min * 60 + sec;
945 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
946 DisplayFatalError(buf, 0, 2);
955 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
956 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
958 GetTimeMark(&programStartTime);
959 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
960 pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
963 programStats.ok_to_send = 1;
964 programStats.seen_stat = 0;
967 * Initialize game list
973 * Internet chess server status
975 if (appData.icsActive) {
976 appData.matchMode = FALSE;
977 appData.matchGames = 0;
979 appData.noChessProgram = !appData.zippyPlay;
981 appData.zippyPlay = FALSE;
982 appData.zippyTalk = FALSE;
983 appData.noChessProgram = TRUE;
985 if (*appData.icsHelper != NULLCHAR) {
986 appData.useTelnet = TRUE;
987 appData.telnetProgram = appData.icsHelper;
990 appData.zippyTalk = appData.zippyPlay = FALSE;
993 /* [AS] Initialize pv info list [HGM] and game state */
997 for( i=0; i<=framePtr; i++ ) {
998 pvInfoList[i].depth = -1;
999 boards[i][EP_STATUS] = EP_NONE;
1000 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1006 /* [AS] Adjudication threshold */
1007 adjudicateLossThreshold = appData.adjudicateLossThreshold;
1009 InitEngine(&first, 0);
1010 InitEngine(&second, 1);
1013 if (appData.icsActive) {
1014 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
1015 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1016 appData.clockMode = FALSE;
1017 first.sendTime = second.sendTime = 0;
1021 /* Override some settings from environment variables, for backward
1022 compatibility. Unfortunately it's not feasible to have the env
1023 vars just set defaults, at least in xboard. Ugh.
1025 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1030 if (!appData.icsActive) {
1034 /* Check for variants that are supported only in ICS mode,
1035 or not at all. Some that are accepted here nevertheless
1036 have bugs; see comments below.
1038 VariantClass variant = StringToVariant(appData.variant);
1040 case VariantBughouse: /* need four players and two boards */
1041 case VariantKriegspiel: /* need to hide pieces and move details */
1042 /* case VariantFischeRandom: (Fabien: moved below) */
1043 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1044 if( (len > MSG_SIZ) && appData.debugMode )
1045 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1047 DisplayFatalError(buf, 0, 2);
1050 case VariantUnknown:
1051 case VariantLoadable:
1061 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1062 if( (len > MSG_SIZ) && appData.debugMode )
1063 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1065 DisplayFatalError(buf, 0, 2);
1068 case VariantXiangqi: /* [HGM] repetition rules not implemented */
1069 case VariantFairy: /* [HGM] TestLegality definitely off! */
1070 case VariantGothic: /* [HGM] should work */
1071 case VariantCapablanca: /* [HGM] should work */
1072 case VariantCourier: /* [HGM] initial forced moves not implemented */
1073 case VariantShogi: /* [HGM] could still mate with pawn drop */
1074 case VariantKnightmate: /* [HGM] should work */
1075 case VariantCylinder: /* [HGM] untested */
1076 case VariantFalcon: /* [HGM] untested */
1077 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1078 offboard interposition not understood */
1079 case VariantNormal: /* definitely works! */
1080 case VariantWildCastle: /* pieces not automatically shuffled */
1081 case VariantNoCastle: /* pieces not automatically shuffled */
1082 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1083 case VariantLosers: /* should work except for win condition,
1084 and doesn't know captures are mandatory */
1085 case VariantSuicide: /* should work except for win condition,
1086 and doesn't know captures are mandatory */
1087 case VariantGiveaway: /* should work except for win condition,
1088 and doesn't know captures are mandatory */
1089 case VariantTwoKings: /* should work */
1090 case VariantAtomic: /* should work except for win condition */
1091 case Variant3Check: /* should work except for win condition */
1092 case VariantShatranj: /* should work except for all win conditions */
1093 case VariantMakruk: /* should work except for daw countdown */
1094 case VariantBerolina: /* might work if TestLegality is off */
1095 case VariantCapaRandom: /* should work */
1096 case VariantJanus: /* should work */
1097 case VariantSuper: /* experimental */
1098 case VariantGreat: /* experimental, requires legality testing to be off */
1099 case VariantSChess: /* S-Chess, should work */
1100 case VariantSpartan: /* should work */
1107 int NextIntegerFromString( char ** str, long * value )
1112 while( *s == ' ' || *s == '\t' ) {
1118 if( *s >= '0' && *s <= '9' ) {
1119 while( *s >= '0' && *s <= '9' ) {
1120 *value = *value * 10 + (*s - '0');
1132 int NextTimeControlFromString( char ** str, long * value )
1135 int result = NextIntegerFromString( str, &temp );
1138 *value = temp * 60; /* Minutes */
1139 if( **str == ':' ) {
1141 result = NextIntegerFromString( str, &temp );
1142 *value += temp; /* Seconds */
1149 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1150 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1151 int result = -1, type = 0; long temp, temp2;
1153 if(**str != ':') return -1; // old params remain in force!
1155 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1156 if( NextIntegerFromString( str, &temp ) ) return -1;
1157 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1160 /* time only: incremental or sudden-death time control */
1161 if(**str == '+') { /* increment follows; read it */
1163 if(**str == '!') type = *(*str)++; // Bronstein TC
1164 if(result = NextIntegerFromString( str, &temp2)) return -1;
1165 *inc = temp2 * 1000;
1166 if(**str == '.') { // read fraction of increment
1167 char *start = ++(*str);
1168 if(result = NextIntegerFromString( str, &temp2)) return -1;
1170 while(start++ < *str) temp2 /= 10;
1174 *moves = 0; *tc = temp * 1000; *incType = type;
1178 (*str)++; /* classical time control */
1179 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1190 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1191 { /* [HGM] get time to add from the multi-session time-control string */
1192 int incType, moves=1; /* kludge to force reading of first session */
1193 long time, increment;
1196 if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1197 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1199 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1200 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1201 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1202 if(movenr == -1) return time; /* last move before new session */
1203 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1204 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1205 if(!moves) return increment; /* current session is incremental */
1206 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1207 } while(movenr >= -1); /* try again for next session */
1209 return 0; // no new time quota on this move
1213 ParseTimeControl(tc, ti, mps)
1220 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1223 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1224 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1225 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1229 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1231 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1234 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1236 snprintf(buf, MSG_SIZ, ":%s", mytc);
1238 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1240 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1245 /* Parse second time control */
1248 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1256 timeControl_2 = tc2 * 1000;
1266 timeControl = tc1 * 1000;
1269 timeIncrement = ti * 1000; /* convert to ms */
1270 movesPerSession = 0;
1273 movesPerSession = mps;
1281 if (appData.debugMode) {
1282 fprintf(debugFP, "%s\n", programVersion);
1285 set_cont_sequence(appData.wrapContSeq);
1286 if (appData.matchGames > 0) {
1287 appData.matchMode = TRUE;
1288 } else if (appData.matchMode) {
1289 appData.matchGames = 1;
1291 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1292 appData.matchGames = appData.sameColorGames;
1293 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1294 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1295 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1298 if (appData.noChessProgram || first.protocolVersion == 1) {
1301 /* kludge: allow timeout for initial "feature" commands */
1303 DisplayMessage("", _("Starting chess program"));
1304 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1309 CalculateIndex(int index, int gameNr)
1310 { // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1312 if(index > 0) return index; // fixed nmber
1313 if(index == 0) return 1;
1314 res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1315 if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1320 LoadGameOrPosition(int gameNr)
1321 { // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1322 if (*appData.loadGameFile != NULLCHAR) {
1323 if (!LoadGameFromFile(appData.loadGameFile,
1324 CalculateIndex(appData.loadGameIndex, gameNr),
1325 appData.loadGameFile, FALSE)) {
1326 DisplayFatalError(_("Bad game file"), 0, 1);
1329 } else if (*appData.loadPositionFile != NULLCHAR) {
1330 if (!LoadPositionFromFile(appData.loadPositionFile,
1331 CalculateIndex(appData.loadPositionIndex, gameNr),
1332 appData.loadPositionFile)) {
1333 DisplayFatalError(_("Bad position file"), 0, 1);
1341 ReserveGame(int gameNr, char resChar)
1343 FILE *tf = fopen(appData.tourneyFile, "r+");
1344 char *p, *q, c, buf[MSG_SIZ];
1345 if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1346 safeStrCpy(buf, lastMsg, MSG_SIZ);
1347 DisplayMessage(_("Pick new game"), "");
1348 flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1349 ParseArgsFromFile(tf);
1350 p = q = appData.results;
1351 if(appData.debugMode) {
1352 char *r = appData.participants;
1353 fprintf(debugFP, "results = '%s'\n", p);
1354 while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1355 fprintf(debugFP, "\n");
1357 while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1359 q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1360 safeStrCpy(q, p, strlen(p) + 2);
1361 if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1362 if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1363 if(nextGame <= appData.matchGames && resChar != ' ') { // already reserve next game, if tourney not yet done
1364 if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1367 fseek(tf, -(strlen(p)+4), SEEK_END);
1369 if(c != '"') // depending on DOS or Unix line endings we can be one off
1370 fseek(tf, -(strlen(p)+2), SEEK_END);
1371 else fseek(tf, -(strlen(p)+3), SEEK_END);
1372 fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1373 DisplayMessage(buf, "");
1374 free(p); appData.results = q;
1375 if(nextGame <= appData.matchGames && resChar != ' ' &&
1376 (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1377 UnloadEngine(&first); // next game belongs to other pairing;
1378 UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1383 MatchEvent(int mode)
1384 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1386 if(matchMode) { // already in match mode: switch it off
1388 appData.matchGames = appData.tourneyFile[0] ? nextGame: matchGame; // kludge to let match terminate after next game.
1389 ModeHighlight(); // kludgey way to remove checkmark...
1392 // if(gameMode != BeginningOfGame) {
1393 // DisplayError(_("You can only start a match from the initial position."), 0);
1397 appData.matchGames = appData.defaultMatchGames;
1398 /* Set up machine vs. machine match */
1400 NextTourneyGame(0, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1401 if(appData.tourneyFile[0]) {
1403 if(nextGame > appData.matchGames) {
1405 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1406 DisplayError(buf, 0);
1407 appData.tourneyFile[0] = 0;
1411 if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically
1412 DisplayFatalError(_("Can't have a match with no chess programs"),
1417 matchGame = roundNr = 1;
1418 first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
\r
1423 InitBackEnd3 P((void))
1425 GameMode initialMode;
1429 InitChessProgram(&first, startedFromSetupPosition);
1431 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1432 free(programVersion);
1433 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1434 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1437 if (appData.icsActive) {
1439 /* [DM] Make a console window if needed [HGM] merged ifs */
1445 if (*appData.icsCommPort != NULLCHAR)
1446 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1447 appData.icsCommPort);
1449 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1450 appData.icsHost, appData.icsPort);
1452 if( (len > MSG_SIZ) && appData.debugMode )
1453 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1455 DisplayFatalError(buf, err, 1);
1460 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1462 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1463 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1464 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1465 } else if (appData.noChessProgram) {
1471 if (*appData.cmailGameName != NULLCHAR) {
1473 OpenLoopback(&cmailPR);
1475 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1479 DisplayMessage("", "");
1480 if (StrCaseCmp(appData.initialMode, "") == 0) {
1481 initialMode = BeginningOfGame;
1482 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1483 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1484 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1485 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1488 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1489 initialMode = TwoMachinesPlay;
1490 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1491 initialMode = AnalyzeFile;
1492 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1493 initialMode = AnalyzeMode;
1494 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1495 initialMode = MachinePlaysWhite;
1496 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1497 initialMode = MachinePlaysBlack;
1498 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1499 initialMode = EditGame;
1500 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1501 initialMode = EditPosition;
1502 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1503 initialMode = Training;
1505 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1506 if( (len > MSG_SIZ) && appData.debugMode )
1507 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1509 DisplayFatalError(buf, 0, 2);
1513 if (appData.matchMode) {
1514 if(appData.tourneyFile[0]) { // start tourney from command line
1516 if(f = fopen(appData.tourneyFile, "r")) {
1517 ParseArgsFromFile(f); // make sure tourney parmeters re known
1519 } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1522 } else if (*appData.cmailGameName != NULLCHAR) {
1523 /* Set up cmail mode */
1524 ReloadCmailMsgEvent(TRUE);
1526 /* Set up other modes */
1527 if (initialMode == AnalyzeFile) {
1528 if (*appData.loadGameFile == NULLCHAR) {
1529 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1533 if (*appData.loadGameFile != NULLCHAR) {
1534 (void) LoadGameFromFile(appData.loadGameFile,
1535 appData.loadGameIndex,
1536 appData.loadGameFile, TRUE);
1537 } else if (*appData.loadPositionFile != NULLCHAR) {
1538 (void) LoadPositionFromFile(appData.loadPositionFile,
1539 appData.loadPositionIndex,
1540 appData.loadPositionFile);
1541 /* [HGM] try to make self-starting even after FEN load */
1542 /* to allow automatic setup of fairy variants with wtm */
1543 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1544 gameMode = BeginningOfGame;
1545 setboardSpoiledMachineBlack = 1;
1547 /* [HGM] loadPos: make that every new game uses the setup */
1548 /* from file as long as we do not switch variant */
1549 if(!blackPlaysFirst) {
1550 startedFromPositionFile = TRUE;
1551 CopyBoard(filePosition, boards[0]);
1554 if (initialMode == AnalyzeMode) {
1555 if (appData.noChessProgram) {
1556 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1559 if (appData.icsActive) {
1560 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1564 } else if (initialMode == AnalyzeFile) {
1565 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1566 ShowThinkingEvent();
1568 AnalysisPeriodicEvent(1);
1569 } else if (initialMode == MachinePlaysWhite) {
1570 if (appData.noChessProgram) {
1571 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1575 if (appData.icsActive) {
1576 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1580 MachineWhiteEvent();
1581 } else if (initialMode == MachinePlaysBlack) {
1582 if (appData.noChessProgram) {
1583 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1587 if (appData.icsActive) {
1588 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1592 MachineBlackEvent();
1593 } else if (initialMode == TwoMachinesPlay) {
1594 if (appData.noChessProgram) {
1595 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1599 if (appData.icsActive) {
1600 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1605 } else if (initialMode == EditGame) {
1607 } else if (initialMode == EditPosition) {
1608 EditPositionEvent();
1609 } else if (initialMode == Training) {
1610 if (*appData.loadGameFile == NULLCHAR) {
1611 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1620 * Establish will establish a contact to a remote host.port.
1621 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1622 * used to talk to the host.
1623 * Returns 0 if okay, error code if not.
1630 if (*appData.icsCommPort != NULLCHAR) {
1631 /* Talk to the host through a serial comm port */
1632 return OpenCommPort(appData.icsCommPort, &icsPR);
1634 } else if (*appData.gateway != NULLCHAR) {
1635 if (*appData.remoteShell == NULLCHAR) {
1636 /* Use the rcmd protocol to run telnet program on a gateway host */
1637 snprintf(buf, sizeof(buf), "%s %s %s",
1638 appData.telnetProgram, appData.icsHost, appData.icsPort);
1639 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1642 /* Use the rsh program to run telnet program on a gateway host */
1643 if (*appData.remoteUser == NULLCHAR) {
1644 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1645 appData.gateway, appData.telnetProgram,
1646 appData.icsHost, appData.icsPort);
1648 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1649 appData.remoteShell, appData.gateway,
1650 appData.remoteUser, appData.telnetProgram,
1651 appData.icsHost, appData.icsPort);
1653 return StartChildProcess(buf, "", &icsPR);
1656 } else if (appData.useTelnet) {
1657 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1660 /* TCP socket interface differs somewhat between
1661 Unix and NT; handle details in the front end.
1663 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1667 void EscapeExpand(char *p, char *q)
1668 { // [HGM] initstring: routine to shape up string arguments
1669 while(*p++ = *q++) if(p[-1] == '\\')
1671 case 'n': p[-1] = '\n'; break;
1672 case 'r': p[-1] = '\r'; break;
1673 case 't': p[-1] = '\t'; break;
1674 case '\\': p[-1] = '\\'; break;
1675 case 0: *p = 0; return;
1676 default: p[-1] = q[-1]; break;
1681 show_bytes(fp, buf, count)
1687 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1688 fprintf(fp, "\\%03o", *buf & 0xff);
1697 /* Returns an errno value */
1699 OutputMaybeTelnet(pr, message, count, outError)
1705 char buf[8192], *p, *q, *buflim;
1706 int left, newcount, outcount;
1708 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1709 *appData.gateway != NULLCHAR) {
1710 if (appData.debugMode) {
1711 fprintf(debugFP, ">ICS: ");
1712 show_bytes(debugFP, message, count);
1713 fprintf(debugFP, "\n");
1715 return OutputToProcess(pr, message, count, outError);
1718 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1725 if (appData.debugMode) {
1726 fprintf(debugFP, ">ICS: ");
1727 show_bytes(debugFP, buf, newcount);
1728 fprintf(debugFP, "\n");
1730 outcount = OutputToProcess(pr, buf, newcount, outError);
1731 if (outcount < newcount) return -1; /* to be sure */
1738 } else if (((unsigned char) *p) == TN_IAC) {
1739 *q++ = (char) TN_IAC;
1746 if (appData.debugMode) {
1747 fprintf(debugFP, ">ICS: ");
1748 show_bytes(debugFP, buf, newcount);
1749 fprintf(debugFP, "\n");
1751 outcount = OutputToProcess(pr, buf, newcount, outError);
1752 if (outcount < newcount) return -1; /* to be sure */
1757 read_from_player(isr, closure, message, count, error)
1764 int outError, outCount;
1765 static int gotEof = 0;
1767 /* Pass data read from player on to ICS */
1770 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1771 if (outCount < count) {
1772 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1774 } else if (count < 0) {
1775 RemoveInputSource(isr);
1776 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1777 } else if (gotEof++ > 0) {
1778 RemoveInputSource(isr);
1779 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1785 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1786 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1787 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1788 SendToICS("date\n");
1789 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1792 /* added routine for printf style output to ics */
1793 void ics_printf(char *format, ...)
1795 char buffer[MSG_SIZ];
1798 va_start(args, format);
1799 vsnprintf(buffer, sizeof(buffer), format, args);
1800 buffer[sizeof(buffer)-1] = '\0';
1809 int count, outCount, outError;
1811 if (icsPR == NULL) return;
1814 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1815 if (outCount < count) {
1816 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1820 /* This is used for sending logon scripts to the ICS. Sending
1821 without a delay causes problems when using timestamp on ICC
1822 (at least on my machine). */
1824 SendToICSDelayed(s,msdelay)
1828 int count, outCount, outError;
1830 if (icsPR == NULL) return;
1833 if (appData.debugMode) {
1834 fprintf(debugFP, ">ICS: ");
1835 show_bytes(debugFP, s, count);
1836 fprintf(debugFP, "\n");
1838 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1840 if (outCount < count) {
1841 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1846 /* Remove all highlighting escape sequences in s
1847 Also deletes any suffix starting with '('
1850 StripHighlightAndTitle(s)
1853 static char retbuf[MSG_SIZ];
1856 while (*s != NULLCHAR) {
1857 while (*s == '\033') {
1858 while (*s != NULLCHAR && !isalpha(*s)) s++;
1859 if (*s != NULLCHAR) s++;
1861 while (*s != NULLCHAR && *s != '\033') {
1862 if (*s == '(' || *s == '[') {
1873 /* Remove all highlighting escape sequences in s */
1878 static char retbuf[MSG_SIZ];
1881 while (*s != NULLCHAR) {
1882 while (*s == '\033') {
1883 while (*s != NULLCHAR && !isalpha(*s)) s++;
1884 if (*s != NULLCHAR) s++;
1886 while (*s != NULLCHAR && *s != '\033') {
1894 char *variantNames[] = VARIANT_NAMES;
1899 return variantNames[v];
1903 /* Identify a variant from the strings the chess servers use or the
1904 PGN Variant tag names we use. */
1911 VariantClass v = VariantNormal;
1912 int i, found = FALSE;
1918 /* [HGM] skip over optional board-size prefixes */
1919 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1920 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1921 while( *e++ != '_');
1924 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1928 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1929 if (StrCaseStr(e, variantNames[i])) {
1930 v = (VariantClass) i;
1937 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1938 || StrCaseStr(e, "wild/fr")
1939 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1940 v = VariantFischeRandom;
1941 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1942 (i = 1, p = StrCaseStr(e, "w"))) {
1944 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1951 case 0: /* FICS only, actually */
1953 /* Castling legal even if K starts on d-file */
1954 v = VariantWildCastle;
1959 /* Castling illegal even if K & R happen to start in
1960 normal positions. */
1961 v = VariantNoCastle;
1974 /* Castling legal iff K & R start in normal positions */
1980 /* Special wilds for position setup; unclear what to do here */
1981 v = VariantLoadable;
1984 /* Bizarre ICC game */
1985 v = VariantTwoKings;
1988 v = VariantKriegspiel;
1994 v = VariantFischeRandom;
1997 v = VariantCrazyhouse;
2000 v = VariantBughouse;
2006 /* Not quite the same as FICS suicide! */
2007 v = VariantGiveaway;
2013 v = VariantShatranj;
2016 /* Temporary names for future ICC types. The name *will* change in
2017 the next xboard/WinBoard release after ICC defines it. */
2055 v = VariantCapablanca;
2058 v = VariantKnightmate;
2064 v = VariantCylinder;
2070 v = VariantCapaRandom;
2073 v = VariantBerolina;
2085 /* Found "wild" or "w" in the string but no number;
2086 must assume it's normal chess. */
2090 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2091 if( (len > MSG_SIZ) && appData.debugMode )
2092 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2094 DisplayError(buf, 0);
2100 if (appData.debugMode) {
2101 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2102 e, wnum, VariantName(v));
2107 static int leftover_start = 0, leftover_len = 0;
2108 char star_match[STAR_MATCH_N][MSG_SIZ];
2110 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2111 advance *index beyond it, and set leftover_start to the new value of
2112 *index; else return FALSE. If pattern contains the character '*', it
2113 matches any sequence of characters not containing '\r', '\n', or the
2114 character following the '*' (if any), and the matched sequence(s) are
2115 copied into star_match.
2118 looking_at(buf, index, pattern)
2123 char *bufp = &buf[*index], *patternp = pattern;
2125 char *matchp = star_match[0];
2128 if (*patternp == NULLCHAR) {
2129 *index = leftover_start = bufp - buf;
2133 if (*bufp == NULLCHAR) return FALSE;
2134 if (*patternp == '*') {
2135 if (*bufp == *(patternp + 1)) {
2137 matchp = star_match[++star_count];
2141 } else if (*bufp == '\n' || *bufp == '\r') {
2143 if (*patternp == NULLCHAR)
2148 *matchp++ = *bufp++;
2152 if (*patternp != *bufp) return FALSE;
2159 SendToPlayer(data, length)
2163 int error, outCount;
2164 outCount = OutputToProcess(NoProc, data, length, &error);
2165 if (outCount < length) {
2166 DisplayFatalError(_("Error writing to display"), error, 1);
2171 PackHolding(packed, holding)
2183 switch (runlength) {
2194 sprintf(q, "%d", runlength);
2206 /* Telnet protocol requests from the front end */
2208 TelnetRequest(ddww, option)
2209 unsigned char ddww, option;
2211 unsigned char msg[3];
2212 int outCount, outError;
2214 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2216 if (appData.debugMode) {
2217 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2233 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2242 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2245 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2250 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2252 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2259 if (!appData.icsActive) return;
2260 TelnetRequest(TN_DO, TN_ECHO);
2266 if (!appData.icsActive) return;
2267 TelnetRequest(TN_DONT, TN_ECHO);
2271 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2273 /* put the holdings sent to us by the server on the board holdings area */
2274 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2278 if(gameInfo.holdingsWidth < 2) return;
2279 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2280 return; // prevent overwriting by pre-board holdings
2282 if( (int)lowestPiece >= BlackPawn ) {
2285 holdingsStartRow = BOARD_HEIGHT-1;
2288 holdingsColumn = BOARD_WIDTH-1;
2289 countsColumn = BOARD_WIDTH-2;
2290 holdingsStartRow = 0;
2294 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2295 board[i][holdingsColumn] = EmptySquare;
2296 board[i][countsColumn] = (ChessSquare) 0;
2298 while( (p=*holdings++) != NULLCHAR ) {
2299 piece = CharToPiece( ToUpper(p) );
2300 if(piece == EmptySquare) continue;
2301 /*j = (int) piece - (int) WhitePawn;*/
2302 j = PieceToNumber(piece);
2303 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2304 if(j < 0) continue; /* should not happen */
2305 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2306 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2307 board[holdingsStartRow+j*direction][countsColumn]++;
2313 VariantSwitch(Board board, VariantClass newVariant)
2315 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2316 static Board oldBoard;
2318 startedFromPositionFile = FALSE;
2319 if(gameInfo.variant == newVariant) return;
2321 /* [HGM] This routine is called each time an assignment is made to
2322 * gameInfo.variant during a game, to make sure the board sizes
2323 * are set to match the new variant. If that means adding or deleting
2324 * holdings, we shift the playing board accordingly
2325 * This kludge is needed because in ICS observe mode, we get boards
2326 * of an ongoing game without knowing the variant, and learn about the
2327 * latter only later. This can be because of the move list we requested,
2328 * in which case the game history is refilled from the beginning anyway,
2329 * but also when receiving holdings of a crazyhouse game. In the latter
2330 * case we want to add those holdings to the already received position.
2334 if (appData.debugMode) {
2335 fprintf(debugFP, "Switch board from %s to %s\n",
2336 VariantName(gameInfo.variant), VariantName(newVariant));
2337 setbuf(debugFP, NULL);
2339 shuffleOpenings = 0; /* [HGM] shuffle */
2340 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2344 newWidth = 9; newHeight = 9;
2345 gameInfo.holdingsSize = 7;
2346 case VariantBughouse:
2347 case VariantCrazyhouse:
2348 newHoldingsWidth = 2; break;
2352 newHoldingsWidth = 2;
2353 gameInfo.holdingsSize = 8;
2356 case VariantCapablanca:
2357 case VariantCapaRandom:
2360 newHoldingsWidth = gameInfo.holdingsSize = 0;
2363 if(newWidth != gameInfo.boardWidth ||
2364 newHeight != gameInfo.boardHeight ||
2365 newHoldingsWidth != gameInfo.holdingsWidth ) {
2367 /* shift position to new playing area, if needed */
2368 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2369 for(i=0; i<BOARD_HEIGHT; i++)
2370 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2371 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2373 for(i=0; i<newHeight; i++) {
2374 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2375 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2377 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2378 for(i=0; i<BOARD_HEIGHT; i++)
2379 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2380 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2383 gameInfo.boardWidth = newWidth;
2384 gameInfo.boardHeight = newHeight;
2385 gameInfo.holdingsWidth = newHoldingsWidth;
2386 gameInfo.variant = newVariant;
2387 InitDrawingSizes(-2, 0);
2388 } else gameInfo.variant = newVariant;
2389 CopyBoard(oldBoard, board); // remember correctly formatted board
2390 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2391 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2394 static int loggedOn = FALSE;
2396 /*-- Game start info cache: --*/
2398 char gs_kind[MSG_SIZ];
2399 static char player1Name[128] = "";
2400 static char player2Name[128] = "";
2401 static char cont_seq[] = "\n\\ ";
2402 static int player1Rating = -1;
2403 static int player2Rating = -1;
2404 /*----------------------------*/
2406 ColorClass curColor = ColorNormal;
2407 int suppressKibitz = 0;
2410 Boolean soughtPending = FALSE;
2411 Boolean seekGraphUp;
2412 #define MAX_SEEK_ADS 200
2414 char *seekAdList[MAX_SEEK_ADS];
2415 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2416 float tcList[MAX_SEEK_ADS];
2417 char colorList[MAX_SEEK_ADS];
2418 int nrOfSeekAds = 0;
2419 int minRating = 1010, maxRating = 2800;
2420 int hMargin = 10, vMargin = 20, h, w;
2421 extern int squareSize, lineGap;
2426 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2427 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2428 if(r < minRating+100 && r >=0 ) r = minRating+100;
2429 if(r > maxRating) r = maxRating;
2430 if(tc < 1.) tc = 1.;
2431 if(tc > 95.) tc = 95.;
2432 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2433 y = ((double)r - minRating)/(maxRating - minRating)
2434 * (h-vMargin-squareSize/8-1) + vMargin;
2435 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2436 if(strstr(seekAdList[i], " u ")) color = 1;
2437 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2438 !strstr(seekAdList[i], "bullet") &&
2439 !strstr(seekAdList[i], "blitz") &&
2440 !strstr(seekAdList[i], "standard") ) color = 2;
2441 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2442 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2446 AddAd(char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2448 char buf[MSG_SIZ], *ext = "";
2449 VariantClass v = StringToVariant(type);
2450 if(strstr(type, "wild")) {
2451 ext = type + 4; // append wild number
2452 if(v == VariantFischeRandom) type = "chess960"; else
2453 if(v == VariantLoadable) type = "setup"; else
2454 type = VariantName(v);
2456 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2457 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2458 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2459 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2460 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2461 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2462 seekNrList[nrOfSeekAds] = nr;
2463 zList[nrOfSeekAds] = 0;
2464 seekAdList[nrOfSeekAds++] = StrSave(buf);
2465 if(plot) PlotSeekAd(nrOfSeekAds-1);
2472 int x = xList[i], y = yList[i], d=squareSize/4, k;
2473 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2474 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2475 // now replot every dot that overlapped
2476 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2477 int xx = xList[k], yy = yList[k];
2478 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2479 DrawSeekDot(xx, yy, colorList[k]);
2484 RemoveSeekAd(int nr)
2487 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2489 if(seekAdList[i]) free(seekAdList[i]);
2490 seekAdList[i] = seekAdList[--nrOfSeekAds];
2491 seekNrList[i] = seekNrList[nrOfSeekAds];
2492 ratingList[i] = ratingList[nrOfSeekAds];
2493 colorList[i] = colorList[nrOfSeekAds];
2494 tcList[i] = tcList[nrOfSeekAds];
2495 xList[i] = xList[nrOfSeekAds];
2496 yList[i] = yList[nrOfSeekAds];
2497 zList[i] = zList[nrOfSeekAds];
2498 seekAdList[nrOfSeekAds] = NULL;
2504 MatchSoughtLine(char *line)
2506 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2507 int nr, base, inc, u=0; char dummy;
2509 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2510 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2512 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2513 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2514 // match: compact and save the line
2515 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2525 if(!seekGraphUp) return FALSE;
2526 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2527 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2529 DrawSeekBackground(0, 0, w, h);
2530 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2531 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2532 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2533 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2535 DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2538 snprintf(buf, MSG_SIZ, "%d", i);
2539 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2542 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2543 for(i=1; i<100; i+=(i<10?1:5)) {
2544 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2545 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2546 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2548 snprintf(buf, MSG_SIZ, "%d", i);
2549 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2552 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2556 int SeekGraphClick(ClickType click, int x, int y, int moving)
2558 static int lastDown = 0, displayed = 0, lastSecond;
2559 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2560 if(click == Release || moving) return FALSE;
2562 soughtPending = TRUE;
2563 SendToICS(ics_prefix);
2564 SendToICS("sought\n"); // should this be "sought all"?
2565 } else { // issue challenge based on clicked ad
2566 int dist = 10000; int i, closest = 0, second = 0;
2567 for(i=0; i<nrOfSeekAds; i++) {
2568 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2569 if(d < dist) { dist = d; closest = i; }
2570 second += (d - zList[i] < 120); // count in-range ads
2571 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2575 second = (second > 1);
2576 if(displayed != closest || second != lastSecond) {
2577 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2578 lastSecond = second; displayed = closest;
2580 if(click == Press) {
2581 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2584 } // on press 'hit', only show info
2585 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2586 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2587 SendToICS(ics_prefix);
2589 return TRUE; // let incoming board of started game pop down the graph
2590 } else if(click == Release) { // release 'miss' is ignored
2591 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2592 if(moving == 2) { // right up-click
2593 nrOfSeekAds = 0; // refresh graph
2594 soughtPending = TRUE;
2595 SendToICS(ics_prefix);
2596 SendToICS("sought\n"); // should this be "sought all"?
2599 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2600 // press miss or release hit 'pop down' seek graph
2601 seekGraphUp = FALSE;
2602 DrawPosition(TRUE, NULL);
2608 read_from_ics(isr, closure, data, count, error)
2615 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2616 #define STARTED_NONE 0
2617 #define STARTED_MOVES 1
2618 #define STARTED_BOARD 2
2619 #define STARTED_OBSERVE 3
2620 #define STARTED_HOLDINGS 4
2621 #define STARTED_CHATTER 5
2622 #define STARTED_COMMENT 6
2623 #define STARTED_MOVES_NOHIDE 7
2625 static int started = STARTED_NONE;
2626 static char parse[20000];
2627 static int parse_pos = 0;
2628 static char buf[BUF_SIZE + 1];
2629 static int firstTime = TRUE, intfSet = FALSE;
2630 static ColorClass prevColor = ColorNormal;
2631 static int savingComment = FALSE;
2632 static int cmatch = 0; // continuation sequence match
2639 int backup; /* [DM] For zippy color lines */
2641 char talker[MSG_SIZ]; // [HGM] chat
2644 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2646 if (appData.debugMode) {
2648 fprintf(debugFP, "<ICS: ");
2649 show_bytes(debugFP, data, count);
2650 fprintf(debugFP, "\n");
2654 if (appData.debugMode) { int f = forwardMostMove;
2655 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2656 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2657 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2660 /* If last read ended with a partial line that we couldn't parse,
2661 prepend it to the new read and try again. */
2662 if (leftover_len > 0) {
2663 for (i=0; i<leftover_len; i++)
2664 buf[i] = buf[leftover_start + i];
2667 /* copy new characters into the buffer */
2668 bp = buf + leftover_len;
2669 buf_len=leftover_len;
2670 for (i=0; i<count; i++)
2673 if (data[i] == '\r')
2676 // join lines split by ICS?
2677 if (!appData.noJoin)
2680 Joining just consists of finding matches against the
2681 continuation sequence, and discarding that sequence
2682 if found instead of copying it. So, until a match
2683 fails, there's nothing to do since it might be the
2684 complete sequence, and thus, something we don't want
2687 if (data[i] == cont_seq[cmatch])
2690 if (cmatch == strlen(cont_seq))
2692 cmatch = 0; // complete match. just reset the counter
2695 it's possible for the ICS to not include the space
2696 at the end of the last word, making our [correct]
2697 join operation fuse two separate words. the server
2698 does this when the space occurs at the width setting.
2700 if (!buf_len || buf[buf_len-1] != ' ')
2711 match failed, so we have to copy what matched before
2712 falling through and copying this character. In reality,
2713 this will only ever be just the newline character, but
2714 it doesn't hurt to be precise.
2716 strncpy(bp, cont_seq, cmatch);
2728 buf[buf_len] = NULLCHAR;
2729 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2734 while (i < buf_len) {
2735 /* Deal with part of the TELNET option negotiation
2736 protocol. We refuse to do anything beyond the
2737 defaults, except that we allow the WILL ECHO option,
2738 which ICS uses to turn off password echoing when we are
2739 directly connected to it. We reject this option
2740 if localLineEditing mode is on (always on in xboard)
2741 and we are talking to port 23, which might be a real
2742 telnet server that will try to keep WILL ECHO on permanently.
2744 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2745 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2746 unsigned char option;
2748 switch ((unsigned char) buf[++i]) {
2750 if (appData.debugMode)
2751 fprintf(debugFP, "\n<WILL ");
2752 switch (option = (unsigned char) buf[++i]) {
2754 if (appData.debugMode)
2755 fprintf(debugFP, "ECHO ");
2756 /* Reply only if this is a change, according
2757 to the protocol rules. */
2758 if (remoteEchoOption) break;
2759 if (appData.localLineEditing &&
2760 atoi(appData.icsPort) == TN_PORT) {
2761 TelnetRequest(TN_DONT, TN_ECHO);
2764 TelnetRequest(TN_DO, TN_ECHO);
2765 remoteEchoOption = TRUE;
2769 if (appData.debugMode)
2770 fprintf(debugFP, "%d ", option);
2771 /* Whatever this is, we don't want it. */
2772 TelnetRequest(TN_DONT, option);
2777 if (appData.debugMode)
2778 fprintf(debugFP, "\n<WONT ");
2779 switch (option = (unsigned char) buf[++i]) {
2781 if (appData.debugMode)
2782 fprintf(debugFP, "ECHO ");
2783 /* Reply only if this is a change, according
2784 to the protocol rules. */
2785 if (!remoteEchoOption) break;
2787 TelnetRequest(TN_DONT, TN_ECHO);
2788 remoteEchoOption = FALSE;
2791 if (appData.debugMode)
2792 fprintf(debugFP, "%d ", (unsigned char) option);
2793 /* Whatever this is, it must already be turned
2794 off, because we never agree to turn on
2795 anything non-default, so according to the
2796 protocol rules, we don't reply. */
2801 if (appData.debugMode)
2802 fprintf(debugFP, "\n<DO ");
2803 switch (option = (unsigned char) buf[++i]) {
2805 /* Whatever this is, we refuse to do it. */
2806 if (appData.debugMode)
2807 fprintf(debugFP, "%d ", option);
2808 TelnetRequest(TN_WONT, option);
2813 if (appData.debugMode)
2814 fprintf(debugFP, "\n<DONT ");
2815 switch (option = (unsigned char) buf[++i]) {
2817 if (appData.debugMode)
2818 fprintf(debugFP, "%d ", option);
2819 /* Whatever this is, we are already not doing
2820 it, because we never agree to do anything
2821 non-default, so according to the protocol
2822 rules, we don't reply. */
2827 if (appData.debugMode)
2828 fprintf(debugFP, "\n<IAC ");
2829 /* Doubled IAC; pass it through */
2833 if (appData.debugMode)
2834 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2835 /* Drop all other telnet commands on the floor */
2838 if (oldi > next_out)
2839 SendToPlayer(&buf[next_out], oldi - next_out);
2845 /* OK, this at least will *usually* work */
2846 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2850 if (loggedOn && !intfSet) {
2851 if (ics_type == ICS_ICC) {
2852 snprintf(str, MSG_SIZ,
2853 "/set-quietly interface %s\n/set-quietly style 12\n",
2855 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2856 strcat(str, "/set-2 51 1\n/set seek 1\n");
2857 } else if (ics_type == ICS_CHESSNET) {
2858 snprintf(str, MSG_SIZ, "/style 12\n");
2860 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2861 strcat(str, programVersion);
2862 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2863 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2864 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2866 strcat(str, "$iset nohighlight 1\n");
2868 strcat(str, "$iset lock 1\n$style 12\n");
2871 NotifyFrontendLogin();
2875 if (started == STARTED_COMMENT) {
2876 /* Accumulate characters in comment */
2877 parse[parse_pos++] = buf[i];
2878 if (buf[i] == '\n') {
2879 parse[parse_pos] = NULLCHAR;
2880 if(chattingPartner>=0) {
2882 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2883 OutputChatMessage(chattingPartner, mess);
2884 chattingPartner = -1;
2885 next_out = i+1; // [HGM] suppress printing in ICS window
2887 if(!suppressKibitz) // [HGM] kibitz
2888 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2889 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2890 int nrDigit = 0, nrAlph = 0, j;
2891 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2892 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2893 parse[parse_pos] = NULLCHAR;
2894 // try to be smart: if it does not look like search info, it should go to
2895 // ICS interaction window after all, not to engine-output window.
2896 for(j=0; j<parse_pos; j++) { // count letters and digits
2897 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2898 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2899 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2901 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2902 int depth=0; float score;
2903 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2904 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2905 pvInfoList[forwardMostMove-1].depth = depth;
2906 pvInfoList[forwardMostMove-1].score = 100*score;
2908 OutputKibitz(suppressKibitz, parse);
2911 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2912 SendToPlayer(tmp, strlen(tmp));
2914 next_out = i+1; // [HGM] suppress printing in ICS window
2916 started = STARTED_NONE;
2918 /* Don't match patterns against characters in comment */
2923 if (started == STARTED_CHATTER) {
2924 if (buf[i] != '\n') {
2925 /* Don't match patterns against characters in chatter */
2929 started = STARTED_NONE;
2930 if(suppressKibitz) next_out = i+1;
2933 /* Kludge to deal with rcmd protocol */
2934 if (firstTime && looking_at(buf, &i, "\001*")) {
2935 DisplayFatalError(&buf[1], 0, 1);
2941 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2944 if (appData.debugMode)
2945 fprintf(debugFP, "ics_type %d\n", ics_type);
2948 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2949 ics_type = ICS_FICS;
2951 if (appData.debugMode)
2952 fprintf(debugFP, "ics_type %d\n", ics_type);
2955 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2956 ics_type = ICS_CHESSNET;
2958 if (appData.debugMode)
2959 fprintf(debugFP, "ics_type %d\n", ics_type);
2964 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2965 looking_at(buf, &i, "Logging you in as \"*\"") ||
2966 looking_at(buf, &i, "will be \"*\""))) {
2967 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2971 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2973 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2974 DisplayIcsInteractionTitle(buf);
2975 have_set_title = TRUE;
2978 /* skip finger notes */
2979 if (started == STARTED_NONE &&
2980 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2981 (buf[i] == '1' && buf[i+1] == '0')) &&
2982 buf[i+2] == ':' && buf[i+3] == ' ') {
2983 started = STARTED_CHATTER;
2989 // [HGM] seekgraph: recognize sought lines and end-of-sought message
2990 if(appData.seekGraph) {
2991 if(soughtPending && MatchSoughtLine(buf+i)) {
2992 i = strstr(buf+i, "rated") - buf;
2993 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2994 next_out = leftover_start = i;
2995 started = STARTED_CHATTER;
2996 suppressKibitz = TRUE;
2999 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3000 && looking_at(buf, &i, "* ads displayed")) {
3001 soughtPending = FALSE;
3006 if(appData.autoRefresh) {
3007 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3008 int s = (ics_type == ICS_ICC); // ICC format differs
3010 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3011 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3012 looking_at(buf, &i, "*% "); // eat prompt
3013 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3014 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3015 next_out = i; // suppress
3018 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3019 char *p = star_match[0];
3021 if(seekGraphUp) RemoveSeekAd(atoi(p));
3022 while(*p && *p++ != ' '); // next
3024 looking_at(buf, &i, "*% "); // eat prompt
3025 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3032 /* skip formula vars */
3033 if (started == STARTED_NONE &&
3034 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3035 started = STARTED_CHATTER;
3040 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3041 if (appData.autoKibitz && started == STARTED_NONE &&
3042 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3043 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3044 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3045 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3046 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3047 suppressKibitz = TRUE;
3048 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3050 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3051 && (gameMode == IcsPlayingWhite)) ||
3052 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3053 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3054 started = STARTED_CHATTER; // own kibitz we simply discard
3056 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3057 parse_pos = 0; parse[0] = NULLCHAR;
3058 savingComment = TRUE;
3059 suppressKibitz = gameMode != IcsObserving ? 2 :
3060 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3064 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3065 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3066 && atoi(star_match[0])) {
3067 // suppress the acknowledgements of our own autoKibitz
3069 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3070 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3071 SendToPlayer(star_match[0], strlen(star_match[0]));
3072 if(looking_at(buf, &i, "*% ")) // eat prompt
3073 suppressKibitz = FALSE;
3077 } // [HGM] kibitz: end of patch
3079 // [HGM] chat: intercept tells by users for which we have an open chat window
3081 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3082 looking_at(buf, &i, "* whispers:") ||
3083 looking_at(buf, &i, "* kibitzes:") ||
3084 looking_at(buf, &i, "* shouts:") ||
3085 looking_at(buf, &i, "* c-shouts:") ||
3086 looking_at(buf, &i, "--> * ") ||
3087 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3088 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3089 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3090 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3092 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3093 chattingPartner = -1;
3095 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3096 for(p=0; p<MAX_CHAT; p++) {
3097 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3098 talker[0] = '['; strcat(talker, "] ");
3099 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3100 chattingPartner = p; break;
3103 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3104 for(p=0; p<MAX_CHAT; p++) {
3105 if(!strcmp("kibitzes", chatPartner[p])) {
3106 talker[0] = '['; strcat(talker, "] ");
3107 chattingPartner = p; break;
3110 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3111 for(p=0; p<MAX_CHAT; p++) {
3112 if(!strcmp("whispers", chatPartner[p])) {
3113 talker[0] = '['; strcat(talker, "] ");
3114 chattingPartner = p; break;
3117 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3118 if(buf[i-8] == '-' && buf[i-3] == 't')
3119 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3120 if(!strcmp("c-shouts", chatPartner[p])) {
3121 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3122 chattingPartner = p; break;
3125 if(chattingPartner < 0)
3126 for(p=0; p<MAX_CHAT; p++) {
3127 if(!strcmp("shouts", chatPartner[p])) {
3128 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3129 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3130 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3131 chattingPartner = p; break;
3135 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3136 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3137 talker[0] = 0; Colorize(ColorTell, FALSE);
3138 chattingPartner = p; break;
3140 if(chattingPartner<0) i = oldi; else {
3141 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3142 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3143 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3144 started = STARTED_COMMENT;
3145 parse_pos = 0; parse[0] = NULLCHAR;
3146 savingComment = 3 + chattingPartner; // counts as TRUE
3147 suppressKibitz = TRUE;
3150 } // [HGM] chat: end of patch
3153 if (appData.zippyTalk || appData.zippyPlay) {
3154 /* [DM] Backup address for color zippy lines */
3156 if (loggedOn == TRUE)
3157 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3158 (appData.zippyPlay && ZippyMatch(buf, &backup)));
3160 } // [DM] 'else { ' deleted
3162 /* Regular tells and says */
3163 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3164 looking_at(buf, &i, "* (your partner) tells you: ") ||
3165 looking_at(buf, &i, "* says: ") ||
3166 /* Don't color "message" or "messages" output */
3167 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3168 looking_at(buf, &i, "*. * at *:*: ") ||
3169 looking_at(buf, &i, "--* (*:*): ") ||
3170 /* Message notifications (same color as tells) */
3171 looking_at(buf, &i, "* has left a message ") ||
3172 looking_at(buf, &i, "* just sent you a message:\n") ||
3173 /* Whispers and kibitzes */
3174 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3175 looking_at(buf, &i, "* kibitzes: ") ||
3177 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3179 if (tkind == 1 && strchr(star_match[0], ':')) {
3180 /* Avoid "tells you:" spoofs in channels */
3183 if (star_match[0][0] == NULLCHAR ||
3184 strchr(star_match[0], ' ') ||
3185 (tkind == 3 && strchr(star_match[1], ' '))) {
3186 /* Reject bogus matches */
3189 if (appData.colorize) {
3190 if (oldi > next_out) {
3191 SendToPlayer(&buf[next_out], oldi - next_out);
3196 Colorize(ColorTell, FALSE);
3197 curColor = ColorTell;
3200 Colorize(ColorKibitz, FALSE);
3201 curColor = ColorKibitz;
3204 p = strrchr(star_match[1], '(');
3211 Colorize(ColorChannel1, FALSE);
3212 curColor = ColorChannel1;
3214 Colorize(ColorChannel, FALSE);
3215 curColor = ColorChannel;
3219 curColor = ColorNormal;
3223 if (started == STARTED_NONE && appData.autoComment &&
3224 (gameMode == IcsObserving ||
3225 gameMode == IcsPlayingWhite ||
3226 gameMode == IcsPlayingBlack)) {
3227 parse_pos = i - oldi;
3228 memcpy(parse, &buf[oldi], parse_pos);
3229 parse[parse_pos] = NULLCHAR;
3230 started = STARTED_COMMENT;
3231 savingComment = TRUE;
3233 started = STARTED_CHATTER;
3234 savingComment = FALSE;
3241 if (looking_at(buf, &i, "* s-shouts: ") ||
3242 looking_at(buf, &i, "* c-shouts: ")) {
3243 if (appData.colorize) {
3244 if (oldi > next_out) {
3245 SendToPlayer(&buf[next_out], oldi - next_out);
3248 Colorize(ColorSShout, FALSE);
3249 curColor = ColorSShout;
3252 started = STARTED_CHATTER;
3256 if (looking_at(buf, &i, "--->")) {
3261 if (looking_at(buf, &i, "* shouts: ") ||
3262 looking_at(buf, &i, "--> ")) {
3263 if (appData.colorize) {
3264 if (oldi > next_out) {
3265 SendToPlayer(&buf[next_out], oldi - next_out);
3268 Colorize(ColorShout, FALSE);
3269 curColor = ColorShout;
3272 started = STARTED_CHATTER;
3276 if (looking_at( buf, &i, "Challenge:")) {
3277 if (appData.colorize) {
3278 if (oldi > next_out) {
3279 SendToPlayer(&buf[next_out], oldi - next_out);
3282 Colorize(ColorChallenge, FALSE);
3283 curColor = ColorChallenge;
3289 if (looking_at(buf, &i, "* offers you") ||
3290 looking_at(buf, &i, "* offers to be") ||
3291 looking_at(buf, &i, "* would like to") ||
3292 looking_at(buf, &i, "* requests to") ||
3293 looking_at(buf, &i, "Your opponent offers") ||
3294 looking_at(buf, &i, "Your opponent requests")) {
3296 if (appData.colorize) {
3297 if (oldi > next_out) {
3298 SendToPlayer(&buf[next_out], oldi - next_out);
3301 Colorize(ColorRequest, FALSE);
3302 curColor = ColorRequest;
3307 if (looking_at(buf, &i, "* (*) seeking")) {
3308 if (appData.colorize) {
3309 if (oldi > next_out) {
3310 SendToPlayer(&buf[next_out], oldi - next_out);
3313 Colorize(ColorSeek, FALSE);
3314 curColor = ColorSeek;
3319 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3321 if (looking_at(buf, &i, "\\ ")) {
3322 if (prevColor != ColorNormal) {
3323 if (oldi > next_out) {
3324 SendToPlayer(&buf[next_out], oldi - next_out);
3327 Colorize(prevColor, TRUE);
3328 curColor = prevColor;
3330 if (savingComment) {
3331 parse_pos = i - oldi;
3332 memcpy(parse, &buf[oldi], parse_pos);
3333 parse[parse_pos] = NULLCHAR;
3334 started = STARTED_COMMENT;
3335 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3336 chattingPartner = savingComment - 3; // kludge to remember the box
3338 started = STARTED_CHATTER;
3343 if (looking_at(buf, &i, "Black Strength :") ||
3344 looking_at(buf, &i, "<<< style 10 board >>>") ||
3345 looking_at(buf, &i, "<10>") ||
3346 looking_at(buf, &i, "#@#")) {
3347 /* Wrong board style */
3349 SendToICS(ics_prefix);
3350 SendToICS("set style 12\n");
3351 SendToICS(ics_prefix);
3352 SendToICS("refresh\n");
3356 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3358 have_sent_ICS_logon = 1;
3362 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3363 (looking_at(buf, &i, "\n<12> ") ||
3364 looking_at(buf, &i, "<12> "))) {
3366 if (oldi > next_out) {
3367 SendToPlayer(&buf[next_out], oldi - next_out);
3370 started = STARTED_BOARD;
3375 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3376 looking_at(buf, &i, "<b1> ")) {
3377 if (oldi > next_out) {
3378 SendToPlayer(&buf[next_out], oldi - next_out);
3381 started = STARTED_HOLDINGS;
3386 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3388 /* Header for a move list -- first line */
3390 switch (ics_getting_history) {
3394 case BeginningOfGame:
3395 /* User typed "moves" or "oldmoves" while we
3396 were idle. Pretend we asked for these
3397 moves and soak them up so user can step
3398 through them and/or save them.
3401 gameMode = IcsObserving;
3404 ics_getting_history = H_GOT_UNREQ_HEADER;
3406 case EditGame: /*?*/
3407 case EditPosition: /*?*/
3408 /* Should above feature work in these modes too? */
3409 /* For now it doesn't */
3410 ics_getting_history = H_GOT_UNWANTED_HEADER;
3413 ics_getting_history = H_GOT_UNWANTED_HEADER;
3418 /* Is this the right one? */
3419 if (gameInfo.white && gameInfo.black &&
3420 strcmp(gameInfo.white, star_match[0]) == 0 &&
3421 strcmp(gameInfo.black, star_match[2]) == 0) {
3423 ics_getting_history = H_GOT_REQ_HEADER;
3426 case H_GOT_REQ_HEADER:
3427 case H_GOT_UNREQ_HEADER:
3428 case H_GOT_UNWANTED_HEADER:
3429 case H_GETTING_MOVES:
3430 /* Should not happen */
3431 DisplayError(_("Error gathering move list: two headers"), 0);
3432 ics_getting_history = H_FALSE;
3436 /* Save player ratings into gameInfo if needed */
3437 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3438 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3439 (gameInfo.whiteRating == -1 ||
3440 gameInfo.blackRating == -1)) {
3442 gameInfo.whiteRating = string_to_rating(star_match[1]);
3443 gameInfo.blackRating = string_to_rating(star_match[3]);
3444 if (appData.debugMode)
3445 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3446 gameInfo.whiteRating, gameInfo.blackRating);
3451 if (looking_at(buf, &i,
3452 "* * match, initial time: * minute*, increment: * second")) {
3453 /* Header for a move list -- second line */
3454 /* Initial board will follow if this is a wild game */
3455 if (gameInfo.event != NULL) free(gameInfo.event);
3456 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3457 gameInfo.event = StrSave(str);
3458 /* [HGM] we switched variant. Translate boards if needed. */
3459 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3463 if (looking_at(buf, &i, "Move ")) {
3464 /* Beginning of a move list */
3465 switch (ics_getting_history) {
3467 /* Normally should not happen */
3468 /* Maybe user hit reset while we were parsing */
3471 /* Happens if we are ignoring a move list that is not
3472 * the one we just requested. Common if the user
3473 * tries to observe two games without turning off
3476 case H_GETTING_MOVES:
3477 /* Should not happen */
3478 DisplayError(_("Error gathering move list: nested"), 0);
3479 ics_getting_history = H_FALSE;
3481 case H_GOT_REQ_HEADER:
3482 ics_getting_history = H_GETTING_MOVES;
3483 started = STARTED_MOVES;
3485 if (oldi > next_out) {
3486 SendToPlayer(&buf[next_out], oldi - next_out);
3489 case H_GOT_UNREQ_HEADER:
3490 ics_getting_history = H_GETTING_MOVES;
3491 started = STARTED_MOVES_NOHIDE;
3494 case H_GOT_UNWANTED_HEADER:
3495 ics_getting_history = H_FALSE;
3501 if (looking_at(buf, &i, "% ") ||
3502 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3503 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3504 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3505 soughtPending = FALSE;
3509 if(suppressKibitz) next_out = i;
3510 savingComment = FALSE;
3514 case STARTED_MOVES_NOHIDE:
3515 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3516 parse[parse_pos + i - oldi] = NULLCHAR;
3517 ParseGameHistory(parse);
3519 if (appData.zippyPlay && first.initDone) {
3520 FeedMovesToProgram(&first, forwardMostMove);
3521 if (gameMode == IcsPlayingWhite) {
3522 if (WhiteOnMove(forwardMostMove)) {
3523 if (first.sendTime) {
3524 if (first.useColors) {
3525 SendToProgram("black\n", &first);
3527 SendTimeRemaining(&first, TRUE);
3529 if (first.useColors) {
3530 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3532 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3533 first.maybeThinking = TRUE;
3535 if (first.usePlayother) {
3536 if (first.sendTime) {
3537 SendTimeRemaining(&first, TRUE);
3539 SendToProgram("playother\n", &first);
3545 } else if (gameMode == IcsPlayingBlack) {
3546 if (!WhiteOnMove(forwardMostMove)) {
3547 if (first.sendTime) {
3548 if (first.useColors) {
3549 SendToProgram("white\n", &first);
3551 SendTimeRemaining(&first, FALSE);
3553 if (first.useColors) {
3554 SendToProgram("black\n", &first);
3556 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3557 first.maybeThinking = TRUE;
3559 if (first.usePlayother) {
3560 if (first.sendTime) {
3561 SendTimeRemaining(&first, FALSE);
3563 SendToProgram("playother\n", &first);
3572 if (gameMode == IcsObserving && ics_gamenum == -1) {
3573 /* Moves came from oldmoves or moves command
3574 while we weren't doing anything else.
3576 currentMove = forwardMostMove;
3577 ClearHighlights();/*!!could figure this out*/
3578 flipView = appData.flipView;
3579 DrawPosition(TRUE, boards[currentMove]);
3580 DisplayBothClocks();
3581 snprintf(str, MSG_SIZ, "%s vs. %s",
3582 gameInfo.white, gameInfo.black);
3586 /* Moves were history of an active game */
3587 if (gameInfo.resultDetails != NULL) {
3588 free(gameInfo.resultDetails);
3589 gameInfo.resultDetails = NULL;
3592 HistorySet(parseList, backwardMostMove,
3593 forwardMostMove, currentMove-1);
3594 DisplayMove(currentMove - 1);
3595 if (started == STARTED_MOVES) next_out = i;
3596 started = STARTED_NONE;
3597 ics_getting_history = H_FALSE;
3600 case STARTED_OBSERVE:
3601 started = STARTED_NONE;
3602 SendToICS(ics_prefix);
3603 SendToICS("refresh\n");
3609 if(bookHit) { // [HGM] book: simulate book reply
3610 static char bookMove[MSG_SIZ]; // a bit generous?
3612 programStats.nodes = programStats.depth = programStats.time =
3613 programStats.score = programStats.got_only_move = 0;
3614 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3616 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3617 strcat(bookMove, bookHit);
3618 HandleMachineMove(bookMove, &first);
3623 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3624 started == STARTED_HOLDINGS ||
3625 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3626 /* Accumulate characters in move list or board */
3627 parse[parse_pos++] = buf[i];
3630 /* Start of game messages. Mostly we detect start of game
3631 when the first board image arrives. On some versions
3632 of the ICS, though, we need to do a "refresh" after starting
3633 to observe in order to get the current board right away. */
3634 if (looking_at(buf, &i, "Adding game * to observation list")) {
3635 started = STARTED_OBSERVE;
3639 /* Handle auto-observe */
3640 if (appData.autoObserve &&
3641 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3642 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3644 /* Choose the player that was highlighted, if any. */
3645 if (star_match[0][0] == '\033' ||
3646 star_match[1][0] != '\033') {
3647 player = star_match[0];
3649 player = star_match[2];
3651 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3652 ics_prefix, StripHighlightAndTitle(player));
3655 /* Save ratings from notify string */
3656 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3657 player1Rating = string_to_rating(star_match[1]);
3658 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3659 player2Rating = string_to_rating(star_match[3]);
3661 if (appData.debugMode)
3663 "Ratings from 'Game notification:' %s %d, %s %d\n",
3664 player1Name, player1Rating,
3665 player2Name, player2Rating);
3670 /* Deal with automatic examine mode after a game,
3671 and with IcsObserving -> IcsExamining transition */
3672 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3673 looking_at(buf, &i, "has made you an examiner of game *")) {
3675 int gamenum = atoi(star_match[0]);
3676 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3677 gamenum == ics_gamenum) {
3678 /* We were already playing or observing this game;
3679 no need to refetch history */
3680 gameMode = IcsExamining;
3682 pauseExamForwardMostMove = forwardMostMove;
3683 } else if (currentMove < forwardMostMove) {
3684 ForwardInner(forwardMostMove);
3687 /* I don't think this case really can happen */
3688 SendToICS(ics_prefix);
3689 SendToICS("refresh\n");
3694 /* Error messages */
3695 // if (ics_user_moved) {
3696 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3697 if (looking_at(buf, &i, "Illegal move") ||
3698 looking_at(buf, &i, "Not a legal move") ||
3699 looking_at(buf, &i, "Your king is in check") ||
3700 looking_at(buf, &i, "It isn't your turn") ||
3701 looking_at(buf, &i, "It is not your move")) {
3703 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3704 currentMove = forwardMostMove-1;
3705 DisplayMove(currentMove - 1); /* before DMError */
3706 DrawPosition(FALSE, boards[currentMove]);
3707 SwitchClocks(forwardMostMove-1); // [HGM] race
3708 DisplayBothClocks();
3710 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3716 if (looking_at(buf, &i, "still have time") ||
3717 looking_at(buf, &i, "not out of time") ||
3718 looking_at(buf, &i, "either player is out of time") ||
3719 looking_at(buf, &i, "has timeseal; checking")) {
3720 /* We must have called his flag a little too soon */
3721 whiteFlag = blackFlag = FALSE;
3725 if (looking_at(buf, &i, "added * seconds to") ||
3726 looking_at(buf, &i, "seconds were added to")) {
3727 /* Update the clocks */
3728 SendToICS(ics_prefix);
3729 SendToICS("refresh\n");
3733 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3734 ics_clock_paused = TRUE;
3739 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3740 ics_clock_paused = FALSE;
3745 /* Grab player ratings from the Creating: message.
3746 Note we have to check for the special case when
3747 the ICS inserts things like [white] or [black]. */
3748 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3749 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3751 0 player 1 name (not necessarily white)
3753 2 empty, white, or black (IGNORED)
3754 3 player 2 name (not necessarily black)
3757 The names/ratings are sorted out when the game
3758 actually starts (below).
3760 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3761 player1Rating = string_to_rating(star_match[1]);
3762 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3763 player2Rating = string_to_rating(star_match[4]);
3765 if (appData.debugMode)
3767 "Ratings from 'Creating:' %s %d, %s %d\n",
3768 player1Name, player1Rating,
3769 player2Name, player2Rating);
3774 /* Improved generic start/end-of-game messages */
3775 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3776 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3777 /* If tkind == 0: */
3778 /* star_match[0] is the game number */
3779 /* [1] is the white player's name */
3780 /* [2] is the black player's name */
3781 /* For end-of-game: */
3782 /* [3] is the reason for the game end */
3783 /* [4] is a PGN end game-token, preceded by " " */
3784 /* For start-of-game: */
3785 /* [3] begins with "Creating" or "Continuing" */
3786 /* [4] is " *" or empty (don't care). */
3787 int gamenum = atoi(star_match[0]);
3788 char *whitename, *blackname, *why, *endtoken;
3789 ChessMove endtype = EndOfFile;
3792 whitename = star_match[1];
3793 blackname = star_match[2];
3794 why = star_match[3];
3795 endtoken = star_match[4];
3797 whitename = star_match[1];
3798 blackname = star_match[3];
3799 why = star_match[5];
3800 endtoken = star_match[6];
3803 /* Game start messages */
3804 if (strncmp(why, "Creating ", 9) == 0 ||
3805 strncmp(why, "Continuing ", 11) == 0) {
3806 gs_gamenum = gamenum;
3807 safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3808 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3810 if (appData.zippyPlay) {
3811 ZippyGameStart(whitename, blackname);
3814 partnerBoardValid = FALSE; // [HGM] bughouse
3818 /* Game end messages */
3819 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3820 ics_gamenum != gamenum) {
3823 while (endtoken[0] == ' ') endtoken++;
3824 switch (endtoken[0]) {
3827 endtype = GameUnfinished;
3830 endtype = BlackWins;
3833 if (endtoken[1] == '/')
3834 endtype = GameIsDrawn;
3836 endtype = WhiteWins;
3839 GameEnds(endtype, why, GE_ICS);
3841 if (appData.zippyPlay && first.initDone) {
3842 ZippyGameEnd(endtype, why);
3843 if (first.pr == NULL) {
3844 /* Start the next process early so that we'll
3845 be ready for the next challenge */
3846 StartChessProgram(&first);
3848 /* Send "new" early, in case this command takes
3849 a long time to finish, so that we'll be ready
3850 for the next challenge. */
3851 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3855 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3859 if (looking_at(buf, &i, "Removing game * from observation") ||
3860 looking_at(buf, &i, "no longer observing game *") ||
3861 looking_at(buf, &i, "Game * (*) has no examiners")) {
3862 if (gameMode == IcsObserving &&
3863 atoi(star_match[0]) == ics_gamenum)