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, 2012 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 int flock(int f, int code);
75 #include <sys/types.h>
84 #else /* not STDC_HEADERS */
87 # else /* not HAVE_STRING_H */
89 # endif /* not HAVE_STRING_H */
90 #endif /* not STDC_HEADERS */
93 # include <sys/fcntl.h>
94 #else /* not HAVE_SYS_FCNTL_H */
97 # endif /* HAVE_FCNTL_H */
98 #endif /* not HAVE_SYS_FCNTL_H */
100 #if TIME_WITH_SYS_TIME
101 # include <sys/time.h>
105 # include <sys/time.h>
111 #if defined(_amigados) && !defined(__GNUC__)
116 extern int gettimeofday(struct timeval *, struct timezone *);
124 #include "frontend.h"
131 #include "backendz.h"
135 # define _(s) gettext (s)
136 # define N_(s) gettext_noop (s)
137 # define T_(s) gettext(s)
150 int establish P((void));
151 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
152 char *buf, int count, int error));
153 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
154 char *buf, int count, int error));
155 void ics_printf P((char *format, ...));
156 void SendToICS P((char *s));
157 void SendToICSDelayed P((char *s, long msdelay));
158 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
159 void HandleMachineMove P((char *message, ChessProgramState *cps));
160 int AutoPlayOneMove P((void));
161 int LoadGameOneMove P((ChessMove readAhead));
162 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
163 int LoadPositionFromFile P((char *filename, int n, char *title));
164 int SavePositionToFile P((char *filename));
165 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
166 void ShowMove P((int fromX, int fromY, int toX, int toY));
167 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
168 /*char*/int promoChar));
169 void BackwardInner P((int target));
170 void ForwardInner P((int target));
171 int Adjudicate P((ChessProgramState *cps));
172 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
173 void EditPositionDone P((Boolean fakeRights));
174 void PrintOpponents P((FILE *fp));
175 void PrintPosition P((FILE *fp, int move));
176 void StartChessProgram P((ChessProgramState *cps));
177 void SendToProgram P((char *message, ChessProgramState *cps));
178 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
179 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
180 char *buf, int count, int error));
181 void SendTimeControl P((ChessProgramState *cps,
182 int mps, long tc, int inc, int sd, int st));
183 char *TimeControlTagValue P((void));
184 void Attention P((ChessProgramState *cps));
185 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
186 int ResurrectChessProgram P((void));
187 void DisplayComment P((int moveNumber, char *text));
188 void DisplayMove P((int moveNumber));
190 void ParseGameHistory P((char *game));
191 void ParseBoard12 P((char *string));
192 void KeepAlive P((void));
193 void StartClocks P((void));
194 void SwitchClocks P((int nr));
195 void StopClocks P((void));
196 void ResetClocks P((void));
197 char *PGNDate P((void));
198 void SetGameInfo P((void));
199 int RegisterMove P((void));
200 void MakeRegisteredMove P((void));
201 void TruncateGame P((void));
202 int looking_at P((char *, int *, char *));
203 void CopyPlayerNameIntoFileName P((char **, char *));
204 char *SavePart P((char *));
205 int SaveGameOldStyle P((FILE *));
206 int SaveGamePGN P((FILE *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
220 void NextMatchGame P((void));
221 int NextTourneyGame P((int nr, int *swap));
222 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
223 FILE *WriteTourneyFile P((char *results, FILE *f));
224 void DisplayTwoMachinesTitle P(());
227 extern void ConsoleCreate();
230 ChessProgramState *WhitePlayer();
231 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
232 int VerifyDisplayMode P(());
234 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
235 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
236 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
237 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
238 void ics_update_width P((int new_width));
239 extern char installDir[MSG_SIZ];
240 VariantClass startVariant; /* [HGM] nicks: initial variant */
243 extern int tinyLayout, smallLayout;
244 ChessProgramStats programStats;
245 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
247 static int exiting = 0; /* [HGM] moved to top */
248 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
249 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
250 Board partnerBoard; /* [HGM] bughouse: for peeking at partner game */
251 int partnerHighlight[2];
252 Boolean partnerBoardValid = 0;
253 char partnerStatus[MSG_SIZ];
255 Boolean originalFlip;
256 Boolean twoBoards = 0;
257 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
258 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
259 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
260 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
261 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
262 int opponentKibitzes;
263 int lastSavedGame; /* [HGM] save: ID of game */
264 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
265 extern int chatCount;
267 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
268 char lastMsg[MSG_SIZ];
269 ChessSquare pieceSweep = EmptySquare;
270 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
271 int promoDefaultAltered;
273 /* States for ics_getting_history */
275 #define H_REQUESTED 1
276 #define H_GOT_REQ_HEADER 2
277 #define H_GOT_UNREQ_HEADER 3
278 #define H_GETTING_MOVES 4
279 #define H_GOT_UNWANTED_HEADER 5
281 /* whosays values for GameEnds */
290 /* Maximum number of games in a cmail message */
291 #define CMAIL_MAX_GAMES 20
293 /* Different types of move when calling RegisterMove */
295 #define CMAIL_RESIGN 1
297 #define CMAIL_ACCEPT 3
299 /* Different types of result to remember for each game */
300 #define CMAIL_NOT_RESULT 0
301 #define CMAIL_OLD_RESULT 1
302 #define CMAIL_NEW_RESULT 2
304 /* Telnet protocol constants */
315 safeStrCpy( char *dst, const char *src, size_t count )
318 assert( dst != NULL );
319 assert( src != NULL );
322 for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
323 if( i == count && dst[count-1] != NULLCHAR)
325 dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
326 if(appData.debugMode)
327 fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
333 /* Some compiler can't cast u64 to double
334 * This function do the job for us:
336 * We use the highest bit for cast, this only
337 * works if the highest bit is not
338 * in use (This should not happen)
340 * We used this for all compiler
343 u64ToDouble(u64 value)
346 u64 tmp = value & u64Const(0x7fffffffffffffff);
347 r = (double)(s64)tmp;
348 if (value & u64Const(0x8000000000000000))
349 r += 9.2233720368547758080e18; /* 2^63 */
353 /* Fake up flags for now, as we aren't keeping track of castling
354 availability yet. [HGM] Change of logic: the flag now only
355 indicates the type of castlings allowed by the rule of the game.
356 The actual rights themselves are maintained in the array
357 castlingRights, as part of the game history, and are not probed
363 int flags = F_ALL_CASTLE_OK;
364 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
365 switch (gameInfo.variant) {
367 flags &= ~F_ALL_CASTLE_OK;
368 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
369 flags |= F_IGNORE_CHECK;
371 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
374 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
376 case VariantKriegspiel:
377 flags |= F_KRIEGSPIEL_CAPTURE;
379 case VariantCapaRandom:
380 case VariantFischeRandom:
381 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
382 case VariantNoCastle:
383 case VariantShatranj:
387 flags &= ~F_ALL_CASTLE_OK;
395 FILE *gameFileFP, *debugFP;
398 [AS] Note: sometimes, the sscanf() function is used to parse the input
399 into a fixed-size buffer. Because of this, we must be prepared to
400 receive strings as long as the size of the input buffer, which is currently
401 set to 4K for Windows and 8K for the rest.
402 So, we must either allocate sufficiently large buffers here, or
403 reduce the size of the input buffer in the input reading part.
406 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
407 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
408 char thinkOutput1[MSG_SIZ*10];
410 ChessProgramState first, second, pairing;
412 /* premove variables */
415 int premoveFromX = 0;
416 int premoveFromY = 0;
417 int premovePromoChar = 0;
419 Boolean alarmSounded;
420 /* end premove variables */
422 char *ics_prefix = "$";
423 int ics_type = ICS_GENERIC;
425 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
426 int pauseExamForwardMostMove = 0;
427 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
428 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
429 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
430 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
431 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
432 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
433 int whiteFlag = FALSE, blackFlag = FALSE;
434 int userOfferedDraw = FALSE;
435 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
436 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
437 int cmailMoveType[CMAIL_MAX_GAMES];
438 long ics_clock_paused = 0;
439 ProcRef icsPR = NoProc, cmailPR = NoProc;
440 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
441 GameMode gameMode = BeginningOfGame;
442 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
443 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
444 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
445 int hiddenThinkOutputState = 0; /* [AS] */
446 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
447 int adjudicateLossPlies = 6;
448 char white_holding[64], black_holding[64];
449 TimeMark lastNodeCountTime;
450 long lastNodeCount=0;
451 int shiftKey; // [HGM] set by mouse handler
453 int have_sent_ICS_logon = 0;
455 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
456 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
457 Boolean adjustedClock;
458 long timeControl_2; /* [AS] Allow separate time controls */
459 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
460 long timeRemaining[2][MAX_MOVES];
461 int matchGame = 0, nextGame = 0, roundNr = 0;
462 Boolean waitingForGame = FALSE;
463 TimeMark programStartTime, pauseStart;
464 char ics_handle[MSG_SIZ];
465 int have_set_title = 0;
467 /* animateTraining preserves the state of appData.animate
468 * when Training mode is activated. This allows the
469 * response to be animated when appData.animate == TRUE and
470 * appData.animateDragging == TRUE.
472 Boolean animateTraining;
478 Board boards[MAX_MOVES];
479 /* [HGM] Following 7 needed for accurate legality tests: */
480 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
481 signed char initialRights[BOARD_FILES];
482 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
483 int initialRulePlies, FENrulePlies;
484 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
486 Boolean shuffleOpenings;
487 int mute; // mute all sounds
489 // [HGM] vari: next 12 to save and restore variations
490 #define MAX_VARIATIONS 10
491 int framePtr = MAX_MOVES-1; // points to free stack entry
493 int savedFirst[MAX_VARIATIONS];
494 int savedLast[MAX_VARIATIONS];
495 int savedFramePtr[MAX_VARIATIONS];
496 char *savedDetails[MAX_VARIATIONS];
497 ChessMove savedResult[MAX_VARIATIONS];
499 void PushTail P((int firstMove, int lastMove));
500 Boolean PopTail P((Boolean annotate));
501 void PushInner P((int firstMove, int lastMove));
502 void PopInner P((Boolean annotate));
503 void CleanupTail P((void));
505 ChessSquare FIDEArray[2][BOARD_FILES] = {
506 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
507 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
508 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
509 BlackKing, BlackBishop, BlackKnight, BlackRook }
512 ChessSquare twoKingsArray[2][BOARD_FILES] = {
513 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
514 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
515 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
516 BlackKing, BlackKing, BlackKnight, BlackRook }
519 ChessSquare KnightmateArray[2][BOARD_FILES] = {
520 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
521 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
522 { BlackRook, BlackMan, BlackBishop, BlackQueen,
523 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
526 ChessSquare SpartanArray[2][BOARD_FILES] = {
527 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
528 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
529 { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
530 BlackDragon, BlackKing, BlackAngel, BlackAlfil }
533 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
534 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
535 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
536 { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
537 BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
540 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
541 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
542 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
543 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
544 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
547 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
548 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
549 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
550 { BlackRook, BlackKnight, BlackMan, BlackFerz,
551 BlackKing, BlackMan, BlackKnight, BlackRook }
555 #if (BOARD_FILES>=10)
556 ChessSquare ShogiArray[2][BOARD_FILES] = {
557 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
558 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
559 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
560 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
563 ChessSquare XiangqiArray[2][BOARD_FILES] = {
564 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
565 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
566 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
567 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
570 ChessSquare CapablancaArray[2][BOARD_FILES] = {
571 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
572 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
573 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
574 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
577 ChessSquare GreatArray[2][BOARD_FILES] = {
578 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
579 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
580 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
581 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
584 ChessSquare JanusArray[2][BOARD_FILES] = {
585 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
586 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
587 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
588 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
591 ChessSquare GrandArray[2][BOARD_FILES] = {
592 { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
593 WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
594 { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
595 BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
599 ChessSquare GothicArray[2][BOARD_FILES] = {
600 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
601 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
602 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
603 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
606 #define GothicArray CapablancaArray
610 ChessSquare FalconArray[2][BOARD_FILES] = {
611 { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
612 WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
613 { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
614 BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
617 #define FalconArray CapablancaArray
620 #else // !(BOARD_FILES>=10)
621 #define XiangqiPosition FIDEArray
622 #define CapablancaArray FIDEArray
623 #define GothicArray FIDEArray
624 #define GreatArray FIDEArray
625 #endif // !(BOARD_FILES>=10)
627 #if (BOARD_FILES>=12)
628 ChessSquare CourierArray[2][BOARD_FILES] = {
629 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
630 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
631 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
632 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
634 #else // !(BOARD_FILES>=12)
635 #define CourierArray CapablancaArray
636 #endif // !(BOARD_FILES>=12)
639 Board initialPosition;
642 /* Convert str to a rating. Checks for special cases of "----",
644 "++++", etc. Also strips ()'s */
646 string_to_rating(str)
649 while(*str && !isdigit(*str)) ++str;
651 return 0; /* One of the special "no rating" cases */
659 /* Init programStats */
660 programStats.movelist[0] = 0;
661 programStats.depth = 0;
662 programStats.nr_moves = 0;
663 programStats.moves_left = 0;
664 programStats.nodes = 0;
665 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
666 programStats.score = 0;
667 programStats.got_only_move = 0;
668 programStats.got_fail = 0;
669 programStats.line_is_book = 0;
674 { // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
675 if (appData.firstPlaysBlack) {
676 first.twoMachinesColor = "black\n";
677 second.twoMachinesColor = "white\n";
679 first.twoMachinesColor = "white\n";
680 second.twoMachinesColor = "black\n";
683 first.other = &second;
684 second.other = &first;
687 if(appData.timeOddsMode) {
688 norm = appData.timeOdds[0];
689 if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
691 first.timeOdds = appData.timeOdds[0]/norm;
692 second.timeOdds = appData.timeOdds[1]/norm;
695 if(programVersion) free(programVersion);
696 if (appData.noChessProgram) {
697 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
698 sprintf(programVersion, "%s", PACKAGE_STRING);
700 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
701 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
702 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
707 UnloadEngine(ChessProgramState *cps)
709 /* Kill off first chess program */
710 if (cps->isr != NULL)
711 RemoveInputSource(cps->isr);
714 if (cps->pr != NoProc) {
716 DoSleep( appData.delayBeforeQuit );
717 SendToProgram("quit\n", cps);
718 DoSleep( appData.delayAfterQuit );
719 DestroyChildProcess(cps->pr, cps->useSigterm);
722 if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
726 ClearOptions(ChessProgramState *cps)
729 cps->nrOptions = cps->comboCnt = 0;
730 for(i=0; i<MAX_OPTIONS; i++) {
731 cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
732 cps->option[i].textValue = 0;
736 char *engineNames[] = {
742 InitEngine(ChessProgramState *cps, int n)
743 { // [HGM] all engine initialiation put in a function that does one engine
747 cps->which = engineNames[n];
748 cps->maybeThinking = FALSE;
752 cps->sendDrawOffers = 1;
754 cps->program = appData.chessProgram[n];
755 cps->host = appData.host[n];
756 cps->dir = appData.directory[n];
757 cps->initString = appData.engInitString[n];
758 cps->computerString = appData.computerString[n];
759 cps->useSigint = TRUE;
760 cps->useSigterm = TRUE;
761 cps->reuse = appData.reuse[n];
762 cps->nps = appData.NPS[n]; // [HGM] nps: copy nodes per second
763 cps->useSetboard = FALSE;
765 cps->usePing = FALSE;
768 cps->usePlayother = FALSE;
769 cps->useColors = TRUE;
770 cps->useUsermove = FALSE;
771 cps->sendICS = FALSE;
772 cps->sendName = appData.icsActive;
773 cps->sdKludge = FALSE;
774 cps->stKludge = FALSE;
775 TidyProgramName(cps->program, cps->host, cps->tidy);
777 safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
778 cps->analysisSupport = 2; /* detect */
779 cps->analyzing = FALSE;
780 cps->initDone = FALSE;
782 /* New features added by Tord: */
783 cps->useFEN960 = FALSE;
784 cps->useOOCastle = TRUE;
785 /* End of new features added by Tord. */
786 cps->fenOverride = appData.fenOverride[n];
788 /* [HGM] time odds: set factor for each machine */
789 cps->timeOdds = appData.timeOdds[n];
791 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
792 cps->accumulateTC = appData.accumulateTC[n];
793 cps->maxNrOfSessions = 1;
798 cps->supportsNPS = UNKNOWN;
799 cps->memSize = FALSE;
800 cps->maxCores = FALSE;
801 cps->egtFormats[0] = NULLCHAR;
804 cps->optionSettings = appData.engOptions[n];
806 cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
807 cps->isUCI = appData.isUCI[n]; /* [AS] */
808 cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
810 if (appData.protocolVersion[n] > PROTOVER
811 || appData.protocolVersion[n] < 1)
816 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
817 appData.protocolVersion[n]);
818 if( (len >= MSG_SIZ) && appData.debugMode )
819 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
821 DisplayFatalError(buf, 0, 2);
825 cps->protocolVersion = appData.protocolVersion[n];
828 InitEngineUCI( installDir, cps ); // [HGM] moved here from winboard.c, to make available in xboard
829 ParseFeatures(appData.featureDefaults, cps);
832 ChessProgramState *savCps;
838 if(WaitForEngine(savCps, LoadEngine)) return;
839 CommonEngineInit(); // recalculate time odds
840 if(gameInfo.variant != StringToVariant(appData.variant)) {
841 // we changed variant when loading the engine; this forces us to reset
842 Reset(TRUE, savCps != &first);
843 EditGameEvent(); // for consistency with other path, as Reset changes mode
845 InitChessProgram(savCps, FALSE);
846 SendToProgram("force\n", savCps);
847 DisplayMessage("", "");
848 if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
849 for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
855 ReplaceEngine(ChessProgramState *cps, int n)
859 appData.noChessProgram = FALSE;
860 appData.clockMode = TRUE;
863 if(n) return; // only startup first engine immediately; second can wait
864 savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
868 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
869 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
871 static char resetOptions[] =
872 "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
873 "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
874 "-firstOptions \"\" -firstNPS -1 -fn \"\"";
877 Load(ChessProgramState *cps, int i)
879 char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
880 if(engineLine && engineLine[0]) { // an engine was selected from the combo box
881 snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
882 SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
883 ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
884 ParseArgsFromString(buf);
886 ReplaceEngine(cps, i);
890 while(q = strchr(p, SLASH)) p = q+1;
891 if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
892 if(engineDir[0] != NULLCHAR)
893 appData.directory[i] = engineDir;
894 else if(p != engineName) { // derive directory from engine path, when not given
896 appData.directory[i] = strdup(engineName);
898 } else appData.directory[i] = ".";
900 if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
901 snprintf(command, MSG_SIZ, "%s %s", p, params);
904 appData.chessProgram[i] = strdup(p);
905 appData.isUCI[i] = isUCI;
906 appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
907 appData.hasOwnBookUCI[i] = hasBook;
908 if(!nickName[0]) useNick = FALSE;
909 if(useNick) ASSIGN(appData.pgnName[i], nickName);
913 q = firstChessProgramNames;
914 if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
915 quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
916 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
917 quote, p, quote, appData.directory[i],
918 useNick ? " -fn \"" : "",
919 useNick ? nickName : "",
921 v1 ? " -firstProtocolVersion 1" : "",
922 hasBook ? "" : " -fNoOwnBookUCI",
923 isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
924 storeVariant ? " -variant " : "",
925 storeVariant ? VariantName(gameInfo.variant) : "");
926 firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
927 snprintf(firstChessProgramNames, len, "%s%s", q, buf);
930 ReplaceEngine(cps, i);
936 int matched, min, sec;
938 * Parse timeControl resource
940 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
941 appData.movesPerSession)) {
943 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
944 DisplayFatalError(buf, 0, 2);
948 * Parse searchTime resource
950 if (*appData.searchTime != NULLCHAR) {
951 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
953 searchTime = min * 60;
954 } else if (matched == 2) {
955 searchTime = min * 60 + sec;
958 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
959 DisplayFatalError(buf, 0, 2);
968 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
969 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
971 GetTimeMark(&programStartTime);
972 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
973 appData.seedBase = random() + (random()<<15);
974 pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
977 programStats.ok_to_send = 1;
978 programStats.seen_stat = 0;
981 * Initialize game list
987 * Internet chess server status
989 if (appData.icsActive) {
990 appData.matchMode = FALSE;
991 appData.matchGames = 0;
993 appData.noChessProgram = !appData.zippyPlay;
995 appData.zippyPlay = FALSE;
996 appData.zippyTalk = FALSE;
997 appData.noChessProgram = TRUE;
999 if (*appData.icsHelper != NULLCHAR) {
1000 appData.useTelnet = TRUE;
1001 appData.telnetProgram = appData.icsHelper;
1004 appData.zippyTalk = appData.zippyPlay = FALSE;
1007 /* [AS] Initialize pv info list [HGM] and game state */
1011 for( i=0; i<=framePtr; i++ ) {
1012 pvInfoList[i].depth = -1;
1013 boards[i][EP_STATUS] = EP_NONE;
1014 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1020 /* [AS] Adjudication threshold */
1021 adjudicateLossThreshold = appData.adjudicateLossThreshold;
1023 InitEngine(&first, 0);
1024 InitEngine(&second, 1);
1027 pairing.which = "pairing"; // pairing engine
1028 pairing.pr = NoProc;
1030 pairing.program = appData.pairingEngine;
1031 pairing.host = "localhost";
1034 if (appData.icsActive) {
1035 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
1036 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1037 appData.clockMode = FALSE;
1038 first.sendTime = second.sendTime = 0;
1042 /* Override some settings from environment variables, for backward
1043 compatibility. Unfortunately it's not feasible to have the env
1044 vars just set defaults, at least in xboard. Ugh.
1046 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1051 if (!appData.icsActive) {
1055 /* Check for variants that are supported only in ICS mode,
1056 or not at all. Some that are accepted here nevertheless
1057 have bugs; see comments below.
1059 VariantClass variant = StringToVariant(appData.variant);
1061 case VariantBughouse: /* need four players and two boards */
1062 case VariantKriegspiel: /* need to hide pieces and move details */
1063 /* case VariantFischeRandom: (Fabien: moved below) */
1064 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1065 if( (len >= MSG_SIZ) && appData.debugMode )
1066 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1068 DisplayFatalError(buf, 0, 2);
1071 case VariantUnknown:
1072 case VariantLoadable:
1082 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1083 if( (len >= MSG_SIZ) && appData.debugMode )
1084 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1086 DisplayFatalError(buf, 0, 2);
1089 case VariantXiangqi: /* [HGM] repetition rules not implemented */
1090 case VariantFairy: /* [HGM] TestLegality definitely off! */
1091 case VariantGothic: /* [HGM] should work */
1092 case VariantCapablanca: /* [HGM] should work */
1093 case VariantCourier: /* [HGM] initial forced moves not implemented */
1094 case VariantShogi: /* [HGM] could still mate with pawn drop */
1095 case VariantKnightmate: /* [HGM] should work */
1096 case VariantCylinder: /* [HGM] untested */
1097 case VariantFalcon: /* [HGM] untested */
1098 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1099 offboard interposition not understood */
1100 case VariantNormal: /* definitely works! */
1101 case VariantWildCastle: /* pieces not automatically shuffled */
1102 case VariantNoCastle: /* pieces not automatically shuffled */
1103 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1104 case VariantLosers: /* should work except for win condition,
1105 and doesn't know captures are mandatory */
1106 case VariantSuicide: /* should work except for win condition,
1107 and doesn't know captures are mandatory */
1108 case VariantGiveaway: /* should work except for win condition,
1109 and doesn't know captures are mandatory */
1110 case VariantTwoKings: /* should work */
1111 case VariantAtomic: /* should work except for win condition */
1112 case Variant3Check: /* should work except for win condition */
1113 case VariantShatranj: /* should work except for all win conditions */
1114 case VariantMakruk: /* should work except for draw countdown */
1115 case VariantBerolina: /* might work if TestLegality is off */
1116 case VariantCapaRandom: /* should work */
1117 case VariantJanus: /* should work */
1118 case VariantSuper: /* experimental */
1119 case VariantGreat: /* experimental, requires legality testing to be off */
1120 case VariantSChess: /* S-Chess, should work */
1121 case VariantGrand: /* 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, NULL)) { // 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 HistorySet( char movelist[][2*MOVE_LEN], int first, int last, int current )
1660 DisplayBook(current+1);
1662 MoveHistorySet( movelist, first, last, current, pvInfoList );
1664 EvalGraphSet( first, last, current, pvInfoList );
1666 MakeEngineOutputTitle();
1670 * Establish will establish a contact to a remote host.port.
1671 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1672 * used to talk to the host.
1673 * Returns 0 if okay, error code if not.
1680 if (*appData.icsCommPort != NULLCHAR) {
1681 /* Talk to the host through a serial comm port */
1682 return OpenCommPort(appData.icsCommPort, &icsPR);
1684 } else if (*appData.gateway != NULLCHAR) {
1685 if (*appData.remoteShell == NULLCHAR) {
1686 /* Use the rcmd protocol to run telnet program on a gateway host */
1687 snprintf(buf, sizeof(buf), "%s %s %s",
1688 appData.telnetProgram, appData.icsHost, appData.icsPort);
1689 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1692 /* Use the rsh program to run telnet program on a gateway host */
1693 if (*appData.remoteUser == NULLCHAR) {
1694 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1695 appData.gateway, appData.telnetProgram,
1696 appData.icsHost, appData.icsPort);
1698 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1699 appData.remoteShell, appData.gateway,
1700 appData.remoteUser, appData.telnetProgram,
1701 appData.icsHost, appData.icsPort);
1703 return StartChildProcess(buf, "", &icsPR);
1706 } else if (appData.useTelnet) {
1707 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1710 /* TCP socket interface differs somewhat between
1711 Unix and NT; handle details in the front end.
1713 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1717 void EscapeExpand(char *p, char *q)
1718 { // [HGM] initstring: routine to shape up string arguments
1719 while(*p++ = *q++) if(p[-1] == '\\')
1721 case 'n': p[-1] = '\n'; break;
1722 case 'r': p[-1] = '\r'; break;
1723 case 't': p[-1] = '\t'; break;
1724 case '\\': p[-1] = '\\'; break;
1725 case 0: *p = 0; return;
1726 default: p[-1] = q[-1]; break;
1731 show_bytes(fp, buf, count)
1737 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1738 fprintf(fp, "\\%03o", *buf & 0xff);
1747 /* Returns an errno value */
1749 OutputMaybeTelnet(pr, message, count, outError)
1755 char buf[8192], *p, *q, *buflim;
1756 int left, newcount, outcount;
1758 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1759 *appData.gateway != NULLCHAR) {
1760 if (appData.debugMode) {
1761 fprintf(debugFP, ">ICS: ");
1762 show_bytes(debugFP, message, count);
1763 fprintf(debugFP, "\n");
1765 return OutputToProcess(pr, message, count, outError);
1768 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1775 if (appData.debugMode) {
1776 fprintf(debugFP, ">ICS: ");
1777 show_bytes(debugFP, buf, newcount);
1778 fprintf(debugFP, "\n");
1780 outcount = OutputToProcess(pr, buf, newcount, outError);
1781 if (outcount < newcount) return -1; /* to be sure */
1788 } else if (((unsigned char) *p) == TN_IAC) {
1789 *q++ = (char) TN_IAC;
1796 if (appData.debugMode) {
1797 fprintf(debugFP, ">ICS: ");
1798 show_bytes(debugFP, buf, newcount);
1799 fprintf(debugFP, "\n");
1801 outcount = OutputToProcess(pr, buf, newcount, outError);
1802 if (outcount < newcount) return -1; /* to be sure */
1807 read_from_player(isr, closure, message, count, error)
1814 int outError, outCount;
1815 static int gotEof = 0;
1817 /* Pass data read from player on to ICS */
1820 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1821 if (outCount < count) {
1822 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1824 } else if (count < 0) {
1825 RemoveInputSource(isr);
1826 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1827 } else if (gotEof++ > 0) {
1828 RemoveInputSource(isr);
1829 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1835 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1836 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1837 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1838 SendToICS("date\n");
1839 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1842 /* added routine for printf style output to ics */
1843 void ics_printf(char *format, ...)
1845 char buffer[MSG_SIZ];
1848 va_start(args, format);
1849 vsnprintf(buffer, sizeof(buffer), format, args);
1850 buffer[sizeof(buffer)-1] = '\0';
1859 int count, outCount, outError;
1861 if (icsPR == NoProc) return;
1864 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1865 if (outCount < count) {
1866 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1870 /* This is used for sending logon scripts to the ICS. Sending
1871 without a delay causes problems when using timestamp on ICC
1872 (at least on my machine). */
1874 SendToICSDelayed(s,msdelay)
1878 int count, outCount, outError;
1880 if (icsPR == NoProc) return;
1883 if (appData.debugMode) {
1884 fprintf(debugFP, ">ICS: ");
1885 show_bytes(debugFP, s, count);
1886 fprintf(debugFP, "\n");
1888 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1890 if (outCount < count) {
1891 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1896 /* Remove all highlighting escape sequences in s
1897 Also deletes any suffix starting with '('
1900 StripHighlightAndTitle(s)
1903 static char retbuf[MSG_SIZ];
1906 while (*s != NULLCHAR) {
1907 while (*s == '\033') {
1908 while (*s != NULLCHAR && !isalpha(*s)) s++;
1909 if (*s != NULLCHAR) s++;
1911 while (*s != NULLCHAR && *s != '\033') {
1912 if (*s == '(' || *s == '[') {
1923 /* Remove all highlighting escape sequences in s */
1928 static char retbuf[MSG_SIZ];
1931 while (*s != NULLCHAR) {
1932 while (*s == '\033') {
1933 while (*s != NULLCHAR && !isalpha(*s)) s++;
1934 if (*s != NULLCHAR) s++;
1936 while (*s != NULLCHAR && *s != '\033') {
1944 char *variantNames[] = VARIANT_NAMES;
1949 return variantNames[v];
1953 /* Identify a variant from the strings the chess servers use or the
1954 PGN Variant tag names we use. */
1961 VariantClass v = VariantNormal;
1962 int i, found = FALSE;
1968 /* [HGM] skip over optional board-size prefixes */
1969 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1970 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1971 while( *e++ != '_');
1974 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1978 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1979 if (StrCaseStr(e, variantNames[i])) {
1980 v = (VariantClass) i;
1987 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1988 || StrCaseStr(e, "wild/fr")
1989 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1990 v = VariantFischeRandom;
1991 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1992 (i = 1, p = StrCaseStr(e, "w"))) {
1994 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2001 case 0: /* FICS only, actually */
2003 /* Castling legal even if K starts on d-file */
2004 v = VariantWildCastle;
2009 /* Castling illegal even if K & R happen to start in
2010 normal positions. */
2011 v = VariantNoCastle;
2024 /* Castling legal iff K & R start in normal positions */
2030 /* Special wilds for position setup; unclear what to do here */
2031 v = VariantLoadable;
2034 /* Bizarre ICC game */
2035 v = VariantTwoKings;
2038 v = VariantKriegspiel;
2044 v = VariantFischeRandom;
2047 v = VariantCrazyhouse;
2050 v = VariantBughouse;
2056 /* Not quite the same as FICS suicide! */
2057 v = VariantGiveaway;
2063 v = VariantShatranj;
2066 /* Temporary names for future ICC types. The name *will* change in
2067 the next xboard/WinBoard release after ICC defines it. */
2105 v = VariantCapablanca;
2108 v = VariantKnightmate;
2114 v = VariantCylinder;
2120 v = VariantCapaRandom;
2123 v = VariantBerolina;
2135 /* Found "wild" or "w" in the string but no number;
2136 must assume it's normal chess. */
2140 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2141 if( (len >= MSG_SIZ) && appData.debugMode )
2142 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2144 DisplayError(buf, 0);
2150 if (appData.debugMode) {
2151 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2152 e, wnum, VariantName(v));
2157 static int leftover_start = 0, leftover_len = 0;
2158 char star_match[STAR_MATCH_N][MSG_SIZ];
2160 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2161 advance *index beyond it, and set leftover_start to the new value of
2162 *index; else return FALSE. If pattern contains the character '*', it
2163 matches any sequence of characters not containing '\r', '\n', or the
2164 character following the '*' (if any), and the matched sequence(s) are
2165 copied into star_match.
2168 looking_at(buf, index, pattern)
2173 char *bufp = &buf[*index], *patternp = pattern;
2175 char *matchp = star_match[0];
2178 if (*patternp == NULLCHAR) {
2179 *index = leftover_start = bufp - buf;
2183 if (*bufp == NULLCHAR) return FALSE;
2184 if (*patternp == '*') {
2185 if (*bufp == *(patternp + 1)) {
2187 matchp = star_match[++star_count];
2191 } else if (*bufp == '\n' || *bufp == '\r') {
2193 if (*patternp == NULLCHAR)
2198 *matchp++ = *bufp++;
2202 if (*patternp != *bufp) return FALSE;
2209 SendToPlayer(data, length)
2213 int error, outCount;
2214 outCount = OutputToProcess(NoProc, data, length, &error);
2215 if (outCount < length) {
2216 DisplayFatalError(_("Error writing to display"), error, 1);
2221 PackHolding(packed, holding)
2233 switch (runlength) {
2244 sprintf(q, "%d", runlength);
2256 /* Telnet protocol requests from the front end */
2258 TelnetRequest(ddww, option)
2259 unsigned char ddww, option;
2261 unsigned char msg[3];
2262 int outCount, outError;
2264 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2266 if (appData.debugMode) {
2267 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2283 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2292 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2295 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2300 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2302 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2309 if (!appData.icsActive) return;
2310 TelnetRequest(TN_DO, TN_ECHO);
2316 if (!appData.icsActive) return;
2317 TelnetRequest(TN_DONT, TN_ECHO);
2321 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2323 /* put the holdings sent to us by the server on the board holdings area */
2324 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2328 if(gameInfo.holdingsWidth < 2) return;
2329 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2330 return; // prevent overwriting by pre-board holdings
2332 if( (int)lowestPiece >= BlackPawn ) {
2335 holdingsStartRow = BOARD_HEIGHT-1;
2338 holdingsColumn = BOARD_WIDTH-1;
2339 countsColumn = BOARD_WIDTH-2;
2340 holdingsStartRow = 0;
2344 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2345 board[i][holdingsColumn] = EmptySquare;
2346 board[i][countsColumn] = (ChessSquare) 0;
2348 while( (p=*holdings++) != NULLCHAR ) {
2349 piece = CharToPiece( ToUpper(p) );
2350 if(piece == EmptySquare) continue;
2351 /*j = (int) piece - (int) WhitePawn;*/
2352 j = PieceToNumber(piece);
2353 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2354 if(j < 0) continue; /* should not happen */
2355 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2356 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2357 board[holdingsStartRow+j*direction][countsColumn]++;
2363 VariantSwitch(Board board, VariantClass newVariant)
2365 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2366 static Board oldBoard;
2368 startedFromPositionFile = FALSE;
2369 if(gameInfo.variant == newVariant) return;
2371 /* [HGM] This routine is called each time an assignment is made to
2372 * gameInfo.variant during a game, to make sure the board sizes
2373 * are set to match the new variant. If that means adding or deleting
2374 * holdings, we shift the playing board accordingly
2375 * This kludge is needed because in ICS observe mode, we get boards
2376 * of an ongoing game without knowing the variant, and learn about the
2377 * latter only later. This can be because of the move list we requested,
2378 * in which case the game history is refilled from the beginning anyway,
2379 * but also when receiving holdings of a crazyhouse game. In the latter
2380 * case we want to add those holdings to the already received position.
2384 if (appData.debugMode) {
2385 fprintf(debugFP, "Switch board from %s to %s\n",
2386 VariantName(gameInfo.variant), VariantName(newVariant));
2387 setbuf(debugFP, NULL);
2389 shuffleOpenings = 0; /* [HGM] shuffle */
2390 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2394 newWidth = 9; newHeight = 9;
2395 gameInfo.holdingsSize = 7;
2396 case VariantBughouse:
2397 case VariantCrazyhouse:
2398 newHoldingsWidth = 2; break;
2402 newHoldingsWidth = 2;
2403 gameInfo.holdingsSize = 8;
2406 case VariantCapablanca:
2407 case VariantCapaRandom:
2410 newHoldingsWidth = gameInfo.holdingsSize = 0;
2413 if(newWidth != gameInfo.boardWidth ||
2414 newHeight != gameInfo.boardHeight ||
2415 newHoldingsWidth != gameInfo.holdingsWidth ) {
2417 /* shift position to new playing area, if needed */
2418 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2419 for(i=0; i<BOARD_HEIGHT; i++)
2420 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2421 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2423 for(i=0; i<newHeight; i++) {
2424 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2425 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2427 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2428 for(i=0; i<BOARD_HEIGHT; i++)
2429 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2430 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2433 gameInfo.boardWidth = newWidth;
2434 gameInfo.boardHeight = newHeight;
2435 gameInfo.holdingsWidth = newHoldingsWidth;
2436 gameInfo.variant = newVariant;
2437 InitDrawingSizes(-2, 0);
2438 } else gameInfo.variant = newVariant;
2439 CopyBoard(oldBoard, board); // remember correctly formatted board
2440 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2441 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2444 static int loggedOn = FALSE;
2446 /*-- Game start info cache: --*/
2448 char gs_kind[MSG_SIZ];
2449 static char player1Name[128] = "";
2450 static char player2Name[128] = "";
2451 static char cont_seq[] = "\n\\ ";
2452 static int player1Rating = -1;
2453 static int player2Rating = -1;
2454 /*----------------------------*/
2456 ColorClass curColor = ColorNormal;
2457 int suppressKibitz = 0;
2460 Boolean soughtPending = FALSE;
2461 Boolean seekGraphUp;
2462 #define MAX_SEEK_ADS 200
2464 char *seekAdList[MAX_SEEK_ADS];
2465 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2466 float tcList[MAX_SEEK_ADS];
2467 char colorList[MAX_SEEK_ADS];
2468 int nrOfSeekAds = 0;
2469 int minRating = 1010, maxRating = 2800;
2470 int hMargin = 10, vMargin = 20, h, w;
2471 extern int squareSize, lineGap;
2476 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2477 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2478 if(r < minRating+100 && r >=0 ) r = minRating+100;
2479 if(r > maxRating) r = maxRating;
2480 if(tc < 1.) tc = 1.;
2481 if(tc > 95.) tc = 95.;
2482 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2483 y = ((double)r - minRating)/(maxRating - minRating)
2484 * (h-vMargin-squareSize/8-1) + vMargin;
2485 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2486 if(strstr(seekAdList[i], " u ")) color = 1;
2487 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2488 !strstr(seekAdList[i], "bullet") &&
2489 !strstr(seekAdList[i], "blitz") &&
2490 !strstr(seekAdList[i], "standard") ) color = 2;
2491 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2492 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2496 AddAd(char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2498 char buf[MSG_SIZ], *ext = "";
2499 VariantClass v = StringToVariant(type);
2500 if(strstr(type, "wild")) {
2501 ext = type + 4; // append wild number
2502 if(v == VariantFischeRandom) type = "chess960"; else
2503 if(v == VariantLoadable) type = "setup"; else
2504 type = VariantName(v);
2506 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2507 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2508 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2509 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2510 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2511 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2512 seekNrList[nrOfSeekAds] = nr;
2513 zList[nrOfSeekAds] = 0;
2514 seekAdList[nrOfSeekAds++] = StrSave(buf);
2515 if(plot) PlotSeekAd(nrOfSeekAds-1);
2522 int x = xList[i], y = yList[i], d=squareSize/4, k;
2523 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2524 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2525 // now replot every dot that overlapped
2526 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2527 int xx = xList[k], yy = yList[k];
2528 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2529 DrawSeekDot(xx, yy, colorList[k]);
2534 RemoveSeekAd(int nr)
2537 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2539 if(seekAdList[i]) free(seekAdList[i]);
2540 seekAdList[i] = seekAdList[--nrOfSeekAds];
2541 seekNrList[i] = seekNrList[nrOfSeekAds];
2542 ratingList[i] = ratingList[nrOfSeekAds];
2543 colorList[i] = colorList[nrOfSeekAds];
2544 tcList[i] = tcList[nrOfSeekAds];
2545 xList[i] = xList[nrOfSeekAds];
2546 yList[i] = yList[nrOfSeekAds];
2547 zList[i] = zList[nrOfSeekAds];
2548 seekAdList[nrOfSeekAds] = NULL;
2554 MatchSoughtLine(char *line)
2556 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2557 int nr, base, inc, u=0; char dummy;
2559 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2560 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2562 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2563 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2564 // match: compact and save the line
2565 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2575 if(!seekGraphUp) return FALSE;
2576 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2577 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2579 DrawSeekBackground(0, 0, w, h);
2580 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2581 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2582 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2583 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2585 DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2588 snprintf(buf, MSG_SIZ, "%d", i);
2589 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2592 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2593 for(i=1; i<100; i+=(i<10?1:5)) {
2594 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2595 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2596 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2598 snprintf(buf, MSG_SIZ, "%d", i);
2599 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2602 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2606 int SeekGraphClick(ClickType click, int x, int y, int moving)
2608 static int lastDown = 0, displayed = 0, lastSecond;
2609 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2610 if(click == Release || moving) return FALSE;
2612 soughtPending = TRUE;
2613 SendToICS(ics_prefix);
2614 SendToICS("sought\n"); // should this be "sought all"?
2615 } else { // issue challenge based on clicked ad
2616 int dist = 10000; int i, closest = 0, second = 0;
2617 for(i=0; i<nrOfSeekAds; i++) {
2618 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2619 if(d < dist) { dist = d; closest = i; }
2620 second += (d - zList[i] < 120); // count in-range ads
2621 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2625 second = (second > 1);
2626 if(displayed != closest || second != lastSecond) {
2627 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2628 lastSecond = second; displayed = closest;
2630 if(click == Press) {
2631 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2634 } // on press 'hit', only show info
2635 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2636 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2637 SendToICS(ics_prefix);
2639 return TRUE; // let incoming board of started game pop down the graph
2640 } else if(click == Release) { // release 'miss' is ignored
2641 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2642 if(moving == 2) { // right up-click
2643 nrOfSeekAds = 0; // refresh graph
2644 soughtPending = TRUE;
2645 SendToICS(ics_prefix);
2646 SendToICS("sought\n"); // should this be "sought all"?
2649 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2650 // press miss or release hit 'pop down' seek graph
2651 seekGraphUp = FALSE;
2652 DrawPosition(TRUE, NULL);
2658 read_from_ics(isr, closure, data, count, error)
2665 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2666 #define STARTED_NONE 0
2667 #define STARTED_MOVES 1
2668 #define STARTED_BOARD 2
2669 #define STARTED_OBSERVE 3
2670 #define STARTED_HOLDINGS 4
2671 #define STARTED_CHATTER 5
2672 #define STARTED_COMMENT 6
2673 #define STARTED_MOVES_NOHIDE 7
2675 static int started = STARTED_NONE;
2676 static char parse[20000];
2677 static int parse_pos = 0;
2678 static char buf[BUF_SIZE + 1];
2679 static int firstTime = TRUE, intfSet = FALSE;
2680 static ColorClass prevColor = ColorNormal;
2681 static int savingComment = FALSE;
2682 static int cmatch = 0; // continuation sequence match
2689 int backup; /* [DM] For zippy color lines */
2691 char talker[MSG_SIZ]; // [HGM] chat
2694 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2696 if (appData.debugMode) {
2698 fprintf(debugFP, "<ICS: ");
2699 show_bytes(debugFP, data, count);
2700 fprintf(debugFP, "\n");
2704 if (appData.debugMode) { int f = forwardMostMove;
2705 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2706 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2707 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2710 /* If last read ended with a partial line that we couldn't parse,
2711 prepend it to the new read and try again. */
2712 if (leftover_len > 0) {
2713 for (i=0; i<leftover_len; i++)
2714 buf[i] = buf[leftover_start + i];
2717 /* copy new characters into the buffer */
2718 bp = buf + leftover_len;
2719 buf_len=leftover_len;
2720 for (i=0; i<count; i++)
2723 if (data[i] == '\r')
2726 // join lines split by ICS?
2727 if (!appData.noJoin)
2730 Joining just consists of finding matches against the
2731 continuation sequence, and discarding that sequence
2732 if found instead of copying it. So, until a match
2733 fails, there's nothing to do since it might be the
2734 complete sequence, and thus, something we don't want
2737 if (data[i] == cont_seq[cmatch])
2740 if (cmatch == strlen(cont_seq))
2742 cmatch = 0; // complete match. just reset the counter
2745 it's possible for the ICS to not include the space
2746 at the end of the last word, making our [correct]
2747 join operation fuse two separate words. the server
2748 does this when the space occurs at the width setting.
2750 if (!buf_len || buf[buf_len-1] != ' ')
2761 match failed, so we have to copy what matched before
2762 falling through and copying this character. In reality,
2763 this will only ever be just the newline character, but
2764 it doesn't hurt to be precise.
2766 strncpy(bp, cont_seq, cmatch);
2778 buf[buf_len] = NULLCHAR;
2779 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2784 while (i < buf_len) {
2785 /* Deal with part of the TELNET option negotiation
2786 protocol. We refuse to do anything beyond the
2787 defaults, except that we allow the WILL ECHO option,
2788 which ICS uses to turn off password echoing when we are
2789 directly connected to it. We reject this option
2790 if localLineEditing mode is on (always on in xboard)
2791 and we are talking to port 23, which might be a real
2792 telnet server that will try to keep WILL ECHO on permanently.
2794 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2795 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2796 unsigned char option;
2798 switch ((unsigned char) buf[++i]) {
2800 if (appData.debugMode)
2801 fprintf(debugFP, "\n<WILL ");
2802 switch (option = (unsigned char) buf[++i]) {
2804 if (appData.debugMode)
2805 fprintf(debugFP, "ECHO ");
2806 /* Reply only if this is a change, according
2807 to the protocol rules. */
2808 if (remoteEchoOption) break;
2809 if (appData.localLineEditing &&
2810 atoi(appData.icsPort) == TN_PORT) {
2811 TelnetRequest(TN_DONT, TN_ECHO);
2814 TelnetRequest(TN_DO, TN_ECHO);
2815 remoteEchoOption = TRUE;
2819 if (appData.debugMode)
2820 fprintf(debugFP, "%d ", option);
2821 /* Whatever this is, we don't want it. */
2822 TelnetRequest(TN_DONT, option);
2827 if (appData.debugMode)
2828 fprintf(debugFP, "\n<WONT ");
2829 switch (option = (unsigned char) buf[++i]) {
2831 if (appData.debugMode)
2832 fprintf(debugFP, "ECHO ");
2833 /* Reply only if this is a change, according
2834 to the protocol rules. */
2835 if (!remoteEchoOption) break;
2837 TelnetRequest(TN_DONT, TN_ECHO);
2838 remoteEchoOption = FALSE;
2841 if (appData.debugMode)
2842 fprintf(debugFP, "%d ", (unsigned char) option);
2843 /* Whatever this is, it must already be turned
2844 off, because we never agree to turn on
2845 anything non-default, so according to the
2846 protocol rules, we don't reply. */
2851 if (appData.debugMode)
2852 fprintf(debugFP, "\n<DO ");
2853 switch (option = (unsigned char) buf[++i]) {
2855 /* Whatever this is, we refuse to do it. */
2856 if (appData.debugMode)
2857 fprintf(debugFP, "%d ", option);
2858 TelnetRequest(TN_WONT, option);
2863 if (appData.debugMode)
2864 fprintf(debugFP, "\n<DONT ");
2865 switch (option = (unsigned char) buf[++i]) {
2867 if (appData.debugMode)
2868 fprintf(debugFP, "%d ", option);
2869 /* Whatever this is, we are already not doing
2870 it, because we never agree to do anything
2871 non-default, so according to the protocol
2872 rules, we don't reply. */
2877 if (appData.debugMode)
2878 fprintf(debugFP, "\n<IAC ");
2879 /* Doubled IAC; pass it through */
2883 if (appData.debugMode)
2884 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2885 /* Drop all other telnet commands on the floor */
2888 if (oldi > next_out)
2889 SendToPlayer(&buf[next_out], oldi - next_out);
2895 /* OK, this at least will *usually* work */
2896 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2900 if (loggedOn && !intfSet) {
2901 if (ics_type == ICS_ICC) {
2902 snprintf(str, MSG_SIZ,
2903 "/set-quietly interface %s\n/set-quietly style 12\n",
2905 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2906 strcat(str, "/set-2 51 1\n/set seek 1\n");
2907 } else if (ics_type == ICS_CHESSNET) {
2908 snprintf(str, MSG_SIZ, "/style 12\n");
2910 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2911 strcat(str, programVersion);
2912 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2913 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2914 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2916 strcat(str, "$iset nohighlight 1\n");
2918 strcat(str, "$iset lock 1\n$style 12\n");
2921 NotifyFrontendLogin();
2925 if (started == STARTED_COMMENT) {
2926 /* Accumulate characters in comment */
2927 parse[parse_pos++] = buf[i];
2928 if (buf[i] == '\n') {
2929 parse[parse_pos] = NULLCHAR;
2930 if(chattingPartner>=0) {
2932 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2933 OutputChatMessage(chattingPartner, mess);
2934 chattingPartner = -1;
2935 next_out = i+1; // [HGM] suppress printing in ICS window
2937 if(!suppressKibitz) // [HGM] kibitz
2938 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2939 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2940 int nrDigit = 0, nrAlph = 0, j;
2941 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2942 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2943 parse[parse_pos] = NULLCHAR;
2944 // try to be smart: if it does not look like search info, it should go to
2945 // ICS interaction window after all, not to engine-output window.
2946 for(j=0; j<parse_pos; j++) { // count letters and digits
2947 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2948 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2949 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2951 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2952 int depth=0; float score;
2953 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2954 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2955 pvInfoList[forwardMostMove-1].depth = depth;
2956 pvInfoList[forwardMostMove-1].score = 100*score;
2958 OutputKibitz(suppressKibitz, parse);
2961 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2962 SendToPlayer(tmp, strlen(tmp));
2964 next_out = i+1; // [HGM] suppress printing in ICS window
2966 started = STARTED_NONE;
2968 /* Don't match patterns against characters in comment */
2973 if (started == STARTED_CHATTER) {
2974 if (buf[i] != '\n') {
2975 /* Don't match patterns against characters in chatter */
2979 started = STARTED_NONE;
2980 if(suppressKibitz) next_out = i+1;
2983 /* Kludge to deal with rcmd protocol */
2984 if (firstTime && looking_at(buf, &i, "\001*")) {
2985 DisplayFatalError(&buf[1], 0, 1);
2991 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2994 if (appData.debugMode)
2995 fprintf(debugFP, "ics_type %d\n", ics_type);
2998 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2999 ics_type = ICS_FICS;
3001 if (appData.debugMode)
3002 fprintf(debugFP, "ics_type %d\n", ics_type);
3005 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3006 ics_type = ICS_CHESSNET;
3008 if (appData.debugMode)
3009 fprintf(debugFP, "ics_type %d\n", ics_type);
3014 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3015 looking_at(buf, &i, "Logging you in as \"*\"") ||
3016 looking_at(buf, &i, "will be \"*\""))) {
3017 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3021 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3023 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3024 DisplayIcsInteractionTitle(buf);
3025 have_set_title = TRUE;
3028 /* skip finger notes */
3029 if (started == STARTED_NONE &&
3030 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3031 (buf[i] == '1' && buf[i+1] == '0')) &&
3032 buf[i+2] == ':' && buf[i+3] == ' ') {
3033 started = STARTED_CHATTER;
3039 // [HGM] seekgraph: recognize sought lines and end-of-sought message
3040 if(appData.seekGraph) {
3041 if(soughtPending && MatchSoughtLine(buf+i)) {
3042 i = strstr(buf+i, "rated") - buf;
3043 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3044 next_out = leftover_start = i;
3045 started = STARTED_CHATTER;
3046 suppressKibitz = TRUE;
3049 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3050 && looking_at(buf, &i, "* ads displayed")) {
3051 soughtPending = FALSE;
3056 if(appData.autoRefresh) {
3057 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3058 int s = (ics_type == ICS_ICC); // ICC format differs
3060 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3061 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3062 looking_at(buf, &i, "*% "); // eat prompt
3063 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3064 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3065 next_out = i; // suppress
3068 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3069 char *p = star_match[0];
3071 if(seekGraphUp) RemoveSeekAd(atoi(p));
3072 while(*p && *p++ != ' '); // next
3074 looking_at(buf, &i, "*% "); // eat prompt
3075 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3082 /* skip formula vars */
3083 if (started == STARTED_NONE &&
3084 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3085 started = STARTED_CHATTER;
3090 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3091 if (appData.autoKibitz && started == STARTED_NONE &&
3092 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3093 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3094 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3095 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3096 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3097 suppressKibitz = TRUE;
3098 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3100 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3101 && (gameMode == IcsPlayingWhite)) ||
3102 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3103 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3104 started = STARTED_CHATTER; // own kibitz we simply discard
3106 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3107 parse_pos = 0; parse[0] = NULLCHAR;
3108 savingComment = TRUE;
3109 suppressKibitz = gameMode != IcsObserving ? 2 :
3110 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3114 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3115 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3116 && atoi(star_match[0])) {
3117 // suppress the acknowledgements of our own autoKibitz
3119 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3120 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3121 SendToPlayer(star_match[0], strlen(star_match[0]));
3122 if(looking_at(buf, &i, "*% ")) // eat prompt
3123 suppressKibitz = FALSE;
3127 } // [HGM] kibitz: end of patch
3129 if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3131 // [HGM] chat: intercept tells by users for which we have an open chat window
3133 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3134 looking_at(buf, &i, "* whispers:") ||
3135 looking_at(buf, &i, "* kibitzes:") ||
3136 looking_at(buf, &i, "* shouts:") ||
3137 looking_at(buf, &i, "* c-shouts:") ||
3138 looking_at(buf, &i, "--> * ") ||
3139 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3140 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3141 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3142 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3144 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3145 chattingPartner = -1;
3147 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3148 for(p=0; p<MAX_CHAT; p++) {
3149 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3150 talker[0] = '['; strcat(talker, "] ");
3151 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3152 chattingPartner = p; break;
3155 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3156 for(p=0; p<MAX_CHAT; p++) {
3157 if(!strcmp("kibitzes", chatPartner[p])) {
3158 talker[0] = '['; strcat(talker, "] ");
3159 chattingPartner = p; break;
3162 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3163 for(p=0; p<MAX_CHAT; p++) {
3164 if(!strcmp("whispers", chatPartner[p])) {
3165 talker[0] = '['; strcat(talker, "] ");
3166 chattingPartner = p; break;
3169 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3170 if(buf[i-8] == '-' && buf[i-3] == 't')
3171 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3172 if(!strcmp("c-shouts", chatPartner[p])) {
3173 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3174 chattingPartner = p; break;
3177 if(chattingPartner < 0)
3178 for(p=0; p<MAX_CHAT; p++) {
3179 if(!strcmp("shouts", chatPartner[p])) {
3180 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3181 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3182 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3183 chattingPartner = p; break;
3187 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3188 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3189 talker[0] = 0; Colorize(ColorTell, FALSE);
3190 chattingPartner = p; break;
3192 if(chattingPartner<0) i = oldi; else {
3193 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3194 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3195 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3196 started = STARTED_COMMENT;
3197 parse_pos = 0; parse[0] = NULLCHAR;
3198 savingComment = 3 + chattingPartner; // counts as TRUE
3199 suppressKibitz = TRUE;
3202 } // [HGM] chat: end of patch
3205 if (appData.zippyTalk || appData.zippyPlay) {
3206 /* [DM] Backup address for color zippy lines */
3208 if (loggedOn == TRUE)
3209 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3210 (appData.zippyPlay && ZippyMatch(buf, &backup)));
3212 } // [DM] 'else { ' deleted
3214 /* Regular tells and says */
3215 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3216 looking_at(buf, &i, "* (your partner) tells you: ") ||
3217 looking_at(buf, &i, "* says: ") ||
3218 /* Don't color "message" or "messages" output */
3219 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3220 looking_at(buf, &i, "*. * at *:*: ") ||
3221 looking_at(buf, &i, "--* (*:*): ") ||
3222 /* Message notifications (same color as tells) */
3223 looking_at(buf, &i, "* has left a message ") ||
3224 looking_at(buf, &i, "* just sent you a message:\n") ||
3225 /* Whispers and kibitzes */
3226 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3227 looking_at(buf, &i, "* kibitzes: ") ||
3229 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3231 if (tkind == 1 && strchr(star_match[0], ':')) {
3232 /* Avoid "tells you:" spoofs in channels */
3235 if (star_match[0][0] == NULLCHAR ||
3236 strchr(star_match[0], ' ') ||
3237 (tkind == 3 && strchr(star_match[1], ' '))) {
3238 /* Reject bogus matches */
3241 if (appData.colorize) {
3242 if (oldi > next_out) {
3243 SendToPlayer(&buf[next_out], oldi - next_out);
3248 Colorize(ColorTell, FALSE);
3249 curColor = ColorTell;
3252 Colorize(ColorKibitz, FALSE);
3253 curColor = ColorKibitz;
3256 p = strrchr(star_match[1], '(');
3263 Colorize(ColorChannel1, FALSE);
3264 curColor = ColorChannel1;
3266 Colorize(ColorChannel, FALSE);
3267 curColor = ColorChannel;
3271 curColor = ColorNormal;
3275 if (started == STARTED_NONE && appData.autoComment &&
3276 (gameMode == IcsObserving ||
3277 gameMode == IcsPlayingWhite ||
3278 gameMode == IcsPlayingBlack)) {
3279 parse_pos = i - oldi;
3280 memcpy(parse, &buf[oldi], parse_pos);
3281 parse[parse_pos] = NULLCHAR;
3282 started = STARTED_COMMENT;
3283 savingComment = TRUE;
3285 started = STARTED_CHATTER;
3286 savingComment = FALSE;
3293 if (looking_at(buf, &i, "* s-shouts: ") ||
3294 looking_at(buf, &i, "* c-shouts: ")) {
3295 if (appData.colorize) {
3296 if (oldi > next_out) {
3297 SendToPlayer(&buf[next_out], oldi - next_out);
3300 Colorize(ColorSShout, FALSE);
3301 curColor = ColorSShout;
3304 started = STARTED_CHATTER;
3308 if (looking_at(buf, &i, "--->")) {
3313 if (looking_at(buf, &i, "* shouts: ") ||
3314 looking_at(buf, &i, "--> ")) {
3315 if (appData.colorize) {
3316 if (oldi > next_out) {
3317 SendToPlayer(&buf[next_out], oldi - next_out);
3320 Colorize(ColorShout, FALSE);
3321 curColor = ColorShout;
3324 started = STARTED_CHATTER;
3328 if (looking_at( buf, &i, "Challenge:")) {
3329 if (appData.colorize) {
3330 if (oldi > next_out) {
3331 SendToPlayer(&buf[next_out], oldi - next_out);
3334 Colorize(ColorChallenge, FALSE);
3335 curColor = ColorChallenge;
3341 if (looking_at(buf, &i, "* offers you") ||
3342 looking_at(buf, &i, "* offers to be") ||
3343 looking_at(buf, &i, "* would like to") ||
3344 looking_at(buf, &i, "* requests to") ||
3345 looking_at(buf, &i, "Your opponent offers") ||
3346 looking_at(buf, &i, "Your opponent requests")) {
3348 if (appData.colorize) {
3349 if (oldi > next_out) {
3350 SendToPlayer(&buf[next_out], oldi - next_out);
3353 Colorize(ColorRequest, FALSE);
3354 curColor = ColorRequest;
3359 if (looking_at(buf, &i, "* (*) seeking")) {
3360 if (appData.colorize) {
3361 if (oldi > next_out) {
3362 SendToPlayer(&buf[next_out], oldi - next_out);
3365 Colorize(ColorSeek, FALSE);
3366 curColor = ColorSeek;
3371 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3373 if (looking_at(buf, &i, "\\ ")) {
3374 if (prevColor != ColorNormal) {
3375 if (oldi > next_out) {
3376 SendToPlayer(&buf[next_out], oldi - next_out);
3379 Colorize(prevColor, TRUE);
3380 curColor = prevColor;
3382 if (savingComment) {
3383 parse_pos = i - oldi;
3384 memcpy(parse, &buf[oldi], parse_pos);
3385 parse[parse_pos] = NULLCHAR;
3386 started = STARTED_COMMENT;
3387 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3388 chattingPartner = savingComment - 3; // kludge to remember the box
3390 started = STARTED_CHATTER;
3395 if (looking_at(buf, &i, "Black Strength :") ||
3396 looking_at(buf, &i, "<<< style 10 board >>>") ||
3397 looking_at(buf, &i, "<10>") ||
3398 looking_at(buf, &i, "#@#")) {
3399 /* Wrong board style */
3401 SendToICS(ics_prefix);
3402 SendToICS("set style 12\n");
3403 SendToICS(ics_prefix);
3404 SendToICS("refresh\n");
3408 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3410 have_sent_ICS_logon = 1;
3414 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3415 (looking_at(buf, &i, "\n<12> ") ||
3416 looking_at(buf, &i, "<12> "))) {
3418 if (oldi > next_out) {
3419 SendToPlayer(&buf[next_out], oldi - next_out);
3422 started = STARTED_BOARD;
3427 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3428 looking_at(buf, &i, "<b1> ")) {
3429 if (oldi > next_out) {
3430 SendToPlayer(&buf[next_out], oldi - next_out);
3433 started = STARTED_HOLDINGS;
3438 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3440 /* Header for a move list -- first line */
3442 switch (ics_getting_history) {
3446 case BeginningOfGame:
3447 /* User typed "moves" or "oldmoves" while we
3448 were idle. Pretend we asked for these
3449 moves and soak them up so user can step
3450 through them and/or save them.
3453 gameMode = IcsObserving;
3456 ics_getting_history = H_GOT_UNREQ_HEADER;
3458 case EditGame: /*?*/
3459 case EditPosition: /*?*/
3460 /* Should above feature work in these modes too? */
3461 /* For now it doesn't */
3462 ics_getting_history = H_GOT_UNWANTED_HEADER;
3465 ics_getting_history = H_GOT_UNWANTED_HEADER;
3470 /* Is this the right one? */
3471 if (gameInfo.white && gameInfo.black &&
3472 strcmp(gameInfo.white, star_match[0]) == 0 &&
3473 strcmp(gameInfo.black, star_match[2]) == 0) {
3475 ics_getting_history = H_GOT_REQ_HEADER;
3478 case H_GOT_REQ_HEADER:
3479 case H_GOT_UNREQ_HEADER:
3480 case H_GOT_UNWANTED_HEADER:
3481 case H_GETTING_MOVES:
3482 /* Should not happen */
3483 DisplayError(_("Error gathering move list: two headers"), 0);
3484 ics_getting_history = H_FALSE;
3488 /* Save player ratings into gameInfo if needed */
3489 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3490 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3491 (gameInfo.whiteRating == -1 ||
3492 gameInfo.blackRating == -1)) {
3494 gameInfo.whiteRating = string_to_rating(star_match[1]);
3495 gameInfo.blackRating = string_to_rating(star_match[3]);
3496 if (appData.debugMode)
3497 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3498 gameInfo.whiteRating, gameInfo.blackRating);
3503 if (looking_at(buf, &i,
3504 "* * match, initial time: * minute*, increment: * second")) {
3505 /* Header for a move list -- second line */
3506 /* Initial board will follow if this is a wild game */
3507 if (gameInfo.event != NULL) free(gameInfo.event);
3508 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3509 gameInfo.event = StrSave(str);
3510 /* [HGM] we switched variant. Translate boards if needed. */
3511 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3515 if (looking_at(buf, &i, "Move ")) {
3516 /* Beginning of a move list */
3517 switch (ics_getting_history) {
3519 /* Normally should not happen */
3520 /* Maybe user hit reset while we were parsing */
3523 /* Happens if we are ignoring a move list that is not
3524 * the one we just requested. Common if the user
3525 * tries to observe two games without turning off
3528 case H_GETTING_MOVES:
3529 /* Should not happen */
3530 DisplayError(_("Error gathering move list: nested"), 0);
3531 ics_getting_history = H_FALSE;
3533 case H_GOT_REQ_HEADER:
3534 ics_getting_history = H_GETTING_MOVES;
3535 started = STARTED_MOVES;
3537 if (oldi > next_out) {
3538 SendToPlayer(&buf[next_out], oldi - next_out);
3541 case H_GOT_UNREQ_HEADER:
3542 ics_getting_history = H_GETTING_MOVES;
3543 started = STARTED_MOVES_NOHIDE;
3546 case H_GOT_UNWANTED_HEADER:
3547 ics_getting_history = H_FALSE;
3553 if (looking_at(buf, &i, "% ") ||
3554 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3555 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3556 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3557 soughtPending = FALSE;
3561 if(suppressKibitz) next_out = i;
3562 savingComment = FALSE;
3566 case STARTED_MOVES_NOHIDE:
3567 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3568 parse[parse_pos + i - oldi] = NULLCHAR;
3569 ParseGameHistory(parse);
3571 if (appData.zippyPlay && first.initDone) {
3572 FeedMovesToProgram(&first, forwardMostMove);
3573 if (gameMode == IcsPlayingWhite) {
3574 if (WhiteOnMove(forwardMostMove)) {
3575 if (first.sendTime) {
3576 if (first.useColors) {
3577 SendToProgram("black\n", &first);
3579 SendTimeRemaining(&first, TRUE);
3581 if (first.useColors) {
3582 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3584 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3585 first.maybeThinking = TRUE;
3587 if (first.usePlayother) {
3588 if (first.sendTime) {
3589 SendTimeRemaining(&first, TRUE);
3591 SendToProgram("playother\n", &first);
3597 } else if (gameMode == IcsPlayingBlack) {
3598 if (!WhiteOnMove(forwardMostMove)) {
3599 if (first.sendTime) {
3600 if (first.useColors) {
3601 SendToProgram("white\n", &first);
3603 SendTimeRemaining(&first, FALSE);
3605 if (first.useColors) {
3606 SendToProgram("black\n", &first);
3608 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3609 first.maybeThinking = TRUE;
3611 if (first.usePlayother) {
3612 if (first.sendTime) {
3613 SendTimeRemaining(&first, FALSE);
3615 SendToProgram("playother\n", &first);
3624 if (gameMode == IcsObserving && ics_gamenum == -1) {
3625 /* Moves came from oldmoves or moves command
3626 while we weren't doing anything else.
3628 currentMove = forwardMostMove;
3629 ClearHighlights();/*!!could figure this out*/
3630 flipView = appData.flipView;
3631 DrawPosition(TRUE, boards[currentMove]);
3632 DisplayBothClocks();
3633 snprintf(str, MSG_SIZ, _("%s vs. %s"),
3634 gameInfo.white, gameInfo.black);
3638 /* Moves were history of an active game */
3639 if (gameInfo.resultDetails != NULL) {
3640 free(gameInfo.resultDetails);
3641 gameInfo.resultDetails = NULL;
3644 HistorySet(parseList, backwardMostMove,
3645 forwardMostMove, currentMove-1);
3646 DisplayMove(currentMove - 1);
3647 if (started == STARTED_MOVES) next_out = i;
3648 started = STARTED_NONE;
3649 ics_getting_history = H_FALSE;
3652 case STARTED_OBSERVE:
3653 started = STARTED_NONE;
3654 SendToICS(ics_prefix);
3655 SendToICS("refresh\n");
3661 if(bookHit) { // [HGM] book: simulate book reply
3662 static char bookMove[MSG_SIZ]; // a bit generous?
3664 programStats.nodes = programStats.depth = programStats.time =
3665 programStats.score = programStats.got_only_move = 0;
3666 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3668 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3669 strcat(bookMove, bookHit);
3670 HandleMachineMove(bookMove, &first);
3675 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3676 started == STARTED_HOLDINGS ||
3677 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3678 /* Accumulate characters in move list or board */
3679 parse[parse_pos++] = buf[i];
3682 /* Start of game messages. Mostly we detect start of game
3683 when the first board image arrives. On some versions
3684 of the ICS, though, we need to do a "refresh" after starting
3685 to observe in order to get the current board right away. */
3686 if (looking_at(buf, &i, "Adding game * to observation list")) {
3687 started = STARTED_OBSERVE;
3691 /* Handle auto-observe */
3692 if (appData.autoObserve &&
3693 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3694 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3696 /* Choose the player that was highlighted, if any. */
3697 if (star_match[0][0] == '\033' ||
3698 star_match[1][0] != '\033') {
3699 player = star_match[0];
3701 player = star_match[2];
3703 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3704 ics_prefix, StripHighlightAndTitle(player));
3707 /* Save ratings from notify string */
3708 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3709 player1Rating = string_to_rating(star_match[1]);
3710 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3711 player2Rating = string_to_rating(star_match[3]);
3713 if (appData.debugMode)
3715 "Ratings from 'Game notification:' %s %d, %s %d\n",
3716 player1Name, player1Rating,
3717 player2Name, player2Rating);
3722 /* Deal with automatic examine mode after a game,
3723 and with IcsObserving -> IcsExamining transition */
3724 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3725 looking_at(buf, &i, "has made you an examiner of game *")) {
3727 int gamenum = atoi(star_match[0]);
3728 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3729 gamenum == ics_gamenum) {
3730 /* We were already playing or observing this game;
3731 no need to refetch history */
3732 gameMode = IcsExamining;
3734 pauseExamForwardMostMove = forwardMostMove;
3735 } else if (currentMove < forwardMostMove) {
3736 ForwardInner(forwardMostMove);
3739 /* I don't think this case really can happen */
3740 SendToICS(ics_prefix);
3741 SendToICS("refresh\n");
3746 /* Error messages */
3747 // if (ics_user_moved) {
3748 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3749 if (looking_at(buf, &i, "Illegal move") ||
3750 looking_at(buf, &i, "Not a legal move") ||
3751 looking_at(buf, &i, "Your king is in check") ||
3752 looking_at(buf, &i, "It isn't your turn") ||
3753 looking_at(buf, &i, "It is not your move")) {
3755 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3756 currentMove = forwardMostMove-1;
3757 DisplayMove(currentMove - 1); /* before DMError */
3758 DrawPosition(FALSE, boards[currentMove]);
3759 SwitchClocks(forwardMostMove-1); // [HGM] race
3760 DisplayBothClocks();
3762 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3768 if (looking_at(buf, &i, "still have time") ||
3769 looking_at(buf, &i, "not out of time") ||
3770 looking_at(buf, &i, "either player is out of time") ||
3771 looking_at(buf, &i, "has timeseal; checking")) {
3772 /* We must have called his flag a little too soon */
3773 whiteFlag = blackFlag = FALSE;
3777 if (looking_at(buf, &i, "added * seconds to") ||
3778 looking_at(buf, &i, "seconds were added to")) {
3779 /* Update the clocks */
3780 SendToICS(ics_prefix);
3781 SendToICS("refresh\n");
3785 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3786 ics_clock_paused = TRUE;
3791 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3792 ics_clock_paused = FALSE;
3797 /* Grab player ratings from the Creating: message.
3798 Note we have to check for the special case when
3799 the ICS inserts things like [white] or [black]. */
3800 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3801 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3803 0 player 1 name (not necessarily white)
3805 2 empty, white, or black (IGNORED)
3806 3 player 2 name (not necessarily black)
3809 The names/ratings are sorted out when the game
3810 actually starts (below).
3812 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3813 player1Rating = string_to_rating(star_match[1]);
3814 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3815 player2Rating = string_to_rating(star_match[4]);
3817 if (appData.debugMode)
3819 "Ratings from 'Creating:' %s %d, %s %d\n",
3820 player1Name, player1Rating,
3821 player2Name, player2Rating);
3826 /* Improved generic start/end-of-game messages */
3827 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3828 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3829 /* If tkind == 0: */
3830 /* star_match[0] is the game number */
3831 /* [1] is the white player's name */
3832 /* [2] is the black player's name */
3833 /* For end-of-game: */
3834 /* [3] is the reason for the game end */
3835 /* [4] is a PGN end game-token, preceded by " " */
3836 /* For start-of-game: */
3837 /* [3] begins with "Creating" or "Continuing" */
3838 /* [4] is " *" or empty (don't care). */
3839 int gamenum = atoi(star_match[0]);
3840 char *whitename, *blackname, *why, *endtoken;
3841 ChessMove endtype = EndOfFile;
3844 whitename = star_match[1];
3845 blackname = star_match[2];
3846 why = star_match[3];
3847 endtoken = star_match[4];
3849 whitename = star_match[1];
3850 blackname = star_match[3];
3851 why = star_match[5];
3852 endtoken = star_match[6];
3855 /* Game start messages */
3856 if (strncmp(why, "Creating ", 9) == 0 ||
3857 strncmp(why, "Continuing ", 11) == 0) {
3858 gs_gamenum = gamenum;
3859 safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3860 if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3861 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3863 if (appData.zippyPlay) {
3864 ZippyGameStart(whitename, blackname);
3867 partnerBoardValid = FALSE; // [HGM] bughouse