2 * backend.c -- Common back end for X and Windows NT versions of
4 * Copyright 1991 by Digital Equipment Corporation, Maynard,
7 * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8 * 2007, 2008, 2009, 2010, 2011 Free Software Foundation, Inc.
10 * Enhancements Copyright 2005 Alessandro Scotti
12 * The following terms apply to Digital Equipment Corporation's copyright
14 * ------------------------------------------------------------------------
17 * Permission to use, copy, modify, and distribute this software and its
18 * documentation for any purpose and without fee is hereby granted,
19 * provided that the above copyright notice appear in all copies and that
20 * both that copyright notice and this permission notice appear in
21 * supporting documentation, and that the name of Digital not be
22 * used in advertising or publicity pertaining to distribution of the
23 * software without specific, written prior permission.
25 * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27 * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
32 * ------------------------------------------------------------------------
34 * The following terms apply to the enhanced version of XBoard
35 * distributed by the Free Software Foundation:
36 * ------------------------------------------------------------------------
38 * GNU XBoard is free software: you can redistribute it and/or modify
39 * it under the terms of the GNU General Public License as published by
40 * the Free Software Foundation, either version 3 of the License, or (at
41 * your option) any later version.
43 * GNU XBoard is distributed in the hope that it will be useful, but
44 * WITHOUT ANY WARRANTY; without even the implied warranty of
45 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46 * General Public License for more details.
48 * You should have received a copy of the GNU General Public License
49 * along with this program. If not, see http://www.gnu.org/licenses/. *
51 *------------------------------------------------------------------------
52 ** See the file ChangeLog for a revision history. */
54 /* [AS] Also useful here for debugging */
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
60 int flock(int f, int code);
66 #define DoSleep( n ) if( (n) >= 0) sleep(n)
77 #include <sys/types.h>
86 #else /* not STDC_HEADERS */
89 # else /* not HAVE_STRING_H */
91 # endif /* not HAVE_STRING_H */
92 #endif /* not STDC_HEADERS */
95 # include <sys/fcntl.h>
96 #else /* not HAVE_SYS_FCNTL_H */
99 # endif /* HAVE_FCNTL_H */
100 #endif /* not HAVE_SYS_FCNTL_H */
102 #if TIME_WITH_SYS_TIME
103 # include <sys/time.h>
107 # include <sys/time.h>
113 #if defined(_amigados) && !defined(__GNUC__)
118 extern int gettimeofday(struct timeval *, struct timezone *);
126 #include "frontend.h"
133 #include "backendz.h"
137 # define _(s) gettext (s)
138 # define N_(s) gettext_noop (s)
139 # define T_(s) gettext(s)
152 /* A point in time */
154 long sec; /* Assuming this is >= 32 bits */
155 int ms; /* Assuming this is >= 16 bits */
158 int establish P((void));
159 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
160 char *buf, int count, int error));
161 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
162 char *buf, int count, int error));
163 void ics_printf P((char *format, ...));
164 void SendToICS P((char *s));
165 void SendToICSDelayed P((char *s, long msdelay));
166 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
167 void HandleMachineMove P((char *message, ChessProgramState *cps));
168 int AutoPlayOneMove P((void));
169 int LoadGameOneMove P((ChessMove readAhead));
170 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
171 int LoadPositionFromFile P((char *filename, int n, char *title));
172 int SavePositionToFile P((char *filename));
173 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
175 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
176 void ShowMove P((int fromX, int fromY, int toX, int toY));
177 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
178 /*char*/int promoChar));
179 void BackwardInner P((int target));
180 void ForwardInner P((int target));
181 int Adjudicate P((ChessProgramState *cps));
182 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
183 void EditPositionDone P((Boolean fakeRights));
184 void PrintOpponents P((FILE *fp));
185 void PrintPosition P((FILE *fp, int move));
186 void StartChessProgram P((ChessProgramState *cps));
187 void SendToProgram P((char *message, ChessProgramState *cps));
188 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
189 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
190 char *buf, int count, int error));
191 void SendTimeControl P((ChessProgramState *cps,
192 int mps, long tc, int inc, int sd, int st));
193 char *TimeControlTagValue P((void));
194 void Attention P((ChessProgramState *cps));
195 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
196 int ResurrectChessProgram P((void));
197 void DisplayComment P((int moveNumber, char *text));
198 void DisplayMove P((int moveNumber));
200 void ParseGameHistory P((char *game));
201 void ParseBoard12 P((char *string));
202 void KeepAlive P((void));
203 void StartClocks P((void));
204 void SwitchClocks P((int nr));
205 void StopClocks P((void));
206 void ResetClocks P((void));
207 char *PGNDate P((void));
208 void SetGameInfo P((void));
209 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
210 int RegisterMove P((void));
211 void MakeRegisteredMove P((void));
212 void TruncateGame P((void));
213 int looking_at P((char *, int *, char *));
214 void CopyPlayerNameIntoFileName P((char **, char *));
215 char *SavePart P((char *));
216 int SaveGameOldStyle P((FILE *));
217 int SaveGamePGN P((FILE *));
218 void GetTimeMark P((TimeMark *));
219 long SubtractTimeMarks P((TimeMark *, TimeMark *));
220 int CheckFlags P((void));
221 long NextTickLength P((long));
222 void CheckTimeControl P((void));
223 void show_bytes P((FILE *, char *, int));
224 int string_to_rating P((char *str));
225 void ParseFeatures P((char* args, ChessProgramState *cps));
226 void InitBackEnd3 P((void));
227 void FeatureDone P((ChessProgramState* cps, int val));
228 void InitChessProgram P((ChessProgramState *cps, int setup));
229 void OutputKibitz(int window, char *text);
230 int PerpetualChase(int first, int last);
231 int EngineOutputIsUp();
232 void InitDrawingSizes(int x, int y);
233 void NextMatchGame P((void));
234 int NextTourneyGame P((int nr, int *swap));
235 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
236 FILE *WriteTourneyFile P((char *results));
239 extern void ConsoleCreate();
242 ChessProgramState *WhitePlayer();
243 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
244 int VerifyDisplayMode P(());
246 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
247 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
248 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
249 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
250 void ics_update_width P((int new_width));
251 extern char installDir[MSG_SIZ];
252 VariantClass startVariant; /* [HGM] nicks: initial variant */
255 extern int tinyLayout, smallLayout;
256 ChessProgramStats programStats;
257 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
259 static int exiting = 0; /* [HGM] moved to top */
260 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
261 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
262 Board partnerBoard; /* [HGM] bughouse: for peeking at partner game */
263 int partnerHighlight[2];
264 Boolean partnerBoardValid = 0;
265 char partnerStatus[MSG_SIZ];
267 Boolean originalFlip;
268 Boolean twoBoards = 0;
269 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
270 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
271 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
272 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
273 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
274 int opponentKibitzes;
275 int lastSavedGame; /* [HGM] save: ID of game */
276 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
277 extern int chatCount;
279 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
280 char lastMsg[MSG_SIZ];
281 ChessSquare pieceSweep = EmptySquare;
282 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
283 int promoDefaultAltered;
285 /* States for ics_getting_history */
287 #define H_REQUESTED 1
288 #define H_GOT_REQ_HEADER 2
289 #define H_GOT_UNREQ_HEADER 3
290 #define H_GETTING_MOVES 4
291 #define H_GOT_UNWANTED_HEADER 5
293 /* whosays values for GameEnds */
302 /* Maximum number of games in a cmail message */
303 #define CMAIL_MAX_GAMES 20
305 /* Different types of move when calling RegisterMove */
307 #define CMAIL_RESIGN 1
309 #define CMAIL_ACCEPT 3
311 /* Different types of result to remember for each game */
312 #define CMAIL_NOT_RESULT 0
313 #define CMAIL_OLD_RESULT 1
314 #define CMAIL_NEW_RESULT 2
316 /* Telnet protocol constants */
327 safeStrCpy( char *dst, const char *src, size_t count )
330 assert( dst != NULL );
331 assert( src != NULL );
334 for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
335 if( i == count && dst[count-1] != NULLCHAR)
337 dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
338 if(appData.debugMode)
339 fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
345 /* Some compiler can't cast u64 to double
346 * This function do the job for us:
348 * We use the highest bit for cast, this only
349 * works if the highest bit is not
350 * in use (This should not happen)
352 * We used this for all compiler
355 u64ToDouble(u64 value)
358 u64 tmp = value & u64Const(0x7fffffffffffffff);
359 r = (double)(s64)tmp;
360 if (value & u64Const(0x8000000000000000))
361 r += 9.2233720368547758080e18; /* 2^63 */
365 /* Fake up flags for now, as we aren't keeping track of castling
366 availability yet. [HGM] Change of logic: the flag now only
367 indicates the type of castlings allowed by the rule of the game.
368 The actual rights themselves are maintained in the array
369 castlingRights, as part of the game history, and are not probed
375 int flags = F_ALL_CASTLE_OK;
376 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
377 switch (gameInfo.variant) {
379 flags &= ~F_ALL_CASTLE_OK;
380 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
381 flags |= F_IGNORE_CHECK;
383 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
386 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
388 case VariantKriegspiel:
389 flags |= F_KRIEGSPIEL_CAPTURE;
391 case VariantCapaRandom:
392 case VariantFischeRandom:
393 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
394 case VariantNoCastle:
395 case VariantShatranj:
398 flags &= ~F_ALL_CASTLE_OK;
406 FILE *gameFileFP, *debugFP;
409 [AS] Note: sometimes, the sscanf() function is used to parse the input
410 into a fixed-size buffer. Because of this, we must be prepared to
411 receive strings as long as the size of the input buffer, which is currently
412 set to 4K for Windows and 8K for the rest.
413 So, we must either allocate sufficiently large buffers here, or
414 reduce the size of the input buffer in the input reading part.
417 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
418 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
419 char thinkOutput1[MSG_SIZ*10];
421 ChessProgramState first, second, pairing;
423 /* premove variables */
426 int premoveFromX = 0;
427 int premoveFromY = 0;
428 int premovePromoChar = 0;
430 Boolean alarmSounded;
431 /* end premove variables */
433 char *ics_prefix = "$";
434 int ics_type = ICS_GENERIC;
436 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
437 int pauseExamForwardMostMove = 0;
438 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
439 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
440 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
441 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
442 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
443 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
444 int whiteFlag = FALSE, blackFlag = FALSE;
445 int userOfferedDraw = FALSE;
446 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
447 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
448 int cmailMoveType[CMAIL_MAX_GAMES];
449 long ics_clock_paused = 0;
450 ProcRef icsPR = NoProc, cmailPR = NoProc;
451 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
452 GameMode gameMode = BeginningOfGame;
453 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
454 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
455 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
456 int hiddenThinkOutputState = 0; /* [AS] */
457 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
458 int adjudicateLossPlies = 6;
459 char white_holding[64], black_holding[64];
460 TimeMark lastNodeCountTime;
461 long lastNodeCount=0;
462 int shiftKey; // [HGM] set by mouse handler
464 int have_sent_ICS_logon = 0;
466 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
467 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
468 long timeControl_2; /* [AS] Allow separate time controls */
469 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
470 long timeRemaining[2][MAX_MOVES];
471 int matchGame = 0, nextGame = 0, roundNr = 0;
472 Boolean waitingForGame = FALSE;
473 TimeMark programStartTime, pauseStart;
474 char ics_handle[MSG_SIZ];
475 int have_set_title = 0;
477 /* animateTraining preserves the state of appData.animate
478 * when Training mode is activated. This allows the
479 * response to be animated when appData.animate == TRUE and
480 * appData.animateDragging == TRUE.
482 Boolean animateTraining;
488 Board boards[MAX_MOVES];
489 /* [HGM] Following 7 needed for accurate legality tests: */
490 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
491 signed char initialRights[BOARD_FILES];
492 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
493 int initialRulePlies, FENrulePlies;
494 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
497 int mute; // mute all sounds
499 // [HGM] vari: next 12 to save and restore variations
500 #define MAX_VARIATIONS 10
501 int framePtr = MAX_MOVES-1; // points to free stack entry
503 int savedFirst[MAX_VARIATIONS];
504 int savedLast[MAX_VARIATIONS];
505 int savedFramePtr[MAX_VARIATIONS];
506 char *savedDetails[MAX_VARIATIONS];
507 ChessMove savedResult[MAX_VARIATIONS];
509 void PushTail P((int firstMove, int lastMove));
510 Boolean PopTail P((Boolean annotate));
511 void PushInner P((int firstMove, int lastMove));
512 void PopInner P((Boolean annotate));
513 void CleanupTail P((void));
515 ChessSquare FIDEArray[2][BOARD_FILES] = {
516 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
517 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
518 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
519 BlackKing, BlackBishop, BlackKnight, BlackRook }
522 ChessSquare twoKingsArray[2][BOARD_FILES] = {
523 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
524 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
525 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
526 BlackKing, BlackKing, BlackKnight, BlackRook }
529 ChessSquare KnightmateArray[2][BOARD_FILES] = {
530 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
531 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
532 { BlackRook, BlackMan, BlackBishop, BlackQueen,
533 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
536 ChessSquare SpartanArray[2][BOARD_FILES] = {
537 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
538 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
539 { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
540 BlackDragon, BlackKing, BlackAngel, BlackAlfil }
543 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
544 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
545 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
546 { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
547 BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
550 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
551 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
552 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
553 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
554 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
557 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
558 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
559 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
560 { BlackRook, BlackKnight, BlackMan, BlackFerz,
561 BlackKing, BlackMan, BlackKnight, BlackRook }
565 #if (BOARD_FILES>=10)
566 ChessSquare ShogiArray[2][BOARD_FILES] = {
567 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
568 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
569 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
570 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
573 ChessSquare XiangqiArray[2][BOARD_FILES] = {
574 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
575 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
576 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
577 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
580 ChessSquare CapablancaArray[2][BOARD_FILES] = {
581 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
582 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
583 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
584 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
587 ChessSquare GreatArray[2][BOARD_FILES] = {
588 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
589 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
590 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
591 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
594 ChessSquare JanusArray[2][BOARD_FILES] = {
595 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
596 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
597 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
598 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
602 ChessSquare GothicArray[2][BOARD_FILES] = {
603 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
604 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
605 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
606 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
609 #define GothicArray CapablancaArray
613 ChessSquare FalconArray[2][BOARD_FILES] = {
614 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
615 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
616 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
617 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
620 #define FalconArray CapablancaArray
623 #else // !(BOARD_FILES>=10)
624 #define XiangqiPosition FIDEArray
625 #define CapablancaArray FIDEArray
626 #define GothicArray FIDEArray
627 #define GreatArray FIDEArray
628 #endif // !(BOARD_FILES>=10)
630 #if (BOARD_FILES>=12)
631 ChessSquare CourierArray[2][BOARD_FILES] = {
632 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
633 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
634 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
635 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
637 #else // !(BOARD_FILES>=12)
638 #define CourierArray CapablancaArray
639 #endif // !(BOARD_FILES>=12)
642 Board initialPosition;
645 /* Convert str to a rating. Checks for special cases of "----",
647 "++++", etc. Also strips ()'s */
649 string_to_rating(str)
652 while(*str && !isdigit(*str)) ++str;
654 return 0; /* One of the special "no rating" cases */
662 /* Init programStats */
663 programStats.movelist[0] = 0;
664 programStats.depth = 0;
665 programStats.nr_moves = 0;
666 programStats.moves_left = 0;
667 programStats.nodes = 0;
668 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
669 programStats.score = 0;
670 programStats.got_only_move = 0;
671 programStats.got_fail = 0;
672 programStats.line_is_book = 0;
677 { // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
678 if (appData.firstPlaysBlack) {
679 first.twoMachinesColor = "black\n";
680 second.twoMachinesColor = "white\n";
682 first.twoMachinesColor = "white\n";
683 second.twoMachinesColor = "black\n";
686 first.other = &second;
687 second.other = &first;
690 if(appData.timeOddsMode) {
691 norm = appData.timeOdds[0];
692 if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
694 first.timeOdds = appData.timeOdds[0]/norm;
695 second.timeOdds = appData.timeOdds[1]/norm;
698 if(programVersion) free(programVersion);
699 if (appData.noChessProgram) {
700 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
701 sprintf(programVersion, "%s", PACKAGE_STRING);
703 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
704 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
705 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
710 UnloadEngine(ChessProgramState *cps)
712 /* Kill off first chess program */
713 if (cps->isr != NULL)
714 RemoveInputSource(cps->isr);
717 if (cps->pr != NoProc) {
719 DoSleep( appData.delayBeforeQuit );
720 SendToProgram("quit\n", cps);
721 DoSleep( appData.delayAfterQuit );
722 DestroyChildProcess(cps->pr, cps->useSigterm);
725 if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
729 ClearOptions(ChessProgramState *cps)
732 cps->nrOptions = cps->comboCnt = 0;
733 for(i=0; i<MAX_OPTIONS; i++) {
734 cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
735 cps->option[i].textValue = 0;
739 char *engineNames[] = {
745 InitEngine(ChessProgramState *cps, int n)
746 { // [HGM] all engine initialiation put in a function that does one engine
750 cps->which = engineNames[n];
751 cps->maybeThinking = FALSE;
755 cps->sendDrawOffers = 1;
757 cps->program = appData.chessProgram[n];
758 cps->host = appData.host[n];
759 cps->dir = appData.directory[n];
760 cps->initString = appData.engInitString[n];
761 cps->computerString = appData.computerString[n];
762 cps->useSigint = TRUE;
763 cps->useSigterm = TRUE;
764 cps->reuse = appData.reuse[n];
765 cps->nps = appData.NPS[n]; // [HGM] nps: copy nodes per second
766 cps->useSetboard = FALSE;
768 cps->usePing = FALSE;
771 cps->usePlayother = FALSE;
772 cps->useColors = TRUE;
773 cps->useUsermove = FALSE;
774 cps->sendICS = FALSE;
775 cps->sendName = appData.icsActive;
776 cps->sdKludge = FALSE;
777 cps->stKludge = FALSE;
778 TidyProgramName(cps->program, cps->host, cps->tidy);
780 safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
781 cps->analysisSupport = 2; /* detect */
782 cps->analyzing = FALSE;
783 cps->initDone = FALSE;
785 /* New features added by Tord: */
786 cps->useFEN960 = FALSE;
787 cps->useOOCastle = TRUE;
788 /* End of new features added by Tord. */
789 cps->fenOverride = appData.fenOverride[n];
791 /* [HGM] time odds: set factor for each machine */
792 cps->timeOdds = appData.timeOdds[n];
794 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
795 cps->accumulateTC = appData.accumulateTC[n];
796 cps->maxNrOfSessions = 1;
801 cps->supportsNPS = UNKNOWN;
802 cps->memSize = FALSE;
803 cps->maxCores = FALSE;
804 cps->egtFormats[0] = NULLCHAR;
807 cps->optionSettings = appData.engOptions[n];
809 cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
810 cps->isUCI = appData.isUCI[n]; /* [AS] */
811 cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
813 if (appData.protocolVersion[n] > PROTOVER
814 || appData.protocolVersion[n] < 1)
819 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
820 appData.protocolVersion[n]);
821 if( (len > MSG_SIZ) && appData.debugMode )
822 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
824 DisplayFatalError(buf, 0, 2);
828 cps->protocolVersion = appData.protocolVersion[n];
831 InitEngineUCI( installDir, cps ); // [HGM] moved here from winboard.c, to make available in xboard
834 ChessProgramState *savCps;
840 if(WaitForEngine(savCps, LoadEngine)) return;
841 CommonEngineInit(); // recalculate time odds
842 if(gameInfo.variant != StringToVariant(appData.variant)) {
843 // we changed variant when loading the engine; this forces us to reset
844 Reset(TRUE, savCps != &first);
845 EditGameEvent(); // for consistency with other path, as Reset changes mode
847 InitChessProgram(savCps, FALSE);
848 SendToProgram("force\n", savCps);
849 DisplayMessage("", "");
850 if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
851 for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
857 ReplaceEngine(ChessProgramState *cps, int n)
861 appData.noChessProgram = FALSE;
862 appData.clockMode = TRUE;
864 if(n) return; // only startup first engine immediately; second can wait
865 savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
869 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
870 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
872 static char resetOptions[] =
873 "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
874 "-firstOptions \"\" -firstNPS -1 -fn \"\"";
877 Load(ChessProgramState *cps, int i)
879 char *p, *q, buf[MSG_SIZ], command[MSG_SIZ];
880 if(engineLine[0]) { // an engine was selected from the combo box
881 snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
882 SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
883 ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL;
884 ParseArgsFromString(buf);
886 ReplaceEngine(cps, i);
890 while(q = strchr(p, SLASH)) p = q+1;
891 if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
892 if(engineDir[0] != NULLCHAR)
893 appData.directory[i] = engineDir;
894 else if(p != engineName) { // derive directory from engine path, when not given
896 appData.directory[i] = strdup(engineName);
898 } else appData.directory[i] = ".";
900 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);
911 q = firstChessProgramNames;
912 if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
913 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "\"%s\" -fd \"%s\"%s%s%s%s%s%s%s%s\n", p, appData.directory[i],
914 useNick ? " -fn \"" : "",
915 useNick ? nickName : "",
917 v1 ? " -firstProtocolVersion 1" : "",
918 hasBook ? "" : " -fNoOwnBookUCI",
919 isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
920 storeVariant ? " -variant " : "",
921 storeVariant ? VariantName(gameInfo.variant) : "");
922 firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
923 snprintf(firstChessProgramNames, len, "%s%s", q, buf);
926 ReplaceEngine(cps, i);
932 int matched, min, sec;
934 * Parse timeControl resource
936 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
937 appData.movesPerSession)) {
939 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
940 DisplayFatalError(buf, 0, 2);
944 * Parse searchTime resource
946 if (*appData.searchTime != NULLCHAR) {
947 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
949 searchTime = min * 60;
950 } else if (matched == 2) {
951 searchTime = min * 60 + sec;
954 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
955 DisplayFatalError(buf, 0, 2);
964 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
965 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
967 GetTimeMark(&programStartTime);
968 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
969 pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
972 programStats.ok_to_send = 1;
973 programStats.seen_stat = 0;
976 * Initialize game list
982 * Internet chess server status
984 if (appData.icsActive) {
985 appData.matchMode = FALSE;
986 appData.matchGames = 0;
988 appData.noChessProgram = !appData.zippyPlay;
990 appData.zippyPlay = FALSE;
991 appData.zippyTalk = FALSE;
992 appData.noChessProgram = TRUE;
994 if (*appData.icsHelper != NULLCHAR) {
995 appData.useTelnet = TRUE;
996 appData.telnetProgram = appData.icsHelper;
999 appData.zippyTalk = appData.zippyPlay = FALSE;
1002 /* [AS] Initialize pv info list [HGM] and game state */
1006 for( i=0; i<=framePtr; i++ ) {
1007 pvInfoList[i].depth = -1;
1008 boards[i][EP_STATUS] = EP_NONE;
1009 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1015 /* [AS] Adjudication threshold */
1016 adjudicateLossThreshold = appData.adjudicateLossThreshold;
1018 InitEngine(&first, 0);
1019 InitEngine(&second, 1);
1022 pairing.which = "pairing"; // pairing engine
1023 pairing.pr = NoProc;
1025 pairing.program = appData.pairingEngine;
1026 pairing.host = "localhost";
1029 if (appData.icsActive) {
1030 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
1031 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1032 appData.clockMode = FALSE;
1033 first.sendTime = second.sendTime = 0;
1037 /* Override some settings from environment variables, for backward
1038 compatibility. Unfortunately it's not feasible to have the env
1039 vars just set defaults, at least in xboard. Ugh.
1041 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1046 if (!appData.icsActive) {
1050 /* Check for variants that are supported only in ICS mode,
1051 or not at all. Some that are accepted here nevertheless
1052 have bugs; see comments below.
1054 VariantClass variant = StringToVariant(appData.variant);
1056 case VariantBughouse: /* need four players and two boards */
1057 case VariantKriegspiel: /* need to hide pieces and move details */
1058 /* case VariantFischeRandom: (Fabien: moved below) */
1059 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1060 if( (len > MSG_SIZ) && appData.debugMode )
1061 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1063 DisplayFatalError(buf, 0, 2);
1066 case VariantUnknown:
1067 case VariantLoadable:
1077 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1078 if( (len > MSG_SIZ) && appData.debugMode )
1079 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1081 DisplayFatalError(buf, 0, 2);
1084 case VariantXiangqi: /* [HGM] repetition rules not implemented */
1085 case VariantFairy: /* [HGM] TestLegality definitely off! */
1086 case VariantGothic: /* [HGM] should work */
1087 case VariantCapablanca: /* [HGM] should work */
1088 case VariantCourier: /* [HGM] initial forced moves not implemented */
1089 case VariantShogi: /* [HGM] could still mate with pawn drop */
1090 case VariantKnightmate: /* [HGM] should work */
1091 case VariantCylinder: /* [HGM] untested */
1092 case VariantFalcon: /* [HGM] untested */
1093 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1094 offboard interposition not understood */
1095 case VariantNormal: /* definitely works! */
1096 case VariantWildCastle: /* pieces not automatically shuffled */
1097 case VariantNoCastle: /* pieces not automatically shuffled */
1098 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1099 case VariantLosers: /* should work except for win condition,
1100 and doesn't know captures are mandatory */
1101 case VariantSuicide: /* should work except for win condition,
1102 and doesn't know captures are mandatory */
1103 case VariantGiveaway: /* should work except for win condition,
1104 and doesn't know captures are mandatory */
1105 case VariantTwoKings: /* should work */
1106 case VariantAtomic: /* should work except for win condition */
1107 case Variant3Check: /* should work except for win condition */
1108 case VariantShatranj: /* should work except for all win conditions */
1109 case VariantMakruk: /* should work except for daw countdown */
1110 case VariantBerolina: /* might work if TestLegality is off */
1111 case VariantCapaRandom: /* should work */
1112 case VariantJanus: /* should work */
1113 case VariantSuper: /* experimental */
1114 case VariantGreat: /* experimental, requires legality testing to be off */
1115 case VariantSChess: /* S-Chess, should work */
1116 case VariantSpartan: /* should work */
1123 int NextIntegerFromString( char ** str, long * value )
1128 while( *s == ' ' || *s == '\t' ) {
1134 if( *s >= '0' && *s <= '9' ) {
1135 while( *s >= '0' && *s <= '9' ) {
1136 *value = *value * 10 + (*s - '0');
1148 int NextTimeControlFromString( char ** str, long * value )
1151 int result = NextIntegerFromString( str, &temp );
1154 *value = temp * 60; /* Minutes */
1155 if( **str == ':' ) {
1157 result = NextIntegerFromString( str, &temp );
1158 *value += temp; /* Seconds */
1165 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1166 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1167 int result = -1, type = 0; long temp, temp2;
1169 if(**str != ':') return -1; // old params remain in force!
1171 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1172 if( NextIntegerFromString( str, &temp ) ) return -1;
1173 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1176 /* time only: incremental or sudden-death time control */
1177 if(**str == '+') { /* increment follows; read it */
1179 if(**str == '!') type = *(*str)++; // Bronstein TC
1180 if(result = NextIntegerFromString( str, &temp2)) return -1;
1181 *inc = temp2 * 1000;
1182 if(**str == '.') { // read fraction of increment
1183 char *start = ++(*str);
1184 if(result = NextIntegerFromString( str, &temp2)) return -1;
1186 while(start++ < *str) temp2 /= 10;
1190 *moves = 0; *tc = temp * 1000; *incType = type;
1194 (*str)++; /* classical time control */
1195 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1206 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1207 { /* [HGM] get time to add from the multi-session time-control string */
1208 int incType, moves=1; /* kludge to force reading of first session */
1209 long time, increment;
1212 if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1213 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1215 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1216 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1217 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1218 if(movenr == -1) return time; /* last move before new session */
1219 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1220 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1221 if(!moves) return increment; /* current session is incremental */
1222 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1223 } while(movenr >= -1); /* try again for next session */
1225 return 0; // no new time quota on this move
1229 ParseTimeControl(tc, ti, mps)
1236 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1239 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1240 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1241 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1245 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1247 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1250 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1252 snprintf(buf, MSG_SIZ, ":%s", mytc);
1254 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1256 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1261 /* Parse second time control */
1264 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1272 timeControl_2 = tc2 * 1000;
1282 timeControl = tc1 * 1000;
1285 timeIncrement = ti * 1000; /* convert to ms */
1286 movesPerSession = 0;
1289 movesPerSession = mps;
1297 if (appData.debugMode) {
1298 fprintf(debugFP, "%s\n", programVersion);
1301 set_cont_sequence(appData.wrapContSeq);
1302 if (appData.matchGames > 0) {
1303 appData.matchMode = TRUE;
1304 } else if (appData.matchMode) {
1305 appData.matchGames = 1;
1307 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1308 appData.matchGames = appData.sameColorGames;
1309 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1310 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1311 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1314 if (appData.noChessProgram || first.protocolVersion == 1) {
1317 /* kludge: allow timeout for initial "feature" commands */
1319 DisplayMessage("", _("Starting chess program"));
1320 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1325 CalculateIndex(int index, int gameNr)
1326 { // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1328 if(index > 0) return index; // fixed nmber
1329 if(index == 0) return 1;
1330 res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1331 if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1336 LoadGameOrPosition(int gameNr)
1337 { // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1338 if (*appData.loadGameFile != NULLCHAR) {
1339 if (!LoadGameFromFile(appData.loadGameFile,
1340 CalculateIndex(appData.loadGameIndex, gameNr),
1341 appData.loadGameFile, FALSE)) {
1342 DisplayFatalError(_("Bad game file"), 0, 1);
1345 } else if (*appData.loadPositionFile != NULLCHAR) {
1346 if (!LoadPositionFromFile(appData.loadPositionFile,
1347 CalculateIndex(appData.loadPositionIndex, gameNr),
1348 appData.loadPositionFile)) {
1349 DisplayFatalError(_("Bad position file"), 0, 1);
1357 ReserveGame(int gameNr, char resChar)
1359 FILE *tf = fopen(appData.tourneyFile, "r+");
1360 char *p, *q, c, buf[MSG_SIZ];
1361 if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1362 safeStrCpy(buf, lastMsg, MSG_SIZ);
1363 DisplayMessage(_("Pick new game"), "");
1364 flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1365 ParseArgsFromFile(tf);
1366 p = q = appData.results;
1367 if(appData.debugMode) {
1368 char *r = appData.participants;
1369 fprintf(debugFP, "results = '%s'\n", p);
1370 while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1371 fprintf(debugFP, "\n");
1373 while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1375 q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1376 safeStrCpy(q, p, strlen(p) + 2);
1377 if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1378 if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1379 if(nextGame <= appData.matchGames && resChar != ' ') { // already reserve next game, if tourney not yet done
1380 if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1383 fseek(tf, -(strlen(p)+4), SEEK_END);
1385 if(c != '"') // depending on DOS or Unix line endings we can be one off
1386 fseek(tf, -(strlen(p)+2), SEEK_END);
1387 else fseek(tf, -(strlen(p)+3), SEEK_END);
1388 fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1389 DisplayMessage(buf, "");
1390 free(p); appData.results = q;
1391 if(nextGame <= appData.matchGames && resChar != ' ' &&
1392 (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1393 UnloadEngine(&first); // next game belongs to other pairing;
1394 UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1399 MatchEvent(int mode)
1400 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1402 if(matchMode) { // already in match mode: switch it off
1404 appData.matchGames = appData.tourneyFile[0] ? nextGame: matchGame; // kludge to let match terminate after next game.
1405 ModeHighlight(); // kludgey way to remove checkmark...
1408 // if(gameMode != BeginningOfGame) {
1409 // DisplayError(_("You can only start a match from the initial position."), 0);
1413 if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1414 /* Set up machine vs. machine match */
1416 NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1417 if(appData.tourneyFile[0]) {
1419 if(nextGame > appData.matchGames) {
1421 if(strchr(appData.results, '*') == NULL) {
1423 appData.tourneyCycles++;
1424 if(f = WriteTourneyFile(appData.results)) { // make a tourney file with increased number of cycles
1426 NextTourneyGame(-1, &dummy);
1428 if(nextGame <= appData.matchGames) {
1429 DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1431 ScheduleDelayedEvent(NextMatchGame, 10000);
1436 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1437 DisplayError(buf, 0);
1438 appData.tourneyFile[0] = 0;
1442 if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically
1443 DisplayFatalError(_("Can't have a match with no chess programs"),
1448 matchGame = roundNr = 1;
1449 first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
\r
1454 InitBackEnd3 P((void))
1456 GameMode initialMode;
1460 InitChessProgram(&first, startedFromSetupPosition);
1462 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1463 free(programVersion);
1464 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1465 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1468 if (appData.icsActive) {
1470 /* [DM] Make a console window if needed [HGM] merged ifs */
1476 if (*appData.icsCommPort != NULLCHAR)
1477 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1478 appData.icsCommPort);
1480 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1481 appData.icsHost, appData.icsPort);
1483 if( (len > MSG_SIZ) && appData.debugMode )
1484 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1486 DisplayFatalError(buf, err, 1);
1491 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1493 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1494 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1495 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1496 } else if (appData.noChessProgram) {
1502 if (*appData.cmailGameName != NULLCHAR) {
1504 OpenLoopback(&cmailPR);
1506 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1510 DisplayMessage("", "");
1511 if (StrCaseCmp(appData.initialMode, "") == 0) {
1512 initialMode = BeginningOfGame;
1513 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1514 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1515 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1516 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1519 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1520 initialMode = TwoMachinesPlay;
1521 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1522 initialMode = AnalyzeFile;
1523 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1524 initialMode = AnalyzeMode;
1525 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1526 initialMode = MachinePlaysWhite;
1527 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1528 initialMode = MachinePlaysBlack;
1529 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1530 initialMode = EditGame;
1531 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1532 initialMode = EditPosition;
1533 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1534 initialMode = Training;
1536 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1537 if( (len > MSG_SIZ) && appData.debugMode )
1538 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1540 DisplayFatalError(buf, 0, 2);
1544 if (appData.matchMode) {
1545 if(appData.tourneyFile[0]) { // start tourney from command line
1547 if(f = fopen(appData.tourneyFile, "r")) {
1548 ParseArgsFromFile(f); // make sure tourney parmeters re known
1550 } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1553 } else if (*appData.cmailGameName != NULLCHAR) {
1554 /* Set up cmail mode */
1555 ReloadCmailMsgEvent(TRUE);
1557 /* Set up other modes */
1558 if (initialMode == AnalyzeFile) {
1559 if (*appData.loadGameFile == NULLCHAR) {
1560 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1564 if (*appData.loadGameFile != NULLCHAR) {
1565 (void) LoadGameFromFile(appData.loadGameFile,
1566 appData.loadGameIndex,
1567 appData.loadGameFile, TRUE);
1568 } else if (*appData.loadPositionFile != NULLCHAR) {
1569 (void) LoadPositionFromFile(appData.loadPositionFile,
1570 appData.loadPositionIndex,
1571 appData.loadPositionFile);
1572 /* [HGM] try to make self-starting even after FEN load */
1573 /* to allow automatic setup of fairy variants with wtm */
1574 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1575 gameMode = BeginningOfGame;
1576 setboardSpoiledMachineBlack = 1;
1578 /* [HGM] loadPos: make that every new game uses the setup */
1579 /* from file as long as we do not switch variant */
1580 if(!blackPlaysFirst) {
1581 startedFromPositionFile = TRUE;
1582 CopyBoard(filePosition, boards[0]);
1585 if (initialMode == AnalyzeMode) {
1586 if (appData.noChessProgram) {
1587 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1590 if (appData.icsActive) {
1591 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1595 } else if (initialMode == AnalyzeFile) {
1596 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1597 ShowThinkingEvent();
1599 AnalysisPeriodicEvent(1);
1600 } else if (initialMode == MachinePlaysWhite) {
1601 if (appData.noChessProgram) {
1602 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1606 if (appData.icsActive) {
1607 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1611 MachineWhiteEvent();
1612 } else if (initialMode == MachinePlaysBlack) {
1613 if (appData.noChessProgram) {
1614 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1618 if (appData.icsActive) {
1619 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1623 MachineBlackEvent();
1624 } else if (initialMode == TwoMachinesPlay) {
1625 if (appData.noChessProgram) {
1626 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1630 if (appData.icsActive) {
1631 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1636 } else if (initialMode == EditGame) {
1638 } else if (initialMode == EditPosition) {
1639 EditPositionEvent();
1640 } else if (initialMode == Training) {
1641 if (*appData.loadGameFile == NULLCHAR) {
1642 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1651 * Establish will establish a contact to a remote host.port.
1652 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1653 * used to talk to the host.
1654 * Returns 0 if okay, error code if not.
1661 if (*appData.icsCommPort != NULLCHAR) {
1662 /* Talk to the host through a serial comm port */
1663 return OpenCommPort(appData.icsCommPort, &icsPR);
1665 } else if (*appData.gateway != NULLCHAR) {
1666 if (*appData.remoteShell == NULLCHAR) {
1667 /* Use the rcmd protocol to run telnet program on a gateway host */
1668 snprintf(buf, sizeof(buf), "%s %s %s",
1669 appData.telnetProgram, appData.icsHost, appData.icsPort);
1670 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1673 /* Use the rsh program to run telnet program on a gateway host */
1674 if (*appData.remoteUser == NULLCHAR) {
1675 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1676 appData.gateway, appData.telnetProgram,
1677 appData.icsHost, appData.icsPort);
1679 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1680 appData.remoteShell, appData.gateway,
1681 appData.remoteUser, appData.telnetProgram,
1682 appData.icsHost, appData.icsPort);
1684 return StartChildProcess(buf, "", &icsPR);
1687 } else if (appData.useTelnet) {
1688 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1691 /* TCP socket interface differs somewhat between
1692 Unix and NT; handle details in the front end.
1694 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1698 void EscapeExpand(char *p, char *q)
1699 { // [HGM] initstring: routine to shape up string arguments
1700 while(*p++ = *q++) if(p[-1] == '\\')
1702 case 'n': p[-1] = '\n'; break;
1703 case 'r': p[-1] = '\r'; break;
1704 case 't': p[-1] = '\t'; break;
1705 case '\\': p[-1] = '\\'; break;
1706 case 0: *p = 0; return;
1707 default: p[-1] = q[-1]; break;
1712 show_bytes(fp, buf, count)
1718 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1719 fprintf(fp, "\\%03o", *buf & 0xff);
1728 /* Returns an errno value */
1730 OutputMaybeTelnet(pr, message, count, outError)
1736 char buf[8192], *p, *q, *buflim;
1737 int left, newcount, outcount;
1739 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1740 *appData.gateway != NULLCHAR) {
1741 if (appData.debugMode) {
1742 fprintf(debugFP, ">ICS: ");
1743 show_bytes(debugFP, message, count);
1744 fprintf(debugFP, "\n");
1746 return OutputToProcess(pr, message, count, outError);
1749 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1756 if (appData.debugMode) {
1757 fprintf(debugFP, ">ICS: ");
1758 show_bytes(debugFP, buf, newcount);
1759 fprintf(debugFP, "\n");
1761 outcount = OutputToProcess(pr, buf, newcount, outError);
1762 if (outcount < newcount) return -1; /* to be sure */
1769 } else if (((unsigned char) *p) == TN_IAC) {
1770 *q++ = (char) TN_IAC;
1777 if (appData.debugMode) {
1778 fprintf(debugFP, ">ICS: ");
1779 show_bytes(debugFP, buf, newcount);
1780 fprintf(debugFP, "\n");
1782 outcount = OutputToProcess(pr, buf, newcount, outError);
1783 if (outcount < newcount) return -1; /* to be sure */
1788 read_from_player(isr, closure, message, count, error)
1795 int outError, outCount;
1796 static int gotEof = 0;
1798 /* Pass data read from player on to ICS */
1801 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1802 if (outCount < count) {
1803 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1805 } else if (count < 0) {
1806 RemoveInputSource(isr);
1807 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1808 } else if (gotEof++ > 0) {
1809 RemoveInputSource(isr);
1810 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1816 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1817 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1818 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1819 SendToICS("date\n");
1820 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1823 /* added routine for printf style output to ics */
1824 void ics_printf(char *format, ...)
1826 char buffer[MSG_SIZ];
1829 va_start(args, format);
1830 vsnprintf(buffer, sizeof(buffer), format, args);
1831 buffer[sizeof(buffer)-1] = '\0';
1840 int count, outCount, outError;
1842 if (icsPR == NULL) return;
1845 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1846 if (outCount < count) {
1847 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1851 /* This is used for sending logon scripts to the ICS. Sending
1852 without a delay causes problems when using timestamp on ICC
1853 (at least on my machine). */
1855 SendToICSDelayed(s,msdelay)
1859 int count, outCount, outError;
1861 if (icsPR == NULL) return;
1864 if (appData.debugMode) {
1865 fprintf(debugFP, ">ICS: ");
1866 show_bytes(debugFP, s, count);
1867 fprintf(debugFP, "\n");
1869 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1871 if (outCount < count) {
1872 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1877 /* Remove all highlighting escape sequences in s
1878 Also deletes any suffix starting with '('
1881 StripHighlightAndTitle(s)
1884 static char retbuf[MSG_SIZ];
1887 while (*s != NULLCHAR) {
1888 while (*s == '\033') {
1889 while (*s != NULLCHAR && !isalpha(*s)) s++;
1890 if (*s != NULLCHAR) s++;
1892 while (*s != NULLCHAR && *s != '\033') {
1893 if (*s == '(' || *s == '[') {
1904 /* Remove all highlighting escape sequences in s */
1909 static char retbuf[MSG_SIZ];
1912 while (*s != NULLCHAR) {
1913 while (*s == '\033') {
1914 while (*s != NULLCHAR && !isalpha(*s)) s++;
1915 if (*s != NULLCHAR) s++;
1917 while (*s != NULLCHAR && *s != '\033') {
1925 char *variantNames[] = VARIANT_NAMES;
1930 return variantNames[v];
1934 /* Identify a variant from the strings the chess servers use or the
1935 PGN Variant tag names we use. */
1942 VariantClass v = VariantNormal;
1943 int i, found = FALSE;
1949 /* [HGM] skip over optional board-size prefixes */
1950 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1951 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1952 while( *e++ != '_');
1955 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1959 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1960 if (StrCaseStr(e, variantNames[i])) {
1961 v = (VariantClass) i;
1968 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1969 || StrCaseStr(e, "wild/fr")
1970 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1971 v = VariantFischeRandom;
1972 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1973 (i = 1, p = StrCaseStr(e, "w"))) {
1975 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1982 case 0: /* FICS only, actually */
1984 /* Castling legal even if K starts on d-file */
1985 v = VariantWildCastle;
1990 /* Castling illegal even if K & R happen to start in
1991 normal positions. */
1992 v = VariantNoCastle;
2005 /* Castling legal iff K & R start in normal positions */
2011 /* Special wilds for position setup; unclear what to do here */
2012 v = VariantLoadable;
2015 /* Bizarre ICC game */
2016 v = VariantTwoKings;
2019 v = VariantKriegspiel;
2025 v = VariantFischeRandom;
2028 v = VariantCrazyhouse;
2031 v = VariantBughouse;
2037 /* Not quite the same as FICS suicide! */
2038 v = VariantGiveaway;
2044 v = VariantShatranj;
2047 /* Temporary names for future ICC types. The name *will* change in
2048 the next xboard/WinBoard release after ICC defines it. */
2086 v = VariantCapablanca;
2089 v = VariantKnightmate;
2095 v = VariantCylinder;
2101 v = VariantCapaRandom;
2104 v = VariantBerolina;
2116 /* Found "wild" or "w" in the string but no number;
2117 must assume it's normal chess. */
2121 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2122 if( (len > MSG_SIZ) && appData.debugMode )
2123 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2125 DisplayError(buf, 0);
2131 if (appData.debugMode) {
2132 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2133 e, wnum, VariantName(v));
2138 static int leftover_start = 0, leftover_len = 0;
2139 char star_match[STAR_MATCH_N][MSG_SIZ];
2141 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2142 advance *index beyond it, and set leftover_start to the new value of
2143 *index; else return FALSE. If pattern contains the character '*', it
2144 matches any sequence of characters not containing '\r', '\n', or the
2145 character following the '*' (if any), and the matched sequence(s) are
2146 copied into star_match.
2149 looking_at(buf, index, pattern)
2154 char *bufp = &buf[*index], *patternp = pattern;
2156 char *matchp = star_match[0];
2159 if (*patternp == NULLCHAR) {
2160 *index = leftover_start = bufp - buf;
2164 if (*bufp == NULLCHAR) return FALSE;
2165 if (*patternp == '*') {
2166 if (*bufp == *(patternp + 1)) {
2168 matchp = star_match[++star_count];
2172 } else if (*bufp == '\n' || *bufp == '\r') {
2174 if (*patternp == NULLCHAR)
2179 *matchp++ = *bufp++;
2183 if (*patternp != *bufp) return FALSE;
2190 SendToPlayer(data, length)
2194 int error, outCount;
2195 outCount = OutputToProcess(NoProc, data, length, &error);
2196 if (outCount < length) {
2197 DisplayFatalError(_("Error writing to display"), error, 1);
2202 PackHolding(packed, holding)
2214 switch (runlength) {
2225 sprintf(q, "%d", runlength);
2237 /* Telnet protocol requests from the front end */
2239 TelnetRequest(ddww, option)
2240 unsigned char ddww, option;
2242 unsigned char msg[3];
2243 int outCount, outError;
2245 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2247 if (appData.debugMode) {
2248 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2264 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2273 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2276 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2281 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2283 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2290 if (!appData.icsActive) return;
2291 TelnetRequest(TN_DO, TN_ECHO);
2297 if (!appData.icsActive) return;
2298 TelnetRequest(TN_DONT, TN_ECHO);
2302 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2304 /* put the holdings sent to us by the server on the board holdings area */
2305 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2309 if(gameInfo.holdingsWidth < 2) return;
2310 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2311 return; // prevent overwriting by pre-board holdings
2313 if( (int)lowestPiece >= BlackPawn ) {
2316 holdingsStartRow = BOARD_HEIGHT-1;
2319 holdingsColumn = BOARD_WIDTH-1;
2320 countsColumn = BOARD_WIDTH-2;
2321 holdingsStartRow = 0;
2325 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2326 board[i][holdingsColumn] = EmptySquare;
2327 board[i][countsColumn] = (ChessSquare) 0;
2329 while( (p=*holdings++) != NULLCHAR ) {
2330 piece = CharToPiece( ToUpper(p) );
2331 if(piece == EmptySquare) continue;
2332 /*j = (int) piece - (int) WhitePawn;*/
2333 j = PieceToNumber(piece);
2334 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2335 if(j < 0) continue; /* should not happen */
2336 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2337 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2338 board[holdingsStartRow+j*direction][countsColumn]++;
2344 VariantSwitch(Board board, VariantClass newVariant)
2346 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2347 static Board oldBoard;
2349 startedFromPositionFile = FALSE;
2350 if(gameInfo.variant == newVariant) return;
2352 /* [HGM] This routine is called each time an assignment is made to
2353 * gameInfo.variant during a game, to make sure the board sizes
2354 * are set to match the new variant. If that means adding or deleting
2355 * holdings, we shift the playing board accordingly
2356 * This kludge is needed because in ICS observe mode, we get boards
2357 * of an ongoing game without knowing the variant, and learn about the
2358 * latter only later. This can be because of the move list we requested,
2359 * in which case the game history is refilled from the beginning anyway,
2360 * but also when receiving holdings of a crazyhouse game. In the latter
2361 * case we want to add those holdings to the already received position.
2365 if (appData.debugMode) {
2366 fprintf(debugFP, "Switch board from %s to %s\n",
2367 VariantName(gameInfo.variant), VariantName(newVariant));
2368 setbuf(debugFP, NULL);
2370 shuffleOpenings = 0; /* [HGM] shuffle */
2371 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2375 newWidth = 9; newHeight = 9;
2376 gameInfo.holdingsSize = 7;
2377 case VariantBughouse:
2378 case VariantCrazyhouse:
2379 newHoldingsWidth = 2; break;
2383 newHoldingsWidth = 2;
2384 gameInfo.holdingsSize = 8;
2387 case VariantCapablanca:
2388 case VariantCapaRandom:
2391 newHoldingsWidth = gameInfo.holdingsSize = 0;
2394 if(newWidth != gameInfo.boardWidth ||
2395 newHeight != gameInfo.boardHeight ||
2396 newHoldingsWidth != gameInfo.holdingsWidth ) {
2398 /* shift position to new playing area, if needed */
2399 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2400 for(i=0; i<BOARD_HEIGHT; i++)
2401 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2402 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2404 for(i=0; i<newHeight; i++) {
2405 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2406 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2408 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2409 for(i=0; i<BOARD_HEIGHT; i++)
2410 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2411 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2414 gameInfo.boardWidth = newWidth;
2415 gameInfo.boardHeight = newHeight;
2416 gameInfo.holdingsWidth = newHoldingsWidth;
2417 gameInfo.variant = newVariant;
2418 InitDrawingSizes(-2, 0);
2419 } else gameInfo.variant = newVariant;
2420 CopyBoard(oldBoard, board); // remember correctly formatted board
2421 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2422 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2425 static int loggedOn = FALSE;
2427 /*-- Game start info cache: --*/
2429 char gs_kind[MSG_SIZ];
2430 static char player1Name[128] = "";
2431 static char player2Name[128] = "";
2432 static char cont_seq[] = "\n\\ ";
2433 static int player1Rating = -1;
2434 static int player2Rating = -1;
2435 /*----------------------------*/
2437 ColorClass curColor = ColorNormal;
2438 int suppressKibitz = 0;
2441 Boolean soughtPending = FALSE;
2442 Boolean seekGraphUp;
2443 #define MAX_SEEK_ADS 200
2445 char *seekAdList[MAX_SEEK_ADS];
2446 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2447 float tcList[MAX_SEEK_ADS];
2448 char colorList[MAX_SEEK_ADS];
2449 int nrOfSeekAds = 0;
2450 int minRating = 1010, maxRating = 2800;
2451 int hMargin = 10, vMargin = 20, h, w;
2452 extern int squareSize, lineGap;
2457 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2458 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2459 if(r < minRating+100 && r >=0 ) r = minRating+100;
2460 if(r > maxRating) r = maxRating;
2461 if(tc < 1.) tc = 1.;
2462 if(tc > 95.) tc = 95.;
2463 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2464 y = ((double)r - minRating)/(maxRating - minRating)
2465 * (h-vMargin-squareSize/8-1) + vMargin;
2466 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2467 if(strstr(seekAdList[i], " u ")) color = 1;
2468 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2469 !strstr(seekAdList[i], "bullet") &&
2470 !strstr(seekAdList[i], "blitz") &&
2471 !strstr(seekAdList[i], "standard") ) color = 2;
2472 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2473 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2477 AddAd(char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2479 char buf[MSG_SIZ], *ext = "";
2480 VariantClass v = StringToVariant(type);
2481 if(strstr(type, "wild")) {
2482 ext = type + 4; // append wild number
2483 if(v == VariantFischeRandom) type = "chess960"; else
2484 if(v == VariantLoadable) type = "setup"; else
2485 type = VariantName(v);
2487 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2488 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2489 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2490 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2491 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2492 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2493 seekNrList[nrOfSeekAds] = nr;
2494 zList[nrOfSeekAds] = 0;
2495 seekAdList[nrOfSeekAds++] = StrSave(buf);
2496 if(plot) PlotSeekAd(nrOfSeekAds-1);
2503 int x = xList[i], y = yList[i], d=squareSize/4, k;
2504 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2505 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2506 // now replot every dot that overlapped
2507 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2508 int xx = xList[k], yy = yList[k];
2509 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2510 DrawSeekDot(xx, yy, colorList[k]);
2515 RemoveSeekAd(int nr)
2518 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2520 if(seekAdList[i]) free(seekAdList[i]);
2521 seekAdList[i] = seekAdList[--nrOfSeekAds];
2522 seekNrList[i] = seekNrList[nrOfSeekAds];
2523 ratingList[i] = ratingList[nrOfSeekAds];
2524 colorList[i] = colorList[nrOfSeekAds];
2525 tcList[i] = tcList[nrOfSeekAds];
2526 xList[i] = xList[nrOfSeekAds];
2527 yList[i] = yList[nrOfSeekAds];
2528 zList[i] = zList[nrOfSeekAds];
2529 seekAdList[nrOfSeekAds] = NULL;
2535 MatchSoughtLine(char *line)
2537 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2538 int nr, base, inc, u=0; char dummy;
2540 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2541 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2543 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2544 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2545 // match: compact and save the line
2546 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2556 if(!seekGraphUp) return FALSE;
2557 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2558 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2560 DrawSeekBackground(0, 0, w, h);
2561 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2562 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2563 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2564 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2566 DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2569 snprintf(buf, MSG_SIZ, "%d", i);
2570 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2573 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2574 for(i=1; i<100; i+=(i<10?1:5)) {
2575 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2576 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2577 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2579 snprintf(buf, MSG_SIZ, "%d", i);
2580 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2583 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2587 int SeekGraphClick(ClickType click, int x, int y, int moving)
2589 static int lastDown = 0, displayed = 0, lastSecond;
2590 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2591 if(click == Release || moving) return FALSE;
2593 soughtPending = TRUE;
2594 SendToICS(ics_prefix);
2595 SendToICS("sought\n"); // should this be "sought all"?
2596 } else { // issue challenge based on clicked ad
2597 int dist = 10000; int i, closest = 0, second = 0;
2598 for(i=0; i<nrOfSeekAds; i++) {
2599 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2600 if(d < dist) { dist = d; closest = i; }
2601 second += (d - zList[i] < 120); // count in-range ads
2602 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2606 second = (second > 1);
2607 if(displayed != closest || second != lastSecond) {
2608 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2609 lastSecond = second; displayed = closest;
2611 if(click == Press) {
2612 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2615 } // on press 'hit', only show info
2616 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2617 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2618 SendToICS(ics_prefix);
2620 return TRUE; // let incoming board of started game pop down the graph
2621 } else if(click == Release) { // release 'miss' is ignored
2622 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2623 if(moving == 2) { // right up-click
2624 nrOfSeekAds = 0; // refresh graph
2625 soughtPending = TRUE;
2626 SendToICS(ics_prefix);
2627 SendToICS("sought\n"); // should this be "sought all"?
2630 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2631 // press miss or release hit 'pop down' seek graph
2632 seekGraphUp = FALSE;
2633 DrawPosition(TRUE, NULL);
2639 read_from_ics(isr, closure, data, count, error)
2646 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2647 #define STARTED_NONE 0
2648 #define STARTED_MOVES 1
2649 #define STARTED_BOARD 2
2650 #define STARTED_OBSERVE 3
2651 #define STARTED_HOLDINGS 4
2652 #define STARTED_CHATTER 5
2653 #define STARTED_COMMENT 6
2654 #define STARTED_MOVES_NOHIDE 7
2656 static int started = STARTED_NONE;
2657 static char parse[20000];
2658 static int parse_pos = 0;
2659 static char buf[BUF_SIZE + 1];
2660 static int firstTime = TRUE, intfSet = FALSE;
2661 static ColorClass prevColor = ColorNormal;
2662 static int savingComment = FALSE;
2663 static int cmatch = 0; // continuation sequence match
2670 int backup; /* [DM] For zippy color lines */
2672 char talker[MSG_SIZ]; // [HGM] chat
2675 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2677 if (appData.debugMode) {
2679 fprintf(debugFP, "<ICS: ");
2680 show_bytes(debugFP, data, count);
2681 fprintf(debugFP, "\n");
2685 if (appData.debugMode) { int f = forwardMostMove;
2686 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2687 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2688 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2691 /* If last read ended with a partial line that we couldn't parse,
2692 prepend it to the new read and try again. */
2693 if (leftover_len > 0) {
2694 for (i=0; i<leftover_len; i++)
2695 buf[i] = buf[leftover_start + i];
2698 /* copy new characters into the buffer */
2699 bp = buf + leftover_len;
2700 buf_len=leftover_len;
2701 for (i=0; i<count; i++)
2704 if (data[i] == '\r')
2707 // join lines split by ICS?
2708 if (!appData.noJoin)
2711 Joining just consists of finding matches against the
2712 continuation sequence, and discarding that sequence
2713 if found instead of copying it. So, until a match
2714 fails, there's nothing to do since it might be the
2715 complete sequence, and thus, something we don't want
2718 if (data[i] == cont_seq[cmatch])
2721 if (cmatch == strlen(cont_seq))
2723 cmatch = 0; // complete match. just reset the counter
2726 it's possible for the ICS to not include the space
2727 at the end of the last word, making our [correct]
2728 join operation fuse two separate words. the server
2729 does this when the space occurs at the width setting.
2731 if (!buf_len || buf[buf_len-1] != ' ')
2742 match failed, so we have to copy what matched before
2743 falling through and copying this character. In reality,
2744 this will only ever be just the newline character, but
2745 it doesn't hurt to be precise.
2747 strncpy(bp, cont_seq, cmatch);
2759 buf[buf_len] = NULLCHAR;
2760 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2765 while (i < buf_len) {
2766 /* Deal with part of the TELNET option negotiation
2767 protocol. We refuse to do anything beyond the
2768 defaults, except that we allow the WILL ECHO option,
2769 which ICS uses to turn off password echoing when we are
2770 directly connected to it. We reject this option
2771 if localLineEditing mode is on (always on in xboard)
2772 and we are talking to port 23, which might be a real
2773 telnet server that will try to keep WILL ECHO on permanently.
2775 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2776 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2777 unsigned char option;
2779 switch ((unsigned char) buf[++i]) {
2781 if (appData.debugMode)
2782 fprintf(debugFP, "\n<WILL ");
2783 switch (option = (unsigned char) buf[++i]) {
2785 if (appData.debugMode)
2786 fprintf(debugFP, "ECHO ");
2787 /* Reply only if this is a change, according
2788 to the protocol rules. */
2789 if (remoteEchoOption) break;
2790 if (appData.localLineEditing &&
2791 atoi(appData.icsPort) == TN_PORT) {
2792 TelnetRequest(TN_DONT, TN_ECHO);
2795 TelnetRequest(TN_DO, TN_ECHO);
2796 remoteEchoOption = TRUE;
2800 if (appData.debugMode)
2801 fprintf(debugFP, "%d ", option);
2802 /* Whatever this is, we don't want it. */
2803 TelnetRequest(TN_DONT, option);
2808 if (appData.debugMode)
2809 fprintf(debugFP, "\n<WONT ");
2810 switch (option = (unsigned char) buf[++i]) {
2812 if (appData.debugMode)
2813 fprintf(debugFP, "ECHO ");
2814 /* Reply only if this is a change, according
2815 to the protocol rules. */
2816 if (!remoteEchoOption) break;
2818 TelnetRequest(TN_DONT, TN_ECHO);
2819 remoteEchoOption = FALSE;
2822 if (appData.debugMode)
2823 fprintf(debugFP, "%d ", (unsigned char) option);
2824 /* Whatever this is, it must already be turned
2825 off, because we never agree to turn on
2826 anything non-default, so according to the
2827 protocol rules, we don't reply. */
2832 if (appData.debugMode)
2833 fprintf(debugFP, "\n<DO ");
2834 switch (option = (unsigned char) buf[++i]) {
2836 /* Whatever this is, we refuse to do it. */
2837 if (appData.debugMode)
2838 fprintf(debugFP, "%d ", option);
2839 TelnetRequest(TN_WONT, option);
2844 if (appData.debugMode)
2845 fprintf(debugFP, "\n<DONT ");
2846 switch (option = (unsigned char) buf[++i]) {
2848 if (appData.debugMode)
2849 fprintf(debugFP, "%d ", option);
2850 /* Whatever this is, we are already not doing
2851 it, because we never agree to do anything
2852 non-default, so according to the protocol
2853 rules, we don't reply. */
2858 if (appData.debugMode)
2859 fprintf(debugFP, "\n<IAC ");
2860 /* Doubled IAC; pass it through */
2864 if (appData.debugMode)
2865 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2866 /* Drop all other telnet commands on the floor */
2869 if (oldi > next_out)
2870 SendToPlayer(&buf[next_out], oldi - next_out);
2876 /* OK, this at least will *usually* work */
2877 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2881 if (loggedOn && !intfSet) {
2882 if (ics_type == ICS_ICC) {
2883 snprintf(str, MSG_SIZ,
2884 "/set-quietly interface %s\n/set-quietly style 12\n",
2886 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2887 strcat(str, "/set-2 51 1\n/set seek 1\n");
2888 } else if (ics_type == ICS_CHESSNET) {
2889 snprintf(str, MSG_SIZ, "/style 12\n");
2891 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2892 strcat(str, programVersion);
2893 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2894 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2895 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2897 strcat(str, "$iset nohighlight 1\n");
2899 strcat(str, "$iset lock 1\n$style 12\n");
2902 NotifyFrontendLogin();
2906 if (started == STARTED_COMMENT) {
2907 /* Accumulate characters in comment */
2908 parse[parse_pos++] = buf[i];
2909 if (buf[i] == '\n') {
2910 parse[parse_pos] = NULLCHAR;
2911 if(chattingPartner>=0) {
2913 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2914 OutputChatMessage(chattingPartner, mess);
2915 chattingPartner = -1;
2916 next_out = i+1; // [HGM] suppress printing in ICS window
2918 if(!suppressKibitz) // [HGM] kibitz
2919 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2920 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2921 int nrDigit = 0, nrAlph = 0, j;
2922 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2923 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2924 parse[parse_pos] = NULLCHAR;
2925 // try to be smart: if it does not look like search info, it should go to
2926 // ICS interaction window after all, not to engine-output window.
2927 for(j=0; j<parse_pos; j++) { // count letters and digits
2928 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2929 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2930 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2932 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2933 int depth=0; float score;
2934 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2935 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2936 pvInfoList[forwardMostMove-1].depth = depth;
2937 pvInfoList[forwardMostMove-1].score = 100*score;
2939 OutputKibitz(suppressKibitz, parse);
2942 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2943 SendToPlayer(tmp, strlen(tmp));
2945 next_out = i+1; // [HGM] suppress printing in ICS window
2947 started = STARTED_NONE;
2949 /* Don't match patterns against characters in comment */
2954 if (started == STARTED_CHATTER) {
2955 if (buf[i] != '\n') {
2956 /* Don't match patterns against characters in chatter */
2960 started = STARTED_NONE;
2961 if(suppressKibitz) next_out = i+1;
2964 /* Kludge to deal with rcmd protocol */
2965 if (firstTime && looking_at(buf, &i, "\001*")) {
2966 DisplayFatalError(&buf[1], 0, 1);
2972 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2975 if (appData.debugMode)
2976 fprintf(debugFP, "ics_type %d\n", ics_type);
2979 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2980 ics_type = ICS_FICS;
2982 if (appData.debugMode)
2983 fprintf(debugFP, "ics_type %d\n", ics_type);
2986 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2987 ics_type = ICS_CHESSNET;
2989 if (appData.debugMode)
2990 fprintf(debugFP, "ics_type %d\n", ics_type);
2995 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2996 looking_at(buf, &i, "Logging you in as \"*\"") ||
2997 looking_at(buf, &i, "will be \"*\""))) {
2998 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3002 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3004 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3005 DisplayIcsInteractionTitle(buf);
3006 have_set_title = TRUE;
3009 /* skip finger notes */
3010 if (started == STARTED_NONE &&
3011 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3012 (buf[i] == '1' && buf[i+1] == '0')) &&
3013 buf[i+2] == ':' && buf[i+3] == ' ') {
3014 started = STARTED_CHATTER;
3020 // [HGM] seekgraph: recognize sought lines and end-of-sought message
3021 if(appData.seekGraph) {
3022 if(soughtPending && MatchSoughtLine(buf+i)) {
3023 i = strstr(buf+i, "rated") - buf;
3024 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3025 next_out = leftover_start = i;
3026 started = STARTED_CHATTER;
3027 suppressKibitz = TRUE;
3030 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3031 && looking_at(buf, &i, "* ads displayed")) {
3032 soughtPending = FALSE;
3037 if(appData.autoRefresh) {
3038 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3039 int s = (ics_type == ICS_ICC); // ICC format differs
3041 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3042 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3043 looking_at(buf, &i, "*% "); // eat prompt
3044 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3045 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3046 next_out = i; // suppress
3049 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3050 char *p = star_match[0];
3052 if(seekGraphUp) RemoveSeekAd(atoi(p));
3053 while(*p && *p++ != ' '); // next
3055 looking_at(buf, &i, "*% "); // eat prompt
3056 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3063 /* skip formula vars */
3064 if (started == STARTED_NONE &&
3065 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3066 started = STARTED_CHATTER;
3071 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3072 if (appData.autoKibitz && started == STARTED_NONE &&
3073 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3074 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3075 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3076 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3077 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3078 suppressKibitz = TRUE;
3079 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3081 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3082 && (gameMode == IcsPlayingWhite)) ||
3083 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3084 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3085 started = STARTED_CHATTER; // own kibitz we simply discard
3087 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3088 parse_pos = 0; parse[0] = NULLCHAR;
3089 savingComment = TRUE;
3090 suppressKibitz = gameMode != IcsObserving ? 2 :
3091 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3095 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3096 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3097 && atoi(star_match[0])) {
3098 // suppress the acknowledgements of our own autoKibitz
3100 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3101 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3102 SendToPlayer(star_match[0], strlen(star_match[0]));
3103 if(looking_at(buf, &i, "*% ")) // eat prompt
3104 suppressKibitz = FALSE;
3108 } // [HGM] kibitz: end of patch
3110 // [HGM] chat: intercept tells by users for which we have an open chat window
3112 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3113 looking_at(buf, &i, "* whispers:") ||
3114 looking_at(buf, &i, "* kibitzes:") ||
3115 looking_at(buf, &i, "* shouts:") ||
3116 looking_at(buf, &i, "* c-shouts:") ||
3117 looking_at(buf, &i, "--> * ") ||
3118 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3119 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3120 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3121 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3123 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3124 chattingPartner = -1;
3126 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3127 for(p=0; p<MAX_CHAT; p++) {
3128 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3129 talker[0] = '['; strcat(talker, "] ");
3130 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3131 chattingPartner = p; break;
3134 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3135 for(p=0; p<MAX_CHAT; p++) {
3136 if(!strcmp("kibitzes", chatPartner[p])) {
3137 talker[0] = '['; strcat(talker, "] ");
3138 chattingPartner = p; break;
3141 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3142 for(p=0; p<MAX_CHAT; p++) {
3143 if(!strcmp("whispers", chatPartner[p])) {
3144 talker[0] = '['; strcat(talker, "] ");
3145 chattingPartner = p; break;
3148 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3149 if(buf[i-8] == '-' && buf[i-3] == 't')
3150 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3151 if(!strcmp("c-shouts", chatPartner[p])) {
3152 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3153 chattingPartner = p; break;
3156 if(chattingPartner < 0)
3157 for(p=0; p<MAX_CHAT; p++) {
3158 if(!strcmp("shouts", chatPartner[p])) {
3159 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3160 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3161 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3162 chattingPartner = p; break;
3166 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3167 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3168 talker[0] = 0; Colorize(ColorTell, FALSE);
3169 chattingPartner = p; break;
3171 if(chattingPartner<0) i = oldi; else {
3172 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3173 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3174 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3175 started = STARTED_COMMENT;
3176 parse_pos = 0; parse[0] = NULLCHAR;
3177 savingComment = 3 + chattingPartner; // counts as TRUE
3178 suppressKibitz = TRUE;
3181 } // [HGM] chat: end of patch
3184 if (appData.zippyTalk || appData.zippyPlay) {
3185 /* [DM] Backup address for color zippy lines */
3187 if (loggedOn == TRUE)
3188 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3189 (appData.zippyPlay && ZippyMatch(buf, &backup)));
3191 } // [DM] 'else { ' deleted
3193 /* Regular tells and says */
3194 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3195 looking_at(buf, &i, "* (your partner) tells you: ") ||
3196 looking_at(buf, &i, "* says: ") ||
3197 /* Don't color "message" or "messages" output */
3198 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3199 looking_at(buf, &i, "*. * at *:*: ") ||
3200 looking_at(buf, &i, "--* (*:*): ") ||
3201 /* Message notifications (same color as tells) */
3202 looking_at(buf, &i, "* has left a message ") ||
3203 looking_at(buf, &i, "* just sent you a message:\n") ||
3204 /* Whispers and kibitzes */
3205 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3206 looking_at(buf, &i, "* kibitzes: ") ||
3208 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3210 if (tkind == 1 && strchr(star_match[0], ':')) {
3211 /* Avoid "tells you:" spoofs in channels */
3214 if (star_match[0][0] == NULLCHAR ||
3215 strchr(star_match[0], ' ') ||
3216 (tkind == 3 && strchr(star_match[1], ' '))) {
3217 /* Reject bogus matches */
3220 if (appData.colorize) {
3221 if (oldi > next_out) {
3222 SendToPlayer(&buf[next_out], oldi - next_out);
3227 Colorize(ColorTell, FALSE);
3228 curColor = ColorTell;
3231 Colorize(ColorKibitz, FALSE);
3232 curColor = ColorKibitz;
3235 p = strrchr(star_match[1], '(');
3242 Colorize(ColorChannel1, FALSE);
3243 curColor = ColorChannel1;
3245 Colorize(ColorChannel, FALSE);
3246 curColor = ColorChannel;
3250 curColor = ColorNormal;
3254 if (started == STARTED_NONE && appData.autoComment &&
3255 (gameMode == IcsObserving ||
3256 gameMode == IcsPlayingWhite ||
3257 gameMode == IcsPlayingBlack)) {
3258 parse_pos = i - oldi;
3259 memcpy(parse, &buf[oldi], parse_pos);
3260 parse[parse_pos] = NULLCHAR;
3261 started = STARTED_COMMENT;
3262 savingComment = TRUE;
3264 started = STARTED_CHATTER;
3265 savingComment = FALSE;
3272 if (looking_at(buf, &i, "* s-shouts: ") ||
3273 looking_at(buf, &i, "* c-shouts: ")) {
3274 if (appData.colorize) {
3275 if (oldi > next_out) {
3276 SendToPlayer(&buf[next_out], oldi - next_out);
3279 Colorize(ColorSShout, FALSE);
3280 curColor = ColorSShout;
3283 started = STARTED_CHATTER;
3287 if (looking_at(buf, &i, "--->")) {
3292 if (looking_at(buf, &i, "* shouts: ") ||
3293 looking_at(buf, &i, "--> ")) {
3294 if (appData.colorize) {
3295 if (oldi > next_out) {
3296 SendToPlayer(&buf[next_out], oldi - next_out);
3299 Colorize(ColorShout, FALSE);
3300 curColor = ColorShout;
3303 started = STARTED_CHATTER;
3307 if (looking_at( buf, &i, "Challenge:")) {
3308 if (appData.colorize) {
3309 if (oldi > next_out) {
3310 SendToPlayer(&buf[next_out], oldi - next_out);
3313 Colorize(ColorChallenge, FALSE);
3314 curColor = ColorChallenge;
3320 if (looking_at(buf, &i, "* offers you") ||
3321 looking_at(buf, &i, "* offers to be") ||
3322 looking_at(buf, &i, "* would like to") ||
3323 looking_at(buf, &i, "* requests to") ||
3324 looking_at(buf, &i, "Your opponent offers") ||
3325 looking_at(buf, &i, "Your opponent requests")) {
3327 if (appData.colorize) {
3328 if (oldi > next_out) {
3329 SendToPlayer(&buf[next_out], oldi - next_out);
3332 Colorize(ColorRequest, FALSE);
3333 curColor = ColorRequest;
3338 if (looking_at(buf, &i, "* (*) seeking")) {
3339 if (appData.colorize) {
3340 if (oldi > next_out) {
3341 SendToPlayer(&buf[next_out], oldi - next_out);
3344 Colorize(ColorSeek, FALSE);
3345 curColor = ColorSeek;
3350 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3352 if (looking_at(buf, &i, "\\ ")) {
3353 if (prevColor != ColorNormal) {
3354 if (oldi > next_out) {
3355 SendToPlayer(&buf[next_out], oldi - next_out);
3358 Colorize(prevColor, TRUE);
3359 curColor = prevColor;
3361 if (savingComment) {
3362 parse_pos = i - oldi;
3363 memcpy(parse, &buf[oldi], parse_pos);
3364 parse[parse_pos] = NULLCHAR;
3365 started = STARTED_COMMENT;
3366 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3367 chattingPartner = savingComment - 3; // kludge to remember the box
3369 started = STARTED_CHATTER;
3374 if (looking_at(buf, &i, "Black Strength :") ||
3375 looking_at(buf, &i, "<<< style 10 board >>>") ||
3376 looking_at(buf, &i, "<10>") ||
3377 looking_at(buf, &i, "#@#")) {
3378 /* Wrong board style */
3380 SendToICS(ics_prefix);
3381 SendToICS("set style 12\n");
3382 SendToICS(ics_prefix);
3383 SendToICS("refresh\n");
3387 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3389 have_sent_ICS_logon = 1;
3393 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3394 (looking_at(buf, &i, "\n<12> ") ||
3395 looking_at(buf, &i, "<12> "))) {
3397 if (oldi > next_out) {
3398 SendToPlayer(&buf[next_out], oldi - next_out);
3401 started = STARTED_BOARD;
3406 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3407 looking_at(buf, &i, "<b1> ")) {
3408 if (oldi > next_out) {
3409 SendToPlayer(&buf[next_out], oldi - next_out);
3412 started = STARTED_HOLDINGS;
3417 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3419 /* Header for a move list -- first line */
3421 switch (ics_getting_history) {
3425 case BeginningOfGame:
3426 /* User typed "moves" or "oldmoves" while we
3427 were idle. Pretend we asked for these
3428 moves and soak them up so user can step
3429 through them and/or save them.
3432 gameMode = IcsObserving;
3435 ics_getting_history = H_GOT_UNREQ_HEADER;
3437 case EditGame: /*?*/
3438 case EditPosition: /*?*/
3439 /* Should above feature work in these modes too? */
3440 /* For now it doesn't */
3441 ics_getting_history = H_GOT_UNWANTED_HEADER;
3444 ics_getting_history = H_GOT_UNWANTED_HEADER;
3449 /* Is this the right one? */
3450 if (gameInfo.white && gameInfo.black &&
3451 strcmp(gameInfo.white, star_match[0]) == 0 &&
3452 strcmp(gameInfo.black, star_match[2]) == 0) {
3454 ics_getting_history = H_GOT_REQ_HEADER;
3457 case H_GOT_REQ_HEADER:
3458 case H_GOT_UNREQ_HEADER:
3459 case H_GOT_UNWANTED_HEADER:
3460 case H_GETTING_MOVES:
3461 /* Should not happen */
3462 DisplayError(_("Error gathering move list: two headers"), 0);
3463 ics_getting_history = H_FALSE;
3467 /* Save player ratings into gameInfo if needed */
3468 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3469 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3470 (gameInfo.whiteRating == -1 ||
3471 gameInfo.blackRating == -1)) {
3473 gameInfo.whiteRating = string_to_rating(star_match[1]);
3474 gameInfo.blackRating = string_to_rating(star_match[3]);
3475 if (appData.debugMode)
3476 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3477 gameInfo.whiteRating, gameInfo.blackRating);
3482 if (looking_at(buf, &i,
3483 "* * match, initial time: * minute*, increment: * second")) {
3484 /* Header for a move list -- second line */
3485 /* Initial board will follow if this is a wild game */
3486 if (gameInfo.event != NULL) free(gameInfo.event);
3487 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3488 gameInfo.event = StrSave(str);
3489 /* [HGM] we switched variant. Translate boards if needed. */
3490 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3494 if (looking_at(buf, &i, "Move ")) {
3495 /* Beginning of a move list */
3496 switch (ics_getting_history) {
3498 /* Normally should not happen */
3499 /* Maybe user hit reset while we were parsing */
3502 /* Happens if we are ignoring a move list that is not
3503 * the one we just requested. Common if the user
3504 * tries to observe two games without turning off
3507 case H_GETTING_MOVES:
3508 /* Should not happen */
3509 DisplayError(_("Error gathering move list: nested"), 0);
3510 ics_getting_history = H_FALSE;
3512 case H_GOT_REQ_HEADER:
3513 ics_getting_history = H_GETTING_MOVES;
3514 started = STARTED_MOVES;
3516 if (oldi > next_out) {
3517 SendToPlayer(&buf[next_out], oldi - next_out);
3520 case H_GOT_UNREQ_HEADER:
3521 ics_getting_history = H_GETTING_MOVES;
3522 started = STARTED_MOVES_NOHIDE;
3525 case H_GOT_UNWANTED_HEADER:
3526 ics_getting_history = H_FALSE;
3532 if (looking_at(buf, &i, "% ") ||
3533 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3534 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3535 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3536 soughtPending = FALSE;
3540 if(suppressKibitz) next_out = i;
3541 savingComment = FALSE;
3545 case STARTED_MOVES_NOHIDE:
3546 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3547 parse[parse_pos + i - oldi] = NULLCHAR;
3548 ParseGameHistory(parse);
3550 if (appData.zippyPlay && first.initDone) {
3551 FeedMovesToProgram(&first, forwardMostMove);
3552 if (gameMode == IcsPlayingWhite) {
3553 if (WhiteOnMove(forwardMostMove)) {
3554 if (first.sendTime) {
3555 if (first.useColors) {
3556 SendToProgram("black\n", &first);
3558 SendTimeRemaining(&first, TRUE);
3560 if (first.useColors) {
3561 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3563 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3564 first.maybeThinking = TRUE;
3566 if (first.usePlayother) {
3567 if (first.sendTime) {
3568 SendTimeRemaining(&first, TRUE);
3570 SendToProgram("playother\n", &first);
3576 } else if (gameMode == IcsPlayingBlack) {
3577 if (!WhiteOnMove(forwardMostMove)) {
3578 if (first.sendTime) {
3579 if (first.useColors) {
3580 SendToProgram("white\n", &first);
3582 SendTimeRemaining(&first, FALSE);
3584 if (first.useColors) {
3585 SendToProgram("black\n", &first);
3587 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3588 first.maybeThinking = TRUE;
3590 if (first.usePlayother) {
3591 if (first.sendTime) {
3592 SendTimeRemaining(&first, FALSE);
3594 SendToProgram("playother\n", &first);
3603 if (gameMode == IcsObserving && ics_gamenum == -1) {
3604 /* Moves came from oldmoves or moves command
3605 while we weren't doing anything else.
3607 currentMove = forwardMostMove;
3608 ClearHighlights();/*!!could figure this out*/
3609 flipView = appData.flipView;
3610 DrawPosition(TRUE, boards[currentMove]);
3611 DisplayBothClocks();
3612 snprintf(str, MSG_SIZ, "%s vs. %s",
3613 gameInfo.white, gameInfo.black);
3617 /* Moves were history of an active game */
3618 if (gameInfo.resultDetails != NULL) {
3619 free(gameInfo.resultDetails);
3620 gameInfo.resultDetails = NULL;
3623 HistorySet(parseList, backwardMostMove,
3624 forwardMostMove, currentMove-1);
3625 DisplayMove(currentMove - 1);
3626 if (started == STARTED_MOVES) next_out = i;
3627 started = STARTED_NONE;
3628 ics_getting_history = H_FALSE;
3631 case STARTED_OBSERVE:
3632 started = STARTED_NONE;
3633 SendToICS(ics_prefix);
3634 SendToICS("refresh\n");
3640 if(bookHit) { // [HGM] book: simulate book reply
3641 static char bookMove[MSG_SIZ]; // a bit generous?
3643 programStats.nodes = programStats.depth = programStats.time =
3644 programStats.score = programStats.got_only_move = 0;
3645 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3647 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3648 strcat(bookMove, bookHit);
3649 HandleMachineMove(bookMove, &first);
3654 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3655 started == STARTED_HOLDINGS ||
3656 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3657 /* Accumulate characters in move list or board */
3658 parse[parse_pos++] = buf[i];
3661 /* Start of game messages. Mostly we detect start of game
3662 when the first board image arrives. On some versions
3663 of the ICS, though, we need to do a "refresh" after starting
3664 to observe in order to get the current board right away. */
3665 if (looking_at(buf, &i, "Adding game * to observation list")) {
3666 started = STARTED_OBSERVE;
3670 /* Handle auto-observe */
3671 if (appData.autoObserve &&
3672 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3673 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3675 /* Choose the player that was highlighted, if any. */
3676 if (star_match[0][0] == '\033' ||
3677 star_match[1][0] != '\033') {
3678 player = star_match[0];
3680 player = star_match[2];
3682 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3683 ics_prefix, StripHighlightAndTitle(player));
3686 /* Save ratings from notify string */
3687 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3688 player1Rating = string_to_rating(star_match[1]);
3689 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3690 player2Rating = string_to_rating(star_match[3]);
3692 if (appData.debugMode)
3694 "Ratings from 'Game notification:' %s %d, %s %d\n",
3695 player1Name, player1Rating,
3696 player2Name, player2Rating);
3701 /* Deal with automatic examine mode after a game,
3702 and with IcsObserving -> IcsExamining transition */
3703 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3704 looking_at(buf, &i, "has made you an examiner of game *")) {
3706 int gamenum = atoi(star_match[0]);
3707 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3708 gamenum == ics_gamenum) {
3709 /* We were already playing or observing this game;
3710 no need to refetch history */
3711 gameMode = IcsExamining;
3713 pauseExamForwardMostMove = forwardMostMove;
3714 } else if (currentMove < forwardMostMove) {
3715 ForwardInner(forwardMostMove);
3718 /* I don't think this case really can happen */
3719 SendToICS(ics_prefix);
3720 SendToICS("refresh\n");
3725 /* Error messages */
3726 // if (ics_user_moved) {
3727 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3728 if (looking_at(buf, &i, "Illegal move") ||
3729 looking_at(buf, &i, "Not a legal move") ||
3730 looking_at(buf, &i, "Your king is in check") ||
3731 looking_at(buf, &i, "It isn't your turn") ||
3732 looking_at(buf, &i, "It is not your move")) {
3734 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3735 currentMove = forwardMostMove-1;
3736 DisplayMove(currentMove - 1); /* before DMError */
3737 DrawPosition(FALSE, boards[currentMove]);
3738 SwitchClocks(forwardMostMove-1); // [HGM] race
3739 DisplayBothClocks();
3741 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3747 if (looking_at(buf, &i, "still have time") ||
3748 looking_at(buf, &i, "not out of time") ||
3749 looking_at(buf, &i, "either player is out of time") ||
3750 looking_at(buf, &i, "has timeseal; checking")) {
3751 /* We must have called his flag a little too soon */
3752 whiteFlag = blackFlag = FALSE;
3756 if (looking_at(buf, &i, "added * seconds to") ||
3757 looking_at(buf, &i, "seconds were added to")) {
3758 /* Update the clocks */
3759 SendToICS(ics_prefix);
3760 SendToICS("refresh\n");
3764 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3765 ics_clock_paused = TRUE;
3770 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3771 ics_clock_paused = FALSE;
3776 /* Grab player ratings from the Creating: message.
3777 Note we have to check for the special case when
3778 the ICS inserts things like [white] or [black]. */
3779 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3780 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3782 0 player 1 name (not necessarily white)
3784 2 empty, white, or black (IGNORED)
3785 3 player 2 name (not necessarily black)
3788 The names/ratings are sorted out when the game
3789 actually starts (below).
3791 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3792 player1Rating = string_to_rating(star_match[1]);
3793 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3794 player2Rating = string_to_rating(star_match[4]);
3796 if (appData.debugMode)
3798 "Ratings from 'Creating:' %s %d, %s %d\n",
3799 player1Name, player1Rating,
3800 player2Name, player2Rating);
3805 /* Improved generic start/end-of-game messages */
3806 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3807 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3808 /* If tkind == 0: */
3809 /* star_match[0] is the game number */
3810 /* [1] is the white player's name */
3811 /* [2] is the black player's name */
3812 /* For end-of-game: */
3813 /* [3] is the reason for the game end */
3814 /* [4] is a PGN end game-token, preceded by " " */
3815 /* For start-of-game: */
3816 /* [3] begins with "Creating" or "Continuing" */
3817 /* [4] is " *" or empty (don't care). */
3818 int gamenum = atoi(star_match[0]);
3819 char *whitename, *blackname, *why, *endtoken;
3820 ChessMove endtype = EndOfFile;
3823 whitename = star_match[1];
3824 blackname = star_match[2];
3825 why = star_match[3];
3826 endtoken = star_match[4];
3828 whitename = star_match[1];
3829 blackname = star_match[3];
3830 why = star_match[5];
3831 endtoken = star_match[6];
3834 /* Game start messages */
3835 if (strncmp(why, "Creating ", 9) == 0 ||
3836 strncmp(why, "Continuing ", 11) == 0) {
3837 gs_gamenum = gamenum;
3838 safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3839 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3841 if (appData.zippyPlay) {
3842 ZippyGameStart(whitename, blackname);
3845 partnerBoardValid = FALSE; // [HGM] bughouse
3849 /* Game end messages */
3850 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3851 ics_gamenum != gamenum) {
3854 while (endtoken[0] == ' ') endtoken++;
3855 switch (endtoken[0]) {
3858 endtype = GameUnfinished;
3861 endtype = BlackWins;
3864 if (endtoken[1] == '/')
3865 endtype = GameIsDrawn;