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 int flock(int f, int code);
75 #include <sys/types.h>
84 #else /* not STDC_HEADERS */
87 # else /* not HAVE_STRING_H */
89 # endif /* not HAVE_STRING_H */
90 #endif /* not STDC_HEADERS */
93 # include <sys/fcntl.h>
94 #else /* not HAVE_SYS_FCNTL_H */
97 # endif /* HAVE_FCNTL_H */
98 #endif /* not HAVE_SYS_FCNTL_H */
100 #if TIME_WITH_SYS_TIME
101 # include <sys/time.h>
105 # include <sys/time.h>
111 #if defined(_amigados) && !defined(__GNUC__)
116 extern int gettimeofday(struct timeval *, struct timezone *);
124 #include "frontend.h"
131 #include "backendz.h"
135 # define _(s) gettext (s)
136 # define N_(s) gettext_noop (s)
137 # define T_(s) gettext(s)
150 /* A point in time */
152 long sec; /* Assuming this is >= 32 bits */
153 int ms; /* Assuming this is >= 16 bits */
156 int establish P((void));
157 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
158 char *buf, int count, int error));
159 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
160 char *buf, int count, int error));
161 void ics_printf P((char *format, ...));
162 void SendToICS P((char *s));
163 void SendToICSDelayed P((char *s, long msdelay));
164 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
165 void HandleMachineMove P((char *message, ChessProgramState *cps));
166 int AutoPlayOneMove P((void));
167 int LoadGameOneMove P((ChessMove readAhead));
168 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
169 int LoadPositionFromFile P((char *filename, int n, char *title));
170 int SavePositionToFile P((char *filename));
171 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
173 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
174 void ShowMove P((int fromX, int fromY, int toX, int toY));
175 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
176 /*char*/int promoChar));
177 void BackwardInner P((int target));
178 void ForwardInner P((int target));
179 int Adjudicate P((ChessProgramState *cps));
180 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
181 void EditPositionDone P((Boolean fakeRights));
182 void PrintOpponents P((FILE *fp));
183 void PrintPosition P((FILE *fp, int move));
184 void StartChessProgram P((ChessProgramState *cps));
185 void SendToProgram P((char *message, ChessProgramState *cps));
186 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
187 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
188 char *buf, int count, int error));
189 void SendTimeControl P((ChessProgramState *cps,
190 int mps, long tc, int inc, int sd, int st));
191 char *TimeControlTagValue P((void));
192 void Attention P((ChessProgramState *cps));
193 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
194 int ResurrectChessProgram P((void));
195 void DisplayComment P((int moveNumber, char *text));
196 void DisplayMove P((int moveNumber));
198 void ParseGameHistory P((char *game));
199 void ParseBoard12 P((char *string));
200 void KeepAlive P((void));
201 void StartClocks P((void));
202 void SwitchClocks P((int nr));
203 void StopClocks P((void));
204 void ResetClocks P((void));
205 char *PGNDate P((void));
206 void SetGameInfo P((void));
207 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
208 int RegisterMove P((void));
209 void MakeRegisteredMove P((void));
210 void TruncateGame P((void));
211 int looking_at P((char *, int *, char *));
212 void CopyPlayerNameIntoFileName P((char **, char *));
213 char *SavePart P((char *));
214 int SaveGameOldStyle P((FILE *));
215 int SaveGamePGN P((FILE *));
216 void GetTimeMark P((TimeMark *));
217 long SubtractTimeMarks P((TimeMark *, TimeMark *));
218 int CheckFlags P((void));
219 long NextTickLength P((long));
220 void CheckTimeControl P((void));
221 void show_bytes P((FILE *, char *, int));
222 int string_to_rating P((char *str));
223 void ParseFeatures P((char* args, ChessProgramState *cps));
224 void InitBackEnd3 P((void));
225 void FeatureDone P((ChessProgramState* cps, int val));
226 void InitChessProgram P((ChessProgramState *cps, int setup));
227 void OutputKibitz(int window, char *text);
228 int PerpetualChase(int first, int last);
229 int EngineOutputIsUp();
230 void InitDrawingSizes(int x, int y);
231 void NextMatchGame P((void));
232 int NextTourneyGame P((int nr, int *swap));
233 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
234 FILE *WriteTourneyFile P((char *results, FILE *f));
235 void DisplayTwoMachinesTitle P(());
238 extern void ConsoleCreate();
241 ChessProgramState *WhitePlayer();
242 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
243 int VerifyDisplayMode P(());
245 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
246 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
247 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
248 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
249 void ics_update_width P((int new_width));
250 extern char installDir[MSG_SIZ];
251 VariantClass startVariant; /* [HGM] nicks: initial variant */
254 extern int tinyLayout, smallLayout;
255 ChessProgramStats programStats;
256 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
258 static int exiting = 0; /* [HGM] moved to top */
259 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
260 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
261 Board partnerBoard; /* [HGM] bughouse: for peeking at partner game */
262 int partnerHighlight[2];
263 Boolean partnerBoardValid = 0;
264 char partnerStatus[MSG_SIZ];
266 Boolean originalFlip;
267 Boolean twoBoards = 0;
268 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
269 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
270 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
271 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
272 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
273 int opponentKibitzes;
274 int lastSavedGame; /* [HGM] save: ID of game */
275 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
276 extern int chatCount;
278 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
279 char lastMsg[MSG_SIZ];
280 ChessSquare pieceSweep = EmptySquare;
281 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
282 int promoDefaultAltered;
284 /* States for ics_getting_history */
286 #define H_REQUESTED 1
287 #define H_GOT_REQ_HEADER 2
288 #define H_GOT_UNREQ_HEADER 3
289 #define H_GETTING_MOVES 4
290 #define H_GOT_UNWANTED_HEADER 5
292 /* whosays values for GameEnds */
301 /* Maximum number of games in a cmail message */
302 #define CMAIL_MAX_GAMES 20
304 /* Different types of move when calling RegisterMove */
306 #define CMAIL_RESIGN 1
308 #define CMAIL_ACCEPT 3
310 /* Different types of result to remember for each game */
311 #define CMAIL_NOT_RESULT 0
312 #define CMAIL_OLD_RESULT 1
313 #define CMAIL_NEW_RESULT 2
315 /* Telnet protocol constants */
326 safeStrCpy( char *dst, const char *src, size_t count )
329 assert( dst != NULL );
330 assert( src != NULL );
333 for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
334 if( i == count && dst[count-1] != NULLCHAR)
336 dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
337 if(appData.debugMode)
338 fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
344 /* Some compiler can't cast u64 to double
345 * This function do the job for us:
347 * We use the highest bit for cast, this only
348 * works if the highest bit is not
349 * in use (This should not happen)
351 * We used this for all compiler
354 u64ToDouble(u64 value)
357 u64 tmp = value & u64Const(0x7fffffffffffffff);
358 r = (double)(s64)tmp;
359 if (value & u64Const(0x8000000000000000))
360 r += 9.2233720368547758080e18; /* 2^63 */
364 /* Fake up flags for now, as we aren't keeping track of castling
365 availability yet. [HGM] Change of logic: the flag now only
366 indicates the type of castlings allowed by the rule of the game.
367 The actual rights themselves are maintained in the array
368 castlingRights, as part of the game history, and are not probed
374 int flags = F_ALL_CASTLE_OK;
375 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
376 switch (gameInfo.variant) {
378 flags &= ~F_ALL_CASTLE_OK;
379 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
380 flags |= F_IGNORE_CHECK;
382 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
385 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
387 case VariantKriegspiel:
388 flags |= F_KRIEGSPIEL_CAPTURE;
390 case VariantCapaRandom:
391 case VariantFischeRandom:
392 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
393 case VariantNoCastle:
394 case VariantShatranj:
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 Boolean adjustedClock;
469 long timeControl_2; /* [AS] Allow separate time controls */
470 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
471 long timeRemaining[2][MAX_MOVES];
472 int matchGame = 0, nextGame = 0, roundNr = 0;
473 Boolean waitingForGame = FALSE;
474 TimeMark programStartTime, pauseStart;
475 char ics_handle[MSG_SIZ];
476 int have_set_title = 0;
478 /* animateTraining preserves the state of appData.animate
479 * when Training mode is activated. This allows the
480 * response to be animated when appData.animate == TRUE and
481 * appData.animateDragging == TRUE.
483 Boolean animateTraining;
489 Board boards[MAX_MOVES];
490 /* [HGM] Following 7 needed for accurate legality tests: */
491 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
492 signed char initialRights[BOARD_FILES];
493 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
494 int initialRulePlies, FENrulePlies;
495 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
497 Boolean shuffleOpenings;
498 int mute; // mute all sounds
500 // [HGM] vari: next 12 to save and restore variations
501 #define MAX_VARIATIONS 10
502 int framePtr = MAX_MOVES-1; // points to free stack entry
504 int savedFirst[MAX_VARIATIONS];
505 int savedLast[MAX_VARIATIONS];
506 int savedFramePtr[MAX_VARIATIONS];
507 char *savedDetails[MAX_VARIATIONS];
508 ChessMove savedResult[MAX_VARIATIONS];
510 void PushTail P((int firstMove, int lastMove));
511 Boolean PopTail P((Boolean annotate));
512 void PushInner P((int firstMove, int lastMove));
513 void PopInner P((Boolean annotate));
514 void CleanupTail P((void));
516 ChessSquare FIDEArray[2][BOARD_FILES] = {
517 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
518 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
519 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
520 BlackKing, BlackBishop, BlackKnight, BlackRook }
523 ChessSquare twoKingsArray[2][BOARD_FILES] = {
524 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
525 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
526 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
527 BlackKing, BlackKing, BlackKnight, BlackRook }
530 ChessSquare KnightmateArray[2][BOARD_FILES] = {
531 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
532 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
533 { BlackRook, BlackMan, BlackBishop, BlackQueen,
534 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
537 ChessSquare SpartanArray[2][BOARD_FILES] = {
538 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
539 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
540 { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
541 BlackDragon, BlackKing, BlackAngel, BlackAlfil }
544 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
545 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
546 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
547 { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
548 BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
551 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
552 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
553 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
554 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
555 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
558 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
559 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
560 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
561 { BlackRook, BlackKnight, BlackMan, BlackFerz,
562 BlackKing, BlackMan, BlackKnight, BlackRook }
566 #if (BOARD_FILES>=10)
567 ChessSquare ShogiArray[2][BOARD_FILES] = {
568 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
569 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
570 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
571 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
574 ChessSquare XiangqiArray[2][BOARD_FILES] = {
575 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
576 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
577 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
578 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
581 ChessSquare CapablancaArray[2][BOARD_FILES] = {
582 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
583 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
584 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
585 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
588 ChessSquare GreatArray[2][BOARD_FILES] = {
589 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
590 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
591 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
592 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
595 ChessSquare JanusArray[2][BOARD_FILES] = {
596 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
597 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
598 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
599 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
602 ChessSquare GrandArray[2][BOARD_FILES] = {
603 { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
604 WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
605 { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
606 BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
610 ChessSquare GothicArray[2][BOARD_FILES] = {
611 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
612 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
613 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
614 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
617 #define GothicArray CapablancaArray
621 ChessSquare FalconArray[2][BOARD_FILES] = {
622 { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
623 WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
624 { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
625 BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
628 #define FalconArray CapablancaArray
631 #else // !(BOARD_FILES>=10)
632 #define XiangqiPosition FIDEArray
633 #define CapablancaArray FIDEArray
634 #define GothicArray FIDEArray
635 #define GreatArray FIDEArray
636 #endif // !(BOARD_FILES>=10)
638 #if (BOARD_FILES>=12)
639 ChessSquare CourierArray[2][BOARD_FILES] = {
640 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
641 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
642 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
643 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
645 #else // !(BOARD_FILES>=12)
646 #define CourierArray CapablancaArray
647 #endif // !(BOARD_FILES>=12)
650 Board initialPosition;
653 /* Convert str to a rating. Checks for special cases of "----",
655 "++++", etc. Also strips ()'s */
657 string_to_rating(str)
660 while(*str && !isdigit(*str)) ++str;
662 return 0; /* One of the special "no rating" cases */
670 /* Init programStats */
671 programStats.movelist[0] = 0;
672 programStats.depth = 0;
673 programStats.nr_moves = 0;
674 programStats.moves_left = 0;
675 programStats.nodes = 0;
676 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
677 programStats.score = 0;
678 programStats.got_only_move = 0;
679 programStats.got_fail = 0;
680 programStats.line_is_book = 0;
685 { // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
686 if (appData.firstPlaysBlack) {
687 first.twoMachinesColor = "black\n";
688 second.twoMachinesColor = "white\n";
690 first.twoMachinesColor = "white\n";
691 second.twoMachinesColor = "black\n";
694 first.other = &second;
695 second.other = &first;
698 if(appData.timeOddsMode) {
699 norm = appData.timeOdds[0];
700 if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
702 first.timeOdds = appData.timeOdds[0]/norm;
703 second.timeOdds = appData.timeOdds[1]/norm;
706 if(programVersion) free(programVersion);
707 if (appData.noChessProgram) {
708 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
709 sprintf(programVersion, "%s", PACKAGE_STRING);
711 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
712 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
713 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
718 UnloadEngine(ChessProgramState *cps)
720 /* Kill off first chess program */
721 if (cps->isr != NULL)
722 RemoveInputSource(cps->isr);
725 if (cps->pr != NoProc) {
727 DoSleep( appData.delayBeforeQuit );
728 SendToProgram("quit\n", cps);
729 DoSleep( appData.delayAfterQuit );
730 DestroyChildProcess(cps->pr, cps->useSigterm);
733 if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
737 ClearOptions(ChessProgramState *cps)
740 cps->nrOptions = cps->comboCnt = 0;
741 for(i=0; i<MAX_OPTIONS; i++) {
742 cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
743 cps->option[i].textValue = 0;
747 char *engineNames[] = {
753 InitEngine(ChessProgramState *cps, int n)
754 { // [HGM] all engine initialiation put in a function that does one engine
758 cps->which = engineNames[n];
759 cps->maybeThinking = FALSE;
763 cps->sendDrawOffers = 1;
765 cps->program = appData.chessProgram[n];
766 cps->host = appData.host[n];
767 cps->dir = appData.directory[n];
768 cps->initString = appData.engInitString[n];
769 cps->computerString = appData.computerString[n];
770 cps->useSigint = TRUE;
771 cps->useSigterm = TRUE;
772 cps->reuse = appData.reuse[n];
773 cps->nps = appData.NPS[n]; // [HGM] nps: copy nodes per second
774 cps->useSetboard = FALSE;
776 cps->usePing = FALSE;
779 cps->usePlayother = FALSE;
780 cps->useColors = TRUE;
781 cps->useUsermove = FALSE;
782 cps->sendICS = FALSE;
783 cps->sendName = appData.icsActive;
784 cps->sdKludge = FALSE;
785 cps->stKludge = FALSE;
786 TidyProgramName(cps->program, cps->host, cps->tidy);
788 safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
789 cps->analysisSupport = 2; /* detect */
790 cps->analyzing = FALSE;
791 cps->initDone = FALSE;
793 /* New features added by Tord: */
794 cps->useFEN960 = FALSE;
795 cps->useOOCastle = TRUE;
796 /* End of new features added by Tord. */
797 cps->fenOverride = appData.fenOverride[n];
799 /* [HGM] time odds: set factor for each machine */
800 cps->timeOdds = appData.timeOdds[n];
802 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
803 cps->accumulateTC = appData.accumulateTC[n];
804 cps->maxNrOfSessions = 1;
809 cps->supportsNPS = UNKNOWN;
810 cps->memSize = FALSE;
811 cps->maxCores = FALSE;
812 cps->egtFormats[0] = NULLCHAR;
815 cps->optionSettings = appData.engOptions[n];
817 cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
818 cps->isUCI = appData.isUCI[n]; /* [AS] */
819 cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
821 if (appData.protocolVersion[n] > PROTOVER
822 || appData.protocolVersion[n] < 1)
827 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
828 appData.protocolVersion[n]);
829 if( (len > MSG_SIZ) && appData.debugMode )
830 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
832 DisplayFatalError(buf, 0, 2);
836 cps->protocolVersion = appData.protocolVersion[n];
839 InitEngineUCI( installDir, cps ); // [HGM] moved here from winboard.c, to make available in xboard
842 ChessProgramState *savCps;
848 if(WaitForEngine(savCps, LoadEngine)) return;
849 CommonEngineInit(); // recalculate time odds
850 if(gameInfo.variant != StringToVariant(appData.variant)) {
851 // we changed variant when loading the engine; this forces us to reset
852 Reset(TRUE, savCps != &first);
853 EditGameEvent(); // for consistency with other path, as Reset changes mode
855 InitChessProgram(savCps, FALSE);
856 SendToProgram("force\n", savCps);
857 DisplayMessage("", "");
858 if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
859 for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
865 ReplaceEngine(ChessProgramState *cps, int n)
869 appData.noChessProgram = FALSE;
870 appData.clockMode = TRUE;
873 if(n) return; // only startup first engine immediately; second can wait
874 savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
878 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
879 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
881 static char resetOptions[] =
882 "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
883 "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
884 "-firstOptions \"\" -firstNPS -1 -fn \"\"";
887 Load(ChessProgramState *cps, int i)
889 char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
890 if(engineLine && engineLine[0]) { // an engine was selected from the combo box
891 snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
892 SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
893 ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL; appData.pvSAN[0] = FALSE;
894 ParseArgsFromString(buf);
896 ReplaceEngine(cps, i);
900 while(q = strchr(p, SLASH)) p = q+1;
901 if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
902 if(engineDir[0] != NULLCHAR)
903 appData.directory[i] = engineDir;
904 else if(p != engineName) { // derive directory from engine path, when not given
906 appData.directory[i] = strdup(engineName);
908 } else appData.directory[i] = ".";
910 if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
911 snprintf(command, MSG_SIZ, "%s %s", p, params);
914 appData.chessProgram[i] = strdup(p);
915 appData.isUCI[i] = isUCI;
916 appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
917 appData.hasOwnBookUCI[i] = hasBook;
918 if(!nickName[0]) useNick = FALSE;
919 if(useNick) ASSIGN(appData.pgnName[i], nickName);
923 q = firstChessProgramNames;
924 if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
925 quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
926 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
927 quote, p, quote, appData.directory[i],
928 useNick ? " -fn \"" : "",
929 useNick ? nickName : "",
931 v1 ? " -firstProtocolVersion 1" : "",
932 hasBook ? "" : " -fNoOwnBookUCI",
933 isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
934 storeVariant ? " -variant " : "",
935 storeVariant ? VariantName(gameInfo.variant) : "");
936 firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
937 snprintf(firstChessProgramNames, len, "%s%s", q, buf);
940 ReplaceEngine(cps, i);
946 int matched, min, sec;
948 * Parse timeControl resource
950 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
951 appData.movesPerSession)) {
953 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
954 DisplayFatalError(buf, 0, 2);
958 * Parse searchTime resource
960 if (*appData.searchTime != NULLCHAR) {
961 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
963 searchTime = min * 60;
964 } else if (matched == 2) {
965 searchTime = min * 60 + sec;
968 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
969 DisplayFatalError(buf, 0, 2);
978 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
979 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
981 GetTimeMark(&programStartTime);
982 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
983 appData.seedBase = random() + (random()<<15);
984 pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
987 programStats.ok_to_send = 1;
988 programStats.seen_stat = 0;
991 * Initialize game list
997 * Internet chess server status
999 if (appData.icsActive) {
1000 appData.matchMode = FALSE;
1001 appData.matchGames = 0;
1003 appData.noChessProgram = !appData.zippyPlay;
1005 appData.zippyPlay = FALSE;
1006 appData.zippyTalk = FALSE;
1007 appData.noChessProgram = TRUE;
1009 if (*appData.icsHelper != NULLCHAR) {
1010 appData.useTelnet = TRUE;
1011 appData.telnetProgram = appData.icsHelper;
1014 appData.zippyTalk = appData.zippyPlay = FALSE;
1017 /* [AS] Initialize pv info list [HGM] and game state */
1021 for( i=0; i<=framePtr; i++ ) {
1022 pvInfoList[i].depth = -1;
1023 boards[i][EP_STATUS] = EP_NONE;
1024 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1030 /* [AS] Adjudication threshold */
1031 adjudicateLossThreshold = appData.adjudicateLossThreshold;
1033 InitEngine(&first, 0);
1034 InitEngine(&second, 1);
1037 pairing.which = "pairing"; // pairing engine
1038 pairing.pr = NoProc;
1040 pairing.program = appData.pairingEngine;
1041 pairing.host = "localhost";
1044 if (appData.icsActive) {
1045 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
1046 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1047 appData.clockMode = FALSE;
1048 first.sendTime = second.sendTime = 0;
1052 /* Override some settings from environment variables, for backward
1053 compatibility. Unfortunately it's not feasible to have the env
1054 vars just set defaults, at least in xboard. Ugh.
1056 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1061 if (!appData.icsActive) {
1065 /* Check for variants that are supported only in ICS mode,
1066 or not at all. Some that are accepted here nevertheless
1067 have bugs; see comments below.
1069 VariantClass variant = StringToVariant(appData.variant);
1071 case VariantBughouse: /* need four players and two boards */
1072 case VariantKriegspiel: /* need to hide pieces and move details */
1073 /* case VariantFischeRandom: (Fabien: moved below) */
1074 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1075 if( (len > MSG_SIZ) && appData.debugMode )
1076 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1078 DisplayFatalError(buf, 0, 2);
1081 case VariantUnknown:
1082 case VariantLoadable:
1092 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1093 if( (len > MSG_SIZ) && appData.debugMode )
1094 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1096 DisplayFatalError(buf, 0, 2);
1099 case VariantXiangqi: /* [HGM] repetition rules not implemented */
1100 case VariantFairy: /* [HGM] TestLegality definitely off! */
1101 case VariantGothic: /* [HGM] should work */
1102 case VariantCapablanca: /* [HGM] should work */
1103 case VariantCourier: /* [HGM] initial forced moves not implemented */
1104 case VariantShogi: /* [HGM] could still mate with pawn drop */
1105 case VariantKnightmate: /* [HGM] should work */
1106 case VariantCylinder: /* [HGM] untested */
1107 case VariantFalcon: /* [HGM] untested */
1108 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1109 offboard interposition not understood */
1110 case VariantNormal: /* definitely works! */
1111 case VariantWildCastle: /* pieces not automatically shuffled */
1112 case VariantNoCastle: /* pieces not automatically shuffled */
1113 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1114 case VariantLosers: /* should work except for win condition,
1115 and doesn't know captures are mandatory */
1116 case VariantSuicide: /* should work except for win condition,
1117 and doesn't know captures are mandatory */
1118 case VariantGiveaway: /* should work except for win condition,
1119 and doesn't know captures are mandatory */
1120 case VariantTwoKings: /* should work */
1121 case VariantAtomic: /* should work except for win condition */
1122 case Variant3Check: /* should work except for win condition */
1123 case VariantShatranj: /* should work except for all win conditions */
1124 case VariantMakruk: /* should work except for draw countdown */
1125 case VariantBerolina: /* might work if TestLegality is off */
1126 case VariantCapaRandom: /* should work */
1127 case VariantJanus: /* should work */
1128 case VariantSuper: /* experimental */
1129 case VariantGreat: /* experimental, requires legality testing to be off */
1130 case VariantSChess: /* S-Chess, should work */
1131 case VariantGrand: /* should work */
1132 case VariantSpartan: /* should work */
1139 int NextIntegerFromString( char ** str, long * value )
1144 while( *s == ' ' || *s == '\t' ) {
1150 if( *s >= '0' && *s <= '9' ) {
1151 while( *s >= '0' && *s <= '9' ) {
1152 *value = *value * 10 + (*s - '0');
1164 int NextTimeControlFromString( char ** str, long * value )
1167 int result = NextIntegerFromString( str, &temp );
1170 *value = temp * 60; /* Minutes */
1171 if( **str == ':' ) {
1173 result = NextIntegerFromString( str, &temp );
1174 *value += temp; /* Seconds */
1181 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1182 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1183 int result = -1, type = 0; long temp, temp2;
1185 if(**str != ':') return -1; // old params remain in force!
1187 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1188 if( NextIntegerFromString( str, &temp ) ) return -1;
1189 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1192 /* time only: incremental or sudden-death time control */
1193 if(**str == '+') { /* increment follows; read it */
1195 if(**str == '!') type = *(*str)++; // Bronstein TC
1196 if(result = NextIntegerFromString( str, &temp2)) return -1;
1197 *inc = temp2 * 1000;
1198 if(**str == '.') { // read fraction of increment
1199 char *start = ++(*str);
1200 if(result = NextIntegerFromString( str, &temp2)) return -1;
1202 while(start++ < *str) temp2 /= 10;
1206 *moves = 0; *tc = temp * 1000; *incType = type;
1210 (*str)++; /* classical time control */
1211 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1222 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1223 { /* [HGM] get time to add from the multi-session time-control string */
1224 int incType, moves=1; /* kludge to force reading of first session */
1225 long time, increment;
1228 if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1229 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1231 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1232 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1233 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1234 if(movenr == -1) return time; /* last move before new session */
1235 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1236 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1237 if(!moves) return increment; /* current session is incremental */
1238 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1239 } while(movenr >= -1); /* try again for next session */
1241 return 0; // no new time quota on this move
1245 ParseTimeControl(tc, ti, mps)
1252 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1255 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1256 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1257 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1261 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1263 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1266 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1268 snprintf(buf, MSG_SIZ, ":%s", mytc);
1270 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1272 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1277 /* Parse second time control */
1280 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1288 timeControl_2 = tc2 * 1000;
1298 timeControl = tc1 * 1000;
1301 timeIncrement = ti * 1000; /* convert to ms */
1302 movesPerSession = 0;
1305 movesPerSession = mps;
1313 if (appData.debugMode) {
1314 fprintf(debugFP, "%s\n", programVersion);
1317 set_cont_sequence(appData.wrapContSeq);
1318 if (appData.matchGames > 0) {
1319 appData.matchMode = TRUE;
1320 } else if (appData.matchMode) {
1321 appData.matchGames = 1;
1323 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1324 appData.matchGames = appData.sameColorGames;
1325 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1326 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1327 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1330 if (appData.noChessProgram || first.protocolVersion == 1) {
1333 /* kludge: allow timeout for initial "feature" commands */
1335 DisplayMessage("", _("Starting chess program"));
1336 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1341 CalculateIndex(int index, int gameNr)
1342 { // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1344 if(index > 0) return index; // fixed nmber
1345 if(index == 0) return 1;
1346 res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1347 if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1352 LoadGameOrPosition(int gameNr)
1353 { // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1354 if (*appData.loadGameFile != NULLCHAR) {
1355 if (!LoadGameFromFile(appData.loadGameFile,
1356 CalculateIndex(appData.loadGameIndex, gameNr),
1357 appData.loadGameFile, FALSE)) {
1358 DisplayFatalError(_("Bad game file"), 0, 1);
1361 } else if (*appData.loadPositionFile != NULLCHAR) {
1362 if (!LoadPositionFromFile(appData.loadPositionFile,
1363 CalculateIndex(appData.loadPositionIndex, gameNr),
1364 appData.loadPositionFile)) {
1365 DisplayFatalError(_("Bad position file"), 0, 1);
1373 ReserveGame(int gameNr, char resChar)
1375 FILE *tf = fopen(appData.tourneyFile, "r+");
1376 char *p, *q, c, buf[MSG_SIZ];
1377 if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1378 safeStrCpy(buf, lastMsg, MSG_SIZ);
1379 DisplayMessage(_("Pick new game"), "");
1380 flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1381 ParseArgsFromFile(tf);
1382 p = q = appData.results;
1383 if(appData.debugMode) {
1384 char *r = appData.participants;
1385 fprintf(debugFP, "results = '%s'\n", p);
1386 while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1387 fprintf(debugFP, "\n");
1389 while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1391 q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1392 safeStrCpy(q, p, strlen(p) + 2);
1393 if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1394 if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1395 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1396 if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1399 fseek(tf, -(strlen(p)+4), SEEK_END);
1401 if(c != '"') // depending on DOS or Unix line endings we can be one off
1402 fseek(tf, -(strlen(p)+2), SEEK_END);
1403 else fseek(tf, -(strlen(p)+3), SEEK_END);
1404 fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1405 DisplayMessage(buf, "");
1406 free(p); appData.results = q;
1407 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1408 (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1409 UnloadEngine(&first); // next game belongs to other pairing;
1410 UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1415 MatchEvent(int mode)
1416 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1418 if(matchMode) { // already in match mode: switch it off
1420 if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1423 // if(gameMode != BeginningOfGame) {
1424 // DisplayError(_("You can only start a match from the initial position."), 0);
1428 if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1429 /* Set up machine vs. machine match */
1431 NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1432 if(appData.tourneyFile[0]) {
1434 if(nextGame > appData.matchGames) {
1436 if(strchr(appData.results, '*') == NULL) {
1438 appData.tourneyCycles++;
1439 if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1441 NextTourneyGame(-1, &dummy);
1443 if(nextGame <= appData.matchGames) {
1444 DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1446 ScheduleDelayedEvent(NextMatchGame, 10000);
1451 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1452 DisplayError(buf, 0);
1453 appData.tourneyFile[0] = 0;
1457 if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically
1458 DisplayFatalError(_("Can't have a match with no chess programs"),
1463 matchGame = roundNr = 1;
1464 first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1469 InitBackEnd3 P((void))
1471 GameMode initialMode;
1475 InitChessProgram(&first, startedFromSetupPosition);
1477 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1478 free(programVersion);
1479 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1480 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1483 if (appData.icsActive) {
1485 /* [DM] Make a console window if needed [HGM] merged ifs */
1491 if (*appData.icsCommPort != NULLCHAR)
1492 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1493 appData.icsCommPort);
1495 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1496 appData.icsHost, appData.icsPort);
1498 if( (len > MSG_SIZ) && appData.debugMode )
1499 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1501 DisplayFatalError(buf, err, 1);
1506 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1508 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1509 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1510 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1511 } else if (appData.noChessProgram) {
1517 if (*appData.cmailGameName != NULLCHAR) {
1519 OpenLoopback(&cmailPR);
1521 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1525 DisplayMessage("", "");
1526 if (StrCaseCmp(appData.initialMode, "") == 0) {
1527 initialMode = BeginningOfGame;
1528 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1529 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1530 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1531 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1534 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1535 initialMode = TwoMachinesPlay;
1536 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1537 initialMode = AnalyzeFile;
1538 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1539 initialMode = AnalyzeMode;
1540 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1541 initialMode = MachinePlaysWhite;
1542 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1543 initialMode = MachinePlaysBlack;
1544 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1545 initialMode = EditGame;
1546 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1547 initialMode = EditPosition;
1548 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1549 initialMode = Training;
1551 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1552 if( (len > MSG_SIZ) && appData.debugMode )
1553 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1555 DisplayFatalError(buf, 0, 2);
1559 if (appData.matchMode) {
1560 if(appData.tourneyFile[0]) { // start tourney from command line
1562 if(f = fopen(appData.tourneyFile, "r")) {
1563 ParseArgsFromFile(f); // make sure tourney parmeters re known
1565 appData.clockMode = TRUE;
1567 } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1570 } else if (*appData.cmailGameName != NULLCHAR) {
1571 /* Set up cmail mode */
1572 ReloadCmailMsgEvent(TRUE);
1574 /* Set up other modes */
1575 if (initialMode == AnalyzeFile) {
1576 if (*appData.loadGameFile == NULLCHAR) {
1577 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1581 if (*appData.loadGameFile != NULLCHAR) {
1582 (void) LoadGameFromFile(appData.loadGameFile,
1583 appData.loadGameIndex,
1584 appData.loadGameFile, TRUE);
1585 } else if (*appData.loadPositionFile != NULLCHAR) {
1586 (void) LoadPositionFromFile(appData.loadPositionFile,
1587 appData.loadPositionIndex,
1588 appData.loadPositionFile);
1589 /* [HGM] try to make self-starting even after FEN load */
1590 /* to allow automatic setup of fairy variants with wtm */
1591 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1592 gameMode = BeginningOfGame;
1593 setboardSpoiledMachineBlack = 1;
1595 /* [HGM] loadPos: make that every new game uses the setup */
1596 /* from file as long as we do not switch variant */
1597 if(!blackPlaysFirst) {
1598 startedFromPositionFile = TRUE;
1599 CopyBoard(filePosition, boards[0]);
1602 if (initialMode == AnalyzeMode) {
1603 if (appData.noChessProgram) {
1604 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1607 if (appData.icsActive) {
1608 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1612 } else if (initialMode == AnalyzeFile) {
1613 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1614 ShowThinkingEvent();
1616 AnalysisPeriodicEvent(1);
1617 } else if (initialMode == MachinePlaysWhite) {
1618 if (appData.noChessProgram) {
1619 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1623 if (appData.icsActive) {
1624 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1628 MachineWhiteEvent();
1629 } else if (initialMode == MachinePlaysBlack) {
1630 if (appData.noChessProgram) {
1631 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1635 if (appData.icsActive) {
1636 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1640 MachineBlackEvent();
1641 } else if (initialMode == TwoMachinesPlay) {
1642 if (appData.noChessProgram) {
1643 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1647 if (appData.icsActive) {
1648 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1653 } else if (initialMode == EditGame) {
1655 } else if (initialMode == EditPosition) {
1656 EditPositionEvent();
1657 } else if (initialMode == Training) {
1658 if (*appData.loadGameFile == NULLCHAR) {
1659 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1668 HistorySet( char movelist[][2*MOVE_LEN], int first, int last, int current )
1670 DisplayBook(current+1);
1672 MoveHistorySet( movelist, first, last, current, pvInfoList );
1674 EvalGraphSet( first, last, current, pvInfoList );
1676 MakeEngineOutputTitle();
1680 * Establish will establish a contact to a remote host.port.
1681 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1682 * used to talk to the host.
1683 * Returns 0 if okay, error code if not.
1690 if (*appData.icsCommPort != NULLCHAR) {
1691 /* Talk to the host through a serial comm port */
1692 return OpenCommPort(appData.icsCommPort, &icsPR);
1694 } else if (*appData.gateway != NULLCHAR) {
1695 if (*appData.remoteShell == NULLCHAR) {
1696 /* Use the rcmd protocol to run telnet program on a gateway host */
1697 snprintf(buf, sizeof(buf), "%s %s %s",
1698 appData.telnetProgram, appData.icsHost, appData.icsPort);
1699 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1702 /* Use the rsh program to run telnet program on a gateway host */
1703 if (*appData.remoteUser == NULLCHAR) {
1704 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1705 appData.gateway, appData.telnetProgram,
1706 appData.icsHost, appData.icsPort);
1708 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1709 appData.remoteShell, appData.gateway,
1710 appData.remoteUser, appData.telnetProgram,
1711 appData.icsHost, appData.icsPort);
1713 return StartChildProcess(buf, "", &icsPR);
1716 } else if (appData.useTelnet) {
1717 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1720 /* TCP socket interface differs somewhat between
1721 Unix and NT; handle details in the front end.
1723 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1727 void EscapeExpand(char *p, char *q)
1728 { // [HGM] initstring: routine to shape up string arguments
1729 while(*p++ = *q++) if(p[-1] == '\\')
1731 case 'n': p[-1] = '\n'; break;
1732 case 'r': p[-1] = '\r'; break;
1733 case 't': p[-1] = '\t'; break;
1734 case '\\': p[-1] = '\\'; break;
1735 case 0: *p = 0; return;
1736 default: p[-1] = q[-1]; break;
1741 show_bytes(fp, buf, count)
1747 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1748 fprintf(fp, "\\%03o", *buf & 0xff);
1757 /* Returns an errno value */
1759 OutputMaybeTelnet(pr, message, count, outError)
1765 char buf[8192], *p, *q, *buflim;
1766 int left, newcount, outcount;
1768 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1769 *appData.gateway != NULLCHAR) {
1770 if (appData.debugMode) {
1771 fprintf(debugFP, ">ICS: ");
1772 show_bytes(debugFP, message, count);
1773 fprintf(debugFP, "\n");
1775 return OutputToProcess(pr, message, count, outError);
1778 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1785 if (appData.debugMode) {
1786 fprintf(debugFP, ">ICS: ");
1787 show_bytes(debugFP, buf, newcount);
1788 fprintf(debugFP, "\n");
1790 outcount = OutputToProcess(pr, buf, newcount, outError);
1791 if (outcount < newcount) return -1; /* to be sure */
1798 } else if (((unsigned char) *p) == TN_IAC) {
1799 *q++ = (char) TN_IAC;
1806 if (appData.debugMode) {
1807 fprintf(debugFP, ">ICS: ");
1808 show_bytes(debugFP, buf, newcount);
1809 fprintf(debugFP, "\n");
1811 outcount = OutputToProcess(pr, buf, newcount, outError);
1812 if (outcount < newcount) return -1; /* to be sure */
1817 read_from_player(isr, closure, message, count, error)
1824 int outError, outCount;
1825 static int gotEof = 0;
1827 /* Pass data read from player on to ICS */
1830 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1831 if (outCount < count) {
1832 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1834 } else if (count < 0) {
1835 RemoveInputSource(isr);
1836 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1837 } else if (gotEof++ > 0) {
1838 RemoveInputSource(isr);
1839 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1845 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1846 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1847 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1848 SendToICS("date\n");
1849 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1852 /* added routine for printf style output to ics */
1853 void ics_printf(char *format, ...)
1855 char buffer[MSG_SIZ];
1858 va_start(args, format);
1859 vsnprintf(buffer, sizeof(buffer), format, args);
1860 buffer[sizeof(buffer)-1] = '\0';
1869 int count, outCount, outError;
1871 if (icsPR == NoProc) return;
1874 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1875 if (outCount < count) {
1876 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1880 /* This is used for sending logon scripts to the ICS. Sending
1881 without a delay causes problems when using timestamp on ICC
1882 (at least on my machine). */
1884 SendToICSDelayed(s,msdelay)
1888 int count, outCount, outError;
1890 if (icsPR == NoProc) return;
1893 if (appData.debugMode) {
1894 fprintf(debugFP, ">ICS: ");
1895 show_bytes(debugFP, s, count);
1896 fprintf(debugFP, "\n");
1898 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1900 if (outCount < count) {
1901 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1906 /* Remove all highlighting escape sequences in s
1907 Also deletes any suffix starting with '('
1910 StripHighlightAndTitle(s)
1913 static char retbuf[MSG_SIZ];
1916 while (*s != NULLCHAR) {
1917 while (*s == '\033') {
1918 while (*s != NULLCHAR && !isalpha(*s)) s++;
1919 if (*s != NULLCHAR) s++;
1921 while (*s != NULLCHAR && *s != '\033') {
1922 if (*s == '(' || *s == '[') {
1933 /* Remove all highlighting escape sequences in s */
1938 static char retbuf[MSG_SIZ];
1941 while (*s != NULLCHAR) {
1942 while (*s == '\033') {
1943 while (*s != NULLCHAR && !isalpha(*s)) s++;
1944 if (*s != NULLCHAR) s++;
1946 while (*s != NULLCHAR && *s != '\033') {
1954 char *variantNames[] = VARIANT_NAMES;
1959 return variantNames[v];
1963 /* Identify a variant from the strings the chess servers use or the
1964 PGN Variant tag names we use. */
1971 VariantClass v = VariantNormal;
1972 int i, found = FALSE;
1978 /* [HGM] skip over optional board-size prefixes */
1979 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1980 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1981 while( *e++ != '_');
1984 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1988 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1989 if (StrCaseStr(e, variantNames[i])) {
1990 v = (VariantClass) i;
1997 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1998 || StrCaseStr(e, "wild/fr")
1999 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2000 v = VariantFischeRandom;
2001 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2002 (i = 1, p = StrCaseStr(e, "w"))) {
2004 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2011 case 0: /* FICS only, actually */
2013 /* Castling legal even if K starts on d-file */
2014 v = VariantWildCastle;
2019 /* Castling illegal even if K & R happen to start in
2020 normal positions. */
2021 v = VariantNoCastle;
2034 /* Castling legal iff K & R start in normal positions */
2040 /* Special wilds for position setup; unclear what to do here */
2041 v = VariantLoadable;
2044 /* Bizarre ICC game */
2045 v = VariantTwoKings;
2048 v = VariantKriegspiel;
2054 v = VariantFischeRandom;
2057 v = VariantCrazyhouse;
2060 v = VariantBughouse;
2066 /* Not quite the same as FICS suicide! */
2067 v = VariantGiveaway;
2073 v = VariantShatranj;
2076 /* Temporary names for future ICC types. The name *will* change in
2077 the next xboard/WinBoard release after ICC defines it. */
2115 v = VariantCapablanca;
2118 v = VariantKnightmate;
2124 v = VariantCylinder;
2130 v = VariantCapaRandom;
2133 v = VariantBerolina;
2145 /* Found "wild" or "w" in the string but no number;
2146 must assume it's normal chess. */
2150 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2151 if( (len > MSG_SIZ) && appData.debugMode )
2152 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2154 DisplayError(buf, 0);
2160 if (appData.debugMode) {
2161 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2162 e, wnum, VariantName(v));
2167 static int leftover_start = 0, leftover_len = 0;
2168 char star_match[STAR_MATCH_N][MSG_SIZ];
2170 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2171 advance *index beyond it, and set leftover_start to the new value of
2172 *index; else return FALSE. If pattern contains the character '*', it
2173 matches any sequence of characters not containing '\r', '\n', or the
2174 character following the '*' (if any), and the matched sequence(s) are
2175 copied into star_match.
2178 looking_at(buf, index, pattern)
2183 char *bufp = &buf[*index], *patternp = pattern;
2185 char *matchp = star_match[0];
2188 if (*patternp == NULLCHAR) {
2189 *index = leftover_start = bufp - buf;
2193 if (*bufp == NULLCHAR) return FALSE;
2194 if (*patternp == '*') {
2195 if (*bufp == *(patternp + 1)) {
2197 matchp = star_match[++star_count];
2201 } else if (*bufp == '\n' || *bufp == '\r') {
2203 if (*patternp == NULLCHAR)
2208 *matchp++ = *bufp++;
2212 if (*patternp != *bufp) return FALSE;
2219 SendToPlayer(data, length)
2223 int error, outCount;
2224 outCount = OutputToProcess(NoProc, data, length, &error);
2225 if (outCount < length) {
2226 DisplayFatalError(_("Error writing to display"), error, 1);
2231 PackHolding(packed, holding)
2243 switch (runlength) {
2254 sprintf(q, "%d", runlength);
2266 /* Telnet protocol requests from the front end */
2268 TelnetRequest(ddww, option)
2269 unsigned char ddww, option;
2271 unsigned char msg[3];
2272 int outCount, outError;
2274 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2276 if (appData.debugMode) {
2277 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2293 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2302 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2305 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2310 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2312 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2319 if (!appData.icsActive) return;
2320 TelnetRequest(TN_DO, TN_ECHO);
2326 if (!appData.icsActive) return;
2327 TelnetRequest(TN_DONT, TN_ECHO);
2331 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2333 /* put the holdings sent to us by the server on the board holdings area */
2334 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2338 if(gameInfo.holdingsWidth < 2) return;
2339 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2340 return; // prevent overwriting by pre-board holdings
2342 if( (int)lowestPiece >= BlackPawn ) {
2345 holdingsStartRow = BOARD_HEIGHT-1;
2348 holdingsColumn = BOARD_WIDTH-1;
2349 countsColumn = BOARD_WIDTH-2;
2350 holdingsStartRow = 0;
2354 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2355 board[i][holdingsColumn] = EmptySquare;
2356 board[i][countsColumn] = (ChessSquare) 0;
2358 while( (p=*holdings++) != NULLCHAR ) {
2359 piece = CharToPiece( ToUpper(p) );
2360 if(piece == EmptySquare) continue;
2361 /*j = (int) piece - (int) WhitePawn;*/
2362 j = PieceToNumber(piece);
2363 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2364 if(j < 0) continue; /* should not happen */
2365 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2366 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2367 board[holdingsStartRow+j*direction][countsColumn]++;
2373 VariantSwitch(Board board, VariantClass newVariant)
2375 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2376 static Board oldBoard;
2378 startedFromPositionFile = FALSE;
2379 if(gameInfo.variant == newVariant) return;
2381 /* [HGM] This routine is called each time an assignment is made to
2382 * gameInfo.variant during a game, to make sure the board sizes
2383 * are set to match the new variant. If that means adding or deleting
2384 * holdings, we shift the playing board accordingly
2385 * This kludge is needed because in ICS observe mode, we get boards
2386 * of an ongoing game without knowing the variant, and learn about the
2387 * latter only later. This can be because of the move list we requested,
2388 * in which case the game history is refilled from the beginning anyway,
2389 * but also when receiving holdings of a crazyhouse game. In the latter
2390 * case we want to add those holdings to the already received position.
2394 if (appData.debugMode) {
2395 fprintf(debugFP, "Switch board from %s to %s\n",
2396 VariantName(gameInfo.variant), VariantName(newVariant));
2397 setbuf(debugFP, NULL);
2399 shuffleOpenings = 0; /* [HGM] shuffle */
2400 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2404 newWidth = 9; newHeight = 9;
2405 gameInfo.holdingsSize = 7;
2406 case VariantBughouse:
2407 case VariantCrazyhouse:
2408 newHoldingsWidth = 2; break;
2412 newHoldingsWidth = 2;
2413 gameInfo.holdingsSize = 8;
2416 case VariantCapablanca:
2417 case VariantCapaRandom:
2420 newHoldingsWidth = gameInfo.holdingsSize = 0;
2423 if(newWidth != gameInfo.boardWidth ||
2424 newHeight != gameInfo.boardHeight ||
2425 newHoldingsWidth != gameInfo.holdingsWidth ) {
2427 /* shift position to new playing area, if needed */
2428 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2429 for(i=0; i<BOARD_HEIGHT; i++)
2430 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2431 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2433 for(i=0; i<newHeight; i++) {
2434 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2435 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2437 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2438 for(i=0; i<BOARD_HEIGHT; i++)
2439 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2440 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2443 gameInfo.boardWidth = newWidth;
2444 gameInfo.boardHeight = newHeight;
2445 gameInfo.holdingsWidth = newHoldingsWidth;
2446 gameInfo.variant = newVariant;
2447 InitDrawingSizes(-2, 0);
2448 } else gameInfo.variant = newVariant;
2449 CopyBoard(oldBoard, board); // remember correctly formatted board
2450 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2451 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2454 static int loggedOn = FALSE;
2456 /*-- Game start info cache: --*/
2458 char gs_kind[MSG_SIZ];
2459 static char player1Name[128] = "";
2460 static char player2Name[128] = "";
2461 static char cont_seq[] = "\n\\ ";
2462 static int player1Rating = -1;
2463 static int player2Rating = -1;
2464 /*----------------------------*/
2466 ColorClass curColor = ColorNormal;
2467 int suppressKibitz = 0;
2470 Boolean soughtPending = FALSE;
2471 Boolean seekGraphUp;
2472 #define MAX_SEEK_ADS 200
2474 char *seekAdList[MAX_SEEK_ADS];
2475 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2476 float tcList[MAX_SEEK_ADS];
2477 char colorList[MAX_SEEK_ADS];
2478 int nrOfSeekAds = 0;
2479 int minRating = 1010, maxRating = 2800;
2480 int hMargin = 10, vMargin = 20, h, w;
2481 extern int squareSize, lineGap;
2486 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2487 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2488 if(r < minRating+100 && r >=0 ) r = minRating+100;
2489 if(r > maxRating) r = maxRating;
2490 if(tc < 1.) tc = 1.;
2491 if(tc > 95.) tc = 95.;
2492 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2493 y = ((double)r - minRating)/(maxRating - minRating)
2494 * (h-vMargin-squareSize/8-1) + vMargin;
2495 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2496 if(strstr(seekAdList[i], " u ")) color = 1;
2497 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2498 !strstr(seekAdList[i], "bullet") &&
2499 !strstr(seekAdList[i], "blitz") &&
2500 !strstr(seekAdList[i], "standard") ) color = 2;
2501 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2502 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2506 AddAd(char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2508 char buf[MSG_SIZ], *ext = "";
2509 VariantClass v = StringToVariant(type);
2510 if(strstr(type, "wild")) {
2511 ext = type + 4; // append wild number
2512 if(v == VariantFischeRandom) type = "chess960"; else
2513 if(v == VariantLoadable) type = "setup"; else
2514 type = VariantName(v);
2516 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2517 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2518 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2519 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2520 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2521 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2522 seekNrList[nrOfSeekAds] = nr;
2523 zList[nrOfSeekAds] = 0;
2524 seekAdList[nrOfSeekAds++] = StrSave(buf);
2525 if(plot) PlotSeekAd(nrOfSeekAds-1);
2532 int x = xList[i], y = yList[i], d=squareSize/4, k;
2533 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2534 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2535 // now replot every dot that overlapped
2536 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2537 int xx = xList[k], yy = yList[k];
2538 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2539 DrawSeekDot(xx, yy, colorList[k]);
2544 RemoveSeekAd(int nr)
2547 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2549 if(seekAdList[i]) free(seekAdList[i]);
2550 seekAdList[i] = seekAdList[--nrOfSeekAds];
2551 seekNrList[i] = seekNrList[nrOfSeekAds];
2552 ratingList[i] = ratingList[nrOfSeekAds];
2553 colorList[i] = colorList[nrOfSeekAds];
2554 tcList[i] = tcList[nrOfSeekAds];
2555 xList[i] = xList[nrOfSeekAds];
2556 yList[i] = yList[nrOfSeekAds];
2557 zList[i] = zList[nrOfSeekAds];
2558 seekAdList[nrOfSeekAds] = NULL;
2564 MatchSoughtLine(char *line)
2566 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2567 int nr, base, inc, u=0; char dummy;
2569 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2570 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2572 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2573 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2574 // match: compact and save the line
2575 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2585 if(!seekGraphUp) return FALSE;
2586 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2587 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2589 DrawSeekBackground(0, 0, w, h);
2590 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2591 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2592 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2593 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2595 DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2598 snprintf(buf, MSG_SIZ, "%d", i);
2599 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2602 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2603 for(i=1; i<100; i+=(i<10?1:5)) {
2604 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2605 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2606 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2608 snprintf(buf, MSG_SIZ, "%d", i);
2609 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2612 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2616 int SeekGraphClick(ClickType click, int x, int y, int moving)
2618 static int lastDown = 0, displayed = 0, lastSecond;
2619 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2620 if(click == Release || moving) return FALSE;
2622 soughtPending = TRUE;
2623 SendToICS(ics_prefix);
2624 SendToICS("sought\n"); // should this be "sought all"?
2625 } else { // issue challenge based on clicked ad
2626 int dist = 10000; int i, closest = 0, second = 0;
2627 for(i=0; i<nrOfSeekAds; i++) {
2628 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2629 if(d < dist) { dist = d; closest = i; }
2630 second += (d - zList[i] < 120); // count in-range ads
2631 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2635 second = (second > 1);
2636 if(displayed != closest || second != lastSecond) {
2637 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2638 lastSecond = second; displayed = closest;
2640 if(click == Press) {
2641 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2644 } // on press 'hit', only show info
2645 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2646 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2647 SendToICS(ics_prefix);
2649 return TRUE; // let incoming board of started game pop down the graph
2650 } else if(click == Release) { // release 'miss' is ignored
2651 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2652 if(moving == 2) { // right up-click
2653 nrOfSeekAds = 0; // refresh graph
2654 soughtPending = TRUE;
2655 SendToICS(ics_prefix);
2656 SendToICS("sought\n"); // should this be "sought all"?
2659 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2660 // press miss or release hit 'pop down' seek graph
2661 seekGraphUp = FALSE;
2662 DrawPosition(TRUE, NULL);
2668 read_from_ics(isr, closure, data, count, error)
2675 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2676 #define STARTED_NONE 0
2677 #define STARTED_MOVES 1
2678 #define STARTED_BOARD 2
2679 #define STARTED_OBSERVE 3
2680 #define STARTED_HOLDINGS 4
2681 #define STARTED_CHATTER 5
2682 #define STARTED_COMMENT 6
2683 #define STARTED_MOVES_NOHIDE 7
2685 static int started = STARTED_NONE;
2686 static char parse[20000];
2687 static int parse_pos = 0;
2688 static char buf[BUF_SIZE + 1];
2689 static int firstTime = TRUE, intfSet = FALSE;
2690 static ColorClass prevColor = ColorNormal;
2691 static int savingComment = FALSE;
2692 static int cmatch = 0; // continuation sequence match
2699 int backup; /* [DM] For zippy color lines */
2701 char talker[MSG_SIZ]; // [HGM] chat
2704 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2706 if (appData.debugMode) {
2708 fprintf(debugFP, "<ICS: ");
2709 show_bytes(debugFP, data, count);
2710 fprintf(debugFP, "\n");
2714 if (appData.debugMode) { int f = forwardMostMove;
2715 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2716 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2717 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2720 /* If last read ended with a partial line that we couldn't parse,
2721 prepend it to the new read and try again. */
2722 if (leftover_len > 0) {
2723 for (i=0; i<leftover_len; i++)
2724 buf[i] = buf[leftover_start + i];
2727 /* copy new characters into the buffer */
2728 bp = buf + leftover_len;
2729 buf_len=leftover_len;
2730 for (i=0; i<count; i++)
2733 if (data[i] == '\r')
2736 // join lines split by ICS?
2737 if (!appData.noJoin)
2740 Joining just consists of finding matches against the
2741 continuation sequence, and discarding that sequence
2742 if found instead of copying it. So, until a match
2743 fails, there's nothing to do since it might be the
2744 complete sequence, and thus, something we don't want
2747 if (data[i] == cont_seq[cmatch])
2750 if (cmatch == strlen(cont_seq))
2752 cmatch = 0; // complete match. just reset the counter
2755 it's possible for the ICS to not include the space
2756 at the end of the last word, making our [correct]
2757 join operation fuse two separate words. the server
2758 does this when the space occurs at the width setting.
2760 if (!buf_len || buf[buf_len-1] != ' ')
2771 match failed, so we have to copy what matched before
2772 falling through and copying this character. In reality,
2773 this will only ever be just the newline character, but
2774 it doesn't hurt to be precise.
2776 strncpy(bp, cont_seq, cmatch);
2788 buf[buf_len] = NULLCHAR;
2789 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2794 while (i < buf_len) {
2795 /* Deal with part of the TELNET option negotiation
2796 protocol. We refuse to do anything beyond the
2797 defaults, except that we allow the WILL ECHO option,
2798 which ICS uses to turn off password echoing when we are
2799 directly connected to it. We reject this option
2800 if localLineEditing mode is on (always on in xboard)
2801 and we are talking to port 23, which might be a real
2802 telnet server that will try to keep WILL ECHO on permanently.
2804 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2805 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2806 unsigned char option;
2808 switch ((unsigned char) buf[++i]) {
2810 if (appData.debugMode)
2811 fprintf(debugFP, "\n<WILL ");
2812 switch (option = (unsigned char) buf[++i]) {
2814 if (appData.debugMode)
2815 fprintf(debugFP, "ECHO ");
2816 /* Reply only if this is a change, according
2817 to the protocol rules. */
2818 if (remoteEchoOption) break;
2819 if (appData.localLineEditing &&
2820 atoi(appData.icsPort) == TN_PORT) {
2821 TelnetRequest(TN_DONT, TN_ECHO);
2824 TelnetRequest(TN_DO, TN_ECHO);
2825 remoteEchoOption = TRUE;
2829 if (appData.debugMode)
2830 fprintf(debugFP, "%d ", option);
2831 /* Whatever this is, we don't want it. */
2832 TelnetRequest(TN_DONT, option);
2837 if (appData.debugMode)
2838 fprintf(debugFP, "\n<WONT ");
2839 switch (option = (unsigned char) buf[++i]) {
2841 if (appData.debugMode)
2842 fprintf(debugFP, "ECHO ");
2843 /* Reply only if this is a change, according
2844 to the protocol rules. */
2845 if (!remoteEchoOption) break;
2847 TelnetRequest(TN_DONT, TN_ECHO);
2848 remoteEchoOption = FALSE;
2851 if (appData.debugMode)
2852 fprintf(debugFP, "%d ", (unsigned char) option);
2853 /* Whatever this is, it must already be turned
2854 off, because we never agree to turn on
2855 anything non-default, so according to the
2856 protocol rules, we don't reply. */
2861 if (appData.debugMode)
2862 fprintf(debugFP, "\n<DO ");
2863 switch (option = (unsigned char) buf[++i]) {
2865 /* Whatever this is, we refuse to do it. */
2866 if (appData.debugMode)
2867 fprintf(debugFP, "%d ", option);
2868 TelnetRequest(TN_WONT, option);
2873 if (appData.debugMode)
2874 fprintf(debugFP, "\n<DONT ");
2875 switch (option = (unsigned char) buf[++i]) {
2877 if (appData.debugMode)
2878 fprintf(debugFP, "%d ", option);
2879 /* Whatever this is, we are already not doing
2880 it, because we never agree to do anything
2881 non-default, so according to the protocol
2882 rules, we don't reply. */
2887 if (appData.debugMode)
2888 fprintf(debugFP, "\n<IAC ");
2889 /* Doubled IAC; pass it through */
2893 if (appData.debugMode)
2894 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2895 /* Drop all other telnet commands on the floor */
2898 if (oldi > next_out)
2899 SendToPlayer(&buf[next_out], oldi - next_out);
2905 /* OK, this at least will *usually* work */
2906 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2910 if (loggedOn && !intfSet) {
2911 if (ics_type == ICS_ICC) {
2912 snprintf(str, MSG_SIZ,
2913 "/set-quietly interface %s\n/set-quietly style 12\n",
2915 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2916 strcat(str, "/set-2 51 1\n/set seek 1\n");
2917 } else if (ics_type == ICS_CHESSNET) {
2918 snprintf(str, MSG_SIZ, "/style 12\n");
2920 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2921 strcat(str, programVersion);
2922 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2923 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2924 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2926 strcat(str, "$iset nohighlight 1\n");
2928 strcat(str, "$iset lock 1\n$style 12\n");
2931 NotifyFrontendLogin();
2935 if (started == STARTED_COMMENT) {
2936 /* Accumulate characters in comment */
2937 parse[parse_pos++] = buf[i];
2938 if (buf[i] == '\n') {
2939 parse[parse_pos] = NULLCHAR;
2940 if(chattingPartner>=0) {
2942 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2943 OutputChatMessage(chattingPartner, mess);
2944 chattingPartner = -1;
2945 next_out = i+1; // [HGM] suppress printing in ICS window
2947 if(!suppressKibitz) // [HGM] kibitz
2948 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2949 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2950 int nrDigit = 0, nrAlph = 0, j;
2951 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2952 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2953 parse[parse_pos] = NULLCHAR;
2954 // try to be smart: if it does not look like search info, it should go to
2955 // ICS interaction window after all, not to engine-output window.
2956 for(j=0; j<parse_pos; j++) { // count letters and digits
2957 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2958 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2959 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2961 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2962 int depth=0; float score;
2963 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2964 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2965 pvInfoList[forwardMostMove-1].depth = depth;
2966 pvInfoList[forwardMostMove-1].score = 100*score;
2968 OutputKibitz(suppressKibitz, parse);
2971 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2972 SendToPlayer(tmp, strlen(tmp));
2974 next_out = i+1; // [HGM] suppress printing in ICS window
2976 started = STARTED_NONE;
2978 /* Don't match patterns against characters in comment */
2983 if (started == STARTED_CHATTER) {
2984 if (buf[i] != '\n') {
2985 /* Don't match patterns against characters in chatter */
2989 started = STARTED_NONE;
2990 if(suppressKibitz) next_out = i+1;
2993 /* Kludge to deal with rcmd protocol */
2994 if (firstTime && looking_at(buf, &i, "\001*")) {
2995 DisplayFatalError(&buf[1], 0, 1);
3001 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3004 if (appData.debugMode)
3005 fprintf(debugFP, "ics_type %d\n", ics_type);
3008 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3009 ics_type = ICS_FICS;
3011 if (appData.debugMode)
3012 fprintf(debugFP, "ics_type %d\n", ics_type);
3015 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3016 ics_type = ICS_CHESSNET;
3018 if (appData.debugMode)
3019 fprintf(debugFP, "ics_type %d\n", ics_type);
3024 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3025 looking_at(buf, &i, "Logging you in as \"*\"") ||
3026 looking_at(buf, &i, "will be \"*\""))) {
3027 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3031 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3033 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3034 DisplayIcsInteractionTitle(buf);
3035 have_set_title = TRUE;
3038 /* skip finger notes */
3039 if (started == STARTED_NONE &&
3040 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3041 (buf[i] == '1' && buf[i+1] == '0')) &&
3042 buf[i+2] == ':' && buf[i+3] == ' ') {
3043 started = STARTED_CHATTER;
3049 // [HGM] seekgraph: recognize sought lines and end-of-sought message
3050 if(appData.seekGraph) {
3051 if(soughtPending && MatchSoughtLine(buf+i)) {
3052 i = strstr(buf+i, "rated") - buf;
3053 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3054 next_out = leftover_start = i;
3055 started = STARTED_CHATTER;
3056 suppressKibitz = TRUE;
3059 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3060 && looking_at(buf, &i, "* ads displayed")) {
3061 soughtPending = FALSE;
3066 if(appData.autoRefresh) {
3067 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3068 int s = (ics_type == ICS_ICC); // ICC format differs
3070 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3071 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3072 looking_at(buf, &i, "*% "); // eat prompt
3073 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3074 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3075 next_out = i; // suppress
3078 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3079 char *p = star_match[0];
3081 if(seekGraphUp) RemoveSeekAd(atoi(p));
3082 while(*p && *p++ != ' '); // next
3084 looking_at(buf, &i, "*% "); // eat prompt
3085 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3092 /* skip formula vars */
3093 if (started == STARTED_NONE &&
3094 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3095 started = STARTED_CHATTER;
3100 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3101 if (appData.autoKibitz && started == STARTED_NONE &&
3102 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3103 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3104 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3105 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3106 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3107 suppressKibitz = TRUE;
3108 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3110 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3111 && (gameMode == IcsPlayingWhite)) ||
3112 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3113 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3114 started = STARTED_CHATTER; // own kibitz we simply discard
3116 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3117 parse_pos = 0; parse[0] = NULLCHAR;
3118 savingComment = TRUE;
3119 suppressKibitz = gameMode != IcsObserving ? 2 :
3120 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3124 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3125 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3126 && atoi(star_match[0])) {
3127 // suppress the acknowledgements of our own autoKibitz
3129 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3130 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3131 SendToPlayer(star_match[0], strlen(star_match[0]));
3132 if(looking_at(buf, &i, "*% ")) // eat prompt
3133 suppressKibitz = FALSE;
3137 } // [HGM] kibitz: end of patch
3139 // [HGM] chat: intercept tells by users for which we have an open chat window
3141 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3142 looking_at(buf, &i, "* whispers:") ||
3143 looking_at(buf, &i, "* kibitzes:") ||
3144 looking_at(buf, &i, "* shouts:") ||
3145 looking_at(buf, &i, "* c-shouts:") ||
3146 looking_at(buf, &i, "--> * ") ||
3147 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3148 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3149 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3150 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3152 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3153 chattingPartner = -1;
3155 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3156 for(p=0; p<MAX_CHAT; p++) {
3157 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3158 talker[0] = '['; strcat(talker, "] ");
3159 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3160 chattingPartner = p; break;
3163 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3164 for(p=0; p<MAX_CHAT; p++) {
3165 if(!strcmp("kibitzes", chatPartner[p])) {
3166 talker[0] = '['; strcat(talker, "] ");
3167 chattingPartner = p; break;
3170 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3171 for(p=0; p<MAX_CHAT; p++) {
3172 if(!strcmp("whispers", chatPartner[p])) {
3173 talker[0] = '['; strcat(talker, "] ");
3174 chattingPartner = p; break;
3177 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3178 if(buf[i-8] == '-' && buf[i-3] == 't')
3179 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3180 if(!strcmp("c-shouts", chatPartner[p])) {
3181 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3182 chattingPartner = p; break;
3185 if(chattingPartner < 0)
3186 for(p=0; p<MAX_CHAT; p++) {
3187 if(!strcmp("shouts", chatPartner[p])) {
3188 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3189 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3190 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3191 chattingPartner = p; break;
3195 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3196 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3197 talker[0] = 0; Colorize(ColorTell, FALSE);
3198 chattingPartner = p; break;
3200 if(chattingPartner<0) i = oldi; else {
3201 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3202 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3203 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3204 started = STARTED_COMMENT;
3205 parse_pos = 0; parse[0] = NULLCHAR;
3206 savingComment = 3 + chattingPartner; // counts as TRUE
3207 suppressKibitz = TRUE;
3210 } // [HGM] chat: end of patch
3213 if (appData.zippyTalk || appData.zippyPlay) {
3214 /* [DM] Backup address for color zippy lines */
3216 if (loggedOn == TRUE)
3217 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3218 (appData.zippyPlay && ZippyMatch(buf, &backup)));
3220 } // [DM] 'else { ' deleted
3222 /* Regular tells and says */
3223 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3224 looking_at(buf, &i, "* (your partner) tells you: ") ||
3225 looking_at(buf, &i, "* says: ") ||
3226 /* Don't color "message" or "messages" output */
3227 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3228 looking_at(buf, &i, "*. * at *:*: ") ||
3229 looking_at(buf, &i, "--* (*:*): ") ||
3230 /* Message notifications (same color as tells) */
3231 looking_at(buf, &i, "* has left a message ") ||
3232 looking_at(buf, &i, "* just sent you a message:\n") ||
3233 /* Whispers and kibitzes */
3234 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3235 looking_at(buf, &i, "* kibitzes: ") ||
3237 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3239 if (tkind == 1 && strchr(star_match[0], ':')) {
3240 /* Avoid "tells you:" spoofs in channels */
3243 if (star_match[0][0] == NULLCHAR ||
3244 strchr(star_match[0], ' ') ||
3245 (tkind == 3 && strchr(star_match[1], ' '))) {
3246 /* Reject bogus matches */
3249 if (appData.colorize) {
3250 if (oldi > next_out) {
3251 SendToPlayer(&buf[next_out], oldi - next_out);
3256 Colorize(ColorTell, FALSE);
3257 curColor = ColorTell;
3260 Colorize(ColorKibitz, FALSE);
3261 curColor = ColorKibitz;
3264 p = strrchr(star_match[1], '(');
3271 Colorize(ColorChannel1, FALSE);
3272 curColor = ColorChannel1;
3274 Colorize(ColorChannel, FALSE);
3275 curColor = ColorChannel;
3279 curColor = ColorNormal;
3283 if (started == STARTED_NONE && appData.autoComment &&
3284 (gameMode == IcsObserving ||
3285 gameMode == IcsPlayingWhite ||
3286 gameMode == IcsPlayingBlack)) {
3287 parse_pos = i - oldi;
3288 memcpy(parse, &buf[oldi], parse_pos);
3289 parse[parse_pos] = NULLCHAR;
3290 started = STARTED_COMMENT;
3291 savingComment = TRUE;
3293 started = STARTED_CHATTER;
3294 savingComment = FALSE;
3301 if (looking_at(buf, &i, "* s-shouts: ") ||
3302 looking_at(buf, &i, "* c-shouts: ")) {
3303 if (appData.colorize) {
3304 if (oldi > next_out) {
3305 SendToPlayer(&buf[next_out], oldi - next_out);
3308 Colorize(ColorSShout, FALSE);
3309 curColor = ColorSShout;
3312 started = STARTED_CHATTER;
3316 if (looking_at(buf, &i, "--->")) {
3321 if (looking_at(buf, &i, "* shouts: ") ||
3322 looking_at(buf, &i, "--> ")) {
3323 if (appData.colorize) {
3324 if (oldi > next_out) {
3325 SendToPlayer(&buf[next_out], oldi - next_out);
3328 Colorize(ColorShout, FALSE);
3329 curColor = ColorShout;
3332 started = STARTED_CHATTER;
3336 if (looking_at( buf, &i, "Challenge:")) {
3337 if (appData.colorize) {
3338 if (oldi > next_out) {
3339 SendToPlayer(&buf[next_out], oldi - next_out);
3342 Colorize(ColorChallenge, FALSE);
3343 curColor = ColorChallenge;
3349 if (looking_at(buf, &i, "* offers you") ||
3350 looking_at(buf, &i, "* offers to be") ||
3351 looking_at(buf, &i, "* would like to") ||
3352 looking_at(buf, &i, "* requests to") ||
3353 looking_at(buf, &i, "Your opponent offers") ||
3354 looking_at(buf, &i, "Your opponent requests")) {
3356 if (appData.colorize) {
3357 if (oldi > next_out) {
3358 SendToPlayer(&buf[next_out], oldi - next_out);
3361 Colorize(ColorRequest, FALSE);
3362 curColor = ColorRequest;
3367 if (looking_at(buf, &i, "* (*) seeking")) {
3368 if (appData.colorize) {
3369 if (oldi > next_out) {
3370 SendToPlayer(&buf[next_out], oldi - next_out);
3373 Colorize(ColorSeek, FALSE);
3374 curColor = ColorSeek;
3379 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3381 if (looking_at(buf, &i, "\\ ")) {
3382 if (prevColor != ColorNormal) {
3383 if (oldi > next_out) {
3384 SendToPlayer(&buf[next_out], oldi - next_out);
3387 Colorize(prevColor, TRUE);
3388 curColor = prevColor;
3390 if (savingComment) {
3391 parse_pos = i - oldi;
3392 memcpy(parse, &buf[oldi], parse_pos);
3393 parse[parse_pos] = NULLCHAR;
3394 started = STARTED_COMMENT;
3395 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3396 chattingPartner = savingComment - 3; // kludge to remember the box
3398 started = STARTED_CHATTER;
3403 if (looking_at(buf, &i, "Black Strength :") ||
3404 looking_at(buf, &i, "<<< style 10 board >>>") ||
3405 looking_at(buf, &i, "<10>") ||
3406 looking_at(buf, &i, "#@#")) {
3407 /* Wrong board style */
3409 SendToICS(ics_prefix);
3410 SendToICS("set style 12\n");
3411 SendToICS(ics_prefix);
3412 SendToICS("refresh\n");
3416 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3418 have_sent_ICS_logon = 1;