2 * backend.c -- Common back end for X and Windows NT versions of
4 * Copyright 1991 by Digital Equipment Corporation, Maynard,
7 * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8 * 2007, 2008, 2009, 2010, 2011 Free Software Foundation, Inc.
10 * Enhancements Copyright 2005 Alessandro Scotti
12 * The following terms apply to Digital Equipment Corporation's copyright
14 * ------------------------------------------------------------------------
17 * Permission to use, copy, modify, and distribute this software and its
18 * documentation for any purpose and without fee is hereby granted,
19 * provided that the above copyright notice appear in all copies and that
20 * both that copyright notice and this permission notice appear in
21 * supporting documentation, and that the name of Digital not be
22 * used in advertising or publicity pertaining to distribution of the
23 * software without specific, written prior permission.
25 * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27 * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
32 * ------------------------------------------------------------------------
34 * The following terms apply to the enhanced version of XBoard
35 * distributed by the Free Software Foundation:
36 * ------------------------------------------------------------------------
38 * GNU XBoard is free software: you can redistribute it and/or modify
39 * it under the terms of the GNU General Public License as published by
40 * the Free Software Foundation, either version 3 of the License, or (at
41 * your option) any later version.
43 * GNU XBoard is distributed in the hope that it will be useful, but
44 * WITHOUT ANY WARRANTY; without even the implied warranty of
45 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46 * General Public License for more details.
48 * You should have received a copy of the GNU General Public License
49 * along with this program. If not, see http://www.gnu.org/licenses/. *
51 *------------------------------------------------------------------------
52 ** See the file ChangeLog for a revision history. */
54 /* [AS] Also useful here for debugging */
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
60 int flock(int f, int code);
66 #define DoSleep( n ) if( (n) >= 0) sleep(n)
77 #include <sys/types.h>
86 #else /* not STDC_HEADERS */
89 # else /* not HAVE_STRING_H */
91 # endif /* not HAVE_STRING_H */
92 #endif /* not STDC_HEADERS */
95 # include <sys/fcntl.h>
96 #else /* not HAVE_SYS_FCNTL_H */
99 # endif /* HAVE_FCNTL_H */
100 #endif /* not HAVE_SYS_FCNTL_H */
102 #if TIME_WITH_SYS_TIME
103 # include <sys/time.h>
107 # include <sys/time.h>
113 #if defined(_amigados) && !defined(__GNUC__)
118 extern int gettimeofday(struct timeval *, struct timezone *);
126 #include "frontend.h"
133 #include "backendz.h"
137 # define _(s) gettext (s)
138 # define N_(s) gettext_noop (s)
139 # define T_(s) gettext(s)
152 /* A point in time */
154 long sec; /* Assuming this is >= 32 bits */
155 int ms; /* Assuming this is >= 16 bits */
158 int establish P((void));
159 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
160 char *buf, int count, int error));
161 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
162 char *buf, int count, int error));
163 void ics_printf P((char *format, ...));
164 void SendToICS P((char *s));
165 void SendToICSDelayed P((char *s, long msdelay));
166 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
167 void HandleMachineMove P((char *message, ChessProgramState *cps));
168 int AutoPlayOneMove P((void));
169 int LoadGameOneMove P((ChessMove readAhead));
170 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
171 int LoadPositionFromFile P((char *filename, int n, char *title));
172 int SavePositionToFile P((char *filename));
173 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
175 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
176 void ShowMove P((int fromX, int fromY, int toX, int toY));
177 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
178 /*char*/int promoChar));
179 void BackwardInner P((int target));
180 void ForwardInner P((int target));
181 int Adjudicate P((ChessProgramState *cps));
182 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
183 void EditPositionDone P((Boolean fakeRights));
184 void PrintOpponents P((FILE *fp));
185 void PrintPosition P((FILE *fp, int move));
186 void StartChessProgram P((ChessProgramState *cps));
187 void SendToProgram P((char *message, ChessProgramState *cps));
188 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
189 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
190 char *buf, int count, int error));
191 void SendTimeControl P((ChessProgramState *cps,
192 int mps, long tc, int inc, int sd, int st));
193 char *TimeControlTagValue P((void));
194 void Attention P((ChessProgramState *cps));
195 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
196 int ResurrectChessProgram P((void));
197 void DisplayComment P((int moveNumber, char *text));
198 void DisplayMove P((int moveNumber));
200 void ParseGameHistory P((char *game));
201 void ParseBoard12 P((char *string));
202 void KeepAlive P((void));
203 void StartClocks P((void));
204 void SwitchClocks P((int nr));
205 void StopClocks P((void));
206 void ResetClocks P((void));
207 char *PGNDate P((void));
208 void SetGameInfo P((void));
209 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
210 int RegisterMove P((void));
211 void MakeRegisteredMove P((void));
212 void TruncateGame P((void));
213 int looking_at P((char *, int *, char *));
214 void CopyPlayerNameIntoFileName P((char **, char *));
215 char *SavePart P((char *));
216 int SaveGameOldStyle P((FILE *));
217 int SaveGamePGN P((FILE *));
218 void GetTimeMark P((TimeMark *));
219 long SubtractTimeMarks P((TimeMark *, TimeMark *));
220 int CheckFlags P((void));
221 long NextTickLength P((long));
222 void CheckTimeControl P((void));
223 void show_bytes P((FILE *, char *, int));
224 int string_to_rating P((char *str));
225 void ParseFeatures P((char* args, ChessProgramState *cps));
226 void InitBackEnd3 P((void));
227 void FeatureDone P((ChessProgramState* cps, int val));
228 void InitChessProgram P((ChessProgramState *cps, int setup));
229 void OutputKibitz(int window, char *text);
230 int PerpetualChase(int first, int last);
231 int EngineOutputIsUp();
232 void InitDrawingSizes(int x, int y);
233 void NextMatchGame P((void));
234 int NextTourneyGame P((int nr, int *swap));
235 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
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:
397 flags &= ~F_ALL_CASTLE_OK;
405 FILE *gameFileFP, *debugFP;
408 [AS] Note: sometimes, the sscanf() function is used to parse the input
409 into a fixed-size buffer. Because of this, we must be prepared to
410 receive strings as long as the size of the input buffer, which is currently
411 set to 4K for Windows and 8K for the rest.
412 So, we must either allocate sufficiently large buffers here, or
413 reduce the size of the input buffer in the input reading part.
416 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
417 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
418 char thinkOutput1[MSG_SIZ*10];
420 ChessProgramState first, second;
422 /* premove variables */
425 int premoveFromX = 0;
426 int premoveFromY = 0;
427 int premovePromoChar = 0;
429 Boolean alarmSounded;
430 /* end premove variables */
432 char *ics_prefix = "$";
433 int ics_type = ICS_GENERIC;
435 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
436 int pauseExamForwardMostMove = 0;
437 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
438 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
439 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
440 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
441 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
442 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
443 int whiteFlag = FALSE, blackFlag = FALSE;
444 int userOfferedDraw = FALSE;
445 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
446 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
447 int cmailMoveType[CMAIL_MAX_GAMES];
448 long ics_clock_paused = 0;
449 ProcRef icsPR = NoProc, cmailPR = NoProc;
450 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
451 GameMode gameMode = BeginningOfGame;
452 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
453 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
454 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
455 int hiddenThinkOutputState = 0; /* [AS] */
456 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
457 int adjudicateLossPlies = 6;
458 char white_holding[64], black_holding[64];
459 TimeMark lastNodeCountTime;
460 long lastNodeCount=0;
461 int shiftKey; // [HGM] set by mouse handler
463 int have_sent_ICS_logon = 0;
465 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
466 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
467 long timeControl_2; /* [AS] Allow separate time controls */
468 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
469 long timeRemaining[2][MAX_MOVES];
470 int matchGame = 0, nextGame = 0, roundNr = 0;
471 Boolean waitingForGame = FALSE;
472 TimeMark programStartTime, pauseStart;
473 char ics_handle[MSG_SIZ];
474 int have_set_title = 0;
476 /* animateTraining preserves the state of appData.animate
477 * when Training mode is activated. This allows the
478 * response to be animated when appData.animate == TRUE and
479 * appData.animateDragging == TRUE.
481 Boolean animateTraining;
487 Board boards[MAX_MOVES];
488 /* [HGM] Following 7 needed for accurate legality tests: */
489 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
490 signed char initialRights[BOARD_FILES];
491 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
492 int initialRulePlies, FENrulePlies;
493 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
496 int mute; // mute all sounds
498 // [HGM] vari: next 12 to save and restore variations
499 #define MAX_VARIATIONS 10
500 int framePtr = MAX_MOVES-1; // points to free stack entry
502 int savedFirst[MAX_VARIATIONS];
503 int savedLast[MAX_VARIATIONS];
504 int savedFramePtr[MAX_VARIATIONS];
505 char *savedDetails[MAX_VARIATIONS];
506 ChessMove savedResult[MAX_VARIATIONS];
508 void PushTail P((int firstMove, int lastMove));
509 Boolean PopTail P((Boolean annotate));
510 void PushInner P((int firstMove, int lastMove));
511 void PopInner P((Boolean annotate));
512 void CleanupTail P((void));
514 ChessSquare FIDEArray[2][BOARD_FILES] = {
515 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
516 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
517 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
518 BlackKing, BlackBishop, BlackKnight, BlackRook }
521 ChessSquare twoKingsArray[2][BOARD_FILES] = {
522 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
523 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
524 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
525 BlackKing, BlackKing, BlackKnight, BlackRook }
528 ChessSquare KnightmateArray[2][BOARD_FILES] = {
529 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
530 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
531 { BlackRook, BlackMan, BlackBishop, BlackQueen,
532 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
535 ChessSquare SpartanArray[2][BOARD_FILES] = {
536 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
537 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
538 { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
539 BlackDragon, BlackKing, BlackAngel, BlackAlfil }
542 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
543 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
544 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
545 { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
546 BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
549 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
550 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
551 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
552 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
553 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
556 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
557 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
558 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
559 { BlackRook, BlackKnight, BlackMan, BlackFerz,
560 BlackKing, BlackMan, BlackKnight, BlackRook }
564 #if (BOARD_FILES>=10)
565 ChessSquare ShogiArray[2][BOARD_FILES] = {
566 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
567 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
568 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
569 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
572 ChessSquare XiangqiArray[2][BOARD_FILES] = {
573 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
574 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
575 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
576 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
579 ChessSquare CapablancaArray[2][BOARD_FILES] = {
580 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
581 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
582 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
583 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
586 ChessSquare GreatArray[2][BOARD_FILES] = {
587 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
588 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
589 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
590 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
593 ChessSquare JanusArray[2][BOARD_FILES] = {
594 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
595 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
596 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
597 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
601 ChessSquare GothicArray[2][BOARD_FILES] = {
602 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
603 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
604 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
605 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
608 #define GothicArray CapablancaArray
612 ChessSquare FalconArray[2][BOARD_FILES] = {
613 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
614 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
615 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
616 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
619 #define FalconArray CapablancaArray
622 #else // !(BOARD_FILES>=10)
623 #define XiangqiPosition FIDEArray
624 #define CapablancaArray FIDEArray
625 #define GothicArray FIDEArray
626 #define GreatArray FIDEArray
627 #endif // !(BOARD_FILES>=10)
629 #if (BOARD_FILES>=12)
630 ChessSquare CourierArray[2][BOARD_FILES] = {
631 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
632 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
633 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
634 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
636 #else // !(BOARD_FILES>=12)
637 #define CourierArray CapablancaArray
638 #endif // !(BOARD_FILES>=12)
641 Board initialPosition;
644 /* Convert str to a rating. Checks for special cases of "----",
646 "++++", etc. Also strips ()'s */
648 string_to_rating(str)
651 while(*str && !isdigit(*str)) ++str;
653 return 0; /* One of the special "no rating" cases */
661 /* Init programStats */
662 programStats.movelist[0] = 0;
663 programStats.depth = 0;
664 programStats.nr_moves = 0;
665 programStats.moves_left = 0;
666 programStats.nodes = 0;
667 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
668 programStats.score = 0;
669 programStats.got_only_move = 0;
670 programStats.got_fail = 0;
671 programStats.line_is_book = 0;
676 { // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
677 if (appData.firstPlaysBlack) {
678 first.twoMachinesColor = "black\n";
679 second.twoMachinesColor = "white\n";
681 first.twoMachinesColor = "white\n";
682 second.twoMachinesColor = "black\n";
685 first.other = &second;
686 second.other = &first;
689 if(appData.timeOddsMode) {
690 norm = appData.timeOdds[0];
691 if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
693 first.timeOdds = appData.timeOdds[0]/norm;
694 second.timeOdds = appData.timeOdds[1]/norm;
697 if(programVersion) free(programVersion);
698 if (appData.noChessProgram) {
699 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
700 sprintf(programVersion, "%s", PACKAGE_STRING);
702 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
703 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
704 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
709 UnloadEngine(ChessProgramState *cps)
711 /* Kill off first chess program */
712 if (cps->isr != NULL)
713 RemoveInputSource(cps->isr);
716 if (cps->pr != NoProc) {
718 DoSleep( appData.delayBeforeQuit );
719 SendToProgram("quit\n", cps);
720 DoSleep( appData.delayAfterQuit );
721 DestroyChildProcess(cps->pr, cps->useSigterm);
724 if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
728 ClearOptions(ChessProgramState *cps)
731 cps->nrOptions = cps->comboCnt = 0;
732 for(i=0; i<MAX_OPTIONS; i++) {
733 cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
734 cps->option[i].textValue = 0;
738 char *engineNames[] = {
744 InitEngine(ChessProgramState *cps, int n)
745 { // [HGM] all engine initialiation put in a function that does one engine
749 cps->which = engineNames[n];
750 cps->maybeThinking = FALSE;
754 cps->sendDrawOffers = 1;
756 cps->program = appData.chessProgram[n];
757 cps->host = appData.host[n];
758 cps->dir = appData.directory[n];
759 cps->initString = appData.engInitString[n];
760 cps->computerString = appData.computerString[n];
761 cps->useSigint = TRUE;
762 cps->useSigterm = TRUE;
763 cps->reuse = appData.reuse[n];
764 cps->nps = appData.NPS[n]; // [HGM] nps: copy nodes per second
765 cps->useSetboard = FALSE;
767 cps->usePing = FALSE;
770 cps->usePlayother = FALSE;
771 cps->useColors = TRUE;
772 cps->useUsermove = FALSE;
773 cps->sendICS = FALSE;
774 cps->sendName = appData.icsActive;
775 cps->sdKludge = FALSE;
776 cps->stKludge = FALSE;
777 TidyProgramName(cps->program, cps->host, cps->tidy);
779 safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
780 cps->analysisSupport = 2; /* detect */
781 cps->analyzing = FALSE;
782 cps->initDone = FALSE;
784 /* New features added by Tord: */
785 cps->useFEN960 = FALSE;
786 cps->useOOCastle = TRUE;
787 /* End of new features added by Tord. */
788 cps->fenOverride = appData.fenOverride[n];
790 /* [HGM] time odds: set factor for each machine */
791 cps->timeOdds = appData.timeOdds[n];
793 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
794 cps->accumulateTC = appData.accumulateTC[n];
795 cps->maxNrOfSessions = 1;
799 cps->supportsNPS = UNKNOWN;
802 cps->optionSettings = appData.engOptions[n];
804 cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
805 cps->isUCI = appData.isUCI[n]; /* [AS] */
806 cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
808 if (appData.protocolVersion[n] > PROTOVER
809 || appData.protocolVersion[n] < 1)
814 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
815 appData.protocolVersion[n]);
816 if( (len > MSG_SIZ) && appData.debugMode )
817 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
819 DisplayFatalError(buf, 0, 2);
823 cps->protocolVersion = appData.protocolVersion[n];
826 InitEngineUCI( installDir, cps ); // [HGM] moved here from winboard.c, to make available in xboard
829 ChessProgramState *savCps;
835 if(WaitForEngine(savCps, LoadEngine)) return;
836 CommonEngineInit(); // recalculate time odds
837 if(gameInfo.variant != StringToVariant(appData.variant)) {
838 // we changed variant when loading the engine; this forces us to reset
839 Reset(TRUE, savCps != &first);
840 EditGameEvent(); // for consistency with other path, as Reset changes mode
842 InitChessProgram(savCps, FALSE);
843 SendToProgram("force\n", savCps);
844 DisplayMessage("", "");
845 if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
846 for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
852 ReplaceEngine(ChessProgramState *cps, int n)
856 appData.noChessProgram = FALSE;
857 appData.clockMode = TRUE;
859 if(n) return; // only startup first engine immediately; second can wait
860 savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
864 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
865 extern Boolean isUCI, hasBook, storeVariant, v1, addToList;
868 Load(ChessProgramState *cps, int i)
870 char *p, *q, buf[MSG_SIZ], command[MSG_SIZ];
871 if(engineLine[0]) { // an engine was selected from the combo box
872 snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
873 SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
874 ParseArgsFromString("-firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1");
875 ParseArgsFromString(buf);
877 ReplaceEngine(cps, i);
881 while(q = strchr(p, SLASH)) p = q+1;
882 if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
883 if(engineDir[0] != NULLCHAR)
884 appData.directory[i] = engineDir;
885 else if(p != engineName) { // derive directory from engine path, when not given
887 appData.directory[i] = strdup(engineName);
889 } else appData.directory[i] = ".";
891 snprintf(command, MSG_SIZ, "%s %s", p, params);
894 appData.chessProgram[i] = strdup(p);
895 appData.isUCI[i] = isUCI;
896 appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
897 appData.hasOwnBookUCI[i] = hasBook;
900 q = firstChessProgramNames;
901 if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
902 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "\"%s\" -fd \"%s\"%s%s%s%s%s\n", p, appData.directory[i],
903 v1 ? " -firstProtocolVersion 1" : "",
904 hasBook ? "" : " -fNoOwnBookUCI",
905 isUCI ? " -fUCI" : "",
906 storeVariant ? " -variant " : "",
907 storeVariant ? VariantName(gameInfo.variant) : "");
908 firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
909 snprintf(firstChessProgramNames, len, "%s%s", q, buf);
912 ReplaceEngine(cps, i);
918 int matched, min, sec;
920 * Parse timeControl resource
922 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
923 appData.movesPerSession)) {
925 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
926 DisplayFatalError(buf, 0, 2);
930 * Parse searchTime resource
932 if (*appData.searchTime != NULLCHAR) {
933 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
935 searchTime = min * 60;
936 } else if (matched == 2) {
937 searchTime = min * 60 + sec;
940 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
941 DisplayFatalError(buf, 0, 2);
950 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
951 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
953 GetTimeMark(&programStartTime);
954 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
955 pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
958 programStats.ok_to_send = 1;
959 programStats.seen_stat = 0;
962 * Initialize game list
968 * Internet chess server status
970 if (appData.icsActive) {
971 appData.matchMode = FALSE;
972 appData.matchGames = 0;
974 appData.noChessProgram = !appData.zippyPlay;
976 appData.zippyPlay = FALSE;
977 appData.zippyTalk = FALSE;
978 appData.noChessProgram = TRUE;
980 if (*appData.icsHelper != NULLCHAR) {
981 appData.useTelnet = TRUE;
982 appData.telnetProgram = appData.icsHelper;
985 appData.zippyTalk = appData.zippyPlay = FALSE;
988 /* [AS] Initialize pv info list [HGM] and game state */
992 for( i=0; i<=framePtr; i++ ) {
993 pvInfoList[i].depth = -1;
994 boards[i][EP_STATUS] = EP_NONE;
995 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1001 /* [AS] Adjudication threshold */
1002 adjudicateLossThreshold = appData.adjudicateLossThreshold;
1004 InitEngine(&first, 0);
1005 InitEngine(&second, 1);
1008 if (appData.icsActive) {
1009 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
1010 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1011 appData.clockMode = FALSE;
1012 first.sendTime = second.sendTime = 0;
1016 /* Override some settings from environment variables, for backward
1017 compatibility. Unfortunately it's not feasible to have the env
1018 vars just set defaults, at least in xboard. Ugh.
1020 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1025 if (!appData.icsActive) {
1029 /* Check for variants that are supported only in ICS mode,
1030 or not at all. Some that are accepted here nevertheless
1031 have bugs; see comments below.
1033 VariantClass variant = StringToVariant(appData.variant);
1035 case VariantBughouse: /* need four players and two boards */
1036 case VariantKriegspiel: /* need to hide pieces and move details */
1037 /* case VariantFischeRandom: (Fabien: moved below) */
1038 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1039 if( (len > MSG_SIZ) && appData.debugMode )
1040 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1042 DisplayFatalError(buf, 0, 2);
1045 case VariantUnknown:
1046 case VariantLoadable:
1056 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1057 if( (len > MSG_SIZ) && appData.debugMode )
1058 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1060 DisplayFatalError(buf, 0, 2);
1063 case VariantXiangqi: /* [HGM] repetition rules not implemented */
1064 case VariantFairy: /* [HGM] TestLegality definitely off! */
1065 case VariantGothic: /* [HGM] should work */
1066 case VariantCapablanca: /* [HGM] should work */
1067 case VariantCourier: /* [HGM] initial forced moves not implemented */
1068 case VariantShogi: /* [HGM] could still mate with pawn drop */
1069 case VariantKnightmate: /* [HGM] should work */
1070 case VariantCylinder: /* [HGM] untested */
1071 case VariantFalcon: /* [HGM] untested */
1072 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1073 offboard interposition not understood */
1074 case VariantNormal: /* definitely works! */
1075 case VariantWildCastle: /* pieces not automatically shuffled */
1076 case VariantNoCastle: /* pieces not automatically shuffled */
1077 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1078 case VariantLosers: /* should work except for win condition,
1079 and doesn't know captures are mandatory */
1080 case VariantSuicide: /* should work except for win condition,
1081 and doesn't know captures are mandatory */
1082 case VariantGiveaway: /* should work except for win condition,
1083 and doesn't know captures are mandatory */
1084 case VariantTwoKings: /* should work */
1085 case VariantAtomic: /* should work except for win condition */
1086 case Variant3Check: /* should work except for win condition */
1087 case VariantShatranj: /* should work except for all win conditions */
1088 case VariantMakruk: /* should work except for daw countdown */
1089 case VariantBerolina: /* might work if TestLegality is off */
1090 case VariantCapaRandom: /* should work */
1091 case VariantJanus: /* should work */
1092 case VariantSuper: /* experimental */
1093 case VariantGreat: /* experimental, requires legality testing to be off */
1094 case VariantSChess: /* S-Chess, should work */
1095 case VariantSpartan: /* should work */
1102 int NextIntegerFromString( char ** str, long * value )
1107 while( *s == ' ' || *s == '\t' ) {
1113 if( *s >= '0' && *s <= '9' ) {
1114 while( *s >= '0' && *s <= '9' ) {
1115 *value = *value * 10 + (*s - '0');
1127 int NextTimeControlFromString( char ** str, long * value )
1130 int result = NextIntegerFromString( str, &temp );
1133 *value = temp * 60; /* Minutes */
1134 if( **str == ':' ) {
1136 result = NextIntegerFromString( str, &temp );
1137 *value += temp; /* Seconds */
1144 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1145 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1146 int result = -1, type = 0; long temp, temp2;
1148 if(**str != ':') return -1; // old params remain in force!
1150 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1151 if( NextIntegerFromString( str, &temp ) ) return -1;
1152 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1155 /* time only: incremental or sudden-death time control */
1156 if(**str == '+') { /* increment follows; read it */
1158 if(**str == '!') type = *(*str)++; // Bronstein TC
1159 if(result = NextIntegerFromString( str, &temp2)) return -1;
1160 *inc = temp2 * 1000;
1161 if(**str == '.') { // read fraction of increment
1162 char *start = ++(*str);
1163 if(result = NextIntegerFromString( str, &temp2)) return -1;
1165 while(start++ < *str) temp2 /= 10;
1169 *moves = 0; *tc = temp * 1000; *incType = type;
1173 (*str)++; /* classical time control */
1174 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1185 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1186 { /* [HGM] get time to add from the multi-session time-control string */
1187 int incType, moves=1; /* kludge to force reading of first session */
1188 long time, increment;
1191 if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1192 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1194 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1195 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1196 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1197 if(movenr == -1) return time; /* last move before new session */
1198 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1199 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1200 if(!moves) return increment; /* current session is incremental */
1201 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1202 } while(movenr >= -1); /* try again for next session */
1204 return 0; // no new time quota on this move
1208 ParseTimeControl(tc, ti, mps)
1215 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1218 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1219 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1220 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1224 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1226 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1229 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1231 snprintf(buf, MSG_SIZ, ":%s", mytc);
1233 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1235 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1240 /* Parse second time control */
1243 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1251 timeControl_2 = tc2 * 1000;
1261 timeControl = tc1 * 1000;
1264 timeIncrement = ti * 1000; /* convert to ms */
1265 movesPerSession = 0;
1268 movesPerSession = mps;
1276 if (appData.debugMode) {
1277 fprintf(debugFP, "%s\n", programVersion);
1280 set_cont_sequence(appData.wrapContSeq);
1281 if (appData.matchGames > 0) {
1282 appData.matchMode = TRUE;
1283 } else if (appData.matchMode) {
1284 appData.matchGames = 1;
1286 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1287 appData.matchGames = appData.sameColorGames;
1288 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1289 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1290 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1293 if (appData.noChessProgram || first.protocolVersion == 1) {
1296 /* kludge: allow timeout for initial "feature" commands */
1298 DisplayMessage("", _("Starting chess program"));
1299 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1304 CalculateIndex(int index, int gameNr)
1305 { // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1307 if(index > 0) return index; // fixed nmber
1308 if(index == 0) return 1;
1309 res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1310 if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1315 LoadGameOrPosition(int gameNr)
1316 { // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1317 if (*appData.loadGameFile != NULLCHAR) {
1318 if (!LoadGameFromFile(appData.loadGameFile,
1319 CalculateIndex(appData.loadGameIndex, gameNr),
1320 appData.loadGameFile, FALSE)) {
1321 DisplayFatalError(_("Bad game file"), 0, 1);
1324 } else if (*appData.loadPositionFile != NULLCHAR) {
1325 if (!LoadPositionFromFile(appData.loadPositionFile,
1326 CalculateIndex(appData.loadPositionIndex, gameNr),
1327 appData.loadPositionFile)) {
1328 DisplayFatalError(_("Bad position file"), 0, 1);
1336 ReserveGame(int gameNr, char resChar)
1338 FILE *tf = fopen(appData.tourneyFile, "r+");
1339 char *p, *q, c, buf[MSG_SIZ];
1340 if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1341 safeStrCpy(buf, lastMsg, MSG_SIZ);
1342 DisplayMessage(_("Pick new game"), "");
1343 flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1344 ParseArgsFromFile(tf);
1345 p = q = appData.results;
1346 if(appData.debugMode) {
1347 char *r = appData.participants;
1348 fprintf(debugFP, "results = '%s'\n", p);
1349 while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1350 fprintf(debugFP, "\n");
1352 while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1354 q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1355 safeStrCpy(q, p, strlen(p) + 2);
1356 if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1357 if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1358 if(nextGame <= appData.matchGames && resChar != ' ') { // already reserve next game, if tourney not yet done
1359 if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1362 fseek(tf, -(strlen(p)+4), SEEK_END);
1364 if(c != '"') // depending on DOS or Unix line endings we can be one off
1365 fseek(tf, -(strlen(p)+2), SEEK_END);
1366 else fseek(tf, -(strlen(p)+3), SEEK_END);
1367 fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1368 DisplayMessage(buf, "");
1369 free(p); appData.results = q;
1370 if(nextGame <= appData.matchGames && resChar != ' ' &&
1371 (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1372 UnloadEngine(&first); // next game belongs to other pairing;
1373 UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1378 MatchEvent(int mode)
1379 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1381 if(matchMode) { // already in match mode: switch it off
1383 appData.matchGames = appData.tourneyFile[0] ? nextGame: matchGame; // kludge to let match terminate after next game.
1384 ModeHighlight(); // kludgey way to remove checkmark...
1387 // if(gameMode != BeginningOfGame) {
1388 // DisplayError(_("You can only start a match from the initial position."), 0);
1392 appData.matchGames = appData.defaultMatchGames;
1393 /* Set up machine vs. machine match */
1395 NextTourneyGame(0, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1396 if(appData.tourneyFile[0]) {
1398 if(nextGame > appData.matchGames) {
1400 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1401 DisplayError(buf, 0);
1402 appData.tourneyFile[0] = 0;
1406 if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically
1407 DisplayFatalError(_("Can't have a match with no chess programs"),
1412 matchGame = roundNr = 1;
1413 first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
\r
1418 InitBackEnd3 P((void))
1420 GameMode initialMode;
1424 InitChessProgram(&first, startedFromSetupPosition);
1426 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1427 free(programVersion);
1428 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1429 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1432 if (appData.icsActive) {
1434 /* [DM] Make a console window if needed [HGM] merged ifs */
1440 if (*appData.icsCommPort != NULLCHAR)
1441 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1442 appData.icsCommPort);
1444 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1445 appData.icsHost, appData.icsPort);
1447 if( (len > MSG_SIZ) && appData.debugMode )
1448 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1450 DisplayFatalError(buf, err, 1);
1455 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1457 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1458 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1459 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1460 } else if (appData.noChessProgram) {
1466 if (*appData.cmailGameName != NULLCHAR) {
1468 OpenLoopback(&cmailPR);
1470 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1474 DisplayMessage("", "");
1475 if (StrCaseCmp(appData.initialMode, "") == 0) {
1476 initialMode = BeginningOfGame;
1477 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1478 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1479 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1480 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1483 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1484 initialMode = TwoMachinesPlay;
1485 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1486 initialMode = AnalyzeFile;
1487 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1488 initialMode = AnalyzeMode;
1489 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1490 initialMode = MachinePlaysWhite;
1491 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1492 initialMode = MachinePlaysBlack;
1493 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1494 initialMode = EditGame;
1495 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1496 initialMode = EditPosition;
1497 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1498 initialMode = Training;
1500 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1501 if( (len > MSG_SIZ) && appData.debugMode )
1502 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1504 DisplayFatalError(buf, 0, 2);
1508 if (appData.matchMode) {
1509 if(appData.tourneyFile[0]) { // start tourney from command line
1511 if(f = fopen(appData.tourneyFile, "r")) {
1512 ParseArgsFromFile(f); // make sure tourney parmeters re known
1514 } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1517 } else if (*appData.cmailGameName != NULLCHAR) {
1518 /* Set up cmail mode */
1519 ReloadCmailMsgEvent(TRUE);
1521 /* Set up other modes */
1522 if (initialMode == AnalyzeFile) {
1523 if (*appData.loadGameFile == NULLCHAR) {
1524 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1528 if (*appData.loadGameFile != NULLCHAR) {
1529 (void) LoadGameFromFile(appData.loadGameFile,
1530 appData.loadGameIndex,
1531 appData.loadGameFile, TRUE);
1532 } else if (*appData.loadPositionFile != NULLCHAR) {
1533 (void) LoadPositionFromFile(appData.loadPositionFile,
1534 appData.loadPositionIndex,
1535 appData.loadPositionFile);
1536 /* [HGM] try to make self-starting even after FEN load */
1537 /* to allow automatic setup of fairy variants with wtm */
1538 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1539 gameMode = BeginningOfGame;
1540 setboardSpoiledMachineBlack = 1;
1542 /* [HGM] loadPos: make that every new game uses the setup */
1543 /* from file as long as we do not switch variant */
1544 if(!blackPlaysFirst) {
1545 startedFromPositionFile = TRUE;
1546 CopyBoard(filePosition, boards[0]);
1549 if (initialMode == AnalyzeMode) {
1550 if (appData.noChessProgram) {
1551 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1554 if (appData.icsActive) {
1555 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1559 } else if (initialMode == AnalyzeFile) {
1560 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1561 ShowThinkingEvent();
1563 AnalysisPeriodicEvent(1);
1564 } else if (initialMode == MachinePlaysWhite) {
1565 if (appData.noChessProgram) {
1566 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1570 if (appData.icsActive) {
1571 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1575 MachineWhiteEvent();
1576 } else if (initialMode == MachinePlaysBlack) {
1577 if (appData.noChessProgram) {
1578 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1582 if (appData.icsActive) {
1583 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1587 MachineBlackEvent();
1588 } else if (initialMode == TwoMachinesPlay) {
1589 if (appData.noChessProgram) {
1590 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1594 if (appData.icsActive) {
1595 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1600 } else if (initialMode == EditGame) {
1602 } else if (initialMode == EditPosition) {
1603 EditPositionEvent();
1604 } else if (initialMode == Training) {
1605 if (*appData.loadGameFile == NULLCHAR) {
1606 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1615 * Establish will establish a contact to a remote host.port.
1616 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1617 * used to talk to the host.
1618 * Returns 0 if okay, error code if not.
1625 if (*appData.icsCommPort != NULLCHAR) {
1626 /* Talk to the host through a serial comm port */
1627 return OpenCommPort(appData.icsCommPort, &icsPR);
1629 } else if (*appData.gateway != NULLCHAR) {
1630 if (*appData.remoteShell == NULLCHAR) {
1631 /* Use the rcmd protocol to run telnet program on a gateway host */
1632 snprintf(buf, sizeof(buf), "%s %s %s",
1633 appData.telnetProgram, appData.icsHost, appData.icsPort);
1634 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1637 /* Use the rsh program to run telnet program on a gateway host */
1638 if (*appData.remoteUser == NULLCHAR) {
1639 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1640 appData.gateway, appData.telnetProgram,
1641 appData.icsHost, appData.icsPort);
1643 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1644 appData.remoteShell, appData.gateway,
1645 appData.remoteUser, appData.telnetProgram,
1646 appData.icsHost, appData.icsPort);
1648 return StartChildProcess(buf, "", &icsPR);
1651 } else if (appData.useTelnet) {
1652 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1655 /* TCP socket interface differs somewhat between
1656 Unix and NT; handle details in the front end.
1658 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1662 void EscapeExpand(char *p, char *q)
1663 { // [HGM] initstring: routine to shape up string arguments
1664 while(*p++ = *q++) if(p[-1] == '\\')
1666 case 'n': p[-1] = '\n'; break;
1667 case 'r': p[-1] = '\r'; break;
1668 case 't': p[-1] = '\t'; break;
1669 case '\\': p[-1] = '\\'; break;
1670 case 0: *p = 0; return;
1671 default: p[-1] = q[-1]; break;
1676 show_bytes(fp, buf, count)
1682 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1683 fprintf(fp, "\\%03o", *buf & 0xff);
1692 /* Returns an errno value */
1694 OutputMaybeTelnet(pr, message, count, outError)
1700 char buf[8192], *p, *q, *buflim;
1701 int left, newcount, outcount;
1703 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1704 *appData.gateway != NULLCHAR) {
1705 if (appData.debugMode) {
1706 fprintf(debugFP, ">ICS: ");
1707 show_bytes(debugFP, message, count);
1708 fprintf(debugFP, "\n");
1710 return OutputToProcess(pr, message, count, outError);
1713 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1720 if (appData.debugMode) {
1721 fprintf(debugFP, ">ICS: ");
1722 show_bytes(debugFP, buf, newcount);
1723 fprintf(debugFP, "\n");
1725 outcount = OutputToProcess(pr, buf, newcount, outError);
1726 if (outcount < newcount) return -1; /* to be sure */
1733 } else if (((unsigned char) *p) == TN_IAC) {
1734 *q++ = (char) TN_IAC;
1741 if (appData.debugMode) {
1742 fprintf(debugFP, ">ICS: ");
1743 show_bytes(debugFP, buf, newcount);
1744 fprintf(debugFP, "\n");
1746 outcount = OutputToProcess(pr, buf, newcount, outError);
1747 if (outcount < newcount) return -1; /* to be sure */
1752 read_from_player(isr, closure, message, count, error)
1759 int outError, outCount;
1760 static int gotEof = 0;
1762 /* Pass data read from player on to ICS */
1765 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1766 if (outCount < count) {
1767 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1769 } else if (count < 0) {
1770 RemoveInputSource(isr);
1771 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1772 } else if (gotEof++ > 0) {
1773 RemoveInputSource(isr);
1774 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1780 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1781 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1782 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1783 SendToICS("date\n");
1784 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1787 /* added routine for printf style output to ics */
1788 void ics_printf(char *format, ...)
1790 char buffer[MSG_SIZ];
1793 va_start(args, format);
1794 vsnprintf(buffer, sizeof(buffer), format, args);
1795 buffer[sizeof(buffer)-1] = '\0';
1804 int count, outCount, outError;
1806 if (icsPR == NULL) return;
1809 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1810 if (outCount < count) {
1811 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1815 /* This is used for sending logon scripts to the ICS. Sending
1816 without a delay causes problems when using timestamp on ICC
1817 (at least on my machine). */
1819 SendToICSDelayed(s,msdelay)
1823 int count, outCount, outError;
1825 if (icsPR == NULL) return;
1828 if (appData.debugMode) {
1829 fprintf(debugFP, ">ICS: ");
1830 show_bytes(debugFP, s, count);
1831 fprintf(debugFP, "\n");
1833 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1835 if (outCount < count) {
1836 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1841 /* Remove all highlighting escape sequences in s
1842 Also deletes any suffix starting with '('
1845 StripHighlightAndTitle(s)
1848 static char retbuf[MSG_SIZ];
1851 while (*s != NULLCHAR) {
1852 while (*s == '\033') {
1853 while (*s != NULLCHAR && !isalpha(*s)) s++;
1854 if (*s != NULLCHAR) s++;
1856 while (*s != NULLCHAR && *s != '\033') {
1857 if (*s == '(' || *s == '[') {
1868 /* Remove all highlighting escape sequences in s */
1873 static char retbuf[MSG_SIZ];
1876 while (*s != NULLCHAR) {
1877 while (*s == '\033') {
1878 while (*s != NULLCHAR && !isalpha(*s)) s++;
1879 if (*s != NULLCHAR) s++;
1881 while (*s != NULLCHAR && *s != '\033') {
1889 char *variantNames[] = VARIANT_NAMES;
1894 return variantNames[v];
1898 /* Identify a variant from the strings the chess servers use or the
1899 PGN Variant tag names we use. */
1906 VariantClass v = VariantNormal;
1907 int i, found = FALSE;
1913 /* [HGM] skip over optional board-size prefixes */
1914 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1915 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1916 while( *e++ != '_');
1919 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1923 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1924 if (StrCaseStr(e, variantNames[i])) {
1925 v = (VariantClass) i;
1932 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1933 || StrCaseStr(e, "wild/fr")
1934 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1935 v = VariantFischeRandom;
1936 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1937 (i = 1, p = StrCaseStr(e, "w"))) {
1939 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1946 case 0: /* FICS only, actually */
1948 /* Castling legal even if K starts on d-file */
1949 v = VariantWildCastle;
1954 /* Castling illegal even if K & R happen to start in
1955 normal positions. */
1956 v = VariantNoCastle;
1969 /* Castling legal iff K & R start in normal positions */
1975 /* Special wilds for position setup; unclear what to do here */
1976 v = VariantLoadable;
1979 /* Bizarre ICC game */
1980 v = VariantTwoKings;
1983 v = VariantKriegspiel;
1989 v = VariantFischeRandom;
1992 v = VariantCrazyhouse;
1995 v = VariantBughouse;
2001 /* Not quite the same as FICS suicide! */
2002 v = VariantGiveaway;
2008 v = VariantShatranj;
2011 /* Temporary names for future ICC types. The name *will* change in
2012 the next xboard/WinBoard release after ICC defines it. */
2050 v = VariantCapablanca;
2053 v = VariantKnightmate;
2059 v = VariantCylinder;
2065 v = VariantCapaRandom;
2068 v = VariantBerolina;
2080 /* Found "wild" or "w" in the string but no number;
2081 must assume it's normal chess. */
2085 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2086 if( (len > MSG_SIZ) && appData.debugMode )
2087 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2089 DisplayError(buf, 0);
2095 if (appData.debugMode) {
2096 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2097 e, wnum, VariantName(v));
2102 static int leftover_start = 0, leftover_len = 0;
2103 char star_match[STAR_MATCH_N][MSG_SIZ];
2105 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2106 advance *index beyond it, and set leftover_start to the new value of
2107 *index; else return FALSE. If pattern contains the character '*', it
2108 matches any sequence of characters not containing '\r', '\n', or the
2109 character following the '*' (if any), and the matched sequence(s) are
2110 copied into star_match.
2113 looking_at(buf, index, pattern)
2118 char *bufp = &buf[*index], *patternp = pattern;
2120 char *matchp = star_match[0];
2123 if (*patternp == NULLCHAR) {
2124 *index = leftover_start = bufp - buf;
2128 if (*bufp == NULLCHAR) return FALSE;
2129 if (*patternp == '*') {
2130 if (*bufp == *(patternp + 1)) {
2132 matchp = star_match[++star_count];
2136 } else if (*bufp == '\n' || *bufp == '\r') {
2138 if (*patternp == NULLCHAR)
2143 *matchp++ = *bufp++;
2147 if (*patternp != *bufp) return FALSE;
2154 SendToPlayer(data, length)
2158 int error, outCount;
2159 outCount = OutputToProcess(NoProc, data, length, &error);
2160 if (outCount < length) {
2161 DisplayFatalError(_("Error writing to display"), error, 1);
2166 PackHolding(packed, holding)
2178 switch (runlength) {
2189 sprintf(q, "%d", runlength);
2201 /* Telnet protocol requests from the front end */
2203 TelnetRequest(ddww, option)
2204 unsigned char ddww, option;
2206 unsigned char msg[3];
2207 int outCount, outError;
2209 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2211 if (appData.debugMode) {
2212 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2228 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2237 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2240 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2245 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2247 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2254 if (!appData.icsActive) return;
2255 TelnetRequest(TN_DO, TN_ECHO);
2261 if (!appData.icsActive) return;
2262 TelnetRequest(TN_DONT, TN_ECHO);
2266 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2268 /* put the holdings sent to us by the server on the board holdings area */
2269 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2273 if(gameInfo.holdingsWidth < 2) return;
2274 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2275 return; // prevent overwriting by pre-board holdings
2277 if( (int)lowestPiece >= BlackPawn ) {
2280 holdingsStartRow = BOARD_HEIGHT-1;
2283 holdingsColumn = BOARD_WIDTH-1;
2284 countsColumn = BOARD_WIDTH-2;
2285 holdingsStartRow = 0;
2289 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2290 board[i][holdingsColumn] = EmptySquare;
2291 board[i][countsColumn] = (ChessSquare) 0;
2293 while( (p=*holdings++) != NULLCHAR ) {
2294 piece = CharToPiece( ToUpper(p) );
2295 if(piece == EmptySquare) continue;
2296 /*j = (int) piece - (int) WhitePawn;*/
2297 j = PieceToNumber(piece);
2298 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2299 if(j < 0) continue; /* should not happen */
2300 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2301 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2302 board[holdingsStartRow+j*direction][countsColumn]++;
2308 VariantSwitch(Board board, VariantClass newVariant)
2310 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2311 static Board oldBoard;
2313 startedFromPositionFile = FALSE;
2314 if(gameInfo.variant == newVariant) return;
2316 /* [HGM] This routine is called each time an assignment is made to
2317 * gameInfo.variant during a game, to make sure the board sizes
2318 * are set to match the new variant. If that means adding or deleting
2319 * holdings, we shift the playing board accordingly
2320 * This kludge is needed because in ICS observe mode, we get boards
2321 * of an ongoing game without knowing the variant, and learn about the
2322 * latter only later. This can be because of the move list we requested,
2323 * in which case the game history is refilled from the beginning anyway,
2324 * but also when receiving holdings of a crazyhouse game. In the latter
2325 * case we want to add those holdings to the already received position.
2329 if (appData.debugMode) {
2330 fprintf(debugFP, "Switch board from %s to %s\n",
2331 VariantName(gameInfo.variant), VariantName(newVariant));
2332 setbuf(debugFP, NULL);
2334 shuffleOpenings = 0; /* [HGM] shuffle */
2335 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2339 newWidth = 9; newHeight = 9;
2340 gameInfo.holdingsSize = 7;
2341 case VariantBughouse:
2342 case VariantCrazyhouse:
2343 newHoldingsWidth = 2; break;
2347 newHoldingsWidth = 2;
2348 gameInfo.holdingsSize = 8;
2351 case VariantCapablanca:
2352 case VariantCapaRandom:
2355 newHoldingsWidth = gameInfo.holdingsSize = 0;
2358 if(newWidth != gameInfo.boardWidth ||
2359 newHeight != gameInfo.boardHeight ||
2360 newHoldingsWidth != gameInfo.holdingsWidth ) {
2362 /* shift position to new playing area, if needed */
2363 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2364 for(i=0; i<BOARD_HEIGHT; i++)
2365 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2366 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2368 for(i=0; i<newHeight; i++) {
2369 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2370 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2372 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2373 for(i=0; i<BOARD_HEIGHT; i++)
2374 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2375 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2378 gameInfo.boardWidth = newWidth;
2379 gameInfo.boardHeight = newHeight;
2380 gameInfo.holdingsWidth = newHoldingsWidth;
2381 gameInfo.variant = newVariant;
2382 InitDrawingSizes(-2, 0);
2383 } else gameInfo.variant = newVariant;
2384 CopyBoard(oldBoard, board); // remember correctly formatted board
2385 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2386 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2389 static int loggedOn = FALSE;
2391 /*-- Game start info cache: --*/
2393 char gs_kind[MSG_SIZ];
2394 static char player1Name[128] = "";
2395 static char player2Name[128] = "";
2396 static char cont_seq[] = "\n\\ ";
2397 static int player1Rating = -1;
2398 static int player2Rating = -1;
2399 /*----------------------------*/
2401 ColorClass curColor = ColorNormal;
2402 int suppressKibitz = 0;
2405 Boolean soughtPending = FALSE;
2406 Boolean seekGraphUp;
2407 #define MAX_SEEK_ADS 200
2409 char *seekAdList[MAX_SEEK_ADS];
2410 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2411 float tcList[MAX_SEEK_ADS];
2412 char colorList[MAX_SEEK_ADS];
2413 int nrOfSeekAds = 0;
2414 int minRating = 1010, maxRating = 2800;
2415 int hMargin = 10, vMargin = 20, h, w;
2416 extern int squareSize, lineGap;
2421 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2422 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2423 if(r < minRating+100 && r >=0 ) r = minRating+100;
2424 if(r > maxRating) r = maxRating;
2425 if(tc < 1.) tc = 1.;
2426 if(tc > 95.) tc = 95.;
2427 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2428 y = ((double)r - minRating)/(maxRating - minRating)
2429 * (h-vMargin-squareSize/8-1) + vMargin;
2430 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2431 if(strstr(seekAdList[i], " u ")) color = 1;
2432 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2433 !strstr(seekAdList[i], "bullet") &&
2434 !strstr(seekAdList[i], "blitz") &&
2435 !strstr(seekAdList[i], "standard") ) color = 2;
2436 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2437 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2441 AddAd(char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2443 char buf[MSG_SIZ], *ext = "";
2444 VariantClass v = StringToVariant(type);
2445 if(strstr(type, "wild")) {
2446 ext = type + 4; // append wild number
2447 if(v == VariantFischeRandom) type = "chess960"; else
2448 if(v == VariantLoadable) type = "setup"; else
2449 type = VariantName(v);
2451 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2452 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2453 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2454 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2455 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2456 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2457 seekNrList[nrOfSeekAds] = nr;
2458 zList[nrOfSeekAds] = 0;
2459 seekAdList[nrOfSeekAds++] = StrSave(buf);
2460 if(plot) PlotSeekAd(nrOfSeekAds-1);
2467 int x = xList[i], y = yList[i], d=squareSize/4, k;
2468 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2469 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2470 // now replot every dot that overlapped
2471 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2472 int xx = xList[k], yy = yList[k];
2473 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2474 DrawSeekDot(xx, yy, colorList[k]);
2479 RemoveSeekAd(int nr)
2482 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2484 if(seekAdList[i]) free(seekAdList[i]);
2485 seekAdList[i] = seekAdList[--nrOfSeekAds];
2486 seekNrList[i] = seekNrList[nrOfSeekAds];
2487 ratingList[i] = ratingList[nrOfSeekAds];
2488 colorList[i] = colorList[nrOfSeekAds];
2489 tcList[i] = tcList[nrOfSeekAds];
2490 xList[i] = xList[nrOfSeekAds];
2491 yList[i] = yList[nrOfSeekAds];
2492 zList[i] = zList[nrOfSeekAds];
2493 seekAdList[nrOfSeekAds] = NULL;
2499 MatchSoughtLine(char *line)
2501 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2502 int nr, base, inc, u=0; char dummy;
2504 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2505 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2507 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2508 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2509 // match: compact and save the line
2510 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2520 if(!seekGraphUp) return FALSE;
2521 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2522 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2524 DrawSeekBackground(0, 0, w, h);
2525 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2526 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2527 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2528 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2530 DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2533 snprintf(buf, MSG_SIZ, "%d", i);
2534 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2537 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2538 for(i=1; i<100; i+=(i<10?1:5)) {
2539 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2540 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2541 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2543 snprintf(buf, MSG_SIZ, "%d", i);
2544 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2547 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2551 int SeekGraphClick(ClickType click, int x, int y, int moving)
2553 static int lastDown = 0, displayed = 0, lastSecond;
2554 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2555 if(click == Release || moving) return FALSE;
2557 soughtPending = TRUE;
2558 SendToICS(ics_prefix);
2559 SendToICS("sought\n"); // should this be "sought all"?
2560 } else { // issue challenge based on clicked ad
2561 int dist = 10000; int i, closest = 0, second = 0;
2562 for(i=0; i<nrOfSeekAds; i++) {
2563 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2564 if(d < dist) { dist = d; closest = i; }
2565 second += (d - zList[i] < 120); // count in-range ads
2566 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2570 second = (second > 1);
2571 if(displayed != closest || second != lastSecond) {
2572 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2573 lastSecond = second; displayed = closest;
2575 if(click == Press) {
2576 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2579 } // on press 'hit', only show info
2580 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2581 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2582 SendToICS(ics_prefix);
2584 return TRUE; // let incoming board of started game pop down the graph
2585 } else if(click == Release) { // release 'miss' is ignored
2586 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2587 if(moving == 2) { // right up-click
2588 nrOfSeekAds = 0; // refresh graph
2589 soughtPending = TRUE;
2590 SendToICS(ics_prefix);
2591 SendToICS("sought\n"); // should this be "sought all"?
2594 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2595 // press miss or release hit 'pop down' seek graph
2596 seekGraphUp = FALSE;
2597 DrawPosition(TRUE, NULL);
2603 read_from_ics(isr, closure, data, count, error)
2610 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2611 #define STARTED_NONE 0
2612 #define STARTED_MOVES 1
2613 #define STARTED_BOARD 2
2614 #define STARTED_OBSERVE 3
2615 #define STARTED_HOLDINGS 4
2616 #define STARTED_CHATTER 5
2617 #define STARTED_COMMENT 6
2618 #define STARTED_MOVES_NOHIDE 7
2620 static int started = STARTED_NONE;
2621 static char parse[20000];
2622 static int parse_pos = 0;
2623 static char buf[BUF_SIZE + 1];
2624 static int firstTime = TRUE, intfSet = FALSE;
2625 static ColorClass prevColor = ColorNormal;
2626 static int savingComment = FALSE;
2627 static int cmatch = 0; // continuation sequence match
2634 int backup; /* [DM] For zippy color lines */
2636 char talker[MSG_SIZ]; // [HGM] chat
2639 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2641 if (appData.debugMode) {
2643 fprintf(debugFP, "<ICS: ");
2644 show_bytes(debugFP, data, count);
2645 fprintf(debugFP, "\n");
2649 if (appData.debugMode) { int f = forwardMostMove;
2650 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2651 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2652 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2655 /* If last read ended with a partial line that we couldn't parse,
2656 prepend it to the new read and try again. */
2657 if (leftover_len > 0) {
2658 for (i=0; i<leftover_len; i++)
2659 buf[i] = buf[leftover_start + i];
2662 /* copy new characters into the buffer */
2663 bp = buf + leftover_len;
2664 buf_len=leftover_len;
2665 for (i=0; i<count; i++)
2668 if (data[i] == '\r')
2671 // join lines split by ICS?
2672 if (!appData.noJoin)
2675 Joining just consists of finding matches against the
2676 continuation sequence, and discarding that sequence
2677 if found instead of copying it. So, until a match
2678 fails, there's nothing to do since it might be the
2679 complete sequence, and thus, something we don't want
2682 if (data[i] == cont_seq[cmatch])
2685 if (cmatch == strlen(cont_seq))
2687 cmatch = 0; // complete match. just reset the counter
2690 it's possible for the ICS to not include the space
2691 at the end of the last word, making our [correct]
2692 join operation fuse two separate words. the server
2693 does this when the space occurs at the width setting.
2695 if (!buf_len || buf[buf_len-1] != ' ')
2706 match failed, so we have to copy what matched before
2707 falling through and copying this character. In reality,
2708 this will only ever be just the newline character, but
2709 it doesn't hurt to be precise.
2711 strncpy(bp, cont_seq, cmatch);
2723 buf[buf_len] = NULLCHAR;
2724 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2729 while (i < buf_len) {
2730 /* Deal with part of the TELNET option negotiation
2731 protocol. We refuse to do anything beyond the
2732 defaults, except that we allow the WILL ECHO option,
2733 which ICS uses to turn off password echoing when we are
2734 directly connected to it. We reject this option
2735 if localLineEditing mode is on (always on in xboard)
2736 and we are talking to port 23, which might be a real
2737 telnet server that will try to keep WILL ECHO on permanently.
2739 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2740 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2741 unsigned char option;
2743 switch ((unsigned char) buf[++i]) {
2745 if (appData.debugMode)
2746 fprintf(debugFP, "\n<WILL ");
2747 switch (option = (unsigned char) buf[++i]) {
2749 if (appData.debugMode)
2750 fprintf(debugFP, "ECHO ");
2751 /* Reply only if this is a change, according
2752 to the protocol rules. */
2753 if (remoteEchoOption) break;
2754 if (appData.localLineEditing &&
2755 atoi(appData.icsPort) == TN_PORT) {
2756 TelnetRequest(TN_DONT, TN_ECHO);
2759 TelnetRequest(TN_DO, TN_ECHO);
2760 remoteEchoOption = TRUE;
2764 if (appData.debugMode)
2765 fprintf(debugFP, "%d ", option);
2766 /* Whatever this is, we don't want it. */
2767 TelnetRequest(TN_DONT, option);
2772 if (appData.debugMode)
2773 fprintf(debugFP, "\n<WONT ");
2774 switch (option = (unsigned char) buf[++i]) {
2776 if (appData.debugMode)
2777 fprintf(debugFP, "ECHO ");
2778 /* Reply only if this is a change, according
2779 to the protocol rules. */
2780 if (!remoteEchoOption) break;
2782 TelnetRequest(TN_DONT, TN_ECHO);
2783 remoteEchoOption = FALSE;
2786 if (appData.debugMode)
2787 fprintf(debugFP, "%d ", (unsigned char) option);
2788 /* Whatever this is, it must already be turned
2789 off, because we never agree to turn on
2790 anything non-default, so according to the
2791 protocol rules, we don't reply. */
2796 if (appData.debugMode)
2797 fprintf(debugFP, "\n<DO ");
2798 switch (option = (unsigned char) buf[++i]) {
2800 /* Whatever this is, we refuse to do it. */
2801 if (appData.debugMode)
2802 fprintf(debugFP, "%d ", option);
2803 TelnetRequest(TN_WONT, option);
2808 if (appData.debugMode)
2809 fprintf(debugFP, "\n<DONT ");
2810 switch (option = (unsigned char) buf[++i]) {
2812 if (appData.debugMode)
2813 fprintf(debugFP, "%d ", option);
2814 /* Whatever this is, we are already not doing
2815 it, because we never agree to do anything
2816 non-default, so according to the protocol
2817 rules, we don't reply. */
2822 if (appData.debugMode)
2823 fprintf(debugFP, "\n<IAC ");
2824 /* Doubled IAC; pass it through */
2828 if (appData.debugMode)
2829 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2830 /* Drop all other telnet commands on the floor */
2833 if (oldi > next_out)
2834 SendToPlayer(&buf[next_out], oldi - next_out);
2840 /* OK, this at least will *usually* work */
2841 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2845 if (loggedOn && !intfSet) {
2846 if (ics_type == ICS_ICC) {
2847 snprintf(str, MSG_SIZ,
2848 "/set-quietly interface %s\n/set-quietly style 12\n",
2850 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2851 strcat(str, "/set-2 51 1\n/set seek 1\n");
2852 } else if (ics_type == ICS_CHESSNET) {
2853 snprintf(str, MSG_SIZ, "/style 12\n");
2855 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2856 strcat(str, programVersion);
2857 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2858 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2859 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2861 strcat(str, "$iset nohighlight 1\n");
2863 strcat(str, "$iset lock 1\n$style 12\n");
2866 NotifyFrontendLogin();
2870 if (started == STARTED_COMMENT) {
2871 /* Accumulate characters in comment */
2872 parse[parse_pos++] = buf[i];
2873 if (buf[i] == '\n') {
2874 parse[parse_pos] = NULLCHAR;
2875 if(chattingPartner>=0) {
2877 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2878 OutputChatMessage(chattingPartner, mess);
2879 chattingPartner = -1;
2880 next_out = i+1; // [HGM] suppress printing in ICS window
2882 if(!suppressKibitz) // [HGM] kibitz
2883 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2884 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2885 int nrDigit = 0, nrAlph = 0, j;
2886 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2887 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2888 parse[parse_pos] = NULLCHAR;
2889 // try to be smart: if it does not look like search info, it should go to
2890 // ICS interaction window after all, not to engine-output window.
2891 for(j=0; j<parse_pos; j++) { // count letters and digits
2892 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2893 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2894 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2896 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2897 int depth=0; float score;
2898 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2899 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2900 pvInfoList[forwardMostMove-1].depth = depth;
2901 pvInfoList[forwardMostMove-1].score = 100*score;
2903 OutputKibitz(suppressKibitz, parse);
2906 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2907 SendToPlayer(tmp, strlen(tmp));
2909 next_out = i+1; // [HGM] suppress printing in ICS window
2911 started = STARTED_NONE;
2913 /* Don't match patterns against characters in comment */
2918 if (started == STARTED_CHATTER) {
2919 if (buf[i] != '\n') {
2920 /* Don't match patterns against characters in chatter */
2924 started = STARTED_NONE;
2925 if(suppressKibitz) next_out = i+1;
2928 /* Kludge to deal with rcmd protocol */
2929 if (firstTime && looking_at(buf, &i, "\001*")) {
2930 DisplayFatalError(&buf[1], 0, 1);
2936 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2939 if (appData.debugMode)
2940 fprintf(debugFP, "ics_type %d\n", ics_type);
2943 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2944 ics_type = ICS_FICS;
2946 if (appData.debugMode)
2947 fprintf(debugFP, "ics_type %d\n", ics_type);
2950 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2951 ics_type = ICS_CHESSNET;
2953 if (appData.debugMode)
2954 fprintf(debugFP, "ics_type %d\n", ics_type);
2959 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2960 looking_at(buf, &i, "Logging you in as \"*\"") ||
2961 looking_at(buf, &i, "will be \"*\""))) {
2962 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2966 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2968 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2969 DisplayIcsInteractionTitle(buf);
2970 have_set_title = TRUE;
2973 /* skip finger notes */
2974 if (started == STARTED_NONE &&
2975 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2976 (buf[i] == '1' && buf[i+1] == '0')) &&
2977 buf[i+2] == ':' && buf[i+3] == ' ') {
2978 started = STARTED_CHATTER;
2984 // [HGM] seekgraph: recognize sought lines and end-of-sought message
2985 if(appData.seekGraph) {
2986 if(soughtPending && MatchSoughtLine(buf+i)) {
2987 i = strstr(buf+i, "rated") - buf;
2988 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2989 next_out = leftover_start = i;
2990 started = STARTED_CHATTER;
2991 suppressKibitz = TRUE;
2994 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2995 && looking_at(buf, &i, "* ads displayed")) {
2996 soughtPending = FALSE;
3001 if(appData.autoRefresh) {
3002 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3003 int s = (ics_type == ICS_ICC); // ICC format differs
3005 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3006 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3007 looking_at(buf, &i, "*% "); // eat prompt
3008 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3009 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3010 next_out = i; // suppress
3013 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3014 char *p = star_match[0];
3016 if(seekGraphUp) RemoveSeekAd(atoi(p));
3017 while(*p && *p++ != ' '); // next
3019 looking_at(buf, &i, "*% "); // eat prompt
3020 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3027 /* skip formula vars */
3028 if (started == STARTED_NONE &&
3029 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3030 started = STARTED_CHATTER;
3035 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3036 if (appData.autoKibitz && started == STARTED_NONE &&
3037 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3038 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3039 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3040 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3041 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3042 suppressKibitz = TRUE;
3043 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3045 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3046 && (gameMode == IcsPlayingWhite)) ||
3047 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3048 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3049 started = STARTED_CHATTER; // own kibitz we simply discard
3051 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3052 parse_pos = 0; parse[0] = NULLCHAR;
3053 savingComment = TRUE;
3054 suppressKibitz = gameMode != IcsObserving ? 2 :
3055 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3059 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3060 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3061 && atoi(star_match[0])) {
3062 // suppress the acknowledgements of our own autoKibitz
3064 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3065 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3066 SendToPlayer(star_match[0], strlen(star_match[0]));
3067 if(looking_at(buf, &i, "*% ")) // eat prompt
3068 suppressKibitz = FALSE;
3072 } // [HGM] kibitz: end of patch
3074 // [HGM] chat: intercept tells by users for which we have an open chat window
3076 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3077 looking_at(buf, &i, "* whispers:") ||
3078 looking_at(buf, &i, "* kibitzes:") ||
3079 looking_at(buf, &i, "* shouts:") ||
3080 looking_at(buf, &i, "* c-shouts:") ||
3081 looking_at(buf, &i, "--> * ") ||
3082 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3083 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3084 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3085 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3087 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3088 chattingPartner = -1;
3090 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3091 for(p=0; p<MAX_CHAT; p++) {
3092 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3093 talker[0] = '['; strcat(talker, "] ");
3094 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3095 chattingPartner = p; break;
3098 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3099 for(p=0; p<MAX_CHAT; p++) {
3100 if(!strcmp("kibitzes", chatPartner[p])) {
3101 talker[0] = '['; strcat(talker, "] ");
3102 chattingPartner = p; break;
3105 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3106 for(p=0; p<MAX_CHAT; p++) {
3107 if(!strcmp("whispers", chatPartner[p])) {
3108 talker[0] = '['; strcat(talker, "] ");
3109 chattingPartner = p; break;
3112 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3113 if(buf[i-8] == '-' && buf[i-3] == 't')
3114 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3115 if(!strcmp("c-shouts", chatPartner[p])) {
3116 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3117 chattingPartner = p; break;
3120 if(chattingPartner < 0)
3121 for(p=0; p<MAX_CHAT; p++) {
3122 if(!strcmp("shouts", chatPartner[p])) {
3123 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3124 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3125 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3126 chattingPartner = p; break;
3130 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3131 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3132 talker[0] = 0; Colorize(ColorTell, FALSE);
3133 chattingPartner = p; break;
3135 if(chattingPartner<0) i = oldi; else {
3136 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3137 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3138 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3139 started = STARTED_COMMENT;
3140 parse_pos = 0; parse[0] = NULLCHAR;
3141 savingComment = 3 + chattingPartner; // counts as TRUE
3142 suppressKibitz = TRUE;
3145 } // [HGM] chat: end of patch
3148 if (appData.zippyTalk || appData.zippyPlay) {
3149 /* [DM] Backup address for color zippy lines */
3151 if (loggedOn == TRUE)
3152 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3153 (appData.zippyPlay && ZippyMatch(buf, &backup)));
3155 } // [DM] 'else { ' deleted
3157 /* Regular tells and says */
3158 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3159 looking_at(buf, &i, "* (your partner) tells you: ") ||
3160 looking_at(buf, &i, "* says: ") ||
3161 /* Don't color "message" or "messages" output */
3162 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3163 looking_at(buf, &i, "*. * at *:*: ") ||
3164 looking_at(buf, &i, "--* (*:*): ") ||
3165 /* Message notifications (same color as tells) */
3166 looking_at(buf, &i, "* has left a message ") ||
3167 looking_at(buf, &i, "* just sent you a message:\n") ||
3168 /* Whispers and kibitzes */
3169 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3170 looking_at(buf, &i, "* kibitzes: ") ||
3172 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3174 if (tkind == 1 && strchr(star_match[0], ':')) {
3175 /* Avoid "tells you:" spoofs in channels */
3178 if (star_match[0][0] == NULLCHAR ||
3179 strchr(star_match[0], ' ') ||
3180 (tkind == 3 && strchr(star_match[1], ' '))) {
3181 /* Reject bogus matches */
3184 if (appData.colorize) {
3185 if (oldi > next_out) {
3186 SendToPlayer(&buf[next_out], oldi - next_out);
3191 Colorize(ColorTell, FALSE);
3192 curColor = ColorTell;
3195 Colorize(ColorKibitz, FALSE);
3196 curColor = ColorKibitz;
3199 p = strrchr(star_match[1], '(');
3206 Colorize(ColorChannel1, FALSE);
3207 curColor = ColorChannel1;
3209 Colorize(ColorChannel, FALSE);
3210 curColor = ColorChannel;
3214 curColor = ColorNormal;
3218 if (started == STARTED_NONE && appData.autoComment &&
3219 (gameMode == IcsObserving ||
3220 gameMode == IcsPlayingWhite ||
3221 gameMode == IcsPlayingBlack)) {
3222 parse_pos = i - oldi;
3223 memcpy(parse, &buf[oldi], parse_pos);
3224 parse[parse_pos] = NULLCHAR;
3225 started = STARTED_COMMENT;
3226 savingComment = TRUE;
3228 started = STARTED_CHATTER;
3229 savingComment = FALSE;
3236 if (looking_at(buf, &i, "* s-shouts: ") ||
3237 looking_at(buf, &i, "* c-shouts: ")) {
3238 if (appData.colorize) {
3239 if (oldi > next_out) {
3240 SendToPlayer(&buf[next_out], oldi - next_out);
3243 Colorize(ColorSShout, FALSE);
3244 curColor = ColorSShout;
3247 started = STARTED_CHATTER;
3251 if (looking_at(buf, &i, "--->")) {
3256 if (looking_at(buf, &i, "* shouts: ") ||
3257 looking_at(buf, &i, "--> ")) {
3258 if (appData.colorize) {
3259 if (oldi > next_out) {
3260 SendToPlayer(&buf[next_out], oldi - next_out);
3263 Colorize(ColorShout, FALSE);
3264 curColor = ColorShout;
3267 started = STARTED_CHATTER;
3271 if (looking_at( buf, &i, "Challenge:")) {
3272 if (appData.colorize) {
3273 if (oldi > next_out) {
3274 SendToPlayer(&buf[next_out], oldi - next_out);
3277 Colorize(ColorChallenge, FALSE);
3278 curColor = ColorChallenge;
3284 if (looking_at(buf, &i, "* offers you") ||
3285 looking_at(buf, &i, "* offers to be") ||
3286 looking_at(buf, &i, "* would like to") ||
3287 looking_at(buf, &i, "* requests to") ||
3288 looking_at(buf, &i, "Your opponent offers") ||
3289 looking_at(buf, &i, "Your opponent requests")) {
3291 if (appData.colorize) {
3292 if (oldi > next_out) {
3293 SendToPlayer(&buf[next_out], oldi - next_out);
3296 Colorize(ColorRequest, FALSE);
3297 curColor = ColorRequest;
3302 if (looking_at(buf, &i, "* (*) seeking")) {
3303 if (appData.colorize) {
3304 if (oldi > next_out) {
3305 SendToPlayer(&buf[next_out], oldi - next_out);
3308 Colorize(ColorSeek, FALSE);
3309 curColor = ColorSeek;
3314 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3316 if (looking_at(buf, &i, "\\ ")) {
3317 if (prevColor != ColorNormal) {
3318 if (oldi > next_out) {
3319 SendToPlayer(&buf[next_out], oldi - next_out);
3322 Colorize(prevColor, TRUE);
3323 curColor = prevColor;
3325 if (savingComment) {
3326 parse_pos = i - oldi;
3327 memcpy(parse, &buf[oldi], parse_pos);
3328 parse[parse_pos] = NULLCHAR;
3329 started = STARTED_COMMENT;
3330 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3331 chattingPartner = savingComment - 3; // kludge to remember the box
3333 started = STARTED_CHATTER;
3338 if (looking_at(buf, &i, "Black Strength :") ||
3339 looking_at(buf, &i, "<<< style 10 board >>>") ||
3340 looking_at(buf, &i, "<10>") ||
3341 looking_at(buf, &i, "#@#")) {
3342 /* Wrong board style */
3344 SendToICS(ics_prefix);
3345 SendToICS("set style 12\n");
3346 SendToICS(ics_prefix);
3347 SendToICS("refresh\n");
3351 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3353 have_sent_ICS_logon = 1;
3357 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3358 (looking_at(buf, &i, "\n<12> ") ||
3359 looking_at(buf, &i, "<12> "))) {
3361 if (oldi > next_out) {
3362 SendToPlayer(&buf[next_out], oldi - next_out);
3365 started = STARTED_BOARD;
3370 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3371 looking_at(buf, &i, "<b1> ")) {
3372 if (oldi > next_out) {
3373 SendToPlayer(&buf[next_out], oldi - next_out);
3376 started = STARTED_HOLDINGS;
3381 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3383 /* Header for a move list -- first line */
3385 switch (ics_getting_history) {
3389 case BeginningOfGame:
3390 /* User typed "moves" or "oldmoves" while we
3391 were idle. Pretend we asked for these
3392 moves and soak them up so user can step
3393 through them and/or save them.
3396 gameMode = IcsObserving;
3399 ics_getting_history = H_GOT_UNREQ_HEADER;
3401 case EditGame: /*?*/
3402 case EditPosition: /*?*/
3403 /* Should above feature work in these modes too? */
3404 /* For now it doesn't */
3405 ics_getting_history = H_GOT_UNWANTED_HEADER;
3408 ics_getting_history = H_GOT_UNWANTED_HEADER;