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 (char *str)
648 while(*str && !isdigit(*str)) ++str;
650 return 0; /* One of the special "no rating" cases */
658 /* Init programStats */
659 programStats.movelist[0] = 0;
660 programStats.depth = 0;
661 programStats.nr_moves = 0;
662 programStats.moves_left = 0;
663 programStats.nodes = 0;
664 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
665 programStats.score = 0;
666 programStats.got_only_move = 0;
667 programStats.got_fail = 0;
668 programStats.line_is_book = 0;
673 { // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
674 if (appData.firstPlaysBlack) {
675 first.twoMachinesColor = "black\n";
676 second.twoMachinesColor = "white\n";
678 first.twoMachinesColor = "white\n";
679 second.twoMachinesColor = "black\n";
682 first.other = &second;
683 second.other = &first;
686 if(appData.timeOddsMode) {
687 norm = appData.timeOdds[0];
688 if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
690 first.timeOdds = appData.timeOdds[0]/norm;
691 second.timeOdds = appData.timeOdds[1]/norm;
694 if(programVersion) free(programVersion);
695 if (appData.noChessProgram) {
696 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
697 sprintf(programVersion, "%s", PACKAGE_STRING);
699 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
700 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
701 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
706 UnloadEngine (ChessProgramState *cps)
708 /* Kill off first chess program */
709 if (cps->isr != NULL)
710 RemoveInputSource(cps->isr);
713 if (cps->pr != NoProc) {
715 DoSleep( appData.delayBeforeQuit );
716 SendToProgram("quit\n", cps);
717 DoSleep( appData.delayAfterQuit );
718 DestroyChildProcess(cps->pr, cps->useSigterm);
721 if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
725 ClearOptions (ChessProgramState *cps)
728 cps->nrOptions = cps->comboCnt = 0;
729 for(i=0; i<MAX_OPTIONS; i++) {
730 cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
731 cps->option[i].textValue = 0;
735 char *engineNames[] = {
741 InitEngine (ChessProgramState *cps, int n)
742 { // [HGM] all engine initialiation put in a function that does one engine
746 cps->which = engineNames[n];
747 cps->maybeThinking = FALSE;
751 cps->sendDrawOffers = 1;
753 cps->program = appData.chessProgram[n];
754 cps->host = appData.host[n];
755 cps->dir = appData.directory[n];
756 cps->initString = appData.engInitString[n];
757 cps->computerString = appData.computerString[n];
758 cps->useSigint = TRUE;
759 cps->useSigterm = TRUE;
760 cps->reuse = appData.reuse[n];
761 cps->nps = appData.NPS[n]; // [HGM] nps: copy nodes per second
762 cps->useSetboard = FALSE;
764 cps->usePing = FALSE;
767 cps->usePlayother = FALSE;
768 cps->useColors = TRUE;
769 cps->useUsermove = FALSE;
770 cps->sendICS = FALSE;
771 cps->sendName = appData.icsActive;
772 cps->sdKludge = FALSE;
773 cps->stKludge = FALSE;
774 TidyProgramName(cps->program, cps->host, cps->tidy);
776 safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
777 cps->analysisSupport = 2; /* detect */
778 cps->analyzing = FALSE;
779 cps->initDone = FALSE;
781 /* New features added by Tord: */
782 cps->useFEN960 = FALSE;
783 cps->useOOCastle = TRUE;
784 /* End of new features added by Tord. */
785 cps->fenOverride = appData.fenOverride[n];
787 /* [HGM] time odds: set factor for each machine */
788 cps->timeOdds = appData.timeOdds[n];
790 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
791 cps->accumulateTC = appData.accumulateTC[n];
792 cps->maxNrOfSessions = 1;
797 cps->supportsNPS = UNKNOWN;
798 cps->memSize = FALSE;
799 cps->maxCores = FALSE;
800 cps->egtFormats[0] = NULLCHAR;
803 cps->optionSettings = appData.engOptions[n];
805 cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
806 cps->isUCI = appData.isUCI[n]; /* [AS] */
807 cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
809 if (appData.protocolVersion[n] > PROTOVER
810 || appData.protocolVersion[n] < 1)
815 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
816 appData.protocolVersion[n]);
817 if( (len >= MSG_SIZ) && appData.debugMode )
818 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
820 DisplayFatalError(buf, 0, 2);
824 cps->protocolVersion = appData.protocolVersion[n];
827 InitEngineUCI( installDir, cps ); // [HGM] moved here from winboard.c, to make available in xboard
828 ParseFeatures(appData.featureDefaults, cps);
831 ChessProgramState *savCps;
837 if(WaitForEngine(savCps, LoadEngine)) return;
838 CommonEngineInit(); // recalculate time odds
839 if(gameInfo.variant != StringToVariant(appData.variant)) {
840 // we changed variant when loading the engine; this forces us to reset
841 Reset(TRUE, savCps != &first);
842 EditGameEvent(); // for consistency with other path, as Reset changes mode
844 InitChessProgram(savCps, FALSE);
845 SendToProgram("force\n", savCps);
846 DisplayMessage("", "");
847 if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
848 for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
854 ReplaceEngine (ChessProgramState *cps, int n)
858 appData.noChessProgram = FALSE;
859 appData.clockMode = TRUE;
862 if(n) return; // only startup first engine immediately; second can wait
863 savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
867 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
868 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
870 static char resetOptions[] =
871 "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
872 "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
873 "-firstOptions \"\" -firstNPS -1 -fn \"\"";
876 Load (ChessProgramState *cps, int i)
878 char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
879 if(engineLine && engineLine[0]) { // an engine was selected from the combo box
880 snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
881 SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
882 ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
883 ParseArgsFromString(buf);
885 ReplaceEngine(cps, i);
889 while(q = strchr(p, SLASH)) p = q+1;
890 if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
891 if(engineDir[0] != NULLCHAR)
892 appData.directory[i] = engineDir;
893 else if(p != engineName) { // derive directory from engine path, when not given
895 appData.directory[i] = strdup(engineName);
897 } else appData.directory[i] = ".";
899 if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
900 snprintf(command, MSG_SIZ, "%s %s", p, params);
903 appData.chessProgram[i] = strdup(p);
904 appData.isUCI[i] = isUCI;
905 appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
906 appData.hasOwnBookUCI[i] = hasBook;
907 if(!nickName[0]) useNick = FALSE;
908 if(useNick) ASSIGN(appData.pgnName[i], nickName);
912 q = firstChessProgramNames;
913 if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
914 quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
915 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
916 quote, p, quote, appData.directory[i],
917 useNick ? " -fn \"" : "",
918 useNick ? nickName : "",
920 v1 ? " -firstProtocolVersion 1" : "",
921 hasBook ? "" : " -fNoOwnBookUCI",
922 isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
923 storeVariant ? " -variant " : "",
924 storeVariant ? VariantName(gameInfo.variant) : "");
925 firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
926 snprintf(firstChessProgramNames, len, "%s%s", q, buf);
929 ReplaceEngine(cps, i);
935 int matched, min, sec;
937 * Parse timeControl resource
939 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
940 appData.movesPerSession)) {
942 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
943 DisplayFatalError(buf, 0, 2);
947 * Parse searchTime resource
949 if (*appData.searchTime != NULLCHAR) {
950 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
952 searchTime = min * 60;
953 } else if (matched == 2) {
954 searchTime = min * 60 + sec;
957 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
958 DisplayFatalError(buf, 0, 2);
967 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
968 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
970 GetTimeMark(&programStartTime);
971 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
972 appData.seedBase = random() + (random()<<15);
973 pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
976 programStats.ok_to_send = 1;
977 programStats.seen_stat = 0;
980 * Initialize game list
986 * Internet chess server status
988 if (appData.icsActive) {
989 appData.matchMode = FALSE;
990 appData.matchGames = 0;
992 appData.noChessProgram = !appData.zippyPlay;
994 appData.zippyPlay = FALSE;
995 appData.zippyTalk = FALSE;
996 appData.noChessProgram = TRUE;
998 if (*appData.icsHelper != NULLCHAR) {
999 appData.useTelnet = TRUE;
1000 appData.telnetProgram = appData.icsHelper;
1003 appData.zippyTalk = appData.zippyPlay = FALSE;
1006 /* [AS] Initialize pv info list [HGM] and game state */
1010 for( i=0; i<=framePtr; i++ ) {
1011 pvInfoList[i].depth = -1;
1012 boards[i][EP_STATUS] = EP_NONE;
1013 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1019 /* [AS] Adjudication threshold */
1020 adjudicateLossThreshold = appData.adjudicateLossThreshold;
1022 InitEngine(&first, 0);
1023 InitEngine(&second, 1);
1026 pairing.which = "pairing"; // pairing engine
1027 pairing.pr = NoProc;
1029 pairing.program = appData.pairingEngine;
1030 pairing.host = "localhost";
1033 if (appData.icsActive) {
1034 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
1035 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1036 appData.clockMode = FALSE;
1037 first.sendTime = second.sendTime = 0;
1041 /* Override some settings from environment variables, for backward
1042 compatibility. Unfortunately it's not feasible to have the env
1043 vars just set defaults, at least in xboard. Ugh.
1045 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1050 if (!appData.icsActive) {
1054 /* Check for variants that are supported only in ICS mode,
1055 or not at all. Some that are accepted here nevertheless
1056 have bugs; see comments below.
1058 VariantClass variant = StringToVariant(appData.variant);
1060 case VariantBughouse: /* need four players and two boards */
1061 case VariantKriegspiel: /* need to hide pieces and move details */
1062 /* case VariantFischeRandom: (Fabien: moved below) */
1063 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1064 if( (len >= MSG_SIZ) && appData.debugMode )
1065 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1067 DisplayFatalError(buf, 0, 2);
1070 case VariantUnknown:
1071 case VariantLoadable:
1081 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1082 if( (len >= MSG_SIZ) && appData.debugMode )
1083 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1085 DisplayFatalError(buf, 0, 2);
1088 case VariantXiangqi: /* [HGM] repetition rules not implemented */
1089 case VariantFairy: /* [HGM] TestLegality definitely off! */
1090 case VariantGothic: /* [HGM] should work */
1091 case VariantCapablanca: /* [HGM] should work */
1092 case VariantCourier: /* [HGM] initial forced moves not implemented */
1093 case VariantShogi: /* [HGM] could still mate with pawn drop */
1094 case VariantKnightmate: /* [HGM] should work */
1095 case VariantCylinder: /* [HGM] untested */
1096 case VariantFalcon: /* [HGM] untested */
1097 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1098 offboard interposition not understood */
1099 case VariantNormal: /* definitely works! */
1100 case VariantWildCastle: /* pieces not automatically shuffled */
1101 case VariantNoCastle: /* pieces not automatically shuffled */
1102 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1103 case VariantLosers: /* should work except for win condition,
1104 and doesn't know captures are mandatory */
1105 case VariantSuicide: /* should work except for win condition,
1106 and doesn't know captures are mandatory */
1107 case VariantGiveaway: /* should work except for win condition,
1108 and doesn't know captures are mandatory */
1109 case VariantTwoKings: /* should work */
1110 case VariantAtomic: /* should work except for win condition */
1111 case Variant3Check: /* should work except for win condition */
1112 case VariantShatranj: /* should work except for all win conditions */
1113 case VariantMakruk: /* should work except for draw countdown */
1114 case VariantBerolina: /* might work if TestLegality is off */
1115 case VariantCapaRandom: /* should work */
1116 case VariantJanus: /* should work */
1117 case VariantSuper: /* experimental */
1118 case VariantGreat: /* experimental, requires legality testing to be off */
1119 case VariantSChess: /* S-Chess, should work */
1120 case VariantGrand: /* should work */
1121 case VariantSpartan: /* should work */
1129 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');
1155 NextTimeControlFromString (char ** str, long * value)
1158 int result = NextIntegerFromString( str, &temp );
1161 *value = temp * 60; /* Minutes */
1162 if( **str == ':' ) {
1164 result = NextIntegerFromString( str, &temp );
1165 *value += temp; /* Seconds */
1173 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1174 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1175 int result = -1, type = 0; long temp, temp2;
1177 if(**str != ':') return -1; // old params remain in force!
1179 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1180 if( NextIntegerFromString( str, &temp ) ) return -1;
1181 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1184 /* time only: incremental or sudden-death time control */
1185 if(**str == '+') { /* increment follows; read it */
1187 if(**str == '!') type = *(*str)++; // Bronstein TC
1188 if(result = NextIntegerFromString( str, &temp2)) return -1;
1189 *inc = temp2 * 1000;
1190 if(**str == '.') { // read fraction of increment
1191 char *start = ++(*str);
1192 if(result = NextIntegerFromString( str, &temp2)) return -1;
1194 while(start++ < *str) temp2 /= 10;
1198 *moves = 0; *tc = temp * 1000; *incType = type;
1202 (*str)++; /* classical time control */
1203 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1215 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1216 { /* [HGM] get time to add from the multi-session time-control string */
1217 int incType, moves=1; /* kludge to force reading of first session */
1218 long time, increment;
1221 if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1222 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1224 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1225 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1226 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1227 if(movenr == -1) return time; /* last move before new session */
1228 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1229 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1230 if(!moves) return increment; /* current session is incremental */
1231 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1232 } while(movenr >= -1); /* try again for next session */
1234 return 0; // no new time quota on this move
1238 ParseTimeControl (char *tc, float ti, int 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);
1718 EscapeExpand (char *p, char *q)
1719 { // [HGM] initstring: routine to shape up string arguments
1720 while(*p++ = *q++) if(p[-1] == '\\')
1722 case 'n': p[-1] = '\n'; break;
1723 case 'r': p[-1] = '\r'; break;
1724 case 't': p[-1] = '\t'; break;
1725 case '\\': p[-1] = '\\'; break;
1726 case 0: *p = 0; return;
1727 default: p[-1] = q[-1]; break;
1732 show_bytes (FILE *fp, char *buf, int count)
1735 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1736 fprintf(fp, "\\%03o", *buf & 0xff);
1745 /* Returns an errno value */
1747 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1749 char buf[8192], *p, *q, *buflim;
1750 int left, newcount, outcount;
1752 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1753 *appData.gateway != NULLCHAR) {
1754 if (appData.debugMode) {
1755 fprintf(debugFP, ">ICS: ");
1756 show_bytes(debugFP, message, count);
1757 fprintf(debugFP, "\n");
1759 return OutputToProcess(pr, message, count, outError);
1762 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1769 if (appData.debugMode) {
1770 fprintf(debugFP, ">ICS: ");
1771 show_bytes(debugFP, buf, newcount);
1772 fprintf(debugFP, "\n");
1774 outcount = OutputToProcess(pr, buf, newcount, outError);
1775 if (outcount < newcount) return -1; /* to be sure */
1782 } else if (((unsigned char) *p) == TN_IAC) {
1783 *q++ = (char) TN_IAC;
1790 if (appData.debugMode) {
1791 fprintf(debugFP, ">ICS: ");
1792 show_bytes(debugFP, buf, newcount);
1793 fprintf(debugFP, "\n");
1795 outcount = OutputToProcess(pr, buf, newcount, outError);
1796 if (outcount < newcount) return -1; /* to be sure */
1801 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1803 int outError, outCount;
1804 static int gotEof = 0;
1806 /* Pass data read from player on to ICS */
1809 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1810 if (outCount < count) {
1811 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1813 } else if (count < 0) {
1814 RemoveInputSource(isr);
1815 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1816 } else if (gotEof++ > 0) {
1817 RemoveInputSource(isr);
1818 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1824 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1825 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1826 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1827 SendToICS("date\n");
1828 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1831 /* added routine for printf style output to ics */
1833 ics_printf (char *format, ...)
1835 char buffer[MSG_SIZ];
1838 va_start(args, format);
1839 vsnprintf(buffer, sizeof(buffer), format, args);
1840 buffer[sizeof(buffer)-1] = '\0';
1848 int count, outCount, outError;
1850 if (icsPR == NoProc) return;
1853 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1854 if (outCount < count) {
1855 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1859 /* This is used for sending logon scripts to the ICS. Sending
1860 without a delay causes problems when using timestamp on ICC
1861 (at least on my machine). */
1863 SendToICSDelayed (char *s, long msdelay)
1865 int count, outCount, outError;
1867 if (icsPR == NoProc) return;
1870 if (appData.debugMode) {
1871 fprintf(debugFP, ">ICS: ");
1872 show_bytes(debugFP, s, count);
1873 fprintf(debugFP, "\n");
1875 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1877 if (outCount < count) {
1878 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1883 /* Remove all highlighting escape sequences in s
1884 Also deletes any suffix starting with '('
1887 StripHighlightAndTitle (char *s)
1889 static char retbuf[MSG_SIZ];
1892 while (*s != NULLCHAR) {
1893 while (*s == '\033') {
1894 while (*s != NULLCHAR && !isalpha(*s)) s++;
1895 if (*s != NULLCHAR) s++;
1897 while (*s != NULLCHAR && *s != '\033') {
1898 if (*s == '(' || *s == '[') {
1909 /* Remove all highlighting escape sequences in s */
1911 StripHighlight (char *s)
1913 static char retbuf[MSG_SIZ];
1916 while (*s != NULLCHAR) {
1917 while (*s == '\033') {
1918 while (*s != NULLCHAR && !isalpha(*s)) s++;
1919 if (*s != NULLCHAR) s++;
1921 while (*s != NULLCHAR && *s != '\033') {
1929 char *variantNames[] = VARIANT_NAMES;
1931 VariantName (VariantClass v)
1933 return variantNames[v];
1937 /* Identify a variant from the strings the chess servers use or the
1938 PGN Variant tag names we use. */
1940 StringToVariant (char *e)
1944 VariantClass v = VariantNormal;
1945 int i, found = FALSE;
1951 /* [HGM] skip over optional board-size prefixes */
1952 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1953 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1954 while( *e++ != '_');
1957 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1961 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1962 if (StrCaseStr(e, variantNames[i])) {
1963 v = (VariantClass) i;
1970 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1971 || StrCaseStr(e, "wild/fr")
1972 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1973 v = VariantFischeRandom;
1974 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1975 (i = 1, p = StrCaseStr(e, "w"))) {
1977 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1984 case 0: /* FICS only, actually */
1986 /* Castling legal even if K starts on d-file */
1987 v = VariantWildCastle;
1992 /* Castling illegal even if K & R happen to start in
1993 normal positions. */
1994 v = VariantNoCastle;
2007 /* Castling legal iff K & R start in normal positions */
2013 /* Special wilds for position setup; unclear what to do here */
2014 v = VariantLoadable;
2017 /* Bizarre ICC game */
2018 v = VariantTwoKings;
2021 v = VariantKriegspiel;
2027 v = VariantFischeRandom;
2030 v = VariantCrazyhouse;
2033 v = VariantBughouse;
2039 /* Not quite the same as FICS suicide! */
2040 v = VariantGiveaway;
2046 v = VariantShatranj;
2049 /* Temporary names for future ICC types. The name *will* change in
2050 the next xboard/WinBoard release after ICC defines it. */
2088 v = VariantCapablanca;
2091 v = VariantKnightmate;
2097 v = VariantCylinder;
2103 v = VariantCapaRandom;
2106 v = VariantBerolina;
2118 /* Found "wild" or "w" in the string but no number;
2119 must assume it's normal chess. */
2123 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2124 if( (len >= MSG_SIZ) && appData.debugMode )
2125 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2127 DisplayError(buf, 0);
2133 if (appData.debugMode) {
2134 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2135 e, wnum, VariantName(v));
2140 static int leftover_start = 0, leftover_len = 0;
2141 char star_match[STAR_MATCH_N][MSG_SIZ];
2143 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2144 advance *index beyond it, and set leftover_start to the new value of
2145 *index; else return FALSE. If pattern contains the character '*', it
2146 matches any sequence of characters not containing '\r', '\n', or the
2147 character following the '*' (if any), and the matched sequence(s) are
2148 copied into star_match.
2151 looking_at ( char *buf, int *index, char *pattern)
2153 char *bufp = &buf[*index], *patternp = pattern;
2155 char *matchp = star_match[0];
2158 if (*patternp == NULLCHAR) {
2159 *index = leftover_start = bufp - buf;
2163 if (*bufp == NULLCHAR) return FALSE;
2164 if (*patternp == '*') {
2165 if (*bufp == *(patternp + 1)) {
2167 matchp = star_match[++star_count];
2171 } else if (*bufp == '\n' || *bufp == '\r') {
2173 if (*patternp == NULLCHAR)
2178 *matchp++ = *bufp++;
2182 if (*patternp != *bufp) return FALSE;
2189 SendToPlayer (char *data, int length)
2191 int error, outCount;
2192 outCount = OutputToProcess(NoProc, data, length, &error);
2193 if (outCount < length) {
2194 DisplayFatalError(_("Error writing to display"), error, 1);
2199 PackHolding (char packed[], char *holding)
2209 switch (runlength) {
2220 sprintf(q, "%d", runlength);
2232 /* Telnet protocol requests from the front end */
2234 TelnetRequest (unsigned char ddww, unsigned char option)
2236 unsigned char msg[3];
2237 int outCount, outError;
2239 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2241 if (appData.debugMode) {
2242 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2258 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2267 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2270 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2275 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2277 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2284 if (!appData.icsActive) return;
2285 TelnetRequest(TN_DO, TN_ECHO);
2291 if (!appData.icsActive) return;
2292 TelnetRequest(TN_DONT, TN_ECHO);
2296 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2298 /* put the holdings sent to us by the server on the board holdings area */
2299 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2303 if(gameInfo.holdingsWidth < 2) return;
2304 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2305 return; // prevent overwriting by pre-board holdings
2307 if( (int)lowestPiece >= BlackPawn ) {
2310 holdingsStartRow = BOARD_HEIGHT-1;
2313 holdingsColumn = BOARD_WIDTH-1;
2314 countsColumn = BOARD_WIDTH-2;
2315 holdingsStartRow = 0;
2319 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2320 board[i][holdingsColumn] = EmptySquare;
2321 board[i][countsColumn] = (ChessSquare) 0;
2323 while( (p=*holdings++) != NULLCHAR ) {
2324 piece = CharToPiece( ToUpper(p) );
2325 if(piece == EmptySquare) continue;
2326 /*j = (int) piece - (int) WhitePawn;*/
2327 j = PieceToNumber(piece);
2328 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2329 if(j < 0) continue; /* should not happen */
2330 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2331 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2332 board[holdingsStartRow+j*direction][countsColumn]++;
2338 VariantSwitch (Board board, VariantClass newVariant)
2340 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2341 static Board oldBoard;
2343 startedFromPositionFile = FALSE;
2344 if(gameInfo.variant == newVariant) return;
2346 /* [HGM] This routine is called each time an assignment is made to
2347 * gameInfo.variant during a game, to make sure the board sizes
2348 * are set to match the new variant. If that means adding or deleting
2349 * holdings, we shift the playing board accordingly
2350 * This kludge is needed because in ICS observe mode, we get boards
2351 * of an ongoing game without knowing the variant, and learn about the
2352 * latter only later. This can be because of the move list we requested,
2353 * in which case the game history is refilled from the beginning anyway,
2354 * but also when receiving holdings of a crazyhouse game. In the latter
2355 * case we want to add those holdings to the already received position.
2359 if (appData.debugMode) {
2360 fprintf(debugFP, "Switch board from %s to %s\n",
2361 VariantName(gameInfo.variant), VariantName(newVariant));
2362 setbuf(debugFP, NULL);
2364 shuffleOpenings = 0; /* [HGM] shuffle */
2365 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2369 newWidth = 9; newHeight = 9;
2370 gameInfo.holdingsSize = 7;
2371 case VariantBughouse:
2372 case VariantCrazyhouse:
2373 newHoldingsWidth = 2; break;
2377 newHoldingsWidth = 2;
2378 gameInfo.holdingsSize = 8;
2381 case VariantCapablanca:
2382 case VariantCapaRandom:
2385 newHoldingsWidth = gameInfo.holdingsSize = 0;
2388 if(newWidth != gameInfo.boardWidth ||
2389 newHeight != gameInfo.boardHeight ||
2390 newHoldingsWidth != gameInfo.holdingsWidth ) {
2392 /* shift position to new playing area, if needed */
2393 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2394 for(i=0; i<BOARD_HEIGHT; i++)
2395 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2396 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2398 for(i=0; i<newHeight; i++) {
2399 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2400 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2402 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2403 for(i=0; i<BOARD_HEIGHT; i++)
2404 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2405 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2408 gameInfo.boardWidth = newWidth;
2409 gameInfo.boardHeight = newHeight;
2410 gameInfo.holdingsWidth = newHoldingsWidth;
2411 gameInfo.variant = newVariant;
2412 InitDrawingSizes(-2, 0);
2413 } else gameInfo.variant = newVariant;
2414 CopyBoard(oldBoard, board); // remember correctly formatted board
2415 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2416 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2419 static int loggedOn = FALSE;
2421 /*-- Game start info cache: --*/
2423 char gs_kind[MSG_SIZ];
2424 static char player1Name[128] = "";
2425 static char player2Name[128] = "";
2426 static char cont_seq[] = "\n\\ ";
2427 static int player1Rating = -1;
2428 static int player2Rating = -1;
2429 /*----------------------------*/
2431 ColorClass curColor = ColorNormal;
2432 int suppressKibitz = 0;
2435 Boolean soughtPending = FALSE;
2436 Boolean seekGraphUp;
2437 #define MAX_SEEK_ADS 200
2439 char *seekAdList[MAX_SEEK_ADS];
2440 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2441 float tcList[MAX_SEEK_ADS];
2442 char colorList[MAX_SEEK_ADS];
2443 int nrOfSeekAds = 0;
2444 int minRating = 1010, maxRating = 2800;
2445 int hMargin = 10, vMargin = 20, h, w;
2446 extern int squareSize, lineGap;
2451 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2452 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2453 if(r < minRating+100 && r >=0 ) r = minRating+100;
2454 if(r > maxRating) r = maxRating;
2455 if(tc < 1.) tc = 1.;
2456 if(tc > 95.) tc = 95.;
2457 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2458 y = ((double)r - minRating)/(maxRating - minRating)
2459 * (h-vMargin-squareSize/8-1) + vMargin;
2460 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2461 if(strstr(seekAdList[i], " u ")) color = 1;
2462 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2463 !strstr(seekAdList[i], "bullet") &&
2464 !strstr(seekAdList[i], "blitz") &&
2465 !strstr(seekAdList[i], "standard") ) color = 2;
2466 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2467 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2471 AddAd (char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2473 char buf[MSG_SIZ], *ext = "";
2474 VariantClass v = StringToVariant(type);
2475 if(strstr(type, "wild")) {
2476 ext = type + 4; // append wild number
2477 if(v == VariantFischeRandom) type = "chess960"; else
2478 if(v == VariantLoadable) type = "setup"; else
2479 type = VariantName(v);
2481 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2482 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2483 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2484 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2485 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2486 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2487 seekNrList[nrOfSeekAds] = nr;
2488 zList[nrOfSeekAds] = 0;
2489 seekAdList[nrOfSeekAds++] = StrSave(buf);
2490 if(plot) PlotSeekAd(nrOfSeekAds-1);
2495 EraseSeekDot (int i)
2497 int x = xList[i], y = yList[i], d=squareSize/4, k;
2498 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2499 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2500 // now replot every dot that overlapped
2501 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2502 int xx = xList[k], yy = yList[k];
2503 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2504 DrawSeekDot(xx, yy, colorList[k]);
2509 RemoveSeekAd (int nr)
2512 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2514 if(seekAdList[i]) free(seekAdList[i]);
2515 seekAdList[i] = seekAdList[--nrOfSeekAds];
2516 seekNrList[i] = seekNrList[nrOfSeekAds];
2517 ratingList[i] = ratingList[nrOfSeekAds];
2518 colorList[i] = colorList[nrOfSeekAds];
2519 tcList[i] = tcList[nrOfSeekAds];
2520 xList[i] = xList[nrOfSeekAds];
2521 yList[i] = yList[nrOfSeekAds];
2522 zList[i] = zList[nrOfSeekAds];
2523 seekAdList[nrOfSeekAds] = NULL;
2529 MatchSoughtLine (char *line)
2531 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2532 int nr, base, inc, u=0; char dummy;
2534 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2535 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2537 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2538 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2539 // match: compact and save the line
2540 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2550 if(!seekGraphUp) return FALSE;
2551 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2552 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2554 DrawSeekBackground(0, 0, w, h);
2555 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2556 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2557 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2558 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2560 DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2563 snprintf(buf, MSG_SIZ, "%d", i);
2564 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2567 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2568 for(i=1; i<100; i+=(i<10?1:5)) {
2569 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2570 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2571 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2573 snprintf(buf, MSG_SIZ, "%d", i);
2574 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2577 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2582 SeekGraphClick (ClickType click, int x, int y, int moving)
2584 static int lastDown = 0, displayed = 0, lastSecond;
2585 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2586 if(click == Release || moving) return FALSE;
2588 soughtPending = TRUE;
2589 SendToICS(ics_prefix);
2590 SendToICS("sought\n"); // should this be "sought all"?
2591 } else { // issue challenge based on clicked ad
2592 int dist = 10000; int i, closest = 0, second = 0;
2593 for(i=0; i<nrOfSeekAds; i++) {
2594 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2595 if(d < dist) { dist = d; closest = i; }
2596 second += (d - zList[i] < 120); // count in-range ads
2597 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2601 second = (second > 1);
2602 if(displayed != closest || second != lastSecond) {
2603 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2604 lastSecond = second; displayed = closest;
2606 if(click == Press) {
2607 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2610 } // on press 'hit', only show info
2611 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2612 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2613 SendToICS(ics_prefix);
2615 return TRUE; // let incoming board of started game pop down the graph
2616 } else if(click == Release) { // release 'miss' is ignored
2617 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2618 if(moving == 2) { // right up-click
2619 nrOfSeekAds = 0; // refresh graph
2620 soughtPending = TRUE;
2621 SendToICS(ics_prefix);
2622 SendToICS("sought\n"); // should this be "sought all"?
2625 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2626 // press miss or release hit 'pop down' seek graph
2627 seekGraphUp = FALSE;
2628 DrawPosition(TRUE, NULL);
2634 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2636 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2637 #define STARTED_NONE 0
2638 #define STARTED_MOVES 1
2639 #define STARTED_BOARD 2
2640 #define STARTED_OBSERVE 3
2641 #define STARTED_HOLDINGS 4
2642 #define STARTED_CHATTER 5
2643 #define STARTED_COMMENT 6
2644 #define STARTED_MOVES_NOHIDE 7
2646 static int started = STARTED_NONE;
2647 static char parse[20000];
2648 static int parse_pos = 0;
2649 static char buf[BUF_SIZE + 1];
2650 static int firstTime = TRUE, intfSet = FALSE;
2651 static ColorClass prevColor = ColorNormal;
2652 static int savingComment = FALSE;
2653 static int cmatch = 0; // continuation sequence match
2660 int backup; /* [DM] For zippy color lines */
2662 char talker[MSG_SIZ]; // [HGM] chat
2665 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2667 if (appData.debugMode) {
2669 fprintf(debugFP, "<ICS: ");
2670 show_bytes(debugFP, data, count);
2671 fprintf(debugFP, "\n");
2675 if (appData.debugMode) { int f = forwardMostMove;
2676 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2677 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2678 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2681 /* If last read ended with a partial line that we couldn't parse,
2682 prepend it to the new read and try again. */
2683 if (leftover_len > 0) {
2684 for (i=0; i<leftover_len; i++)
2685 buf[i] = buf[leftover_start + i];
2688 /* copy new characters into the buffer */
2689 bp = buf + leftover_len;
2690 buf_len=leftover_len;
2691 for (i=0; i<count; i++)
2694 if (data[i] == '\r')
2697 // join lines split by ICS?
2698 if (!appData.noJoin)
2701 Joining just consists of finding matches against the
2702 continuation sequence, and discarding that sequence
2703 if found instead of copying it. So, until a match
2704 fails, there's nothing to do since it might be the
2705 complete sequence, and thus, something we don't want
2708 if (data[i] == cont_seq[cmatch])
2711 if (cmatch == strlen(cont_seq))
2713 cmatch = 0; // complete match. just reset the counter
2716 it's possible for the ICS to not include the space
2717 at the end of the last word, making our [correct]
2718 join operation fuse two separate words. the server
2719 does this when the space occurs at the width setting.
2721 if (!buf_len || buf[buf_len-1] != ' ')
2732 match failed, so we have to copy what matched before
2733 falling through and copying this character. In reality,
2734 this will only ever be just the newline character, but
2735 it doesn't hurt to be precise.
2737 strncpy(bp, cont_seq, cmatch);
2749 buf[buf_len] = NULLCHAR;
2750 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2755 while (i < buf_len) {
2756 /* Deal with part of the TELNET option negotiation
2757 protocol. We refuse to do anything beyond the
2758 defaults, except that we allow the WILL ECHO option,
2759 which ICS uses to turn off password echoing when we are
2760 directly connected to it. We reject this option
2761 if localLineEditing mode is on (always on in xboard)
2762 and we are talking to port 23, which might be a real
2763 telnet server that will try to keep WILL ECHO on permanently.
2765 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2766 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2767 unsigned char option;
2769 switch ((unsigned char) buf[++i]) {
2771 if (appData.debugMode)
2772 fprintf(debugFP, "\n<WILL ");
2773 switch (option = (unsigned char) buf[++i]) {
2775 if (appData.debugMode)
2776 fprintf(debugFP, "ECHO ");
2777 /* Reply only if this is a change, according
2778 to the protocol rules. */
2779 if (remoteEchoOption) break;
2780 if (appData.localLineEditing &&
2781 atoi(appData.icsPort) == TN_PORT) {
2782 TelnetRequest(TN_DONT, TN_ECHO);
2785 TelnetRequest(TN_DO, TN_ECHO);
2786 remoteEchoOption = TRUE;
2790 if (appData.debugMode)
2791 fprintf(debugFP, "%d ", option);
2792 /* Whatever this is, we don't want it. */
2793 TelnetRequest(TN_DONT, option);
2798 if (appData.debugMode)
2799 fprintf(debugFP, "\n<WONT ");
2800 switch (option = (unsigned char) buf[++i]) {
2802 if (appData.debugMode)
2803 fprintf(debugFP, "ECHO ");
2804 /* Reply only if this is a change, according
2805 to the protocol rules. */
2806 if (!remoteEchoOption) break;
2808 TelnetRequest(TN_DONT, TN_ECHO);
2809 remoteEchoOption = FALSE;
2812 if (appData.debugMode)
2813 fprintf(debugFP, "%d ", (unsigned char) option);
2814 /* Whatever this is, it must already be turned
2815 off, because we never agree to turn on
2816 anything non-default, so according to the
2817 protocol rules, we don't reply. */
2822 if (appData.debugMode)
2823 fprintf(debugFP, "\n<DO ");
2824 switch (option = (unsigned char) buf[++i]) {
2826 /* Whatever this is, we refuse to do it. */
2827 if (appData.debugMode)
2828 fprintf(debugFP, "%d ", option);
2829 TelnetRequest(TN_WONT, option);
2834 if (appData.debugMode)
2835 fprintf(debugFP, "\n<DONT ");
2836 switch (option = (unsigned char) buf[++i]) {
2838 if (appData.debugMode)
2839 fprintf(debugFP, "%d ", option);
2840 /* Whatever this is, we are already not doing
2841 it, because we never agree to do anything
2842 non-default, so according to the protocol
2843 rules, we don't reply. */
2848 if (appData.debugMode)
2849 fprintf(debugFP, "\n<IAC ");
2850 /* Doubled IAC; pass it through */
2854 if (appData.debugMode)
2855 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2856 /* Drop all other telnet commands on the floor */
2859 if (oldi > next_out)
2860 SendToPlayer(&buf[next_out], oldi - next_out);
2866 /* OK, this at least will *usually* work */
2867 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2871 if (loggedOn && !intfSet) {
2872 if (ics_type == ICS_ICC) {
2873 snprintf(str, MSG_SIZ,
2874 "/set-quietly interface %s\n/set-quietly style 12\n",
2876 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2877 strcat(str, "/set-2 51 1\n/set seek 1\n");
2878 } else if (ics_type == ICS_CHESSNET) {
2879 snprintf(str, MSG_SIZ, "/style 12\n");
2881 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2882 strcat(str, programVersion);
2883 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2884 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2885 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2887 strcat(str, "$iset nohighlight 1\n");
2889 strcat(str, "$iset lock 1\n$style 12\n");
2892 NotifyFrontendLogin();
2896 if (started == STARTED_COMMENT) {
2897 /* Accumulate characters in comment */
2898 parse[parse_pos++] = buf[i];
2899 if (buf[i] == '\n') {
2900 parse[parse_pos] = NULLCHAR;
2901 if(chattingPartner>=0) {
2903 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2904 OutputChatMessage(chattingPartner, mess);
2905 chattingPartner = -1;
2906 next_out = i+1; // [HGM] suppress printing in ICS window
2908 if(!suppressKibitz) // [HGM] kibitz
2909 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2910 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2911 int nrDigit = 0, nrAlph = 0, j;
2912 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2913 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2914 parse[parse_pos] = NULLCHAR;
2915 // try to be smart: if it does not look like search info, it should go to
2916 // ICS interaction window after all, not to engine-output window.
2917 for(j=0; j<parse_pos; j++) { // count letters and digits
2918 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2919 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2920 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2922 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2923 int depth=0; float score;
2924 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2925 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2926 pvInfoList[forwardMostMove-1].depth = depth;
2927 pvInfoList[forwardMostMove-1].score = 100*score;
2929 OutputKibitz(suppressKibitz, parse);
2932 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2933 SendToPlayer(tmp, strlen(tmp));
2935 next_out = i+1; // [HGM] suppress printing in ICS window
2937 started = STARTED_NONE;
2939 /* Don't match patterns against characters in comment */
2944 if (started == STARTED_CHATTER) {
2945 if (buf[i] != '\n') {
2946 /* Don't match patterns against characters in chatter */
2950 started = STARTED_NONE;
2951 if(suppressKibitz) next_out = i+1;
2954 /* Kludge to deal with rcmd protocol */
2955 if (firstTime && looking_at(buf, &i, "\001*")) {
2956 DisplayFatalError(&buf[1], 0, 1);
2962 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2965 if (appData.debugMode)
2966 fprintf(debugFP, "ics_type %d\n", ics_type);
2969 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2970 ics_type = ICS_FICS;
2972 if (appData.debugMode)
2973 fprintf(debugFP, "ics_type %d\n", ics_type);
2976 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2977 ics_type = ICS_CHESSNET;
2979 if (appData.debugMode)
2980 fprintf(debugFP, "ics_type %d\n", ics_type);
2985 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2986 looking_at(buf, &i, "Logging you in as \"*\"") ||
2987 looking_at(buf, &i, "will be \"*\""))) {
2988 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2992 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2994 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2995 DisplayIcsInteractionTitle(buf);
2996 have_set_title = TRUE;
2999 /* skip finger notes */
3000 if (started == STARTED_NONE &&
3001 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3002 (buf[i] == '1' && buf[i+1] == '0')) &&
3003 buf[i+2] == ':' && buf[i+3] == ' ') {
3004 started = STARTED_CHATTER;
3010 // [HGM] seekgraph: recognize sought lines and end-of-sought message
3011 if(appData.seekGraph) {
3012 if(soughtPending && MatchSoughtLine(buf+i)) {
3013 i = strstr(buf+i, "rated") - buf;
3014 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3015 next_out = leftover_start = i;
3016 started = STARTED_CHATTER;
3017 suppressKibitz = TRUE;
3020 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3021 && looking_at(buf, &i, "* ads displayed")) {
3022 soughtPending = FALSE;
3027 if(appData.autoRefresh) {
3028 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3029 int s = (ics_type == ICS_ICC); // ICC format differs
3031 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3032 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3033 looking_at(buf, &i, "*% "); // eat prompt
3034 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3035 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3036 next_out = i; // suppress
3039 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3040 char *p = star_match[0];
3042 if(seekGraphUp) RemoveSeekAd(atoi(p));
3043 while(*p && *p++ != ' '); // next
3045 looking_at(buf, &i, "*% "); // eat prompt
3046 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3053 /* skip formula vars */
3054 if (started == STARTED_NONE &&
3055 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3056 started = STARTED_CHATTER;
3061 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3062 if (appData.autoKibitz && started == STARTED_NONE &&
3063 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3064 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3065 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3066 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3067 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3068 suppressKibitz = TRUE;
3069 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3071 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3072 && (gameMode == IcsPlayingWhite)) ||
3073 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3074 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3075 started = STARTED_CHATTER; // own kibitz we simply discard
3077 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3078 parse_pos = 0; parse[0] = NULLCHAR;
3079 savingComment = TRUE;
3080 suppressKibitz = gameMode != IcsObserving ? 2 :
3081 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3085 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3086 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3087 && atoi(star_match[0])) {
3088 // suppress the acknowledgements of our own autoKibitz
3090 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3091 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3092 SendToPlayer(star_match[0], strlen(star_match[0]));
3093 if(looking_at(buf, &i, "*% ")) // eat prompt
3094 suppressKibitz = FALSE;
3098 } // [HGM] kibitz: end of patch
3100 if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3102 // [HGM] chat: intercept tells by users for which we have an open chat window
3104 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3105 looking_at(buf, &i, "* whispers:") ||
3106 looking_at(buf, &i, "* kibitzes:") ||
3107 looking_at(buf, &i, "* shouts:") ||
3108 looking_at(buf, &i, "* c-shouts:") ||
3109 looking_at(buf, &i, "--> * ") ||
3110 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3111 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3112 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3113 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3115 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3116 chattingPartner = -1;
3118 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3119 for(p=0; p<MAX_CHAT; p++) {
3120 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3121 talker[0] = '['; strcat(talker, "] ");
3122 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3123 chattingPartner = p; break;
3126 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3127 for(p=0; p<MAX_CHAT; p++) {
3128 if(!strcmp("kibitzes", chatPartner[p])) {
3129 talker[0] = '['; strcat(talker, "] ");
3130 chattingPartner = p; break;
3133 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3134 for(p=0; p<MAX_CHAT; p++) {
3135 if(!strcmp("whispers", chatPartner[p])) {
3136 talker[0] = '['; strcat(talker, "] ");
3137 chattingPartner = p; break;
3140 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3141 if(buf[i-8] == '-' && buf[i-3] == 't')
3142 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3143 if(!strcmp("c-shouts", chatPartner[p])) {
3144 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3145 chattingPartner = p; break;
3148 if(chattingPartner < 0)
3149 for(p=0; p<MAX_CHAT; p++) {
3150 if(!strcmp("shouts", chatPartner[p])) {
3151 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3152 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3153 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3154 chattingPartner = p; break;
3158 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3159 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3160 talker[0] = 0; Colorize(ColorTell, FALSE);
3161 chattingPartner = p; break;
3163 if(chattingPartner<0) i = oldi; else {
3164 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3165 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3166 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3167 started = STARTED_COMMENT;
3168 parse_pos = 0; parse[0] = NULLCHAR;
3169 savingComment = 3 + chattingPartner; // counts as TRUE
3170 suppressKibitz = TRUE;
3173 } // [HGM] chat: end of patch
3176 if (appData.zippyTalk || appData.zippyPlay) {
3177 /* [DM] Backup address for color zippy lines */
3179 if (loggedOn == TRUE)
3180 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3181 (appData.zippyPlay && ZippyMatch(buf, &backup)));
3183 } // [DM] 'else { ' deleted
3185 /* Regular tells and says */
3186 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3187 looking_at(buf, &i, "* (your partner) tells you: ") ||
3188 looking_at(buf, &i, "* says: ") ||
3189 /* Don't color "message" or "messages" output */
3190 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3191 looking_at(buf, &i, "*. * at *:*: ") ||
3192 looking_at(buf, &i, "--* (*:*): ") ||
3193 /* Message notifications (same color as tells) */
3194 looking_at(buf, &i, "* has left a message ") ||
3195 looking_at(buf, &i, "* just sent you a message:\n") ||
3196 /* Whispers and kibitzes */
3197 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3198 looking_at(buf, &i, "* kibitzes: ") ||
3200 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3202 if (tkind == 1 && strchr(star_match[0], ':')) {
3203 /* Avoid "tells you:" spoofs in channels */
3206 if (star_match[0][0] == NULLCHAR ||
3207 strchr(star_match[0], ' ') ||
3208 (tkind == 3 && strchr(star_match[1], ' '))) {
3209 /* Reject bogus matches */
3212 if (appData.colorize) {
3213 if (oldi > next_out) {
3214 SendToPlayer(&buf[next_out], oldi - next_out);
3219 Colorize(ColorTell, FALSE);
3220 curColor = ColorTell;
3223 Colorize(ColorKibitz, FALSE);
3224 curColor = ColorKibitz;
3227 p = strrchr(star_match[1], '(');
3234 Colorize(ColorChannel1, FALSE);
3235 curColor = ColorChannel1;
3237 Colorize(ColorChannel, FALSE);
3238 curColor = ColorChannel;
3242 curColor = ColorNormal;
3246 if (started == STARTED_NONE && appData.autoComment &&
3247 (gameMode == IcsObserving ||
3248 gameMode == IcsPlayingWhite ||
3249 gameMode == IcsPlayingBlack)) {
3250 parse_pos = i - oldi;
3251 memcpy(parse, &buf[oldi], parse_pos);
3252 parse[parse_pos] = NULLCHAR;
3253 started = STARTED_COMMENT;
3254 savingComment = TRUE;
3256 started = STARTED_CHATTER;
3257 savingComment = FALSE;
3264 if (looking_at(buf, &i, "* s-shouts: ") ||
3265 looking_at(buf, &i, "* c-shouts: ")) {
3266 if (appData.colorize) {
3267 if (oldi > next_out) {
3268 SendToPlayer(&buf[next_out], oldi - next_out);
3271 Colorize(ColorSShout, FALSE);
3272 curColor = ColorSShout;
3275 started = STARTED_CHATTER;
3279 if (looking_at(buf, &i, "--->")) {
3284 if (looking_at(buf, &i, "* shouts: ") ||
3285 looking_at(buf, &i, "--> ")) {
3286 if (appData.colorize) {
3287 if (oldi > next_out) {
3288 SendToPlayer(&buf[next_out], oldi - next_out);
3291 Colorize(ColorShout, FALSE);
3292 curColor = ColorShout;
3295 started = STARTED_CHATTER;
3299 if (looking_at( buf, &i, "Challenge:")) {
3300 if (appData.colorize) {
3301 if (oldi > next_out) {
3302 SendToPlayer(&buf[next_out], oldi - next_out);
3305 Colorize(ColorChallenge, FALSE);
3306 curColor = ColorChallenge;
3312 if (looking_at(buf, &i, "* offers you") ||
3313 looking_at(buf, &i, "* offers to be") ||
3314 looking_at(buf, &i, "* would like to") ||
3315 looking_at(buf, &i, "* requests to") ||
3316 looking_at(buf, &i, "Your opponent offers") ||
3317 looking_at(buf, &i, "Your opponent requests")) {
3319 if (appData.colorize) {
3320 if (oldi > next_out) {
3321 SendToPlayer(&buf[next_out], oldi - next_out);
3324 Colorize(ColorRequest, FALSE);
3325 curColor = ColorRequest;
3330 if (looking_at(buf, &i, "* (*) seeking")) {
3331 if (appData.colorize) {
3332 if (oldi > next_out) {
3333 SendToPlayer(&buf[next_out], oldi - next_out);
3336 Colorize(ColorSeek, FALSE);
3337 curColor = ColorSeek;
3342 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3344 if (looking_at(buf, &i, "\\ ")) {
3345 if (prevColor != ColorNormal) {
3346 if (oldi > next_out) {
3347 SendToPlayer(&buf[next_out], oldi - next_out);
3350 Colorize(prevColor, TRUE);
3351 curColor = prevColor;
3353 if (savingComment) {
3354 parse_pos = i - oldi;
3355 memcpy(parse, &buf[oldi], parse_pos);
3356 parse[parse_pos] = NULLCHAR;
3357 started = STARTED_COMMENT;
3358 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3359 chattingPartner = savingComment - 3; // kludge to remember the box
3361 started = STARTED_CHATTER;
3366 if (looking_at(buf, &i, "Black Strength :") ||
3367 looking_at(buf, &i, "<<< style 10 board >>>") ||
3368 looking_at(buf, &i, "<10>") ||
3369 looking_at(buf, &i, "#@#")) {
3370 /* Wrong board style */
3372 SendToICS(ics_prefix);
3373 SendToICS("set style 12\n");
3374 SendToICS(ics_prefix);
3375 SendToICS("refresh\n");
3379 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3381 have_sent_ICS_logon = 1;
3385 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3386 (looking_at(buf, &i, "\n<12> ") ||
3387 looking_at(buf, &i, "<12> "))) {
3389 if (oldi > next_out) {
3390 SendToPlayer(&buf[next_out], oldi - next_out);
3393 started = STARTED_BOARD;
3398 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3399 looking_at(buf, &i, "<b1> ")) {
3400 if (oldi > next_out) {
3401 SendToPlayer(&buf[next_out], oldi - next_out);
3404 started = STARTED_HOLDINGS;
3409 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3411 /* Header for a move list -- first line */
3413 switch (ics_getting_history) {
3417 case BeginningOfGame:
3418 /* User typed "moves" or "oldmoves" while we
3419 were idle. Pretend we asked for these
3420 moves and soak them up so user can step
3421 through them and/or save them.
3424 gameMode = IcsObserving;
3427 ics_getting_history = H_GOT_UNREQ_HEADER;
3429 case EditGame: /*?*/
3430 case EditPosition: /*?*/
3431 /* Should above feature work in these modes too? */
3432 /* For now it doesn't */
3433 ics_getting_history = H_GOT_UNWANTED_HEADER;
3436 ics_getting_history = H_GOT_UNWANTED_HEADER;
3441 /* Is this the right one? */
3442 if (gameInfo.white && gameInfo.black &&
3443 strcmp(gameInfo.white, star_match[0]) == 0 &&
3444 strcmp(gameInfo.black, star_match[2]) == 0) {
3446 ics_getting_history = H_GOT_REQ_HEADER;
3449 case H_GOT_REQ_HEADER:
3450 case H_GOT_UNREQ_HEADER:
3451 case H_GOT_UNWANTED_HEADER:
3452 case H_GETTING_MOVES:
3453 /* Should not happen */
3454 DisplayError(_("Error gathering move list: two headers"), 0);
3455 ics_getting_history = H_FALSE;
3459 /* Save player ratings into gameInfo if needed */
3460 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3461 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3462 (gameInfo.whiteRating == -1 ||
3463 gameInfo.blackRating == -1)) {
3465 gameInfo.whiteRating = string_to_rating(star_match[1]);
3466 gameInfo.blackRating = string_to_rating(star_match[3]);
3467 if (appData.debugMode)
3468 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3469 gameInfo.whiteRating, gameInfo.blackRating);
3474 if (looking_at(buf, &i,
3475 "* * match, initial time: * minute*, increment: * second")) {
3476 /* Header for a move list -- second line */
3477 /* Initial board will follow if this is a wild game */
3478 if (gameInfo.event != NULL) free(gameInfo.event);
3479 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3480 gameInfo.event = StrSave(str);
3481 /* [HGM] we switched variant. Translate boards if needed. */
3482 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3486 if (looking_at(buf, &i, "Move ")) {
3487 /* Beginning of a move list */
3488 switch (ics_getting_history) {
3490 /* Normally should not happen */
3491 /* Maybe user hit reset while we were parsing */
3494 /* Happens if we are ignoring a move list that is not
3495 * the one we just requested. Common if the user
3496 * tries to observe two games without turning off
3499 case H_GETTING_MOVES:
3500 /* Should not happen */
3501 DisplayError(_("Error gathering move list: nested"), 0);
3502 ics_getting_history = H_FALSE;
3504 case H_GOT_REQ_HEADER:
3505 ics_getting_history = H_GETTING_MOVES;
3506 started = STARTED_MOVES;
3508 if (oldi > next_out) {
3509 SendToPlayer(&buf[next_out], oldi - next_out);
3512 case H_GOT_UNREQ_HEADER:
3513 ics_getting_history = H_GETTING_MOVES;
3514 started = STARTED_MOVES_NOHIDE;
3517 case H_GOT_UNWANTED_HEADER:
3518 ics_getting_history = H_FALSE;
3524 if (looking_at(buf, &i, "% ") ||
3525 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3526 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3527 if(soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3528 soughtPending = FALSE;
3532 if(suppressKibitz) next_out = i;
3533 savingComment = FALSE;
3537 case STARTED_MOVES_NOHIDE:
3538 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3539 parse[parse_pos + i - oldi] = NULLCHAR;
3540 ParseGameHistory(parse);
3542 if (appData.zippyPlay && first.initDone) {
3543 FeedMovesToProgram(&first, forwardMostMove);
3544 if (gameMode == IcsPlayingWhite) {
3545 if (WhiteOnMove(forwardMostMove)) {
3546 if (first.sendTime) {
3547 if (first.useColors) {
3548 SendToProgram("black\n", &first);
3550 SendTimeRemaining(&first, TRUE);
3552 if (first.useColors) {
3553 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3555 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3556 first.maybeThinking = TRUE;
3558 if (first.usePlayother) {
3559 if (first.sendTime) {
3560 SendTimeRemaining(&first, TRUE);
3562 SendToProgram("playother\n", &first);
3568 } else if (gameMode == IcsPlayingBlack) {
3569 if (!WhiteOnMove(forwardMostMove)) {
3570 if (first.sendTime) {
3571 if (first.useColors) {
3572 SendToProgram("white\n", &first);
3574 SendTimeRemaining(&first, FALSE);
3576 if (first.useColors) {
3577 SendToProgram("black\n", &first);
3579 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3580 first.maybeThinking = TRUE;
3582 if (first.usePlayother) {
3583 if (first.sendTime) {
3584 SendTimeRemaining(&first, FALSE);
3586 SendToProgram("playother\n", &first);
3595 if (gameMode == IcsObserving && ics_gamenum == -1) {
3596 /* Moves came from oldmoves or moves command
3597 while we weren't doing anything else.
3599 currentMove = forwardMostMove;
3600 ClearHighlights();/*!!could figure this out*/
3601 flipView = appData.flipView;
3602 DrawPosition(TRUE, boards[currentMove]);
3603 DisplayBothClocks();
3604 snprintf(str, MSG_SIZ, "%s %s %s",
3605 gameInfo.white, _("vs."), gameInfo.black);
3609 /* Moves were history of an active game */
3610 if (gameInfo.resultDetails != NULL) {
3611 free(gameInfo.resultDetails);
3612 gameInfo.resultDetails = NULL;
3615 HistorySet(parseList, backwardMostMove,
3616 forwardMostMove, currentMove-1);
3617 DisplayMove(currentMove - 1);
3618 if (started == STARTED_MOVES) next_out = i;
3619 started = STARTED_NONE;
3620 ics_getting_history = H_FALSE;
3623 case STARTED_OBSERVE:
3624 started = STARTED_NONE;
3625 SendToICS(ics_prefix);
3626 SendToICS("refresh\n");
3632 if(bookHit) { // [HGM] book: simulate book reply
3633 static char bookMove[MSG_SIZ]; // a bit generous?
3635 programStats.nodes = programStats.depth = programStats.time =
3636 programStats.score = programStats.got_only_move = 0;
3637 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3639 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3640 strcat(bookMove, bookHit);
3641 HandleMachineMove(bookMove, &first);
3646 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3647 started == STARTED_HOLDINGS ||
3648 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3649 /* Accumulate characters in move list or board */
3650 parse[parse_pos++] = buf[i];
3653 /* Start of game messages. Mostly we detect start of game
3654 when the first board image arrives. On some versions
3655 of the ICS, though, we need to do a "refresh" after starting
3656 to observe in order to get the current board right away. */
3657 if (looking_at(buf, &i, "Adding game * to observation list")) {
3658 started = STARTED_OBSERVE;
3662 /* Handle auto-observe */
3663 if (appData.autoObserve &&
3664 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3665 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3667 /* Choose the player that was highlighted, if any. */
3668 if (star_match[0][0] == '\033' ||
3669 star_match[1][0] != '\033') {
3670 player = star_match[0];
3672 player = star_match[2];
3674 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3675 ics_prefix, StripHighlightAndTitle(player));
3678 /* Save ratings from notify string */
3679 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3680 player1Rating = string_to_rating(star_match[1]);
3681 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3682 player2Rating = string_to_rating(star_match[3]);
3684 if (appData.debugMode)
3686 "Ratings from 'Game notification:' %s %d, %s %d\n",
3687 player1Name, player1Rating,
3688 player2Name, player2Rating);
3693 /* Deal with automatic examine mode after a game,
3694 and with IcsObserving -> IcsExamining transition */
3695 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3696 looking_at(buf, &i, "has made you an examiner of game *")) {
3698 int gamenum = atoi(star_match[0]);
3699 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3700 gamenum == ics_gamenum) {
3701 /* We were already playing or observing this game;
3702 no need to refetch history */
3703 gameMode = IcsExamining;
3705 pauseExamForwardMostMove = forwardMostMove;
3706 } else if (currentMove < forwardMostMove) {
3707 ForwardInner(forwardMostMove);
3710 /* I don't think this case really can happen */
3711 SendToICS(ics_prefix);
3712 SendToICS("refresh\n");
3717 /* Error messages */
3718 // if (ics_user_moved) {
3719 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3720 if (looking_at(buf, &i, "Illegal move") ||
3721 looking_at(buf, &i, "Not a legal move") ||
3722 looking_at(buf, &i, "Your king is in check") ||
3723 looking_at(buf, &i, "It isn't your turn") ||
3724 looking_at(buf, &i, "It is not your move")) {
3726 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3727 currentMove = forwardMostMove-1;
3728 DisplayMove(currentMove - 1); /* before DMError */
3729 DrawPosition(FALSE, boards[currentMove]);
3730 SwitchClocks(forwardMostMove-1); // [HGM] race
3731 DisplayBothClocks();
3733 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3739 if (looking_at(buf, &i, "still have time") ||
3740 looking_at(buf, &i, "not out of time") ||
3741 looking_at(buf, &i, "either player is out of time") ||
3742 looking_at(buf, &i, "has timeseal; checking")) {
3743 /* We must have called his flag a little too soon */
3744 whiteFlag = blackFlag = FALSE;
3748 if (looking_at(buf, &i, "added * seconds to") ||
3749 looking_at(buf, &i, "seconds were added to")) {
3750 /* Update the clocks */
3751 SendToICS(ics_prefix);
3752 SendToICS("refresh\n");
3756 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3757 ics_clock_paused = TRUE;
3762 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3763 ics_clock_paused = FALSE;
3768 /* Grab player ratings from the Creating: message.
3769 Note we have to check for the special case when
3770 the ICS inserts things like [white] or [black]. */
3771 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3772 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3774 0 player 1 name (not necessarily white)
3776 2 empty, white, or black (IGNORED)
3777 3 player 2 name (not necessarily black)
3780 The names/ratings are sorted out when the game
3781 actually starts (below).
3783 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3784 player1Rating = string_to_rating(star_match[1]);
3785 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3786 player2Rating = string_to_rating(star_match[4]);
3788 if (appData.debugMode)
3790 "Ratings from 'Creating:' %s %d, %s %d\n",
3791 player1Name, player1Rating,
3792 player2Name, player2Rating);
3797 /* Improved generic start/end-of-game messages */
3798 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3799 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3800 /* If tkind == 0: */
3801 /* star_match[0] is the game number */
3802 /* [1] is the white player's name */
3803 /* [2] is the black player's name */
3804 /* For end-of-game: */
3805 /* [3] is the reason for the game end */
3806 /* [4] is a PGN end game-token, preceded by " " */
3807 /* For start-of-game: */
3808 /* [3] begins with "Creating" or "Continuing" */
3809 /* [4] is " *" or empty (don't care). */
3810 int gamenum = atoi(star_match[0]);
3811 char *whitename, *blackname, *why, *endtoken;
3812 ChessMove endtype = EndOfFile;
3815 whitename = star_match[1];
3816 blackname = star_match[2];
3817 why = star_match[3];
3818 endtoken = star_match[4];
3820 whitename = star_match[1];
3821 blackname = star_match[3];
3822 why = star_match[5];
3823 endtoken = star_match[6];
3826 /* Game start messages */
3827 if (strncmp(why, "Creating ", 9) == 0 ||
3828 strncmp(why, "Continuing ", 11) == 0) {
3829 gs_gamenum = gamenum;
3830 safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3831 if(ics_gamenum == -1) // [HGM] only if we are not already involved in a game (because gin=1 sends us such messages)
3832 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3834 if (appData.zippyPlay) {
3835 ZippyGameStart(whitename, blackname);
3838 partnerBoardValid = FALSE; // [HGM] bughouse
3842 /* Game end messages */
3843 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3844 ics_gamenum != gamenum) {
3847 while (endtoken[0] == ' ') endtoken++;
3848 switch (endtoken[0]) {
3851 endtype = GameUnfinished;
3854 endtype = BlackWins;
3857 if (endtoken[1] == '/')
3858 endtype = GameIsDrawn;