2 * backend.c -- Common back end for X and Windows NT versions of
4 * Copyright 1991 by Digital Equipment Corporation, Maynard,
7 * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8 * 2007, 2008, 2009, 2010, 2011 Free Software Foundation, Inc.
10 * Enhancements Copyright 2005 Alessandro Scotti
12 * The following terms apply to Digital Equipment Corporation's copyright
14 * ------------------------------------------------------------------------
17 * Permission to use, copy, modify, and distribute this software and its
18 * documentation for any purpose and without fee is hereby granted,
19 * provided that the above copyright notice appear in all copies and that
20 * both that copyright notice and this permission notice appear in
21 * supporting documentation, and that the name of Digital not be
22 * used in advertising or publicity pertaining to distribution of the
23 * software without specific, written prior permission.
25 * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27 * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
32 * ------------------------------------------------------------------------
34 * The following terms apply to the enhanced version of XBoard
35 * distributed by the Free Software Foundation:
36 * ------------------------------------------------------------------------
38 * GNU XBoard is free software: you can redistribute it and/or modify
39 * it under the terms of the GNU General Public License as published by
40 * the Free Software Foundation, either version 3 of the License, or (at
41 * your option) any later version.
43 * GNU XBoard is distributed in the hope that it will be useful, but
44 * WITHOUT ANY WARRANTY; without even the implied warranty of
45 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46 * General Public License for more details.
48 * You should have received a copy of the GNU General Public License
49 * along with this program. If not, see http://www.gnu.org/licenses/. *
51 *------------------------------------------------------------------------
52 ** See the file ChangeLog for a revision history. */
54 /* [AS] Also useful here for debugging */
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
60 int flock(int f, int code);
66 #define DoSleep( n ) if( (n) >= 0) sleep(n)
77 #include <sys/types.h>
86 #else /* not STDC_HEADERS */
89 # else /* not HAVE_STRING_H */
91 # endif /* not HAVE_STRING_H */
92 #endif /* not STDC_HEADERS */
95 # include <sys/fcntl.h>
96 #else /* not HAVE_SYS_FCNTL_H */
99 # endif /* HAVE_FCNTL_H */
100 #endif /* not HAVE_SYS_FCNTL_H */
102 #if TIME_WITH_SYS_TIME
103 # include <sys/time.h>
107 # include <sys/time.h>
113 #if defined(_amigados) && !defined(__GNUC__)
118 extern int gettimeofday(struct timeval *, struct timezone *);
126 #include "frontend.h"
133 #include "backendz.h"
137 # define _(s) gettext (s)
138 # define N_(s) gettext_noop (s)
139 # define T_(s) gettext(s)
152 /* A point in time */
154 long sec; /* Assuming this is >= 32 bits */
155 int ms; /* Assuming this is >= 16 bits */
158 int establish P((void));
159 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
160 char *buf, int count, int error));
161 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
162 char *buf, int count, int error));
163 void ics_printf P((char *format, ...));
164 void SendToICS P((char *s));
165 void SendToICSDelayed P((char *s, long msdelay));
166 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
167 void HandleMachineMove P((char *message, ChessProgramState *cps));
168 int AutoPlayOneMove P((void));
169 int LoadGameOneMove P((ChessMove readAhead));
170 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
171 int LoadPositionFromFile P((char *filename, int n, char *title));
172 int SavePositionToFile P((char *filename));
173 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
175 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
176 void ShowMove P((int fromX, int fromY, int toX, int toY));
177 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
178 /*char*/int promoChar));
179 void BackwardInner P((int target));
180 void ForwardInner P((int target));
181 int Adjudicate P((ChessProgramState *cps));
182 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
183 void EditPositionDone P((Boolean fakeRights));
184 void PrintOpponents P((FILE *fp));
185 void PrintPosition P((FILE *fp, int move));
186 void StartChessProgram P((ChessProgramState *cps));
187 void SendToProgram P((char *message, ChessProgramState *cps));
188 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
189 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
190 char *buf, int count, int error));
191 void SendTimeControl P((ChessProgramState *cps,
192 int mps, long tc, int inc, int sd, int st));
193 char *TimeControlTagValue P((void));
194 void Attention P((ChessProgramState *cps));
195 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
196 int ResurrectChessProgram P((void));
197 void DisplayComment P((int moveNumber, char *text));
198 void DisplayMove P((int moveNumber));
200 void ParseGameHistory P((char *game));
201 void ParseBoard12 P((char *string));
202 void KeepAlive P((void));
203 void StartClocks P((void));
204 void SwitchClocks P((int nr));
205 void StopClocks P((void));
206 void ResetClocks P((void));
207 char *PGNDate P((void));
208 void SetGameInfo P((void));
209 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
210 int RegisterMove P((void));
211 void MakeRegisteredMove P((void));
212 void TruncateGame P((void));
213 int looking_at P((char *, int *, char *));
214 void CopyPlayerNameIntoFileName P((char **, char *));
215 char *SavePart P((char *));
216 int SaveGameOldStyle P((FILE *));
217 int SaveGamePGN P((FILE *));
218 void GetTimeMark P((TimeMark *));
219 long SubtractTimeMarks P((TimeMark *, TimeMark *));
220 int CheckFlags P((void));
221 long NextTickLength P((long));
222 void CheckTimeControl P((void));
223 void show_bytes P((FILE *, char *, int));
224 int string_to_rating P((char *str));
225 void ParseFeatures P((char* args, ChessProgramState *cps));
226 void InitBackEnd3 P((void));
227 void FeatureDone P((ChessProgramState* cps, int val));
228 void InitChessProgram P((ChessProgramState *cps, int setup));
229 void OutputKibitz(int window, char *text);
230 int PerpetualChase(int first, int last);
231 int EngineOutputIsUp();
232 void InitDrawingSizes(int x, int y);
233 void NextMatchGame P((void));
234 int NextTourneyGame P((int nr, int *swap));
235 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
236 FILE *WriteTourneyFile P((char *results));
237 void DisplayTwoMachinesTitle P(());
240 extern void ConsoleCreate();
243 ChessProgramState *WhitePlayer();
244 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
245 int VerifyDisplayMode P(());
247 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
248 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
249 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
250 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
251 void ics_update_width P((int new_width));
252 extern char installDir[MSG_SIZ];
253 VariantClass startVariant; /* [HGM] nicks: initial variant */
256 extern int tinyLayout, smallLayout;
257 ChessProgramStats programStats;
258 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
260 static int exiting = 0; /* [HGM] moved to top */
261 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
262 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
263 Board partnerBoard; /* [HGM] bughouse: for peeking at partner game */
264 int partnerHighlight[2];
265 Boolean partnerBoardValid = 0;
266 char partnerStatus[MSG_SIZ];
268 Boolean originalFlip;
269 Boolean twoBoards = 0;
270 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
271 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
272 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
273 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
274 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
275 int opponentKibitzes;
276 int lastSavedGame; /* [HGM] save: ID of game */
277 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
278 extern int chatCount;
280 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
281 char lastMsg[MSG_SIZ];
282 ChessSquare pieceSweep = EmptySquare;
283 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
284 int promoDefaultAltered;
286 /* States for ics_getting_history */
288 #define H_REQUESTED 1
289 #define H_GOT_REQ_HEADER 2
290 #define H_GOT_UNREQ_HEADER 3
291 #define H_GETTING_MOVES 4
292 #define H_GOT_UNWANTED_HEADER 5
294 /* whosays values for GameEnds */
303 /* Maximum number of games in a cmail message */
304 #define CMAIL_MAX_GAMES 20
306 /* Different types of move when calling RegisterMove */
308 #define CMAIL_RESIGN 1
310 #define CMAIL_ACCEPT 3
312 /* Different types of result to remember for each game */
313 #define CMAIL_NOT_RESULT 0
314 #define CMAIL_OLD_RESULT 1
315 #define CMAIL_NEW_RESULT 2
317 /* Telnet protocol constants */
328 safeStrCpy( char *dst, const char *src, size_t count )
331 assert( dst != NULL );
332 assert( src != NULL );
335 for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
336 if( i == count && dst[count-1] != NULLCHAR)
338 dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
339 if(appData.debugMode)
340 fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
346 /* Some compiler can't cast u64 to double
347 * This function do the job for us:
349 * We use the highest bit for cast, this only
350 * works if the highest bit is not
351 * in use (This should not happen)
353 * We used this for all compiler
356 u64ToDouble(u64 value)
359 u64 tmp = value & u64Const(0x7fffffffffffffff);
360 r = (double)(s64)tmp;
361 if (value & u64Const(0x8000000000000000))
362 r += 9.2233720368547758080e18; /* 2^63 */
366 /* Fake up flags for now, as we aren't keeping track of castling
367 availability yet. [HGM] Change of logic: the flag now only
368 indicates the type of castlings allowed by the rule of the game.
369 The actual rights themselves are maintained in the array
370 castlingRights, as part of the game history, and are not probed
376 int flags = F_ALL_CASTLE_OK;
377 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
378 switch (gameInfo.variant) {
380 flags &= ~F_ALL_CASTLE_OK;
381 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
382 flags |= F_IGNORE_CHECK;
384 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
387 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
389 case VariantKriegspiel:
390 flags |= F_KRIEGSPIEL_CAPTURE;
392 case VariantCapaRandom:
393 case VariantFischeRandom:
394 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
395 case VariantNoCastle:
396 case VariantShatranj:
399 flags &= ~F_ALL_CASTLE_OK;
407 FILE *gameFileFP, *debugFP;
410 [AS] Note: sometimes, the sscanf() function is used to parse the input
411 into a fixed-size buffer. Because of this, we must be prepared to
412 receive strings as long as the size of the input buffer, which is currently
413 set to 4K for Windows and 8K for the rest.
414 So, we must either allocate sufficiently large buffers here, or
415 reduce the size of the input buffer in the input reading part.
418 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
419 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
420 char thinkOutput1[MSG_SIZ*10];
422 ChessProgramState first, second, pairing;
424 /* premove variables */
427 int premoveFromX = 0;
428 int premoveFromY = 0;
429 int premovePromoChar = 0;
431 Boolean alarmSounded;
432 /* end premove variables */
434 char *ics_prefix = "$";
435 int ics_type = ICS_GENERIC;
437 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
438 int pauseExamForwardMostMove = 0;
439 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
440 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
441 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
442 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
443 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
444 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
445 int whiteFlag = FALSE, blackFlag = FALSE;
446 int userOfferedDraw = FALSE;
447 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
448 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
449 int cmailMoveType[CMAIL_MAX_GAMES];
450 long ics_clock_paused = 0;
451 ProcRef icsPR = NoProc, cmailPR = NoProc;
452 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
453 GameMode gameMode = BeginningOfGame;
454 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
455 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
456 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
457 int hiddenThinkOutputState = 0; /* [AS] */
458 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
459 int adjudicateLossPlies = 6;
460 char white_holding[64], black_holding[64];
461 TimeMark lastNodeCountTime;
462 long lastNodeCount=0;
463 int shiftKey; // [HGM] set by mouse handler
465 int have_sent_ICS_logon = 0;
467 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
468 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
469 long timeControl_2; /* [AS] Allow separate time controls */
470 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
471 long timeRemaining[2][MAX_MOVES];
472 int matchGame = 0, nextGame = 0, roundNr = 0;
473 Boolean waitingForGame = FALSE;
474 TimeMark programStartTime, pauseStart;
475 char ics_handle[MSG_SIZ];
476 int have_set_title = 0;
478 /* animateTraining preserves the state of appData.animate
479 * when Training mode is activated. This allows the
480 * response to be animated when appData.animate == TRUE and
481 * appData.animateDragging == TRUE.
483 Boolean animateTraining;
489 Board boards[MAX_MOVES];
490 /* [HGM] Following 7 needed for accurate legality tests: */
491 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
492 signed char initialRights[BOARD_FILES];
493 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
494 int initialRulePlies, FENrulePlies;
495 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
497 Boolean shuffleOpenings;
498 int mute; // mute all sounds
500 // [HGM] vari: next 12 to save and restore variations
501 #define MAX_VARIATIONS 10
502 int framePtr = MAX_MOVES-1; // points to free stack entry
504 int savedFirst[MAX_VARIATIONS];
505 int savedLast[MAX_VARIATIONS];
506 int savedFramePtr[MAX_VARIATIONS];
507 char *savedDetails[MAX_VARIATIONS];
508 ChessMove savedResult[MAX_VARIATIONS];
510 void PushTail P((int firstMove, int lastMove));
511 Boolean PopTail P((Boolean annotate));
512 void PushInner P((int firstMove, int lastMove));
513 void PopInner P((Boolean annotate));
514 void CleanupTail P((void));
516 ChessSquare FIDEArray[2][BOARD_FILES] = {
517 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
518 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
519 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
520 BlackKing, BlackBishop, BlackKnight, BlackRook }
523 ChessSquare twoKingsArray[2][BOARD_FILES] = {
524 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
525 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
526 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
527 BlackKing, BlackKing, BlackKnight, BlackRook }
530 ChessSquare KnightmateArray[2][BOARD_FILES] = {
531 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
532 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
533 { BlackRook, BlackMan, BlackBishop, BlackQueen,
534 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
537 ChessSquare SpartanArray[2][BOARD_FILES] = {
538 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
539 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
540 { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
541 BlackDragon, BlackKing, BlackAngel, BlackAlfil }
544 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
545 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
546 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
547 { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
548 BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
551 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
552 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
553 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
554 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
555 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
558 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
559 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
560 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
561 { BlackRook, BlackKnight, BlackMan, BlackFerz,
562 BlackKing, BlackMan, BlackKnight, BlackRook }
566 #if (BOARD_FILES>=10)
567 ChessSquare ShogiArray[2][BOARD_FILES] = {
568 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
569 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
570 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
571 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
574 ChessSquare XiangqiArray[2][BOARD_FILES] = {
575 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
576 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
577 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
578 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
581 ChessSquare CapablancaArray[2][BOARD_FILES] = {
582 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
583 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
584 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
585 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
588 ChessSquare GreatArray[2][BOARD_FILES] = {
589 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
590 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
591 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
592 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
595 ChessSquare JanusArray[2][BOARD_FILES] = {
596 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
597 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
598 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
599 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
603 ChessSquare GothicArray[2][BOARD_FILES] = {
604 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
605 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
606 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
607 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
610 #define GothicArray CapablancaArray
614 ChessSquare FalconArray[2][BOARD_FILES] = {
615 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
616 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
617 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
618 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
621 #define FalconArray CapablancaArray
624 #else // !(BOARD_FILES>=10)
625 #define XiangqiPosition FIDEArray
626 #define CapablancaArray FIDEArray
627 #define GothicArray FIDEArray
628 #define GreatArray FIDEArray
629 #endif // !(BOARD_FILES>=10)
631 #if (BOARD_FILES>=12)
632 ChessSquare CourierArray[2][BOARD_FILES] = {
633 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
634 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
635 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
636 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
638 #else // !(BOARD_FILES>=12)
639 #define CourierArray CapablancaArray
640 #endif // !(BOARD_FILES>=12)
643 Board initialPosition;
646 /* Convert str to a rating. Checks for special cases of "----",
648 "++++", etc. Also strips ()'s */
650 string_to_rating(str)
653 while(*str && !isdigit(*str)) ++str;
655 return 0; /* One of the special "no rating" cases */
663 /* Init programStats */
664 programStats.movelist[0] = 0;
665 programStats.depth = 0;
666 programStats.nr_moves = 0;
667 programStats.moves_left = 0;
668 programStats.nodes = 0;
669 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
670 programStats.score = 0;
671 programStats.got_only_move = 0;
672 programStats.got_fail = 0;
673 programStats.line_is_book = 0;
678 { // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
679 if (appData.firstPlaysBlack) {
680 first.twoMachinesColor = "black\n";
681 second.twoMachinesColor = "white\n";
683 first.twoMachinesColor = "white\n";
684 second.twoMachinesColor = "black\n";
687 first.other = &second;
688 second.other = &first;
691 if(appData.timeOddsMode) {
692 norm = appData.timeOdds[0];
693 if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
695 first.timeOdds = appData.timeOdds[0]/norm;
696 second.timeOdds = appData.timeOdds[1]/norm;
699 if(programVersion) free(programVersion);
700 if (appData.noChessProgram) {
701 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
702 sprintf(programVersion, "%s", PACKAGE_STRING);
704 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
705 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
706 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
711 UnloadEngine(ChessProgramState *cps)
713 /* Kill off first chess program */
714 if (cps->isr != NULL)
715 RemoveInputSource(cps->isr);
718 if (cps->pr != NoProc) {
720 DoSleep( appData.delayBeforeQuit );
721 SendToProgram("quit\n", cps);
722 DoSleep( appData.delayAfterQuit );
723 DestroyChildProcess(cps->pr, cps->useSigterm);
726 if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
730 ClearOptions(ChessProgramState *cps)
733 cps->nrOptions = cps->comboCnt = 0;
734 for(i=0; i<MAX_OPTIONS; i++) {
735 cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
736 cps->option[i].textValue = 0;
740 char *engineNames[] = {
746 InitEngine(ChessProgramState *cps, int n)
747 { // [HGM] all engine initialiation put in a function that does one engine
751 cps->which = engineNames[n];
752 cps->maybeThinking = FALSE;
756 cps->sendDrawOffers = 1;
758 cps->program = appData.chessProgram[n];
759 cps->host = appData.host[n];
760 cps->dir = appData.directory[n];
761 cps->initString = appData.engInitString[n];
762 cps->computerString = appData.computerString[n];
763 cps->useSigint = TRUE;
764 cps->useSigterm = TRUE;
765 cps->reuse = appData.reuse[n];
766 cps->nps = appData.NPS[n]; // [HGM] nps: copy nodes per second
767 cps->useSetboard = FALSE;
769 cps->usePing = FALSE;
772 cps->usePlayother = FALSE;
773 cps->useColors = TRUE;
774 cps->useUsermove = FALSE;
775 cps->sendICS = FALSE;
776 cps->sendName = appData.icsActive;
777 cps->sdKludge = FALSE;
778 cps->stKludge = FALSE;
779 TidyProgramName(cps->program, cps->host, cps->tidy);
781 safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
782 cps->analysisSupport = 2; /* detect */
783 cps->analyzing = FALSE;
784 cps->initDone = FALSE;
786 /* New features added by Tord: */
787 cps->useFEN960 = FALSE;
788 cps->useOOCastle = TRUE;
789 /* End of new features added by Tord. */
790 cps->fenOverride = appData.fenOverride[n];
792 /* [HGM] time odds: set factor for each machine */
793 cps->timeOdds = appData.timeOdds[n];
795 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
796 cps->accumulateTC = appData.accumulateTC[n];
797 cps->maxNrOfSessions = 1;
802 cps->supportsNPS = UNKNOWN;
803 cps->memSize = FALSE;
804 cps->maxCores = FALSE;
805 cps->egtFormats[0] = NULLCHAR;
808 cps->optionSettings = appData.engOptions[n];
810 cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
811 cps->isUCI = appData.isUCI[n]; /* [AS] */
812 cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
814 if (appData.protocolVersion[n] > PROTOVER
815 || appData.protocolVersion[n] < 1)
820 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
821 appData.protocolVersion[n]);
822 if( (len > MSG_SIZ) && appData.debugMode )
823 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
825 DisplayFatalError(buf, 0, 2);
829 cps->protocolVersion = appData.protocolVersion[n];
832 InitEngineUCI( installDir, cps ); // [HGM] moved here from winboard.c, to make available in xboard
835 ChessProgramState *savCps;
841 if(WaitForEngine(savCps, LoadEngine)) return;
842 CommonEngineInit(); // recalculate time odds
843 if(gameInfo.variant != StringToVariant(appData.variant)) {
844 // we changed variant when loading the engine; this forces us to reset
845 Reset(TRUE, savCps != &first);
846 EditGameEvent(); // for consistency with other path, as Reset changes mode
848 InitChessProgram(savCps, FALSE);
849 SendToProgram("force\n", savCps);
850 DisplayMessage("", "");
851 if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
852 for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
858 ReplaceEngine(ChessProgramState *cps, int n)
862 appData.noChessProgram = FALSE;
863 appData.clockMode = TRUE;
866 if(n) return; // only startup first engine immediately; second can wait
867 savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
871 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
872 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
874 static char resetOptions[] =
875 "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
876 "-firstOptions \"\" -firstNPS -1 -fn \"\"";
879 Load(ChessProgramState *cps, int i)
881 char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
882 if(engineLine[0]) { // an engine was selected from the combo box
883 snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
884 SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
885 ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL;
886 ParseArgsFromString(buf);
888 ReplaceEngine(cps, i);
892 while(q = strchr(p, SLASH)) p = q+1;
893 if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
894 if(engineDir[0] != NULLCHAR)
895 appData.directory[i] = engineDir;
896 else if(p != engineName) { // derive directory from engine path, when not given
898 appData.directory[i] = strdup(engineName);
900 } else appData.directory[i] = ".";
901 if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
903 snprintf(command, MSG_SIZ, "%s %s", p, params);
906 appData.chessProgram[i] = strdup(p);
907 appData.isUCI[i] = isUCI;
908 appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
909 appData.hasOwnBookUCI[i] = hasBook;
910 if(!nickName[0]) useNick = FALSE;
911 if(useNick) ASSIGN(appData.pgnName[i], nickName);
915 q = firstChessProgramNames;
916 if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
917 quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
918 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
919 quote, p, quote, appData.directory[i],
920 useNick ? " -fn \"" : "",
921 useNick ? nickName : "",
923 v1 ? " -firstProtocolVersion 1" : "",
924 hasBook ? "" : " -fNoOwnBookUCI",
925 isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
926 storeVariant ? " -variant " : "",
927 storeVariant ? VariantName(gameInfo.variant) : "");
928 firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
929 snprintf(firstChessProgramNames, len, "%s%s", q, buf);
932 ReplaceEngine(cps, i);
938 int matched, min, sec;
940 * Parse timeControl resource
942 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
943 appData.movesPerSession)) {
945 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
946 DisplayFatalError(buf, 0, 2);
950 * Parse searchTime resource
952 if (*appData.searchTime != NULLCHAR) {
953 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
955 searchTime = min * 60;
956 } else if (matched == 2) {
957 searchTime = min * 60 + sec;
960 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
961 DisplayFatalError(buf, 0, 2);
970 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
971 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
973 GetTimeMark(&programStartTime);
974 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
975 pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
978 programStats.ok_to_send = 1;
979 programStats.seen_stat = 0;
982 * Initialize game list
988 * Internet chess server status
990 if (appData.icsActive) {
991 appData.matchMode = FALSE;
992 appData.matchGames = 0;
994 appData.noChessProgram = !appData.zippyPlay;
996 appData.zippyPlay = FALSE;
997 appData.zippyTalk = FALSE;
998 appData.noChessProgram = TRUE;
1000 if (*appData.icsHelper != NULLCHAR) {
1001 appData.useTelnet = TRUE;
1002 appData.telnetProgram = appData.icsHelper;
1005 appData.zippyTalk = appData.zippyPlay = FALSE;
1008 /* [AS] Initialize pv info list [HGM] and game state */
1012 for( i=0; i<=framePtr; i++ ) {
1013 pvInfoList[i].depth = -1;
1014 boards[i][EP_STATUS] = EP_NONE;
1015 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1021 /* [AS] Adjudication threshold */
1022 adjudicateLossThreshold = appData.adjudicateLossThreshold;
1024 InitEngine(&first, 0);
1025 InitEngine(&second, 1);
1028 pairing.which = "pairing"; // pairing engine
1029 pairing.pr = NoProc;
1031 pairing.program = appData.pairingEngine;
1032 pairing.host = "localhost";
1035 if (appData.icsActive) {
1036 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
1037 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1038 appData.clockMode = FALSE;
1039 first.sendTime = second.sendTime = 0;
1043 /* Override some settings from environment variables, for backward
1044 compatibility. Unfortunately it's not feasible to have the env
1045 vars just set defaults, at least in xboard. Ugh.
1047 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1052 if (!appData.icsActive) {
1056 /* Check for variants that are supported only in ICS mode,
1057 or not at all. Some that are accepted here nevertheless
1058 have bugs; see comments below.
1060 VariantClass variant = StringToVariant(appData.variant);
1062 case VariantBughouse: /* need four players and two boards */
1063 case VariantKriegspiel: /* need to hide pieces and move details */
1064 /* case VariantFischeRandom: (Fabien: moved below) */
1065 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1066 if( (len > MSG_SIZ) && appData.debugMode )
1067 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1069 DisplayFatalError(buf, 0, 2);
1072 case VariantUnknown:
1073 case VariantLoadable:
1083 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1084 if( (len > MSG_SIZ) && appData.debugMode )
1085 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1087 DisplayFatalError(buf, 0, 2);
1090 case VariantXiangqi: /* [HGM] repetition rules not implemented */
1091 case VariantFairy: /* [HGM] TestLegality definitely off! */
1092 case VariantGothic: /* [HGM] should work */
1093 case VariantCapablanca: /* [HGM] should work */
1094 case VariantCourier: /* [HGM] initial forced moves not implemented */
1095 case VariantShogi: /* [HGM] could still mate with pawn drop */
1096 case VariantKnightmate: /* [HGM] should work */
1097 case VariantCylinder: /* [HGM] untested */
1098 case VariantFalcon: /* [HGM] untested */
1099 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1100 offboard interposition not understood */
1101 case VariantNormal: /* definitely works! */
1102 case VariantWildCastle: /* pieces not automatically shuffled */
1103 case VariantNoCastle: /* pieces not automatically shuffled */
1104 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1105 case VariantLosers: /* should work except for win condition,
1106 and doesn't know captures are mandatory */
1107 case VariantSuicide: /* should work except for win condition,
1108 and doesn't know captures are mandatory */
1109 case VariantGiveaway: /* should work except for win condition,
1110 and doesn't know captures are mandatory */
1111 case VariantTwoKings: /* should work */
1112 case VariantAtomic: /* should work except for win condition */
1113 case Variant3Check: /* should work except for win condition */
1114 case VariantShatranj: /* should work except for all win conditions */
1115 case VariantMakruk: /* should work except for daw countdown */
1116 case VariantBerolina: /* might work if TestLegality is off */
1117 case VariantCapaRandom: /* should work */
1118 case VariantJanus: /* should work */
1119 case VariantSuper: /* experimental */
1120 case VariantGreat: /* experimental, requires legality testing to be off */
1121 case VariantSChess: /* S-Chess, should work */
1122 case VariantSpartan: /* should work */
1129 int NextIntegerFromString( char ** str, long * value )
1134 while( *s == ' ' || *s == '\t' ) {
1140 if( *s >= '0' && *s <= '9' ) {
1141 while( *s >= '0' && *s <= '9' ) {
1142 *value = *value * 10 + (*s - '0');
1154 int NextTimeControlFromString( char ** str, long * value )
1157 int result = NextIntegerFromString( str, &temp );
1160 *value = temp * 60; /* Minutes */
1161 if( **str == ':' ) {
1163 result = NextIntegerFromString( str, &temp );
1164 *value += temp; /* Seconds */
1171 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1172 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1173 int result = -1, type = 0; long temp, temp2;
1175 if(**str != ':') return -1; // old params remain in force!
1177 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1178 if( NextIntegerFromString( str, &temp ) ) return -1;
1179 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1182 /* time only: incremental or sudden-death time control */
1183 if(**str == '+') { /* increment follows; read it */
1185 if(**str == '!') type = *(*str)++; // Bronstein TC
1186 if(result = NextIntegerFromString( str, &temp2)) return -1;
1187 *inc = temp2 * 1000;
1188 if(**str == '.') { // read fraction of increment
1189 char *start = ++(*str);
1190 if(result = NextIntegerFromString( str, &temp2)) return -1;
1192 while(start++ < *str) temp2 /= 10;
1196 *moves = 0; *tc = temp * 1000; *incType = type;
1200 (*str)++; /* classical time control */
1201 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1212 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1213 { /* [HGM] get time to add from the multi-session time-control string */
1214 int incType, moves=1; /* kludge to force reading of first session */
1215 long time, increment;
1218 if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1219 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1221 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1222 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1223 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1224 if(movenr == -1) return time; /* last move before new session */
1225 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1226 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1227 if(!moves) return increment; /* current session is incremental */
1228 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1229 } while(movenr >= -1); /* try again for next session */
1231 return 0; // no new time quota on this move
1235 ParseTimeControl(tc, ti, mps)
1242 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1245 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1246 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1247 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1251 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1253 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1256 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1258 snprintf(buf, MSG_SIZ, ":%s", mytc);
1260 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1262 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1267 /* Parse second time control */
1270 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1278 timeControl_2 = tc2 * 1000;
1288 timeControl = tc1 * 1000;
1291 timeIncrement = ti * 1000; /* convert to ms */
1292 movesPerSession = 0;
1295 movesPerSession = mps;
1303 if (appData.debugMode) {
1304 fprintf(debugFP, "%s\n", programVersion);
1307 set_cont_sequence(appData.wrapContSeq);
1308 if (appData.matchGames > 0) {
1309 appData.matchMode = TRUE;
1310 } else if (appData.matchMode) {
1311 appData.matchGames = 1;
1313 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1314 appData.matchGames = appData.sameColorGames;
1315 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1316 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1317 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1320 if (appData.noChessProgram || first.protocolVersion == 1) {
1323 /* kludge: allow timeout for initial "feature" commands */
1325 DisplayMessage("", _("Starting chess program"));
1326 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1331 CalculateIndex(int index, int gameNr)
1332 { // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1334 if(index > 0) return index; // fixed nmber
1335 if(index == 0) return 1;
1336 res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1337 if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1342 LoadGameOrPosition(int gameNr)
1343 { // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1344 if (*appData.loadGameFile != NULLCHAR) {
1345 if (!LoadGameFromFile(appData.loadGameFile,
1346 CalculateIndex(appData.loadGameIndex, gameNr),
1347 appData.loadGameFile, FALSE)) {
1348 DisplayFatalError(_("Bad game file"), 0, 1);
1351 } else if (*appData.loadPositionFile != NULLCHAR) {
1352 if (!LoadPositionFromFile(appData.loadPositionFile,
1353 CalculateIndex(appData.loadPositionIndex, gameNr),
1354 appData.loadPositionFile)) {
1355 DisplayFatalError(_("Bad position file"), 0, 1);
1363 ReserveGame(int gameNr, char resChar)
1365 FILE *tf = fopen(appData.tourneyFile, "r+");
1366 char *p, *q, c, buf[MSG_SIZ];
1367 if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1368 safeStrCpy(buf, lastMsg, MSG_SIZ);
1369 DisplayMessage(_("Pick new game"), "");
1370 flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1371 ParseArgsFromFile(tf);
1372 p = q = appData.results;
1373 if(appData.debugMode) {
1374 char *r = appData.participants;
1375 fprintf(debugFP, "results = '%s'\n", p);
1376 while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1377 fprintf(debugFP, "\n");
1379 while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1381 q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1382 safeStrCpy(q, p, strlen(p) + 2);
1383 if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1384 if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1385 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1386 if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1389 fseek(tf, -(strlen(p)+4), SEEK_END);
1391 if(c != '"') // depending on DOS or Unix line endings we can be one off
1392 fseek(tf, -(strlen(p)+2), SEEK_END);
1393 else fseek(tf, -(strlen(p)+3), SEEK_END);
1394 fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1395 DisplayMessage(buf, "");
1396 free(p); appData.results = q;
1397 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1398 (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1399 UnloadEngine(&first); // next game belongs to other pairing;
1400 UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1405 MatchEvent(int mode)
1406 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1408 if(matchMode) { // already in match mode: switch it off
1410 if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1413 // if(gameMode != BeginningOfGame) {
1414 // DisplayError(_("You can only start a match from the initial position."), 0);
1418 if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1419 /* Set up machine vs. machine match */
1421 NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1422 if(appData.tourneyFile[0]) {
1424 if(nextGame > appData.matchGames) {
1426 if(strchr(appData.results, '*') == NULL) {
1428 appData.tourneyCycles++;
1429 if(f = WriteTourneyFile(appData.results)) { // make a tourney file with increased number of cycles
1431 NextTourneyGame(-1, &dummy);
1433 if(nextGame <= appData.matchGames) {
1434 DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1436 ScheduleDelayedEvent(NextMatchGame, 10000);
1441 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1442 DisplayError(buf, 0);
1443 appData.tourneyFile[0] = 0;
1447 if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically
1448 DisplayFatalError(_("Can't have a match with no chess programs"),
1453 matchGame = roundNr = 1;
1454 first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1459 InitBackEnd3 P((void))
1461 GameMode initialMode;
1465 InitChessProgram(&first, startedFromSetupPosition);
1467 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1468 free(programVersion);
1469 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1470 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1473 if (appData.icsActive) {
1475 /* [DM] Make a console window if needed [HGM] merged ifs */
1481 if (*appData.icsCommPort != NULLCHAR)
1482 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1483 appData.icsCommPort);
1485 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1486 appData.icsHost, appData.icsPort);
1488 if( (len > MSG_SIZ) && appData.debugMode )
1489 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1491 DisplayFatalError(buf, err, 1);
1496 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1498 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1499 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1500 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1501 } else if (appData.noChessProgram) {
1507 if (*appData.cmailGameName != NULLCHAR) {
1509 OpenLoopback(&cmailPR);
1511 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1515 DisplayMessage("", "");
1516 if (StrCaseCmp(appData.initialMode, "") == 0) {
1517 initialMode = BeginningOfGame;
1518 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1519 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1520 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1521 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1524 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1525 initialMode = TwoMachinesPlay;
1526 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1527 initialMode = AnalyzeFile;
1528 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1529 initialMode = AnalyzeMode;
1530 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1531 initialMode = MachinePlaysWhite;
1532 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1533 initialMode = MachinePlaysBlack;
1534 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1535 initialMode = EditGame;
1536 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1537 initialMode = EditPosition;
1538 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1539 initialMode = Training;
1541 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1542 if( (len > MSG_SIZ) && appData.debugMode )
1543 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1545 DisplayFatalError(buf, 0, 2);
1549 if (appData.matchMode) {
1550 if(appData.tourneyFile[0]) { // start tourney from command line
1552 if(f = fopen(appData.tourneyFile, "r")) {
1553 ParseArgsFromFile(f); // make sure tourney parmeters re known
1555 appData.clockMode = TRUE;
1557 } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1560 } else if (*appData.cmailGameName != NULLCHAR) {
1561 /* Set up cmail mode */
1562 ReloadCmailMsgEvent(TRUE);
1564 /* Set up other modes */
1565 if (initialMode == AnalyzeFile) {
1566 if (*appData.loadGameFile == NULLCHAR) {
1567 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1571 if (*appData.loadGameFile != NULLCHAR) {
1572 (void) LoadGameFromFile(appData.loadGameFile,
1573 appData.loadGameIndex,
1574 appData.loadGameFile, TRUE);
1575 } else if (*appData.loadPositionFile != NULLCHAR) {
1576 (void) LoadPositionFromFile(appData.loadPositionFile,
1577 appData.loadPositionIndex,
1578 appData.loadPositionFile);
1579 /* [HGM] try to make self-starting even after FEN load */
1580 /* to allow automatic setup of fairy variants with wtm */
1581 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1582 gameMode = BeginningOfGame;
1583 setboardSpoiledMachineBlack = 1;
1585 /* [HGM] loadPos: make that every new game uses the setup */
1586 /* from file as long as we do not switch variant */
1587 if(!blackPlaysFirst) {
1588 startedFromPositionFile = TRUE;
1589 CopyBoard(filePosition, boards[0]);
1592 if (initialMode == AnalyzeMode) {
1593 if (appData.noChessProgram) {
1594 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1597 if (appData.icsActive) {
1598 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1602 } else if (initialMode == AnalyzeFile) {
1603 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1604 ShowThinkingEvent();
1606 AnalysisPeriodicEvent(1);
1607 } else if (initialMode == MachinePlaysWhite) {
1608 if (appData.noChessProgram) {
1609 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1613 if (appData.icsActive) {
1614 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1618 MachineWhiteEvent();
1619 } else if (initialMode == MachinePlaysBlack) {
1620 if (appData.noChessProgram) {
1621 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1625 if (appData.icsActive) {
1626 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1630 MachineBlackEvent();
1631 } else if (initialMode == TwoMachinesPlay) {
1632 if (appData.noChessProgram) {
1633 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1637 if (appData.icsActive) {
1638 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1643 } else if (initialMode == EditGame) {
1645 } else if (initialMode == EditPosition) {
1646 EditPositionEvent();
1647 } else if (initialMode == Training) {
1648 if (*appData.loadGameFile == NULLCHAR) {
1649 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1658 * Establish will establish a contact to a remote host.port.
1659 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1660 * used to talk to the host.
1661 * Returns 0 if okay, error code if not.
1668 if (*appData.icsCommPort != NULLCHAR) {
1669 /* Talk to the host through a serial comm port */
1670 return OpenCommPort(appData.icsCommPort, &icsPR);
1672 } else if (*appData.gateway != NULLCHAR) {
1673 if (*appData.remoteShell == NULLCHAR) {
1674 /* Use the rcmd protocol to run telnet program on a gateway host */
1675 snprintf(buf, sizeof(buf), "%s %s %s",
1676 appData.telnetProgram, appData.icsHost, appData.icsPort);
1677 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1680 /* Use the rsh program to run telnet program on a gateway host */
1681 if (*appData.remoteUser == NULLCHAR) {
1682 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1683 appData.gateway, appData.telnetProgram,
1684 appData.icsHost, appData.icsPort);
1686 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1687 appData.remoteShell, appData.gateway,
1688 appData.remoteUser, appData.telnetProgram,
1689 appData.icsHost, appData.icsPort);
1691 return StartChildProcess(buf, "", &icsPR);
1694 } else if (appData.useTelnet) {
1695 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1698 /* TCP socket interface differs somewhat between
1699 Unix and NT; handle details in the front end.
1701 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1705 void EscapeExpand(char *p, char *q)
1706 { // [HGM] initstring: routine to shape up string arguments
1707 while(*p++ = *q++) if(p[-1] == '\\')
1709 case 'n': p[-1] = '\n'; break;
1710 case 'r': p[-1] = '\r'; break;
1711 case 't': p[-1] = '\t'; break;
1712 case '\\': p[-1] = '\\'; break;
1713 case 0: *p = 0; return;
1714 default: p[-1] = q[-1]; break;
1719 show_bytes(fp, buf, count)
1725 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1726 fprintf(fp, "\\%03o", *buf & 0xff);
1735 /* Returns an errno value */
1737 OutputMaybeTelnet(pr, message, count, outError)
1743 char buf[8192], *p, *q, *buflim;
1744 int left, newcount, outcount;
1746 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1747 *appData.gateway != NULLCHAR) {
1748 if (appData.debugMode) {
1749 fprintf(debugFP, ">ICS: ");
1750 show_bytes(debugFP, message, count);
1751 fprintf(debugFP, "\n");
1753 return OutputToProcess(pr, message, count, outError);
1756 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1763 if (appData.debugMode) {
1764 fprintf(debugFP, ">ICS: ");
1765 show_bytes(debugFP, buf, newcount);
1766 fprintf(debugFP, "\n");
1768 outcount = OutputToProcess(pr, buf, newcount, outError);
1769 if (outcount < newcount) return -1; /* to be sure */
1776 } else if (((unsigned char) *p) == TN_IAC) {
1777 *q++ = (char) TN_IAC;
1784 if (appData.debugMode) {
1785 fprintf(debugFP, ">ICS: ");
1786 show_bytes(debugFP, buf, newcount);
1787 fprintf(debugFP, "\n");
1789 outcount = OutputToProcess(pr, buf, newcount, outError);
1790 if (outcount < newcount) return -1; /* to be sure */
1795 read_from_player(isr, closure, message, count, error)
1802 int outError, outCount;
1803 static int gotEof = 0;
1805 /* Pass data read from player on to ICS */
1808 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1809 if (outCount < count) {
1810 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1812 } else if (count < 0) {
1813 RemoveInputSource(isr);
1814 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1815 } else if (gotEof++ > 0) {
1816 RemoveInputSource(isr);
1817 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1823 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1824 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1825 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1826 SendToICS("date\n");
1827 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1830 /* added routine for printf style output to ics */
1831 void ics_printf(char *format, ...)
1833 char buffer[MSG_SIZ];
1836 va_start(args, format);
1837 vsnprintf(buffer, sizeof(buffer), format, args);
1838 buffer[sizeof(buffer)-1] = '\0';
1847 int count, outCount, outError;
1849 if (icsPR == NULL) return;
1852 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1853 if (outCount < count) {
1854 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1858 /* This is used for sending logon scripts to the ICS. Sending
1859 without a delay causes problems when using timestamp on ICC
1860 (at least on my machine). */
1862 SendToICSDelayed(s,msdelay)
1866 int count, outCount, outError;
1868 if (icsPR == NULL) return;
1871 if (appData.debugMode) {
1872 fprintf(debugFP, ">ICS: ");
1873 show_bytes(debugFP, s, count);
1874 fprintf(debugFP, "\n");
1876 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1878 if (outCount < count) {
1879 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1884 /* Remove all highlighting escape sequences in s
1885 Also deletes any suffix starting with '('
1888 StripHighlightAndTitle(s)
1891 static char retbuf[MSG_SIZ];
1894 while (*s != NULLCHAR) {
1895 while (*s == '\033') {
1896 while (*s != NULLCHAR && !isalpha(*s)) s++;
1897 if (*s != NULLCHAR) s++;
1899 while (*s != NULLCHAR && *s != '\033') {
1900 if (*s == '(' || *s == '[') {
1911 /* Remove all highlighting escape sequences in s */
1916 static char retbuf[MSG_SIZ];
1919 while (*s != NULLCHAR) {
1920 while (*s == '\033') {
1921 while (*s != NULLCHAR && !isalpha(*s)) s++;
1922 if (*s != NULLCHAR) s++;
1924 while (*s != NULLCHAR && *s != '\033') {
1932 char *variantNames[] = VARIANT_NAMES;
1937 return variantNames[v];
1941 /* Identify a variant from the strings the chess servers use or the
1942 PGN Variant tag names we use. */
1949 VariantClass v = VariantNormal;
1950 int i, found = FALSE;
1956 /* [HGM] skip over optional board-size prefixes */
1957 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1958 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1959 while( *e++ != '_');
1962 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1966 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1967 if (StrCaseStr(e, variantNames[i])) {
1968 v = (VariantClass) i;
1975 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1976 || StrCaseStr(e, "wild/fr")
1977 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1978 v = VariantFischeRandom;
1979 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1980 (i = 1, p = StrCaseStr(e, "w"))) {
1982 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1989 case 0: /* FICS only, actually */
1991 /* Castling legal even if K starts on d-file */
1992 v = VariantWildCastle;
1997 /* Castling illegal even if K & R happen to start in
1998 normal positions. */
1999 v = VariantNoCastle;
2012 /* Castling legal iff K & R start in normal positions */
2018 /* Special wilds for position setup; unclear what to do here */
2019 v = VariantLoadable;
2022 /* Bizarre ICC game */
2023 v = VariantTwoKings;
2026 v = VariantKriegspiel;
2032 v = VariantFischeRandom;
2035 v = VariantCrazyhouse;
2038 v = VariantBughouse;
2044 /* Not quite the same as FICS suicide! */
2045 v = VariantGiveaway;
2051 v = VariantShatranj;
2054 /* Temporary names for future ICC types. The name *will* change in
2055 the next xboard/WinBoard release after ICC defines it. */
2093 v = VariantCapablanca;
2096 v = VariantKnightmate;
2102 v = VariantCylinder;
2108 v = VariantCapaRandom;
2111 v = VariantBerolina;
2123 /* Found "wild" or "w" in the string but no number;
2124 must assume it's normal chess. */
2128 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2129 if( (len > MSG_SIZ) && appData.debugMode )
2130 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2132 DisplayError(buf, 0);
2138 if (appData.debugMode) {
2139 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2140 e, wnum, VariantName(v));
2145 static int leftover_start = 0, leftover_len = 0;
2146 char star_match[STAR_MATCH_N][MSG_SIZ];
2148 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2149 advance *index beyond it, and set leftover_start to the new value of
2150 *index; else return FALSE. If pattern contains the character '*', it
2151 matches any sequence of characters not containing '\r', '\n', or the
2152 character following the '*' (if any), and the matched sequence(s) are
2153 copied into star_match.
2156 looking_at(buf, index, pattern)
2161 char *bufp = &buf[*index], *patternp = pattern;
2163 char *matchp = star_match[0];
2166 if (*patternp == NULLCHAR) {
2167 *index = leftover_start = bufp - buf;
2171 if (*bufp == NULLCHAR) return FALSE;
2172 if (*patternp == '*') {
2173 if (*bufp == *(patternp + 1)) {
2175 matchp = star_match[++star_count];
2179 } else if (*bufp == '\n' || *bufp == '\r') {
2181 if (*patternp == NULLCHAR)
2186 *matchp++ = *bufp++;
2190 if (*patternp != *bufp) return FALSE;
2197 SendToPlayer(data, length)
2201 int error, outCount;
2202 outCount = OutputToProcess(NoProc, data, length, &error);
2203 if (outCount < length) {
2204 DisplayFatalError(_("Error writing to display"), error, 1);
2209 PackHolding(packed, holding)
2221 switch (runlength) {
2232 sprintf(q, "%d", runlength);
2244 /* Telnet protocol requests from the front end */
2246 TelnetRequest(ddww, option)
2247 unsigned char ddww, option;
2249 unsigned char msg[3];
2250 int outCount, outError;
2252 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2254 if (appData.debugMode) {
2255 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2271 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2280 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2283 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2288 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2290 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2297 if (!appData.icsActive) return;
2298 TelnetRequest(TN_DO, TN_ECHO);
2304 if (!appData.icsActive) return;
2305 TelnetRequest(TN_DONT, TN_ECHO);
2309 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2311 /* put the holdings sent to us by the server on the board holdings area */
2312 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2316 if(gameInfo.holdingsWidth < 2) return;
2317 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2318 return; // prevent overwriting by pre-board holdings
2320 if( (int)lowestPiece >= BlackPawn ) {
2323 holdingsStartRow = BOARD_HEIGHT-1;
2326 holdingsColumn = BOARD_WIDTH-1;
2327 countsColumn = BOARD_WIDTH-2;
2328 holdingsStartRow = 0;
2332 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2333 board[i][holdingsColumn] = EmptySquare;
2334 board[i][countsColumn] = (ChessSquare) 0;
2336 while( (p=*holdings++) != NULLCHAR ) {
2337 piece = CharToPiece( ToUpper(p) );
2338 if(piece == EmptySquare) continue;
2339 /*j = (int) piece - (int) WhitePawn;*/
2340 j = PieceToNumber(piece);
2341 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2342 if(j < 0) continue; /* should not happen */
2343 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2344 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2345 board[holdingsStartRow+j*direction][countsColumn]++;
2351 VariantSwitch(Board board, VariantClass newVariant)
2353 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2354 static Board oldBoard;
2356 startedFromPositionFile = FALSE;
2357 if(gameInfo.variant == newVariant) return;
2359 /* [HGM] This routine is called each time an assignment is made to
2360 * gameInfo.variant during a game, to make sure the board sizes
2361 * are set to match the new variant. If that means adding or deleting
2362 * holdings, we shift the playing board accordingly
2363 * This kludge is needed because in ICS observe mode, we get boards
2364 * of an ongoing game without knowing the variant, and learn about the
2365 * latter only later. This can be because of the move list we requested,
2366 * in which case the game history is refilled from the beginning anyway,
2367 * but also when receiving holdings of a crazyhouse game. In the latter
2368 * case we want to add those holdings to the already received position.
2372 if (appData.debugMode) {
2373 fprintf(debugFP, "Switch board from %s to %s\n",
2374 VariantName(gameInfo.variant), VariantName(newVariant));
2375 setbuf(debugFP, NULL);
2377 shuffleOpenings = 0; /* [HGM] shuffle */
2378 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2382 newWidth = 9; newHeight = 9;
2383 gameInfo.holdingsSize = 7;
2384 case VariantBughouse:
2385 case VariantCrazyhouse:
2386 newHoldingsWidth = 2; break;
2390 newHoldingsWidth = 2;
2391 gameInfo.holdingsSize = 8;
2394 case VariantCapablanca:
2395 case VariantCapaRandom:
2398 newHoldingsWidth = gameInfo.holdingsSize = 0;
2401 if(newWidth != gameInfo.boardWidth ||
2402 newHeight != gameInfo.boardHeight ||
2403 newHoldingsWidth != gameInfo.holdingsWidth ) {
2405 /* shift position to new playing area, if needed */
2406 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2407 for(i=0; i<BOARD_HEIGHT; i++)
2408 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2409 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2411 for(i=0; i<newHeight; i++) {
2412 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2413 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2415 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2416 for(i=0; i<BOARD_HEIGHT; i++)
2417 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2418 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2421 gameInfo.boardWidth = newWidth;
2422 gameInfo.boardHeight = newHeight;
2423 gameInfo.holdingsWidth = newHoldingsWidth;
2424 gameInfo.variant = newVariant;
2425 InitDrawingSizes(-2, 0);
2426 } else gameInfo.variant = newVariant;
2427 CopyBoard(oldBoard, board); // remember correctly formatted board
2428 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2429 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2432 static int loggedOn = FALSE;
2434 /*-- Game start info cache: --*/
2436 char gs_kind[MSG_SIZ];
2437 static char player1Name[128] = "";
2438 static char player2Name[128] = "";
2439 static char cont_seq[] = "\n\\ ";
2440 static int player1Rating = -1;
2441 static int player2Rating = -1;
2442 /*----------------------------*/
2444 ColorClass curColor = ColorNormal;
2445 int suppressKibitz = 0;
2448 Boolean soughtPending = FALSE;
2449 Boolean seekGraphUp;
2450 #define MAX_SEEK_ADS 200
2452 char *seekAdList[MAX_SEEK_ADS];
2453 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2454 float tcList[MAX_SEEK_ADS];
2455 char colorList[MAX_SEEK_ADS];
2456 int nrOfSeekAds = 0;
2457 int minRating = 1010, maxRating = 2800;
2458 int hMargin = 10, vMargin = 20, h, w;
2459 extern int squareSize, lineGap;
2464 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2465 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2466 if(r < minRating+100 && r >=0 ) r = minRating+100;
2467 if(r > maxRating) r = maxRating;
2468 if(tc < 1.) tc = 1.;
2469 if(tc > 95.) tc = 95.;
2470 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2471 y = ((double)r - minRating)/(maxRating - minRating)
2472 * (h-vMargin-squareSize/8-1) + vMargin;
2473 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2474 if(strstr(seekAdList[i], " u ")) color = 1;
2475 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2476 !strstr(seekAdList[i], "bullet") &&
2477 !strstr(seekAdList[i], "blitz") &&
2478 !strstr(seekAdList[i], "standard") ) color = 2;
2479 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2480 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2484 AddAd(char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2486 char buf[MSG_SIZ], *ext = "";
2487 VariantClass v = StringToVariant(type);
2488 if(strstr(type, "wild")) {
2489 ext = type + 4; // append wild number
2490 if(v == VariantFischeRandom) type = "chess960"; else
2491 if(v == VariantLoadable) type = "setup"; else
2492 type = VariantName(v);
2494 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2495 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2496 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2497 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2498 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2499 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2500 seekNrList[nrOfSeekAds] = nr;
2501 zList[nrOfSeekAds] = 0;
2502 seekAdList[nrOfSeekAds++] = StrSave(buf);
2503 if(plot) PlotSeekAd(nrOfSeekAds-1);
2510 int x = xList[i], y = yList[i], d=squareSize/4, k;
2511 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2512 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2513 // now replot every dot that overlapped
2514 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2515 int xx = xList[k], yy = yList[k];
2516 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2517 DrawSeekDot(xx, yy, colorList[k]);
2522 RemoveSeekAd(int nr)
2525 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2527 if(seekAdList[i]) free(seekAdList[i]);
2528 seekAdList[i] = seekAdList[--nrOfSeekAds];
2529 seekNrList[i] = seekNrList[nrOfSeekAds];
2530 ratingList[i] = ratingList[nrOfSeekAds];
2531 colorList[i] = colorList[nrOfSeekAds];
2532 tcList[i] = tcList[nrOfSeekAds];
2533 xList[i] = xList[nrOfSeekAds];
2534 yList[i] = yList[nrOfSeekAds];
2535 zList[i] = zList[nrOfSeekAds];
2536 seekAdList[nrOfSeekAds] = NULL;
2542 MatchSoughtLine(char *line)
2544 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2545 int nr, base, inc, u=0; char dummy;
2547 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2548 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2550 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2551 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2552 // match: compact and save the line
2553 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2563 if(!seekGraphUp) return FALSE;
2564 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2565 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2567 DrawSeekBackground(0, 0, w, h);
2568 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2569 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2570 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2571 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2573 DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2576 snprintf(buf, MSG_SIZ, "%d", i);
2577 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2580 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2581 for(i=1; i<100; i+=(i<10?1:5)) {
2582 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2583 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2584 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2586 snprintf(buf, MSG_SIZ, "%d", i);
2587 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2590 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2594 int SeekGraphClick(ClickType click, int x, int y, int moving)
2596 static int lastDown = 0, displayed = 0, lastSecond;
2597 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2598 if(click == Release || moving) return FALSE;
2600 soughtPending = TRUE;
2601 SendToICS(ics_prefix);
2602 SendToICS("sought\n"); // should this be "sought all"?
2603 } else { // issue challenge based on clicked ad
2604 int dist = 10000; int i, closest = 0, second = 0;
2605 for(i=0; i<nrOfSeekAds; i++) {
2606 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2607 if(d < dist) { dist = d; closest = i; }
2608 second += (d - zList[i] < 120); // count in-range ads
2609 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2613 second = (second > 1);
2614 if(displayed != closest || second != lastSecond) {
2615 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2616 lastSecond = second; displayed = closest;
2618 if(click == Press) {
2619 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2622 } // on press 'hit', only show info
2623 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2624 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2625 SendToICS(ics_prefix);
2627 return TRUE; // let incoming board of started game pop down the graph
2628 } else if(click == Release) { // release 'miss' is ignored
2629 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2630 if(moving == 2) { // right up-click
2631 nrOfSeekAds = 0; // refresh graph
2632 soughtPending = TRUE;
2633 SendToICS(ics_prefix);
2634 SendToICS("sought\n"); // should this be "sought all"?
2637 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2638 // press miss or release hit 'pop down' seek graph
2639 seekGraphUp = FALSE;
2640 DrawPosition(TRUE, NULL);
2646 read_from_ics(isr, closure, data, count, error)
2653 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2654 #define STARTED_NONE 0
2655 #define STARTED_MOVES 1
2656 #define STARTED_BOARD 2
2657 #define STARTED_OBSERVE 3
2658 #define STARTED_HOLDINGS 4
2659 #define STARTED_CHATTER 5
2660 #define STARTED_COMMENT 6
2661 #define STARTED_MOVES_NOHIDE 7
2663 static int started = STARTED_NONE;
2664 static char parse[20000];
2665 static int parse_pos = 0;
2666 static char buf[BUF_SIZE + 1];
2667 static int firstTime = TRUE, intfSet = FALSE;
2668 static ColorClass prevColor = ColorNormal;
2669 static int savingComment = FALSE;
2670 static int cmatch = 0; // continuation sequence match
2677 int backup; /* [DM] For zippy color lines */
2679 char talker[MSG_SIZ]; // [HGM] chat
2682 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2684 if (appData.debugMode) {
2686 fprintf(debugFP, "<ICS: ");
2687 show_bytes(debugFP, data, count);
2688 fprintf(debugFP, "\n");
2692 if (appData.debugMode) { int f = forwardMostMove;
2693 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2694 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2695 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2698 /* If last read ended with a partial line that we couldn't parse,
2699 prepend it to the new read and try again. */
2700 if (leftover_len > 0) {
2701 for (i=0; i<leftover_len; i++)
2702 buf[i] = buf[leftover_start + i];
2705 /* copy new characters into the buffer */
2706 bp = buf + leftover_len;
2707 buf_len=leftover_len;
2708 for (i=0; i<count; i++)
2711 if (data[i] == '\r')
2714 // join lines split by ICS?
2715 if (!appData.noJoin)
2718 Joining just consists of finding matches against the
2719 continuation sequence, and discarding that sequence
2720 if found instead of copying it. So, until a match
2721 fails, there's nothing to do since it might be the
2722 complete sequence, and thus, something we don't want
2725 if (data[i] == cont_seq[cmatch])
2728 if (cmatch == strlen(cont_seq))
2730 cmatch = 0; // complete match. just reset the counter
2733 it's possible for the ICS to not include the space
2734 at the end of the last word, making our [correct]
2735 join operation fuse two separate words. the server
2736 does this when the space occurs at the width setting.
2738 if (!buf_len || buf[buf_len-1] != ' ')
2749 match failed, so we have to copy what matched before
2750 falling through and copying this character. In reality,
2751 this will only ever be just the newline character, but
2752 it doesn't hurt to be precise.
2754 strncpy(bp, cont_seq, cmatch);
2766 buf[buf_len] = NULLCHAR;
2767 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2772 while (i < buf_len) {
2773 /* Deal with part of the TELNET option negotiation
2774 protocol. We refuse to do anything beyond the
2775 defaults, except that we allow the WILL ECHO option,
2776 which ICS uses to turn off password echoing when we are
2777 directly connected to it. We reject this option
2778 if localLineEditing mode is on (always on in xboard)
2779 and we are talking to port 23, which might be a real
2780 telnet server that will try to keep WILL ECHO on permanently.
2782 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2783 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2784 unsigned char option;
2786 switch ((unsigned char) buf[++i]) {
2788 if (appData.debugMode)
2789 fprintf(debugFP, "\n<WILL ");
2790 switch (option = (unsigned char) buf[++i]) {
2792 if (appData.debugMode)
2793 fprintf(debugFP, "ECHO ");
2794 /* Reply only if this is a change, according
2795 to the protocol rules. */
2796 if (remoteEchoOption) break;
2797 if (appData.localLineEditing &&
2798 atoi(appData.icsPort) == TN_PORT) {
2799 TelnetRequest(TN_DONT, TN_ECHO);
2802 TelnetRequest(TN_DO, TN_ECHO);
2803 remoteEchoOption = TRUE;
2807 if (appData.debugMode)
2808 fprintf(debugFP, "%d ", option);
2809 /* Whatever this is, we don't want it. */
2810 TelnetRequest(TN_DONT, option);
2815 if (appData.debugMode)
2816 fprintf(debugFP, "\n<WONT ");
2817 switch (option = (unsigned char) buf[++i]) {
2819 if (appData.debugMode)
2820 fprintf(debugFP, "ECHO ");
2821 /* Reply only if this is a change, according
2822 to the protocol rules. */
2823 if (!remoteEchoOption) break;
2825 TelnetRequest(TN_DONT, TN_ECHO);
2826 remoteEchoOption = FALSE;
2829 if (appData.debugMode)
2830 fprintf(debugFP, "%d ", (unsigned char) option);
2831 /* Whatever this is, it must already be turned
2832 off, because we never agree to turn on
2833 anything non-default, so according to the
2834 protocol rules, we don't reply. */
2839 if (appData.debugMode)
2840 fprintf(debugFP, "\n<DO ");
2841 switch (option = (unsigned char) buf[++i]) {
2843 /* Whatever this is, we refuse to do it. */
2844 if (appData.debugMode)
2845 fprintf(debugFP, "%d ", option);
2846 TelnetRequest(TN_WONT, option);
2851 if (appData.debugMode)
2852 fprintf(debugFP, "\n<DONT ");
2853 switch (option = (unsigned char) buf[++i]) {
2855 if (appData.debugMode)
2856 fprintf(debugFP, "%d ", option);
2857 /* Whatever this is, we are already not doing
2858 it, because we never agree to do anything
2859 non-default, so according to the protocol
2860 rules, we don't reply. */
2865 if (appData.debugMode)
2866 fprintf(debugFP, "\n<IAC ");
2867 /* Doubled IAC; pass it through */
2871 if (appData.debugMode)
2872 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2873 /* Drop all other telnet commands on the floor */
2876 if (oldi > next_out)
2877 SendToPlayer(&buf[next_out], oldi - next_out);
2883 /* OK, this at least will *usually* work */
2884 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2888 if (loggedOn && !intfSet) {
2889 if (ics_type == ICS_ICC) {
2890 snprintf(str, MSG_SIZ,
2891 "/set-quietly interface %s\n/set-quietly style 12\n",
2893 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2894 strcat(str, "/set-2 51 1\n/set seek 1\n");
2895 } else if (ics_type == ICS_CHESSNET) {
2896 snprintf(str, MSG_SIZ, "/style 12\n");
2898 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2899 strcat(str, programVersion);
2900 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2901 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2902 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2904 strcat(str, "$iset nohighlight 1\n");
2906 strcat(str, "$iset lock 1\n$style 12\n");
2909 NotifyFrontendLogin();
2913 if (started == STARTED_COMMENT) {
2914 /* Accumulate characters in comment */
2915 parse[parse_pos++] = buf[i];
2916 if (buf[i] == '\n') {
2917 parse[parse_pos] = NULLCHAR;
2918 if(chattingPartner>=0) {
2920 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2921 OutputChatMessage(chattingPartner, mess);
2922 chattingPartner = -1;
2923 next_out = i+1; // [HGM] suppress printing in ICS window
2925 if(!suppressKibitz) // [HGM] kibitz
2926 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2927 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2928 int nrDigit = 0, nrAlph = 0, j;
2929 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2930 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2931 parse[parse_pos] = NULLCHAR;
2932 // try to be smart: if it does not look like search info, it should go to
2933 // ICS interaction window after all, not to engine-output window.
2934 for(j=0; j<parse_pos; j++) { // count letters and digits
2935 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2936 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2937 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2939 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2940 int depth=0; float score;
2941 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2942 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2943 pvInfoList[forwardMostMove-1].depth = depth;
2944 pvInfoList[forwardMostMove-1].score = 100*score;
2946 OutputKibitz(suppressKibitz, parse);
2949 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2950 SendToPlayer(tmp, strlen(tmp));
2952 next_out = i+1; // [HGM] suppress printing in ICS window
2954 started = STARTED_NONE;
2956 /* Don't match patterns against characters in comment */
2961 if (started == STARTED_CHATTER) {
2962 if (buf[i] != '\n') {
2963 /* Don't match patterns against characters in chatter */
2967 started = STARTED_NONE;
2968 if(suppressKibitz) next_out = i+1;
2971 /* Kludge to deal with rcmd protocol */
2972 if (firstTime && looking_at(buf, &i, "\001*")) {
2973 DisplayFatalError(&buf[1], 0, 1);
2979 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2982 if (appData.debugMode)
2983 fprintf(debugFP, "ics_type %d\n", ics_type);
2986 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2987 ics_type = ICS_FICS;
2989 if (appData.debugMode)
2990 fprintf(debugFP, "ics_type %d\n", ics_type);
2993 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2994 ics_type = ICS_CHESSNET;
2996 if (appData.debugMode)
2997 fprintf(debugFP, "ics_type %d\n", ics_type);
3002 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3003 looking_at(buf, &i, "Logging you in as \"*\"") ||
3004 looking_at(buf, &i, "will be \"*\""))) {
3005 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3009 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3011 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3012 DisplayIcsInteractionTitle(buf);
3013 have_set_title = TRUE;
3016 /* skip finger notes */
3017 if (started == STARTED_NONE &&
3018 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3019 (buf[i] == '1' && buf[i+1] == '0')) &&
3020 buf[i+2] == ':' && buf[i+3] == ' ') {
3021 started = STARTED_CHATTER;
3027 // [HGM] seekgraph: recognize sought lines and end-of-sought message
3028 if(appData.seekGraph) {
3029 if(soughtPending && MatchSoughtLine(buf+i)) {
3030 i = strstr(buf+i, "rated") - buf;
3031 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3032 next_out = leftover_start = i;
3033 started = STARTED_CHATTER;
3034 suppressKibitz = TRUE;
3037 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3038 && looking_at(buf, &i, "* ads displayed")) {
3039 soughtPending = FALSE;
3044 if(appData.autoRefresh) {
3045 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3046 int s = (ics_type == ICS_ICC); // ICC format differs
3048 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3049 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3050 looking_at(buf, &i, "*% "); // eat prompt
3051 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3052 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3053 next_out = i; // suppress
3056 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3057 char *p = star_match[0];
3059 if(seekGraphUp) RemoveSeekAd(atoi(p));
3060 while(*p && *p++ != ' '); // next
3062 looking_at(buf, &i, "*% "); // eat prompt
3063 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3070 /* skip formula vars */
3071 if (started == STARTED_NONE &&
3072 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3073 started = STARTED_CHATTER;
3078 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3079 if (appData.autoKibitz && started == STARTED_NONE &&
3080 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3081 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3082 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3083 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3084 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3085 suppressKibitz = TRUE;
3086 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3088 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3089 && (gameMode == IcsPlayingWhite)) ||
3090 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3091 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3092 started = STARTED_CHATTER; // own kibitz we simply discard
3094 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3095 parse_pos = 0; parse[0] = NULLCHAR;
3096 savingComment = TRUE;
3097 suppressKibitz = gameMode != IcsObserving ? 2 :
3098 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3102 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3103 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3104 && atoi(star_match[0])) {
3105 // suppress the acknowledgements of our own autoKibitz
3107 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3108 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3109 SendToPlayer(star_match[0], strlen(star_match[0]));
3110 if(looking_at(buf, &i, "*% ")) // eat prompt
3111 suppressKibitz = FALSE;
3115 } // [HGM] kibitz: end of patch
3117 // [HGM] chat: intercept tells by users for which we have an open chat window
3119 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3120 looking_at(buf, &i, "* whispers:") ||
3121 looking_at(buf, &i, "* kibitzes:") ||
3122 looking_at(buf, &i, "* shouts:") ||
3123 looking_at(buf, &i, "* c-shouts:") ||
3124 looking_at(buf, &i, "--> * ") ||
3125 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3126 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3127 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3128 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3130 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3131 chattingPartner = -1;
3133 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3134 for(p=0; p<MAX_CHAT; p++) {
3135 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3136 talker[0] = '['; strcat(talker, "] ");
3137 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3138 chattingPartner = p; break;
3141 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3142 for(p=0; p<MAX_CHAT; p++) {
3143 if(!strcmp("kibitzes", chatPartner[p])) {
3144 talker[0] = '['; strcat(talker, "] ");
3145 chattingPartner = p; break;
3148 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3149 for(p=0; p<MAX_CHAT; p++) {
3150 if(!strcmp("whispers", chatPartner[p])) {
3151 talker[0] = '['; strcat(talker, "] ");
3152 chattingPartner = p; break;
3155 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3156 if(buf[i-8] == '-' && buf[i-3] == 't')
3157 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3158 if(!strcmp("c-shouts", chatPartner[p])) {
3159 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3160 chattingPartner = p; break;
3163 if(chattingPartner < 0)
3164 for(p=0; p<MAX_CHAT; p++) {
3165 if(!strcmp("shouts", chatPartner[p])) {
3166 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3167 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3168 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3169 chattingPartner = p; break;
3173 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3174 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3175 talker[0] = 0; Colorize(ColorTell, FALSE);
3176 chattingPartner = p; break;
3178 if(chattingPartner<0) i = oldi; else {
3179 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3180 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3181 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3182 started = STARTED_COMMENT;
3183 parse_pos = 0; parse[0] = NULLCHAR;
3184 savingComment = 3 + chattingPartner; // counts as TRUE
3185 suppressKibitz = TRUE;
3188 } // [HGM] chat: end of patch
3191 if (appData.zippyTalk || appData.zippyPlay) {
3192 /* [DM] Backup address for color zippy lines */
3194 if (loggedOn == TRUE)
3195 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3196 (appData.zippyPlay && ZippyMatch(buf, &backup)));
3198 } // [DM] 'else { ' deleted
3200 /* Regular tells and says */
3201 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3202 looking_at(buf, &i, "* (your partner) tells you: ") ||
3203 looking_at(buf, &i, "* says: ") ||
3204 /* Don't color "message" or "messages" output */
3205 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3206 looking_at(buf, &i, "*. * at *:*: ") ||
3207 looking_at(buf, &i, "--* (*:*): ") ||
3208 /* Message notifications (same color as tells) */
3209 looking_at(buf, &i, "* has left a message ") ||
3210 looking_at(buf, &i, "* just sent you a message:\n") ||
3211 /* Whispers and kibitzes */
3212 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3213 looking_at(buf, &i, "* kibitzes: ") ||
3215 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3217 if (tkind == 1 && strchr(star_match[0], ':')) {
3218 /* Avoid "tells you:" spoofs in channels */
3221 if (star_match[0][0] == NULLCHAR ||
3222 strchr(star_match[0], ' ') ||
3223 (tkind == 3 && strchr(star_match[1], ' '))) {
3224 /* Reject bogus matches */
3227 if (appData.colorize) {
3228 if (oldi > next_out) {
3229 SendToPlayer(&buf[next_out], oldi - next_out);
3234 Colorize(ColorTell, FALSE);
3235 curColor = ColorTell;
3238 Colorize(ColorKibitz, FALSE);
3239 curColor = ColorKibitz;
3242 p = strrchr(star_match[1], '(');
3249 Colorize(ColorChannel1, FALSE);
3250 curColor = ColorChannel1;
3252 Colorize(ColorChannel, FALSE);
3253 curColor = ColorChannel;
3257 curColor = ColorNormal;
3261 if (started == STARTED_NONE && appData.autoComment &&
3262 (gameMode == IcsObserving ||
3263 gameMode == IcsPlayingWhite ||
3264 gameMode == IcsPlayingBlack)) {
3265 parse_pos = i - oldi;
3266 memcpy(parse, &buf[oldi], parse_pos);
3267 parse[parse_pos] = NULLCHAR;
3268 started = STARTED_COMMENT;
3269 savingComment = TRUE;
3271 started = STARTED_CHATTER;
3272 savingComment = FALSE;
3279 if (looking_at(buf, &i, "* s-shouts: ") ||
3280 looking_at(buf, &i, "* c-shouts: ")) {
3281 if (appData.colorize) {
3282 if (oldi > next_out) {
3283 SendToPlayer(&buf[next_out], oldi - next_out);
3286 Colorize(ColorSShout, FALSE);
3287 curColor = ColorSShout;
3290 started = STARTED_CHATTER;
3294 if (looking_at(buf, &i, "--->")) {
3299 if (looking_at(buf, &i, "* shouts: ") ||
3300 looking_at(buf, &i, "--> ")) {
3301 if (appData.colorize) {
3302 if (oldi > next_out) {
3303 SendToPlayer(&buf[next_out], oldi - next_out);
3306 Colorize(ColorShout, FALSE);
3307 curColor = ColorShout;
3310 started = STARTED_CHATTER;
3314 if (looking_at( buf, &i, "Challenge:")) {
3315 if (appData.colorize) {
3316 if (oldi > next_out) {
3317 SendToPlayer(&buf[next_out], oldi - next_out);
3320 Colorize(ColorChallenge, FALSE);
3321 curColor = ColorChallenge;
3327 if (looking_at(buf, &i, "* offers you") ||
3328 looking_at(buf, &i, "* offers to be") ||
3329 looking_at(buf, &i, "* would like to") ||
3330 looking_at(buf, &i, "* requests to") ||
3331 looking_at(buf, &i, "Your opponent offers") ||
3332 looking_at(buf, &i, "Your opponent requests")) {
3334 if (appData.colorize) {
3335 if (oldi > next_out) {
3336 SendToPlayer(&buf[next_out], oldi - next_out);
3339 Colorize(ColorRequest, FALSE);
3340 curColor = ColorRequest;
3345 if (looking_at(buf, &i, "* (*) seeking")) {
3346 if (appData.colorize) {
3347 if (oldi > next_out) {
3348 SendToPlayer(&buf[next_out], oldi - next_out);
3351 Colorize(ColorSeek, FALSE);
3352 curColor = ColorSeek;
3357 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3359 if (looking_at(buf, &i, "\\ ")) {
3360 if (prevColor != ColorNormal) {
3361 if (oldi > next_out) {
3362 SendToPlayer(&buf[next_out], oldi - next_out);
3365 Colorize(prevColor, TRUE);
3366 curColor = prevColor;
3368 if (savingComment) {
3369 parse_pos = i - oldi;
3370 memcpy(parse, &buf[oldi], parse_pos);
3371 parse[parse_pos] = NULLCHAR;
3372 started = STARTED_COMMENT;
3373 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3374 chattingPartner = savingComment - 3; // kludge to remember the box
3376 started = STARTED_CHATTER;
3381 if (looking_at(buf, &i, "Black Strength :") ||
3382 looking_at(buf, &i, "<<< style 10 board >>>") ||
3383 looking_at(buf, &i, "<10>") ||
3384 looking_at(buf, &i, "#@#")) {
3385 /* Wrong board style */
3387 SendToICS(ics_prefix);
3388 SendToICS("set style 12\n");
3389 SendToICS(ics_prefix);
3390 SendToICS("refresh\n");
3394 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3396 have_sent_ICS_logon = 1;
3400 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3401 (looking_at(buf, &i, "\n<12> ") ||
3402 looking_at(buf, &i, "<12> "))) {
3404 if (oldi > next_out) {
3405 SendToPlayer(&buf[next_out], oldi - next_out);
3408 started = STARTED_BOARD;
3413 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3414 looking_at(buf, &i, "<b1> ")) {
3415 if (oldi > next_out) {
3416 SendToPlayer(&buf[next_out], oldi - next_out);
3419 started = STARTED_HOLDINGS;
3424 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3426 /* Header for a move list -- first line */
3428 switch (ics_getting_history) {
3432 case BeginningOfGame:
3433 /* User typed "moves" or "oldmoves" while we
3434 were idle. Pretend we asked for these
3435 moves and soak them up so user can step
3436 through them and/or save them.
3439 gameMode = IcsObserving;
3442 ics_getting_history = H_GOT_UNREQ_HEADER;
3444 case EditGame: /*?*/
3445 case EditPosition: /*?*/
3446 /* Should above feature work in these modes too? */
3447 /* For now it doesn't */
3448 ics_getting_history = H_GOT_UNWANTED_HEADER;
3451 ics_getting_history = H_GOT_UNWANTED_HEADER;
3456 /* Is this the right one? */
3457 if (gameInfo.white && gameInfo.black &&
3458 strcmp(gameInfo.white, star_match[0]) == 0 &&
3459 strcmp(gameInfo.black, star_match[2]) == 0) {
3461 ics_getting_history = H_GOT_REQ_HEADER;
3464 case H_GOT_REQ_HEADER:
3465 case H_GOT_UNREQ_HEADER:
3466 case H_GOT_UNWANTED_HEADER:
3467 case H_GETTING_MOVES:
3468 /* Should not happen */
3469 DisplayError(_("Error gathering move list: two headers"), 0);
3470 ics_getting_history = H_FALSE;
3474 /* Save player ratings into gameInfo if needed */
3475 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3476 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3477 (gameInfo.whiteRating == -1 ||
3478 gameInfo.blackRating == -1)) {
3480 gameInfo.whiteRating = string_to_rating(star_match[1]);
3481 gameInfo.blackRating = string_to_rating(star_match[3]);
3482 if (appData.debugMode)
3483 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3484 gameInfo.whiteRating, gameInfo.blackRating);
3489 if (looking_at(buf, &i,
3490 "* * match, initial time: * minute*, increment: * second")) {
3491 /* Header for a move list -- second line */
3492 /* Initial board will follow if this is a wild game */
3493 if (gameInfo.event != NULL) free(gameInfo.event);
3494 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3495 gameInfo.event = StrSave(str);
3496 /* [HGM] we switched variant. Translate boards if needed. */
3497 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3501 if (looking_at(buf, &i, "Move ")) {
3502 /* Beginning of a move list */
3503 switch (ics_getting_history) {
3505 /* Normally should not happen */
3506 /* Maybe user hit reset while we were parsing */
3509 /* Happens if we are ignoring a move list that is not
3510 * the one we just requested. Common if the user
3511 * tries to observe two games without turning off
3514 case H_GETTING_MOVES:
3515 /* Should not happen */
3516 DisplayError(_("Error gathering move list: nested"), 0);
3517 ics_getting_history = H_FALSE;
3519 case H_GOT_REQ_HEADER:
3520 ics_getting_history = H_GETTING_MOVES;
3521 started = STARTED_MOVES;
3523 if (oldi > next_out) {
3524 SendToPlayer(&buf[next_out], oldi - next_out);
3527 case H_GOT_UNREQ_HEADER:
3528 ics_getting_history = H_GETTING_MOVES;
3529 started = STARTED_MOVES_NOHIDE;
3532 case H_GOT_UNWANTED_HEADER:
3533 ics_getting_history = H_FALSE;
3539 if (looking_at(buf, &i, "% ") ||
3540 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3541 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3542 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3543 soughtPending = FALSE;
3547 if(suppressKibitz) next_out = i;
3548 savingComment = FALSE;
3552 case STARTED_MOVES_NOHIDE:
3553 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3554 parse[parse_pos + i - oldi] = NULLCHAR;
3555 ParseGameHistory(parse);
3557 if (appData.zippyPlay && first.initDone) {
3558 FeedMovesToProgram(&first, forwardMostMove);
3559 if (gameMode == IcsPlayingWhite) {
3560 if (WhiteOnMove(forwardMostMove)) {
3561 if (first.sendTime) {
3562 if (first.useColors) {
3563 SendToProgram("black\n", &first);
3565 SendTimeRemaining(&first, TRUE);
3567 if (first.useColors) {
3568 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3570 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3571 first.maybeThinking = TRUE;
3573 if (first.usePlayother) {
3574 if (first.sendTime) {
3575 SendTimeRemaining(&first, TRUE);
3577 SendToProgram("playother\n", &first);
3583 } else if (gameMode == IcsPlayingBlack) {
3584 if (!WhiteOnMove(forwardMostMove)) {
3585 if (first.sendTime) {
3586 if (first.useColors) {
3587 SendToProgram("white\n", &first);
3589 SendTimeRemaining(&first, FALSE);
3591 if (first.useColors) {
3592 SendToProgram("black\n", &first);
3594 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3595 first.maybeThinking = TRUE;
3597 if (first.usePlayother) {
3598 if (first.sendTime) {
3599 SendTimeRemaining(&first, FALSE);
3601 SendToProgram("playother\n", &first);
3610 if (gameMode == IcsObserving && ics_gamenum == -1) {
3611 /* Moves came from oldmoves or moves command
3612 while we weren't doing anything else.
3614 currentMove = forwardMostMove;
3615 ClearHighlights();/*!!could figure this out*/
3616 flipView = appData.flipView;
3617 DrawPosition(TRUE, boards[currentMove]);
3618 DisplayBothClocks();
3619 snprintf(str, MSG_SIZ, "%s vs. %s",
3620 gameInfo.white, gameInfo.black);
3624 /* Moves were history of an active game */
3625 if (gameInfo.resultDetails != NULL) {
3626 free(gameInfo.resultDetails);
3627 gameInfo.resultDetails = NULL;
3630 HistorySet(parseList, backwardMostMove,
3631 forwardMostMove, currentMove-1);
3632 DisplayMove(currentMove - 1);
3633 if (started == STARTED_MOVES) next_out = i;
3634 started = STARTED_NONE;
3635 ics_getting_history = H_FALSE;
3638 case STARTED_OBSERVE:
3639 started = STARTED_NONE;
3640 SendToICS(ics_prefix);
3641 SendToICS("refresh\n");
3647 if(bookHit) { // [HGM] book: simulate book reply
3648 static char bookMove[MSG_SIZ]; // a bit generous?
3650 programStats.nodes = programStats.depth = programStats.time =
3651 programStats.score = programStats.got_only_move = 0;
3652 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3654 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3655 strcat(bookMove, bookHit);
3656 HandleMachineMove(bookMove, &first);
3661 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3662 started == STARTED_HOLDINGS ||
3663 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3664 /* Accumulate characters in move list or board */
3665 parse[parse_pos++] = buf[i];
3668 /* Start of game messages. Mostly we detect start of game
3669 when the first board image arrives. On some versions
3670 of the ICS, though, we need to do a "refresh" after starting
3671 to observe in order to get the current board right away. */
3672 if (looking_at(buf, &i, "Adding game * to observation list")) {
3673 started = STARTED_OBSERVE;
3677 /* Handle auto-observe */
3678 if (appData.autoObserve &&
3679 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3680 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3682 /* Choose the player that was highlighted, if any. */
3683 if (star_match[0][0] == '\033' ||
3684 star_match[1][0] != '\033') {
3685 player = star_match[0];
3687 player = star_match[2];
3689 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3690 ics_prefix, StripHighlightAndTitle(player));
3693 /* Save ratings from notify string */
3694 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3695 player1Rating = string_to_rating(star_match[1]);
3696 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3697 player2Rating = string_to_rating(star_match[3]);
3699 if (appData.debugMode)
3701 "Ratings from 'Game notification:' %s %d, %s %d\n",
3702 player1Name, player1Rating,
3703 player2Name, player2Rating);
3708 /* Deal with automatic examine mode after a game,
3709 and with IcsObserving -> IcsExamining transition */
3710 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3711 looking_at(buf, &i, "has made you an examiner of game *")) {
3713 int gamenum = atoi(star_match[0]);
3714 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3715 gamenum == ics_gamenum) {
3716 /* We were already playing or observing this game;
3717 no need to refetch history */
3718 gameMode = IcsExamining;
3720 pauseExamForwardMostMove = forwardMostMove;
3721 } else if (currentMove < forwardMostMove) {
3722 ForwardInner(forwardMostMove);
3725 /* I don't think this case really can happen */
3726 SendToICS(ics_prefix);
3727 SendToICS("refresh\n");
3732 /* Error messages */
3733 // if (ics_user_moved) {
3734 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3735 if (looking_at(buf, &i, "Illegal move") ||
3736 looking_at(buf, &i, "Not a legal move") ||
3737 looking_at(buf, &i, "Your king is in check") ||
3738 looking_at(buf, &i, "It isn't your turn") ||
3739 looking_at(buf, &i, "It is not your move")) {
3741 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3742 currentMove = forwardMostMove-1;
3743 DisplayMove(currentMove - 1); /* before DMError */
3744 DrawPosition(FALSE, boards[currentMove]);
3745 SwitchClocks(forwardMostMove-1); // [HGM] race
3746 DisplayBothClocks();
3748 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3754 if (looking_at(buf, &i, "still have time") ||
3755 looking_at(buf, &i, "not out of time") ||
3756 looking_at(buf, &i, "either player is out of time") ||
3757 looking_at(buf, &i, "has timeseal; checking")) {
3758 /* We must have called his flag a little too soon */
3759 whiteFlag = blackFlag = FALSE;
3763 if (looking_at(buf, &i, "added * seconds to") ||
3764 looking_at(buf, &i, "seconds were added to")) {
3765 /* Update the clocks */
3766 SendToICS(ics_prefix);
3767 SendToICS("refresh\n");
3771 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3772 ics_clock_paused = TRUE;
3777 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3778 ics_clock_paused = FALSE;
3783 /* Grab player ratings from the Creating: message.
3784 Note we have to check for the special case when
3785 the ICS inserts things like [white] or [black]. */
3786 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3787 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3789 0 player 1 name (not necessarily white)
3791 2 empty, white, or black (IGNORED)
3792 3 player 2 name (not necessarily black)
3795 The names/ratings are sorted out when the game
3796 actually starts (below).
3798 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3799 player1Rating = string_to_rating(star_match[1]);
3800 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3801 player2Rating = string_to_rating(star_match[4]);
3803 if (appData.debugMode)
3805 "Ratings from 'Creating:' %s %d, %s %d\n",
3806 player1Name, player1Rating,
3807 player2Name, player2Rating);
3812 /* Improved generic start/end-of-game messages */
3813 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3814 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3815 /* If tkind == 0: */
3816 /* star_match[0] is the game number */
3817 /* [1] is the white player's name */
3818 /* [2] is the black player's name */
3819 /* For end-of-game: */
3820 /* [3] is the reason for the game end */
3821 /* [4] is a PGN end game-token, preceded by " " */
3822 /* For start-of-game: */
3823 /* [3] begins with "Creating" or "Continuing" */
3824 /* [4] is " *" or empty (don't care). */
3825 int gamenum = atoi(star_match[0]);
3826 char *whitename, *blackname, *why, *endtoken;
3827 ChessMove endtype = EndOfFile;
3830 whitename = star_match[1];
3831 blackname = star_match[2];
3832 why = star_match[3];
3833 endtoken = star_match[4];
3835 whitename = star_match[1];
3836 blackname = star_match[3];
3837 why = star_match[5];
3838 endtoken = star_match[6];
3841 /* Game start messages */
3842 if (strncmp(why, "Creating ", 9) == 0 ||
3843 strncmp(why, "Continuing ", 11) == 0) {
3844 gs_gamenum = gamenum;
3845 safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3846 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3848 if (appData.zippyPlay) {
3849 ZippyGameStart(whitename, blackname);
3852 partnerBoardValid = FALSE; // [HGM] bughouse
3856 /* Game end messages */
3857 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3858 ics_gamenum != gamenum) {
3861 while (endtoken[0] == ' ') endtoken++;
3862 switch (endtoken[0]) {
3865 endtype = GameUnfinished;
3868 endtype = BlackWins;
3871 if (endtoken[1] == '/')
3872 endtype = GameIsDrawn;
3874 endtype = WhiteWins;
3877 GameEnds(endtype, why, GE_ICS);
3879 if (appData.zippyPlay && first.initDone) {
3880 ZippyGameEnd(endtype, why);
3881 if (first.pr == NULL) {
3882 /* Start the next process early so that&nbs