Fix mate and stalemate test in Chu Shogi
[xboard.git] / backend.c
1 /*
2  * backend.c -- Common back end for X and Windows NT versions of
3  *
4  * Copyright 1991 by Digital Equipment Corporation, Maynard,
5  * Massachusetts.
6  *
7  * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8  * 2007, 2008, 2009, 2010, 2011, 2012, 2013 Free Software Foundation, Inc.
9  *
10  * Enhancements Copyright 2005 Alessandro Scotti
11  *
12  * The following terms apply to Digital Equipment Corporation's copyright
13  * interest in XBoard:
14  * ------------------------------------------------------------------------
15  * All Rights Reserved
16  *
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.
24  *
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
31  * SOFTWARE.
32  * ------------------------------------------------------------------------
33  *
34  * The following terms apply to the enhanced version of XBoard
35  * distributed by the Free Software Foundation:
36  * ------------------------------------------------------------------------
37  *
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.
42  *
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.
47  *
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/.  *
50  *
51  *------------------------------------------------------------------------
52  ** See the file ChangeLog for a revision history.  */
53
54 /* [AS] Also useful here for debugging */
55 #ifdef WIN32
56 #include <windows.h>
57
58 int flock(int f, int code);
59 #define LOCK_EX 2
60 #define SLASH '\\'
61
62 #else
63
64 #include <sys/file.h>
65 #define SLASH '/'
66
67 #endif
68
69 #include "config.h"
70
71 #include <assert.h>
72 #include <stdio.h>
73 #include <ctype.h>
74 #include <errno.h>
75 #include <sys/types.h>
76 #include <sys/stat.h>
77 #include <math.h>
78 #include <ctype.h>
79
80 #if STDC_HEADERS
81 # include <stdlib.h>
82 # include <string.h>
83 # include <stdarg.h>
84 #else /* not STDC_HEADERS */
85 # if HAVE_STRING_H
86 #  include <string.h>
87 # else /* not HAVE_STRING_H */
88 #  include <strings.h>
89 # endif /* not HAVE_STRING_H */
90 #endif /* not STDC_HEADERS */
91
92 #if HAVE_SYS_FCNTL_H
93 # include <sys/fcntl.h>
94 #else /* not HAVE_SYS_FCNTL_H */
95 # if HAVE_FCNTL_H
96 #  include <fcntl.h>
97 # endif /* HAVE_FCNTL_H */
98 #endif /* not HAVE_SYS_FCNTL_H */
99
100 #if TIME_WITH_SYS_TIME
101 # include <sys/time.h>
102 # include <time.h>
103 #else
104 # if HAVE_SYS_TIME_H
105 #  include <sys/time.h>
106 # else
107 #  include <time.h>
108 # endif
109 #endif
110
111 #if defined(_amigados) && !defined(__GNUC__)
112 struct timezone {
113     int tz_minuteswest;
114     int tz_dsttime;
115 };
116 extern int gettimeofday(struct timeval *, struct timezone *);
117 #endif
118
119 #if HAVE_UNISTD_H
120 # include <unistd.h>
121 #endif
122
123 #include "common.h"
124 #include "frontend.h"
125 #include "backend.h"
126 #include "parser.h"
127 #include "moves.h"
128 #if ZIPPY
129 # include "zippy.h"
130 #endif
131 #include "backendz.h"
132 #include "evalgraph.h"
133 #include "gettext.h"
134
135 #ifdef ENABLE_NLS
136 # define _(s) gettext (s)
137 # define N_(s) gettext_noop (s)
138 # define T_(s) gettext(s)
139 #else
140 # ifdef WIN32
141 #   define _(s) T_(s)
142 #   define N_(s) s
143 # else
144 #   define _(s) (s)
145 #   define N_(s) s
146 #   define T_(s) s
147 # endif
148 #endif
149
150
151 int establish P((void));
152 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
153                          char *buf, int count, int error));
154 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
155                       char *buf, int count, int error));
156 void SendToICS P((char *s));
157 void SendToICSDelayed P((char *s, long msdelay));
158 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
159 void HandleMachineMove P((char *message, ChessProgramState *cps));
160 int AutoPlayOneMove P((void));
161 int LoadGameOneMove P((ChessMove readAhead));
162 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
163 int LoadPositionFromFile P((char *filename, int n, char *title));
164 int SavePositionToFile P((char *filename));
165 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
166 void ShowMove P((int fromX, int fromY, int toX, int toY));
167 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
168                    /*char*/int promoChar));
169 void BackwardInner P((int target));
170 void ForwardInner P((int target));
171 int Adjudicate P((ChessProgramState *cps));
172 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
173 void EditPositionDone P((Boolean fakeRights));
174 void PrintOpponents P((FILE *fp));
175 void PrintPosition P((FILE *fp, int move));
176 void StartChessProgram P((ChessProgramState *cps));
177 void SendToProgram P((char *message, ChessProgramState *cps));
178 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
179 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
180                            char *buf, int count, int error));
181 void SendTimeControl P((ChessProgramState *cps,
182                         int mps, long tc, int inc, int sd, int st));
183 char *TimeControlTagValue P((void));
184 void Attention P((ChessProgramState *cps));
185 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
186 int ResurrectChessProgram P((void));
187 void DisplayComment P((int moveNumber, char *text));
188 void DisplayMove P((int moveNumber));
189
190 void ParseGameHistory P((char *game));
191 void ParseBoard12 P((char *string));
192 void KeepAlive P((void));
193 void StartClocks P((void));
194 void SwitchClocks P((int nr));
195 void StopClocks P((void));
196 void ResetClocks P((void));
197 char *PGNDate P((void));
198 void SetGameInfo P((void));
199 int RegisterMove P((void));
200 void MakeRegisteredMove P((void));
201 void TruncateGame P((void));
202 int looking_at P((char *, int *, char *));
203 void CopyPlayerNameIntoFileName P((char **, char *));
204 char *SavePart P((char *));
205 int SaveGameOldStyle P((FILE *));
206 int SaveGamePGN P((FILE *));
207 int CheckFlags P((void));
208 long NextTickLength P((long));
209 void CheckTimeControl P((void));
210 void show_bytes P((FILE *, char *, int));
211 int string_to_rating P((char *str));
212 void ParseFeatures P((char* args, ChessProgramState *cps));
213 void InitBackEnd3 P((void));
214 void FeatureDone P((ChessProgramState* cps, int val));
215 void InitChessProgram P((ChessProgramState *cps, int setup));
216 void OutputKibitz(int window, char *text);
217 int PerpetualChase(int first, int last);
218 int EngineOutputIsUp();
219 void InitDrawingSizes(int x, int y);
220 void NextMatchGame P((void));
221 int NextTourneyGame P((int nr, int *swap));
222 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
223 FILE *WriteTourneyFile P((char *results, FILE *f));
224 void DisplayTwoMachinesTitle P(());
225 static void ExcludeClick P((int index));
226 void ToggleSecond P((void));
227 void PauseEngine P((ChessProgramState *cps));
228 static int NonStandardBoardSize P((VariantClass v, int w, int h, int s));
229
230 #ifdef WIN32
231        extern void ConsoleCreate();
232 #endif
233
234 ChessProgramState *WhitePlayer();
235 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
236 int VerifyDisplayMode P(());
237
238 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
239 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
240 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
241 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
242 void ics_update_width P((int new_width));
243 extern char installDir[MSG_SIZ];
244 VariantClass startVariant; /* [HGM] nicks: initial variant */
245 Boolean abortMatch;
246
247 extern int tinyLayout, smallLayout;
248 ChessProgramStats programStats;
249 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
250 int endPV = -1;
251 static int exiting = 0; /* [HGM] moved to top */
252 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
253 int startedFromPositionFile = FALSE; Board filePosition;       /* [HGM] loadPos */
254 Board partnerBoard;     /* [HGM] bughouse: for peeking at partner game          */
255 int partnerHighlight[2];
256 Boolean partnerBoardValid = 0;
257 char partnerStatus[MSG_SIZ];
258 Boolean partnerUp;
259 Boolean originalFlip;
260 Boolean twoBoards = 0;
261 char endingGame = 0;    /* [HGM] crash: flag to prevent recursion of GameEnds() */
262 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS     */
263 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
264 int lastIndex = 0;      /* [HGM] autoinc: last game/position used in match mode */
265 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing      */
266 int opponentKibitzes;
267 int lastSavedGame; /* [HGM] save: ID of game */
268 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
269 extern int chatCount;
270 int chattingPartner;
271 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
272 char legal[BOARD_RANKS][BOARD_FILES];  /* [HGM] legal target squares */
273 char lastMsg[MSG_SIZ];
274 ChessSquare pieceSweep = EmptySquare;
275 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
276 int promoDefaultAltered;
277 int keepInfo = 0; /* [HGM] to protect PGN tags in auto-step game analysis */
278 static int initPing = -1;
279
280 /* States for ics_getting_history */
281 #define H_FALSE 0
282 #define H_REQUESTED 1
283 #define H_GOT_REQ_HEADER 2
284 #define H_GOT_UNREQ_HEADER 3
285 #define H_GETTING_MOVES 4
286 #define H_GOT_UNWANTED_HEADER 5
287
288 /* whosays values for GameEnds */
289 #define GE_ICS 0
290 #define GE_ENGINE 1
291 #define GE_PLAYER 2
292 #define GE_FILE 3
293 #define GE_XBOARD 4
294 #define GE_ENGINE1 5
295 #define GE_ENGINE2 6
296
297 /* Maximum number of games in a cmail message */
298 #define CMAIL_MAX_GAMES 20
299
300 /* Different types of move when calling RegisterMove */
301 #define CMAIL_MOVE   0
302 #define CMAIL_RESIGN 1
303 #define CMAIL_DRAW   2
304 #define CMAIL_ACCEPT 3
305
306 /* Different types of result to remember for each game */
307 #define CMAIL_NOT_RESULT 0
308 #define CMAIL_OLD_RESULT 1
309 #define CMAIL_NEW_RESULT 2
310
311 /* Telnet protocol constants */
312 #define TN_WILL 0373
313 #define TN_WONT 0374
314 #define TN_DO   0375
315 #define TN_DONT 0376
316 #define TN_IAC  0377
317 #define TN_ECHO 0001
318 #define TN_SGA  0003
319 #define TN_PORT 23
320
321 char*
322 safeStrCpy (char *dst, const char *src, size_t count)
323 { // [HGM] made safe
324   int i;
325   assert( dst != NULL );
326   assert( src != NULL );
327   assert( count > 0 );
328
329   for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
330   if(  i == count && dst[count-1] != NULLCHAR)
331     {
332       dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
333       if(appData.debugMode)
334         fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
335     }
336
337   return dst;
338 }
339
340 /* Some compiler can't cast u64 to double
341  * This function do the job for us:
342
343  * We use the highest bit for cast, this only
344  * works if the highest bit is not
345  * in use (This should not happen)
346  *
347  * We used this for all compiler
348  */
349 double
350 u64ToDouble (u64 value)
351 {
352   double r;
353   u64 tmp = value & u64Const(0x7fffffffffffffff);
354   r = (double)(s64)tmp;
355   if (value & u64Const(0x8000000000000000))
356        r +=  9.2233720368547758080e18; /* 2^63 */
357  return r;
358 }
359
360 /* Fake up flags for now, as we aren't keeping track of castling
361    availability yet. [HGM] Change of logic: the flag now only
362    indicates the type of castlings allowed by the rule of the game.
363    The actual rights themselves are maintained in the array
364    castlingRights, as part of the game history, and are not probed
365    by this function.
366  */
367 int
368 PosFlags (index)
369 {
370   int flags = F_ALL_CASTLE_OK;
371   if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
372   switch (gameInfo.variant) {
373   case VariantSuicide:
374     flags &= ~F_ALL_CASTLE_OK;
375   case VariantGiveaway:         // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
376     flags |= F_IGNORE_CHECK;
377   case VariantLosers:
378     flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
379     break;
380   case VariantAtomic:
381     flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
382     break;
383   case VariantKriegspiel:
384     flags |= F_KRIEGSPIEL_CAPTURE;
385     break;
386   case VariantCapaRandom:
387   case VariantFischeRandom:
388     flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
389   case VariantNoCastle:
390   case VariantShatranj:
391   case VariantCourier:
392   case VariantMakruk:
393   case VariantASEAN:
394   case VariantGrand:
395     flags &= ~F_ALL_CASTLE_OK;
396     break;
397   default:
398     break;
399   }
400   return flags;
401 }
402
403 FILE *gameFileFP, *debugFP, *serverFP;
404 char *currentDebugFile; // [HGM] debug split: to remember name
405
406 /*
407     [AS] Note: sometimes, the sscanf() function is used to parse the input
408     into a fixed-size buffer. Because of this, we must be prepared to
409     receive strings as long as the size of the input buffer, which is currently
410     set to 4K for Windows and 8K for the rest.
411     So, we must either allocate sufficiently large buffers here, or
412     reduce the size of the input buffer in the input reading part.
413 */
414
415 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
416 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
417 char thinkOutput1[MSG_SIZ*10];
418
419 ChessProgramState first, second, pairing;
420
421 /* premove variables */
422 int premoveToX = 0;
423 int premoveToY = 0;
424 int premoveFromX = 0;
425 int premoveFromY = 0;
426 int premovePromoChar = 0;
427 int gotPremove = 0;
428 Boolean alarmSounded;
429 /* end premove variables */
430
431 char *ics_prefix = "$";
432 enum ICS_TYPE ics_type = ICS_GENERIC;
433
434 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
435 int pauseExamForwardMostMove = 0;
436 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
437 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
438 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
439 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
440 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
441 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
442 int whiteFlag = FALSE, blackFlag = FALSE;
443 int userOfferedDraw = FALSE;
444 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
445 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
446 int cmailMoveType[CMAIL_MAX_GAMES];
447 long ics_clock_paused = 0;
448 ProcRef icsPR = NoProc, cmailPR = NoProc;
449 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
450 GameMode gameMode = BeginningOfGame;
451 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
452 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
453 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
454 int hiddenThinkOutputState = 0; /* [AS] */
455 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
456 int adjudicateLossPlies = 6;
457 char white_holding[64], black_holding[64];
458 TimeMark lastNodeCountTime;
459 long lastNodeCount=0;
460 int shiftKey, controlKey; // [HGM] set by mouse handler
461
462 int have_sent_ICS_logon = 0;
463 int movesPerSession;
464 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
465 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack, activePartnerTime;
466 Boolean adjustedClock;
467 long timeControl_2; /* [AS] Allow separate time controls */
468 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC, activePartner; /* [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, startingEngine = FALSE;
472 TimeMark programStartTime, pauseStart;
473 char ics_handle[MSG_SIZ];
474 int have_set_title = 0;
475
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.
480  */
481 Boolean animateTraining;
482
483 GameInfo gameInfo;
484
485 AppData appData;
486
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)
494 int loadFlag = 0;
495 Boolean shuffleOpenings;
496 int mute; // mute all sounds
497
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
501 int storedGames = 0;
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];
507
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));
513
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 }
519 };
520
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 }
526 };
527
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 }
533 };
534
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 }
540 };
541
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 }
547 };
548
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 }
554 };
555
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 }
561 };
562
563 ChessSquare aseanArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
564     { WhiteRook, WhiteKnight, WhiteMan, WhiteFerz,
565         WhiteKing, WhiteMan, WhiteKnight, WhiteRook },
566     { BlackRook, BlackKnight, BlackMan, BlackFerz,
567         BlackKing, BlackMan, BlackKnight, BlackRook }
568 };
569
570 ChessSquare  lionArray[2][BOARD_FILES] = {
571     { WhiteRook, WhiteLion, WhiteBishop, WhiteQueen,
572         WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
573     { BlackRook, BlackLion, BlackBishop, BlackQueen,
574         BlackKing, BlackBishop, BlackKnight, BlackRook }
575 };
576
577
578 #if (BOARD_FILES>=10)
579 ChessSquare ShogiArray[2][BOARD_FILES] = {
580     { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
581         WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
582     { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
583         BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
584 };
585
586 ChessSquare XiangqiArray[2][BOARD_FILES] = {
587     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
588         WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
589     { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
590         BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
591 };
592
593 ChessSquare CapablancaArray[2][BOARD_FILES] = {
594     { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
595         WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
596     { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
597         BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
598 };
599
600 ChessSquare GreatArray[2][BOARD_FILES] = {
601     { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
602         WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
603     { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
604         BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
605 };
606
607 ChessSquare JanusArray[2][BOARD_FILES] = {
608     { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
609         WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
610     { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
611         BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
612 };
613
614 ChessSquare GrandArray[2][BOARD_FILES] = {
615     { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
616         WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
617     { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
618         BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
619 };
620
621 ChessSquare ChuChessArray[2][BOARD_FILES] = {
622     { WhiteMan, WhiteKnight, WhiteBishop, WhiteCardinal, WhiteLion,
623         WhiteQueen, WhiteDragon, WhiteBishop, WhiteKnight, WhiteMan },
624     { BlackMan, BlackKnight, BlackBishop, BlackDragon, BlackQueen,
625         BlackLion, BlackCardinal, BlackBishop, BlackKnight, BlackMan }
626 };
627
628 #ifdef GOTHIC
629 ChessSquare GothicArray[2][BOARD_FILES] = {
630     { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
631         WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
632     { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
633         BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
634 };
635 #else // !GOTHIC
636 #define GothicArray CapablancaArray
637 #endif // !GOTHIC
638
639 #ifdef FALCON
640 ChessSquare FalconArray[2][BOARD_FILES] = {
641     { WhiteRook, WhiteKnight, WhiteBishop, WhiteFalcon, WhiteQueen,
642         WhiteKing, WhiteFalcon, WhiteBishop, WhiteKnight, WhiteRook },
643     { BlackRook, BlackKnight, BlackBishop, BlackFalcon, BlackQueen,
644         BlackKing, BlackFalcon, BlackBishop, BlackKnight, BlackRook }
645 };
646 #else // !FALCON
647 #define FalconArray CapablancaArray
648 #endif // !FALCON
649
650 #else // !(BOARD_FILES>=10)
651 #define XiangqiPosition FIDEArray
652 #define CapablancaArray FIDEArray
653 #define GothicArray FIDEArray
654 #define GreatArray FIDEArray
655 #endif // !(BOARD_FILES>=10)
656
657 #if (BOARD_FILES>=12)
658 ChessSquare CourierArray[2][BOARD_FILES] = {
659     { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
660         WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
661     { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
662         BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
663 };
664 ChessSquare ChuArray[6][BOARD_FILES] = {
665     { WhiteLance, WhiteUnicorn, WhiteMan, WhiteFerz, WhiteWazir, WhiteKing,
666       WhiteAlfil, WhiteWazir, WhiteFerz, WhiteMan, WhiteUnicorn, WhiteLance },
667     { BlackLance, BlackUnicorn, BlackMan, BlackFerz, BlackWazir, BlackAlfil,
668       BlackKing, BlackWazir, BlackFerz, BlackMan, BlackUnicorn, BlackLance },
669     { WhiteCannon, EmptySquare, WhiteBishop, EmptySquare, WhiteNightrider, WhiteMarshall,
670       WhiteAngel, WhiteNightrider, EmptySquare, WhiteBishop, EmptySquare, WhiteCannon },
671     { BlackCannon, EmptySquare, BlackBishop, EmptySquare, BlackNightrider, BlackAngel,
672       BlackMarshall, BlackNightrider, EmptySquare, BlackBishop, EmptySquare, BlackCannon },
673     { WhiteFalcon, WhiteSilver, WhiteRook, WhiteCardinal, WhiteDragon, WhiteLion,
674       WhiteQueen, WhiteDragon, WhiteCardinal, WhiteRook, WhiteSilver, WhiteFalcon },
675     { BlackFalcon, BlackSilver, BlackRook, BlackCardinal, BlackDragon, BlackQueen,
676       BlackLion, BlackDragon, BlackCardinal, BlackRook, BlackSilver, BlackFalcon }
677 };
678 #else // !(BOARD_FILES>=12)
679 #define CourierArray CapablancaArray
680 #define ChuArray CapablancaArray
681 #endif // !(BOARD_FILES>=12)
682
683
684 Board initialPosition;
685
686
687 /* Convert str to a rating. Checks for special cases of "----",
688
689    "++++", etc. Also strips ()'s */
690 int
691 string_to_rating (char *str)
692 {
693   while(*str && !isdigit(*str)) ++str;
694   if (!*str)
695     return 0;   /* One of the special "no rating" cases */
696   else
697     return atoi(str);
698 }
699
700 void
701 ClearProgramStats ()
702 {
703     /* Init programStats */
704     programStats.movelist[0] = 0;
705     programStats.depth = 0;
706     programStats.nr_moves = 0;
707     programStats.moves_left = 0;
708     programStats.nodes = 0;
709     programStats.time = -1;        // [HGM] PGNtime: make invalid to recognize engine output
710     programStats.score = 0;
711     programStats.got_only_move = 0;
712     programStats.got_fail = 0;
713     programStats.line_is_book = 0;
714 }
715
716 void
717 CommonEngineInit ()
718 {   // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
719     if (appData.firstPlaysBlack) {
720         first.twoMachinesColor = "black\n";
721         second.twoMachinesColor = "white\n";
722     } else {
723         first.twoMachinesColor = "white\n";
724         second.twoMachinesColor = "black\n";
725     }
726
727     first.other = &second;
728     second.other = &first;
729
730     { float norm = 1;
731         if(appData.timeOddsMode) {
732             norm = appData.timeOdds[0];
733             if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
734         }
735         first.timeOdds  = appData.timeOdds[0]/norm;
736         second.timeOdds = appData.timeOdds[1]/norm;
737     }
738
739     if(programVersion) free(programVersion);
740     if (appData.noChessProgram) {
741         programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
742         sprintf(programVersion, "%s", PACKAGE_STRING);
743     } else {
744       /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
745       programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
746       sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
747     }
748 }
749
750 void
751 UnloadEngine (ChessProgramState *cps)
752 {
753         /* Kill off first chess program */
754         if (cps->isr != NULL)
755           RemoveInputSource(cps->isr);
756         cps->isr = NULL;
757
758         if (cps->pr != NoProc) {
759             ExitAnalyzeMode();
760             DoSleep( appData.delayBeforeQuit );
761             SendToProgram("quit\n", cps);
762             DoSleep( appData.delayAfterQuit );
763             DestroyChildProcess(cps->pr, cps->useSigterm);
764         }
765         cps->pr = NoProc;
766         if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
767 }
768
769 void
770 ClearOptions (ChessProgramState *cps)
771 {
772     int i;
773     cps->nrOptions = cps->comboCnt = 0;
774     for(i=0; i<MAX_OPTIONS; i++) {
775         cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
776         cps->option[i].textValue = 0;
777     }
778 }
779
780 char *engineNames[] = {
781   /* TRANSLATORS: "first" is the first of possible two chess engines. It is inserted into strings
782      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
783 N_("first"),
784   /* TRANSLATORS: "second" is the second of possible two chess engines. It is inserted into strings
785      such as "%s engine" / "%s chess program" / "%s machine" - all meaning the same thing */
786 N_("second")
787 };
788
789 void
790 InitEngine (ChessProgramState *cps, int n)
791 {   // [HGM] all engine initialiation put in a function that does one engine
792
793     ClearOptions(cps);
794
795     cps->which = engineNames[n];
796     cps->maybeThinking = FALSE;
797     cps->pr = NoProc;
798     cps->isr = NULL;
799     cps->sendTime = 2;
800     cps->sendDrawOffers = 1;
801
802     cps->program = appData.chessProgram[n];
803     cps->host = appData.host[n];
804     cps->dir = appData.directory[n];
805     cps->initString = appData.engInitString[n];
806     cps->computerString = appData.computerString[n];
807     cps->useSigint  = TRUE;
808     cps->useSigterm = TRUE;
809     cps->reuse = appData.reuse[n];
810     cps->nps = appData.NPS[n];   // [HGM] nps: copy nodes per second
811     cps->useSetboard = FALSE;
812     cps->useSAN = FALSE;
813     cps->usePing = FALSE;
814     cps->lastPing = 0;
815     cps->lastPong = 0;
816     cps->usePlayother = FALSE;
817     cps->useColors = TRUE;
818     cps->useUsermove = FALSE;
819     cps->sendICS = FALSE;
820     cps->sendName = appData.icsActive;
821     cps->sdKludge = FALSE;
822     cps->stKludge = FALSE;
823     if(cps->tidy == NULL) cps->tidy = (char*) malloc(MSG_SIZ);
824     TidyProgramName(cps->program, cps->host, cps->tidy);
825     cps->matchWins = 0;
826     ASSIGN(cps->variants, appData.variant);
827     cps->analysisSupport = 2; /* detect */
828     cps->analyzing = FALSE;
829     cps->initDone = FALSE;
830     cps->reload = FALSE;
831
832     /* New features added by Tord: */
833     cps->useFEN960 = FALSE;
834     cps->useOOCastle = TRUE;
835     /* End of new features added by Tord. */
836     cps->fenOverride  = appData.fenOverride[n];
837
838     /* [HGM] time odds: set factor for each machine */
839     cps->timeOdds  = appData.timeOdds[n];
840
841     /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
842     cps->accumulateTC = appData.accumulateTC[n];
843     cps->maxNrOfSessions = 1;
844
845     /* [HGM] debug */
846     cps->debug = FALSE;
847
848     cps->supportsNPS = UNKNOWN;
849     cps->memSize = FALSE;
850     cps->maxCores = FALSE;
851     ASSIGN(cps->egtFormats, "");
852
853     /* [HGM] options */
854     cps->optionSettings  = appData.engOptions[n];
855
856     cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
857     cps->isUCI = appData.isUCI[n]; /* [AS] */
858     cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
859     cps->highlight = 0;
860
861     if (appData.protocolVersion[n] > PROTOVER
862         || appData.protocolVersion[n] < 1)
863       {
864         char buf[MSG_SIZ];
865         int len;
866
867         len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
868                        appData.protocolVersion[n]);
869         if( (len >= MSG_SIZ) && appData.debugMode )
870           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
871
872         DisplayFatalError(buf, 0, 2);
873       }
874     else
875       {
876         cps->protocolVersion = appData.protocolVersion[n];
877       }
878
879     InitEngineUCI( installDir, cps );  // [HGM] moved here from winboard.c, to make available in xboard
880     ParseFeatures(appData.featureDefaults, cps);
881 }
882
883 ChessProgramState *savCps;
884
885 GameMode oldMode;
886
887 void
888 LoadEngine ()
889 {
890     int i;
891     if(WaitForEngine(savCps, LoadEngine)) return;
892     CommonEngineInit(); // recalculate time odds
893     if(gameInfo.variant != StringToVariant(appData.variant)) {
894         // we changed variant when loading the engine; this forces us to reset
895         Reset(TRUE, savCps != &first);
896         oldMode = BeginningOfGame; // to prevent restoring old mode
897     }
898     InitChessProgram(savCps, FALSE);
899     if(gameMode == EditGame) SendToProgram("force\n", savCps); // in EditGame mode engine must be in force mode
900     DisplayMessage("", "");
901     if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
902     for (i = backwardMostMove; i < currentMove; i++) SendMoveToProgram(i, savCps);
903     ThawUI();
904     SetGNUMode();
905     if(oldMode == AnalyzeMode) AnalyzeModeEvent();
906 }
907
908 void
909 ReplaceEngine (ChessProgramState *cps, int n)
910 {
911     oldMode = gameMode; // remember mode, so it can be restored after loading sequence is complete
912     keepInfo = 1;
913     if(oldMode != BeginningOfGame) EditGameEvent();
914     keepInfo = 0;
915     UnloadEngine(cps);
916     appData.noChessProgram = FALSE;
917     appData.clockMode = TRUE;
918     InitEngine(cps, n);
919     UpdateLogos(TRUE);
920     if(n) return; // only startup first engine immediately; second can wait
921     savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
922     LoadEngine();
923 }
924
925 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
926 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
927
928 static char resetOptions[] =
929         "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
930         "-firstInitString \"" INIT_STRING "\" -firstComputerString \"" COMPUTER_STRING "\" "
931         "-firstFeatures \"\" -firstLogo \"\" -firstAccumulateTC 1 "
932         "-firstOptions \"\" -firstNPS -1 -fn \"\" -firstScoreAbs false";
933
934 void
935 FloatToFront(char **list, char *engineLine)
936 {
937     char buf[MSG_SIZ], tidy[MSG_SIZ], *p = buf, *q, *r = buf;
938     int i=0;
939     if(appData.recentEngines <= 0) return;
940     TidyProgramName(engineLine, "localhost", tidy+1);
941     tidy[0] = buf[0] = '\n'; strcat(tidy, "\n");
942     strncpy(buf+1, *list, MSG_SIZ-50);
943     if(p = strstr(buf, tidy)) { // tidy name appears in list
944         q = strchr(++p, '\n'); if(q == NULL) return; // malformed, don't touch
945         while(*p++ = *++q); // squeeze out
946     }
947     strcat(tidy, buf+1); // put list behind tidy name
948     p = tidy + 1; while(q = strchr(p, '\n')) i++, r = p, p = q + 1; // count entries in new list
949     if(i > appData.recentEngines) *r = NULLCHAR; // if maximum rached, strip off last
950     ASSIGN(*list, tidy+1);
951 }
952
953 char *insert, *wbOptions; // point in ChessProgramNames were we should insert new engine
954
955 void
956 Load (ChessProgramState *cps, int i)
957 {
958     char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ], buf3[MSG_SIZ], jar;
959     if(engineLine && engineLine[0]) { // an engine was selected from the combo box
960         snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
961         SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
962         ParseArgsFromString(resetOptions); appData.pvSAN[0] = FALSE;
963         FREE(appData.fenOverride[0]); appData.fenOverride[0] = NULL;
964         appData.firstProtocolVersion = PROTOVER;
965         ParseArgsFromString(buf);
966         SwapEngines(i);
967         ReplaceEngine(cps, i);
968         FloatToFront(&appData.recentEngineList, engineLine);
969         return;
970     }
971     p = engineName;
972     while(q = strchr(p, SLASH)) p = q+1;
973     if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
974     if(engineDir[0] != NULLCHAR) {
975         ASSIGN(appData.directory[i], engineDir); p = engineName;
976     } else if(p != engineName) { // derive directory from engine path, when not given
977         p[-1] = 0;
978         ASSIGN(appData.directory[i], engineName);
979         p[-1] = SLASH;
980         if(SLASH == '/' && p - engineName > 1) *(p -= 2) = '.'; // for XBoard use ./exeName as command after split!
981     } else { ASSIGN(appData.directory[i], "."); }
982     jar = (strstr(p, ".jar") == p + strlen(p) - 4);
983     if(params[0]) {
984         if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
985         snprintf(command, MSG_SIZ, "%s %s", p, params);
986         p = command;
987     }
988     if(jar) { snprintf(buf3, MSG_SIZ, "java -jar %s", p); p = buf3; }
989     ASSIGN(appData.chessProgram[i], p);
990     appData.isUCI[i] = isUCI;
991     appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
992     appData.hasOwnBookUCI[i] = hasBook;
993     if(!nickName[0]) useNick = FALSE;
994     if(useNick) ASSIGN(appData.pgnName[i], nickName);
995     if(addToList) {
996         int len;
997         char quote;
998         q = firstChessProgramNames;
999         if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
1000         quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
1001         snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
1002                         quote, p, quote, appData.directory[i],
1003                         useNick ? " -fn \"" : "",
1004                         useNick ? nickName : "",
1005                         useNick ? "\"" : "",
1006                         v1 ? " -firstProtocolVersion 1" : "",
1007                         hasBook ? "" : " -fNoOwnBookUCI",
1008                         isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
1009                         storeVariant ? " -variant " : "",
1010                         storeVariant ? VariantName(gameInfo.variant) : "");
1011         if(wbOptions && wbOptions[0]) snprintf(buf+strlen(buf)-1, MSG_SIZ-strlen(buf), " %s\n", wbOptions);
1012         firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
1013         if(insert != q) insert[-1] = NULLCHAR;
1014         snprintf(firstChessProgramNames, len, "%s\n%s%s", q, buf, insert);
1015         if(q)   free(q);
1016         FloatToFront(&appData.recentEngineList, buf);
1017     }
1018     ReplaceEngine(cps, i);
1019 }
1020
1021 void
1022 InitTimeControls ()
1023 {
1024     int matched, min, sec;
1025     /*
1026      * Parse timeControl resource
1027      */
1028     if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
1029                           appData.movesPerSession)) {
1030         char buf[MSG_SIZ];
1031         snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
1032         DisplayFatalError(buf, 0, 2);
1033     }
1034
1035     /*
1036      * Parse searchTime resource
1037      */
1038     if (*appData.searchTime != NULLCHAR) {
1039         matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
1040         if (matched == 1) {
1041             searchTime = min * 60;
1042         } else if (matched == 2) {
1043             searchTime = min * 60 + sec;
1044         } else {
1045             char buf[MSG_SIZ];
1046             snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
1047             DisplayFatalError(buf, 0, 2);
1048         }
1049     }
1050 }
1051
1052 void
1053 InitBackEnd1 ()
1054 {
1055
1056     ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
1057     startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
1058
1059     GetTimeMark(&programStartTime);
1060     srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
1061     appData.seedBase = random() + (random()<<15);
1062     pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
1063
1064     ClearProgramStats();
1065     programStats.ok_to_send = 1;
1066     programStats.seen_stat = 0;
1067
1068     /*
1069      * Initialize game list
1070      */
1071     ListNew(&gameList);
1072
1073
1074     /*
1075      * Internet chess server status
1076      */
1077     if (appData.icsActive) {
1078         appData.matchMode = FALSE;
1079         appData.matchGames = 0;
1080 #if ZIPPY
1081         appData.noChessProgram = !appData.zippyPlay;
1082 #else
1083         appData.zippyPlay = FALSE;
1084         appData.zippyTalk = FALSE;
1085         appData.noChessProgram = TRUE;
1086 #endif
1087         if (*appData.icsHelper != NULLCHAR) {
1088             appData.useTelnet = TRUE;
1089             appData.telnetProgram = appData.icsHelper;
1090         }
1091     } else {
1092         appData.zippyTalk = appData.zippyPlay = FALSE;
1093     }
1094
1095     /* [AS] Initialize pv info list [HGM] and game state */
1096     {
1097         int i, j;
1098
1099         for( i=0; i<=framePtr; i++ ) {
1100             pvInfoList[i].depth = -1;
1101             boards[i][EP_STATUS] = EP_NONE;
1102             for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1103         }
1104     }
1105
1106     InitTimeControls();
1107
1108     /* [AS] Adjudication threshold */
1109     adjudicateLossThreshold = appData.adjudicateLossThreshold;
1110
1111     InitEngine(&first, 0);
1112     InitEngine(&second, 1);
1113     CommonEngineInit();
1114
1115     pairing.which = "pairing"; // pairing engine
1116     pairing.pr = NoProc;
1117     pairing.isr = NULL;
1118     pairing.program = appData.pairingEngine;
1119     pairing.host = "localhost";
1120     pairing.dir = ".";
1121
1122     if (appData.icsActive) {
1123         appData.clockMode = TRUE;  /* changes dynamically in ICS mode */
1124     } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1125         appData.clockMode = FALSE;
1126         first.sendTime = second.sendTime = 0;
1127     }
1128
1129 #if ZIPPY
1130     /* Override some settings from environment variables, for backward
1131        compatibility.  Unfortunately it's not feasible to have the env
1132        vars just set defaults, at least in xboard.  Ugh.
1133     */
1134     if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1135       ZippyInit();
1136     }
1137 #endif
1138
1139     if (!appData.icsActive) {
1140       char buf[MSG_SIZ];
1141       int len;
1142
1143       /* Check for variants that are supported only in ICS mode,
1144          or not at all.  Some that are accepted here nevertheless
1145          have bugs; see comments below.
1146       */
1147       VariantClass variant = StringToVariant(appData.variant);
1148       switch (variant) {
1149       case VariantBughouse:     /* need four players and two boards */
1150       case VariantKriegspiel:   /* need to hide pieces and move details */
1151         /* case VariantFischeRandom: (Fabien: moved below) */
1152         len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1153         if( (len >= MSG_SIZ) && appData.debugMode )
1154           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1155
1156         DisplayFatalError(buf, 0, 2);
1157         return;
1158
1159       case VariantUnknown:
1160       case VariantLoadable:
1161       case Variant29:
1162       case Variant30:
1163       case Variant31:
1164       case Variant32:
1165       case Variant33:
1166       case Variant34:
1167       case Variant35:
1168       case Variant36:
1169       default:
1170         len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1171         if( (len >= MSG_SIZ) && appData.debugMode )
1172           fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1173
1174         DisplayFatalError(buf, 0, 2);
1175         return;
1176
1177       case VariantXiangqi:    /* [HGM] repetition rules not implemented */
1178       case VariantFairy:      /* [HGM] TestLegality definitely off! */
1179       case VariantGothic:     /* [HGM] should work */
1180       case VariantCapablanca: /* [HGM] should work */
1181       case VariantCourier:    /* [HGM] initial forced moves not implemented */
1182       case VariantShogi:      /* [HGM] could still mate with pawn drop */
1183       case VariantChu:        /* [HGM] experimental */
1184       case VariantKnightmate: /* [HGM] should work */
1185       case VariantCylinder:   /* [HGM] untested */
1186       case VariantFalcon:     /* [HGM] untested */
1187       case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1188                                  offboard interposition not understood */
1189       case VariantNormal:     /* definitely works! */
1190       case VariantWildCastle: /* pieces not automatically shuffled */
1191       case VariantNoCastle:   /* pieces not automatically shuffled */
1192       case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1193       case VariantLosers:     /* should work except for win condition,
1194                                  and doesn't know captures are mandatory */
1195       case VariantSuicide:    /* should work except for win condition,
1196                                  and doesn't know captures are mandatory */
1197       case VariantGiveaway:   /* should work except for win condition,
1198                                  and doesn't know captures are mandatory */
1199       case VariantTwoKings:   /* should work */
1200       case VariantAtomic:     /* should work except for win condition */
1201       case Variant3Check:     /* should work except for win condition */
1202       case VariantShatranj:   /* should work except for all win conditions */
1203       case VariantMakruk:     /* should work except for draw countdown */
1204       case VariantASEAN :     /* should work except for draw countdown */
1205       case VariantBerolina:   /* might work if TestLegality is off */
1206       case VariantCapaRandom: /* should work */
1207       case VariantJanus:      /* should work */
1208       case VariantSuper:      /* experimental */
1209       case VariantGreat:      /* experimental, requires legality testing to be off */
1210       case VariantSChess:     /* S-Chess, should work */
1211       case VariantGrand:      /* should work */
1212       case VariantSpartan:    /* should work */
1213       case VariantLion:       /* should work */
1214       case VariantChuChess:   /* should work */
1215         break;
1216       }
1217     }
1218
1219 }
1220
1221 int
1222 NextIntegerFromString (char ** str, long * value)
1223 {
1224     int result = -1;
1225     char * s = *str;
1226
1227     while( *s == ' ' || *s == '\t' ) {
1228         s++;
1229     }
1230
1231     *value = 0;
1232
1233     if( *s >= '0' && *s <= '9' ) {
1234         while( *s >= '0' && *s <= '9' ) {
1235             *value = *value * 10 + (*s - '0');
1236             s++;
1237         }
1238
1239         result = 0;
1240     }
1241
1242     *str = s;
1243
1244     return result;
1245 }
1246
1247 int
1248 NextTimeControlFromString (char ** str, long * value)
1249 {
1250     long temp;
1251     int result = NextIntegerFromString( str, &temp );
1252
1253     if( result == 0 ) {
1254         *value = temp * 60; /* Minutes */
1255         if( **str == ':' ) {
1256             (*str)++;
1257             result = NextIntegerFromString( str, &temp );
1258             *value += temp; /* Seconds */
1259         }
1260     }
1261
1262     return result;
1263 }
1264
1265 int
1266 NextSessionFromString (char ** str, int *moves, long * tc, long *inc, int *incType)
1267 {   /* [HGM] routine added to read '+moves/time' for secondary time control. */
1268     int result = -1, type = 0; long temp, temp2;
1269
1270     if(**str != ':') return -1; // old params remain in force!
1271     (*str)++;
1272     if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1273     if( NextIntegerFromString( str, &temp ) ) return -1;
1274     if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1275
1276     if(**str != '/') {
1277         /* time only: incremental or sudden-death time control */
1278         if(**str == '+') { /* increment follows; read it */
1279             (*str)++;
1280             if(**str == '!') type = *(*str)++; // Bronstein TC
1281             if(result = NextIntegerFromString( str, &temp2)) return -1;
1282             *inc = temp2 * 1000;
1283             if(**str == '.') { // read fraction of increment
1284                 char *start = ++(*str);
1285                 if(result = NextIntegerFromString( str, &temp2)) return -1;
1286                 temp2 *= 1000;
1287                 while(start++ < *str) temp2 /= 10;
1288                 *inc += temp2;
1289             }
1290         } else *inc = 0;
1291         *moves = 0; *tc = temp * 1000; *incType = type;
1292         return 0;
1293     }
1294
1295     (*str)++; /* classical time control */
1296     result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1297
1298     if(result == 0) {
1299         *moves = temp;
1300         *tc    = temp2 * 1000;
1301         *inc   = 0;
1302         *incType = type;
1303     }
1304     return result;
1305 }
1306
1307 int
1308 GetTimeQuota (int movenr, int lastUsed, char *tcString)
1309 {   /* [HGM] get time to add from the multi-session time-control string */
1310     int incType, moves=1; /* kludge to force reading of first session */
1311     long time, increment;
1312     char *s = tcString;
1313
1314     if(!s || !*s) return 0; // empty TC string means we ran out of the last sudden-death version
1315     do {
1316         if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1317         nextSession = s; suddenDeath = moves == 0 && increment == 0;
1318         if(movenr == -1) return time;    /* last move before new session     */
1319         if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1320         if(incType == '!' && lastUsed < increment) increment = lastUsed;
1321         if(!moves) return increment;     /* current session is incremental   */
1322         if(movenr >= 0) movenr -= moves; /* we already finished this session */
1323     } while(movenr >= -1);               /* try again for next session       */
1324
1325     return 0; // no new time quota on this move
1326 }
1327
1328 int
1329 ParseTimeControl (char *tc, float ti, int mps)
1330 {
1331   long tc1;
1332   long tc2;
1333   char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1334   int min, sec=0;
1335
1336   if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1337   if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1338       sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1339   if(ti > 0) {
1340
1341     if(mps)
1342       snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1343     else
1344       snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1345   } else {
1346     if(mps)
1347       snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1348     else
1349       snprintf(buf, MSG_SIZ, ":%s", mytc);
1350   }
1351   fullTimeControlString = StrSave(buf); // this should now be in PGN format
1352
1353   if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1354     return FALSE;
1355   }
1356
1357   if( *tc == '/' ) {
1358     /* Parse second time control */
1359     tc++;
1360
1361     if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1362       return FALSE;
1363     }
1364
1365     if( tc2 == 0 ) {
1366       return FALSE;
1367     }
1368
1369     timeControl_2 = tc2 * 1000;
1370   }
1371   else {
1372     timeControl_2 = 0;
1373   }
1374
1375   if( tc1 == 0 ) {
1376     return FALSE;
1377   }
1378
1379   timeControl = tc1 * 1000;
1380
1381   if (ti >= 0) {
1382     timeIncrement = ti * 1000;  /* convert to ms */
1383     movesPerSession = 0;
1384   } else {
1385     timeIncrement = 0;
1386     movesPerSession = mps;
1387   }
1388   return TRUE;
1389 }
1390
1391 void
1392 InitBackEnd2 ()
1393 {
1394     if (appData.debugMode) {
1395 #    ifdef __GIT_VERSION
1396       fprintf(debugFP, "Version: %s (%s)\n", programVersion, __GIT_VERSION);
1397 #    else
1398       fprintf(debugFP, "Version: %s\n", programVersion);
1399 #    endif
1400     }
1401     ASSIGN(currentDebugFile, appData.nameOfDebugFile); // [HGM] debug split: remember initial name in use
1402
1403     set_cont_sequence(appData.wrapContSeq);
1404     if (appData.matchGames > 0) {
1405         appData.matchMode = TRUE;
1406     } else if (appData.matchMode) {
1407         appData.matchGames = 1;
1408     }
1409     if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1410         appData.matchGames = appData.sameColorGames;
1411     if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1412         if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1413         if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1414     }
1415     Reset(TRUE, FALSE);
1416     if (appData.noChessProgram || first.protocolVersion == 1) {
1417       InitBackEnd3();
1418     } else {
1419       /* kludge: allow timeout for initial "feature" commands */
1420       FreezeUI();
1421       DisplayMessage("", _("Starting chess program"));
1422       ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1423     }
1424 }
1425
1426 int
1427 CalculateIndex (int index, int gameNr)
1428 {   // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1429     int res;
1430     if(index > 0) return index; // fixed nmber
1431     if(index == 0) return 1;
1432     res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1433     if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1434     return res;
1435 }
1436
1437 int
1438 LoadGameOrPosition (int gameNr)
1439 {   // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1440     if (*appData.loadGameFile != NULLCHAR) {
1441         if (!LoadGameFromFile(appData.loadGameFile,
1442                 CalculateIndex(appData.loadGameIndex, gameNr),
1443                               appData.loadGameFile, FALSE)) {
1444             DisplayFatalError(_("Bad game file"), 0, 1);
1445             return 0;
1446         }
1447     } else if (*appData.loadPositionFile != NULLCHAR) {
1448         if (!LoadPositionFromFile(appData.loadPositionFile,
1449                 CalculateIndex(appData.loadPositionIndex, gameNr),
1450                                   appData.loadPositionFile)) {
1451             DisplayFatalError(_("Bad position file"), 0, 1);
1452             return 0;
1453         }
1454     }
1455     return 1;
1456 }
1457
1458 void
1459 ReserveGame (int gameNr, char resChar)
1460 {
1461     FILE *tf = fopen(appData.tourneyFile, "r+");
1462     char *p, *q, c, buf[MSG_SIZ];
1463     if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1464     safeStrCpy(buf, lastMsg, MSG_SIZ);
1465     DisplayMessage(_("Pick new game"), "");
1466     flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1467     ParseArgsFromFile(tf);
1468     p = q = appData.results;
1469     if(appData.debugMode) {
1470       char *r = appData.participants;
1471       fprintf(debugFP, "results = '%s'\n", p);
1472       while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1473       fprintf(debugFP, "\n");
1474     }
1475     while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1476     nextGame = q - p;
1477     q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1478     safeStrCpy(q, p, strlen(p) + 2);
1479     if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1480     if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1481     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1482         if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1483         q[nextGame] = '*';
1484     }
1485     fseek(tf, -(strlen(p)+4), SEEK_END);
1486     c = fgetc(tf);
1487     if(c != '"') // depending on DOS or Unix line endings we can be one off
1488          fseek(tf, -(strlen(p)+2), SEEK_END);
1489     else fseek(tf, -(strlen(p)+3), SEEK_END);
1490     fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1491     DisplayMessage(buf, "");
1492     free(p); appData.results = q;
1493     if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1494        (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1495       int round = appData.defaultMatchGames * appData.tourneyType;
1496       if(gameNr < 0 || appData.tourneyType < 1 ||  // gauntlet engine can always stay loaded as first engine
1497          appData.tourneyType > 1 && nextGame/round != gameNr/round) // in multi-gauntlet change only after round
1498         UnloadEngine(&first);  // next game belongs to other pairing;
1499         UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1500     }
1501     if(appData.debugMode) fprintf(debugFP, "Reserved, next=%d, nr=%d\n", nextGame, gameNr);
1502 }
1503
1504 void
1505 MatchEvent (int mode)
1506 {       // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1507         int dummy;
1508         if(matchMode) { // already in match mode: switch it off
1509             abortMatch = TRUE;
1510             if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1511             return;
1512         }
1513 //      if(gameMode != BeginningOfGame) {
1514 //          DisplayError(_("You can only start a match from the initial position."), 0);
1515 //          return;
1516 //      }
1517         abortMatch = FALSE;
1518         if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1519         /* Set up machine vs. machine match */
1520         nextGame = 0;
1521         NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1522         if(appData.tourneyFile[0]) {
1523             ReserveGame(-1, 0);
1524             if(nextGame > appData.matchGames) {
1525                 char buf[MSG_SIZ];
1526                 if(strchr(appData.results, '*') == NULL) {
1527                     FILE *f;
1528                     appData.tourneyCycles++;
1529                     if(f = WriteTourneyFile(appData.results, NULL)) { // make a tourney file with increased number of cycles
1530                         fclose(f);
1531                         NextTourneyGame(-1, &dummy);
1532                         ReserveGame(-1, 0);
1533                         if(nextGame <= appData.matchGames) {
1534                             DisplayNote(_("You restarted an already completed tourney.\nOne more cycle will now be added to it.\nGames commence in 10 sec."));
1535                             matchMode = mode;
1536                             ScheduleDelayedEvent(NextMatchGame, 10000);
1537                             return;
1538                         }
1539                     }
1540                 }
1541                 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1542                 DisplayError(buf, 0);
1543                 appData.tourneyFile[0] = 0;
1544                 return;
1545             }
1546         } else
1547         if (appData.noChessProgram) {  // [HGM] in tourney engines are loaded automatically
1548             DisplayFatalError(_("Can't have a match with no chess programs"),
1549                               0, 2);
1550             return;
1551         }
1552         matchMode = mode;
1553         matchGame = roundNr = 1;
1554         first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1555         NextMatchGame();
1556 }
1557
1558 char *comboLine = NULL; // [HGM] recent: WinBoard's first-engine combobox line
1559
1560 void
1561 InitBackEnd3 P((void))
1562 {
1563     GameMode initialMode;
1564     char buf[MSG_SIZ];
1565     int err, len;
1566
1567     if(!appData.icsActive && !appData.noChessProgram && !appData.matchMode &&                         // mode involves only first engine
1568        !strcmp(appData.variant, "normal") &&                                                          // no explicit variant request
1569         appData.NrRanks == -1 && appData.NrFiles == -1 && appData.holdingsSize == -1 &&               // no size overrides requested
1570        !SupportedVariant(first.variants, VariantNormal, 8, 8, 0, first.protocolVersion, "") &&        // but 'normal' won't work with engine
1571        !SupportedVariant(first.variants, VariantFischeRandom, 8, 8, 0, first.protocolVersion, "") ) { // nor will Chess960
1572         char c, *q = first.variants, *p = strchr(q, ',');
1573         if(p) *p = NULLCHAR;
1574         if(StringToVariant(q) != VariantUnknown) { // the engine can play a recognized variant, however
1575             int w, h, s;
1576             if(sscanf(q, "%dx%d+%d_%c", &w, &h, &s, &c) == 4) // get size overrides the engine needs with it (if any)
1577                 appData.NrFiles = w, appData.NrRanks = h, appData.holdingsSize = s, q = strchr(q, '_') + 1;
1578             ASSIGN(appData.variant, q); // fake user requested the first variant played by the engine
1579             Reset(TRUE, FALSE);         // and re-initialize
1580         }
1581         if(p) *p = ',';
1582     }
1583
1584     InitChessProgram(&first, startedFromSetupPosition);
1585
1586     if(!appData.noChessProgram) {  /* [HGM] tidy: redo program version to use name from myname feature */
1587         free(programVersion);
1588         programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1589         sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1590         FloatToFront(&appData.recentEngineList, comboLine ? comboLine : appData.firstChessProgram);
1591     }
1592
1593     if (appData.icsActive) {
1594 #ifdef WIN32
1595         /* [DM] Make a console window if needed [HGM] merged ifs */
1596         ConsoleCreate();
1597 #endif
1598         err = establish();
1599         if (err != 0)
1600           {
1601             if (*appData.icsCommPort != NULLCHAR)
1602               len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1603                              appData.icsCommPort);
1604             else
1605               len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1606                         appData.icsHost, appData.icsPort);
1607
1608             if( (len >= MSG_SIZ) && appData.debugMode )
1609               fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1610
1611             DisplayFatalError(buf, err, 1);
1612             return;
1613         }
1614         SetICSMode();
1615         telnetISR =
1616           AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1617         fromUserISR =
1618           AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1619         if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1620             ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1621     } else if (appData.noChessProgram) {
1622         SetNCPMode();
1623     } else {
1624         SetGNUMode();
1625     }
1626
1627     if (*appData.cmailGameName != NULLCHAR) {
1628         SetCmailMode();
1629         OpenLoopback(&cmailPR);
1630         cmailISR =
1631           AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1632     }
1633
1634     ThawUI();
1635     DisplayMessage("", "");
1636     if (StrCaseCmp(appData.initialMode, "") == 0) {
1637       initialMode = BeginningOfGame;
1638       if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1639         gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1640         ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1641         gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1642         ModeHighlight();
1643       }
1644     } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1645       initialMode = TwoMachinesPlay;
1646     } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1647       initialMode = AnalyzeFile;
1648     } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1649       initialMode = AnalyzeMode;
1650     } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1651       initialMode = MachinePlaysWhite;
1652     } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1653       initialMode = MachinePlaysBlack;
1654     } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1655       initialMode = EditGame;
1656     } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1657       initialMode = EditPosition;
1658     } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1659       initialMode = Training;
1660     } else {
1661       len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1662       if( (len >= MSG_SIZ) && appData.debugMode )
1663         fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1664
1665       DisplayFatalError(buf, 0, 2);
1666       return;
1667     }
1668
1669     if (appData.matchMode) {
1670         if(appData.tourneyFile[0]) { // start tourney from command line
1671             FILE *f;
1672             if(f = fopen(appData.tourneyFile, "r")) {
1673                 ParseArgsFromFile(f); // make sure tourney parmeters re known
1674                 fclose(f);
1675                 appData.clockMode = TRUE;
1676                 SetGNUMode();
1677             } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1678         }
1679         MatchEvent(TRUE);
1680     } else if (*appData.cmailGameName != NULLCHAR) {
1681         /* Set up cmail mode */
1682         ReloadCmailMsgEvent(TRUE);
1683     } else {
1684         /* Set up other modes */
1685         if (initialMode == AnalyzeFile) {
1686           if (*appData.loadGameFile == NULLCHAR) {
1687             DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1688             return;
1689           }
1690         }
1691         if (*appData.loadGameFile != NULLCHAR) {
1692             (void) LoadGameFromFile(appData.loadGameFile,
1693                                     appData.loadGameIndex,
1694                                     appData.loadGameFile, TRUE);
1695         } else if (*appData.loadPositionFile != NULLCHAR) {
1696             (void) LoadPositionFromFile(appData.loadPositionFile,
1697                                         appData.loadPositionIndex,
1698                                         appData.loadPositionFile);
1699             /* [HGM] try to make self-starting even after FEN load */
1700             /* to allow automatic setup of fairy variants with wtm */
1701             if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1702                 gameMode = BeginningOfGame;
1703                 setboardSpoiledMachineBlack = 1;
1704             }
1705             /* [HGM] loadPos: make that every new game uses the setup */
1706             /* from file as long as we do not switch variant          */
1707             if(!blackPlaysFirst) {
1708                 startedFromPositionFile = TRUE;
1709                 CopyBoard(filePosition, boards[0]);
1710             }
1711         }
1712         if (initialMode == AnalyzeMode) {
1713           if (appData.noChessProgram) {
1714             DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1715             return;
1716           }
1717           if (appData.icsActive) {
1718             DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1719             return;
1720           }
1721           AnalyzeModeEvent();
1722         } else if (initialMode == AnalyzeFile) {
1723           appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1724           ShowThinkingEvent();
1725           AnalyzeFileEvent();
1726           AnalysisPeriodicEvent(1);
1727         } else if (initialMode == MachinePlaysWhite) {
1728           if (appData.noChessProgram) {
1729             DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1730                               0, 2);
1731             return;
1732           }
1733           if (appData.icsActive) {
1734             DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1735                               0, 2);
1736             return;
1737           }
1738           MachineWhiteEvent();
1739         } else if (initialMode == MachinePlaysBlack) {
1740           if (appData.noChessProgram) {
1741             DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1742                               0, 2);
1743             return;
1744           }
1745           if (appData.icsActive) {
1746             DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1747                               0, 2);
1748             return;
1749           }
1750           MachineBlackEvent();
1751         } else if (initialMode == TwoMachinesPlay) {
1752           if (appData.noChessProgram) {
1753             DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1754                               0, 2);
1755             return;
1756           }
1757           if (appData.icsActive) {
1758             DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1759                               0, 2);
1760             return;
1761           }
1762           TwoMachinesEvent();
1763         } else if (initialMode == EditGame) {
1764           EditGameEvent();
1765         } else if (initialMode == EditPosition) {
1766           EditPositionEvent();
1767         } else if (initialMode == Training) {
1768           if (*appData.loadGameFile == NULLCHAR) {
1769             DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1770             return;
1771           }
1772           TrainingEvent();
1773         }
1774     }
1775 }
1776
1777 void
1778 HistorySet (char movelist[][2*MOVE_LEN], int first, int last, int current)
1779 {
1780     DisplayBook(current+1);
1781
1782     MoveHistorySet( movelist, first, last, current, pvInfoList );
1783
1784     EvalGraphSet( first, last, current, pvInfoList );
1785
1786     MakeEngineOutputTitle();
1787 }
1788
1789 /*
1790  * Establish will establish a contact to a remote host.port.
1791  * Sets icsPR to a ProcRef for a process (or pseudo-process)
1792  *  used to talk to the host.
1793  * Returns 0 if okay, error code if not.
1794  */
1795 int
1796 establish ()
1797 {
1798     char buf[MSG_SIZ];
1799
1800     if (*appData.icsCommPort != NULLCHAR) {
1801         /* Talk to the host through a serial comm port */
1802         return OpenCommPort(appData.icsCommPort, &icsPR);
1803
1804     } else if (*appData.gateway != NULLCHAR) {
1805         if (*appData.remoteShell == NULLCHAR) {
1806             /* Use the rcmd protocol to run telnet program on a gateway host */
1807             snprintf(buf, sizeof(buf), "%s %s %s",
1808                     appData.telnetProgram, appData.icsHost, appData.icsPort);
1809             return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1810
1811         } else {
1812             /* Use the rsh program to run telnet program on a gateway host */
1813             if (*appData.remoteUser == NULLCHAR) {
1814                 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1815                         appData.gateway, appData.telnetProgram,
1816                         appData.icsHost, appData.icsPort);
1817             } else {
1818                 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1819                         appData.remoteShell, appData.gateway,
1820                         appData.remoteUser, appData.telnetProgram,
1821                         appData.icsHost, appData.icsPort);
1822             }
1823             return StartChildProcess(buf, "", &icsPR);
1824
1825         }
1826     } else if (appData.useTelnet) {
1827         return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1828
1829     } else {
1830         /* TCP socket interface differs somewhat between
1831            Unix and NT; handle details in the front end.
1832            */
1833         return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1834     }
1835 }
1836
1837 void
1838 EscapeExpand (char *p, char *q)
1839 {       // [HGM] initstring: routine to shape up string arguments
1840         while(*p++ = *q++) if(p[-1] == '\\')
1841             switch(*q++) {
1842                 case 'n': p[-1] = '\n'; break;
1843                 case 'r': p[-1] = '\r'; break;
1844                 case 't': p[-1] = '\t'; break;
1845                 case '\\': p[-1] = '\\'; break;
1846                 case 0: *p = 0; return;
1847                 default: p[-1] = q[-1]; break;
1848             }
1849 }
1850
1851 void
1852 show_bytes (FILE *fp, char *buf, int count)
1853 {
1854     while (count--) {
1855         if (*buf < 040 || *(unsigned char *) buf > 0177) {
1856             fprintf(fp, "\\%03o", *buf & 0xff);
1857         } else {
1858             putc(*buf, fp);
1859         }
1860         buf++;
1861     }
1862     fflush(fp);
1863 }
1864
1865 /* Returns an errno value */
1866 int
1867 OutputMaybeTelnet (ProcRef pr, char *message, int count, int *outError)
1868 {
1869     char buf[8192], *p, *q, *buflim;
1870     int left, newcount, outcount;
1871
1872     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1873         *appData.gateway != NULLCHAR) {
1874         if (appData.debugMode) {
1875             fprintf(debugFP, ">ICS: ");
1876             show_bytes(debugFP, message, count);
1877             fprintf(debugFP, "\n");
1878         }
1879         return OutputToProcess(pr, message, count, outError);
1880     }
1881
1882     buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1883     p = message;
1884     q = buf;
1885     left = count;
1886     newcount = 0;
1887     while (left) {
1888         if (q >= buflim) {
1889             if (appData.debugMode) {
1890                 fprintf(debugFP, ">ICS: ");
1891                 show_bytes(debugFP, buf, newcount);
1892                 fprintf(debugFP, "\n");
1893             }
1894             outcount = OutputToProcess(pr, buf, newcount, outError);
1895             if (outcount < newcount) return -1; /* to be sure */
1896             q = buf;
1897             newcount = 0;
1898         }
1899         if (*p == '\n') {
1900             *q++ = '\r';
1901             newcount++;
1902         } else if (((unsigned char) *p) == TN_IAC) {
1903             *q++ = (char) TN_IAC;
1904             newcount ++;
1905         }
1906         *q++ = *p++;
1907         newcount++;
1908         left--;
1909     }
1910     if (appData.debugMode) {
1911         fprintf(debugFP, ">ICS: ");
1912         show_bytes(debugFP, buf, newcount);
1913         fprintf(debugFP, "\n");
1914     }
1915     outcount = OutputToProcess(pr, buf, newcount, outError);
1916     if (outcount < newcount) return -1; /* to be sure */
1917     return count;
1918 }
1919
1920 void
1921 read_from_player (InputSourceRef isr, VOIDSTAR closure, char *message, int count, int error)
1922 {
1923     int outError, outCount;
1924     static int gotEof = 0;
1925     static FILE *ini;
1926
1927     /* Pass data read from player on to ICS */
1928     if (count > 0) {
1929         gotEof = 0;
1930         outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1931         if (outCount < count) {
1932             DisplayFatalError(_("Error writing to ICS"), outError, 1);
1933         }
1934         if(have_sent_ICS_logon == 2) {
1935           if(ini = fopen(appData.icsLogon, "w")) { // save first two lines (presumably username & password) on init script file
1936             fprintf(ini, "%s", message);
1937             have_sent_ICS_logon = 3;
1938           } else
1939             have_sent_ICS_logon = 1;
1940         } else if(have_sent_ICS_logon == 3) {
1941             fprintf(ini, "%s", message);
1942             fclose(ini);
1943           have_sent_ICS_logon = 1;
1944         }
1945     } else if (count < 0) {
1946         RemoveInputSource(isr);
1947         DisplayFatalError(_("Error reading from keyboard"), error, 1);
1948     } else if (gotEof++ > 0) {
1949         RemoveInputSource(isr);
1950         DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1951     }
1952 }
1953
1954 void
1955 KeepAlive ()
1956 {   // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1957     if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1958     connectionAlive = FALSE; // only sticks if no response to 'date' command.
1959     SendToICS("date\n");
1960     if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1961 }
1962
1963 /* added routine for printf style output to ics */
1964 void
1965 ics_printf (char *format, ...)
1966 {
1967     char buffer[MSG_SIZ];
1968     va_list args;
1969
1970     va_start(args, format);
1971     vsnprintf(buffer, sizeof(buffer), format, args);
1972     buffer[sizeof(buffer)-1] = '\0';
1973     SendToICS(buffer);
1974     va_end(args);
1975 }
1976
1977 void
1978 SendToICS (char *s)
1979 {
1980     int count, outCount, outError;
1981
1982     if (icsPR == NoProc) return;
1983
1984     count = strlen(s);
1985     outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1986     if (outCount < count) {
1987         DisplayFatalError(_("Error writing to ICS"), outError, 1);
1988     }
1989 }
1990
1991 /* This is used for sending logon scripts to the ICS. Sending
1992    without a delay causes problems when using timestamp on ICC
1993    (at least on my machine). */
1994 void
1995 SendToICSDelayed (char *s, long msdelay)
1996 {
1997     int count, outCount, outError;
1998
1999     if (icsPR == NoProc) return;
2000
2001     count = strlen(s);
2002     if (appData.debugMode) {
2003         fprintf(debugFP, ">ICS: ");
2004         show_bytes(debugFP, s, count);
2005         fprintf(debugFP, "\n");
2006     }
2007     outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
2008                                       msdelay);
2009     if (outCount < count) {
2010         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2011     }
2012 }
2013
2014
2015 /* Remove all highlighting escape sequences in s
2016    Also deletes any suffix starting with '('
2017    */
2018 char *
2019 StripHighlightAndTitle (char *s)
2020 {
2021     static char retbuf[MSG_SIZ];
2022     char *p = retbuf;
2023
2024     while (*s != NULLCHAR) {
2025         while (*s == '\033') {
2026             while (*s != NULLCHAR && !isalpha(*s)) s++;
2027             if (*s != NULLCHAR) s++;
2028         }
2029         while (*s != NULLCHAR && *s != '\033') {
2030             if (*s == '(' || *s == '[') {
2031                 *p = NULLCHAR;
2032                 return retbuf;
2033             }
2034             *p++ = *s++;
2035         }
2036     }
2037     *p = NULLCHAR;
2038     return retbuf;
2039 }
2040
2041 /* Remove all highlighting escape sequences in s */
2042 char *
2043 StripHighlight (char *s)
2044 {
2045     static char retbuf[MSG_SIZ];
2046     char *p = retbuf;
2047
2048     while (*s != NULLCHAR) {
2049         while (*s == '\033') {
2050             while (*s != NULLCHAR && !isalpha(*s)) s++;
2051             if (*s != NULLCHAR) s++;
2052         }
2053         while (*s != NULLCHAR && *s != '\033') {
2054             *p++ = *s++;
2055         }
2056     }
2057     *p = NULLCHAR;
2058     return retbuf;
2059 }
2060
2061 char engineVariant[MSG_SIZ];
2062 char *variantNames[] = VARIANT_NAMES;
2063 char *
2064 VariantName (VariantClass v)
2065 {
2066     if(v == VariantUnknown || *engineVariant) return engineVariant;
2067     return variantNames[v];
2068 }
2069
2070
2071 /* Identify a variant from the strings the chess servers use or the
2072    PGN Variant tag names we use. */
2073 VariantClass
2074 StringToVariant (char *e)
2075 {
2076     char *p;
2077     int wnum = -1;
2078     VariantClass v = VariantNormal;
2079     int i, found = FALSE;
2080     char buf[MSG_SIZ];
2081     int len;
2082
2083     if (!e) return v;
2084
2085     /* [HGM] skip over optional board-size prefixes */
2086     if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
2087         sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
2088         while( *e++ != '_');
2089     }
2090
2091     if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
2092         v = VariantNormal;
2093         found = TRUE;
2094     } else
2095     for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
2096       if (p = StrCaseStr(e, variantNames[i])) {
2097         if(p && i >= VariantShogi && isalpha(p[strlen(variantNames[i])])) continue;
2098         v = (VariantClass) i;
2099         found = TRUE;
2100         break;
2101       }
2102     }
2103
2104     if (!found) {
2105       if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
2106           || StrCaseStr(e, "wild/fr")
2107           || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
2108         v = VariantFischeRandom;
2109       } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
2110                  (i = 1, p = StrCaseStr(e, "w"))) {
2111         p += i;
2112         while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
2113         if (isdigit(*p)) {
2114           wnum = atoi(p);
2115         } else {
2116           wnum = -1;
2117         }
2118         switch (wnum) {
2119         case 0: /* FICS only, actually */
2120         case 1:
2121           /* Castling legal even if K starts on d-file */
2122           v = VariantWildCastle;
2123           break;
2124         case 2:
2125         case 3:
2126         case 4:
2127           /* Castling illegal even if K & R happen to start in
2128              normal positions. */
2129           v = VariantNoCastle;
2130           break;
2131         case 5:
2132         case 7:
2133         case 8:
2134         case 10:
2135         case 11:
2136         case 12:
2137         case 13:
2138         case 14:
2139         case 15:
2140         case 18:
2141         case 19:
2142           /* Castling legal iff K & R start in normal positions */
2143           v = VariantNormal;
2144           break;
2145         case 6:
2146         case 20:
2147         case 21:
2148           /* Special wilds for position setup; unclear what to do here */
2149           v = VariantLoadable;
2150           break;
2151         case 9:
2152           /* Bizarre ICC game */
2153           v = VariantTwoKings;
2154           break;
2155         case 16:
2156           v = VariantKriegspiel;
2157           break;
2158         case 17:
2159           v = VariantLosers;
2160           break;
2161         case 22:
2162           v = VariantFischeRandom;
2163           break;
2164         case 23:
2165           v = VariantCrazyhouse;
2166           break;
2167         case 24:
2168           v = VariantBughouse;
2169           break;
2170         case 25:
2171           v = Variant3Check;
2172           break;
2173         case 26:
2174           /* Not quite the same as FICS suicide! */
2175           v = VariantGiveaway;
2176           break;
2177         case 27:
2178           v = VariantAtomic;
2179           break;
2180         case 28:
2181           v = VariantShatranj;
2182           break;
2183
2184         /* Temporary names for future ICC types.  The name *will* change in
2185            the next xboard/WinBoard release after ICC defines it. */
2186         case 29:
2187           v = Variant29;
2188           break;
2189         case 30:
2190           v = Variant30;
2191           break;
2192         case 31:
2193           v = Variant31;
2194           break;
2195         case 32:
2196           v = Variant32;
2197           break;
2198         case 33:
2199           v = Variant33;
2200           break;
2201         case 34:
2202           v = Variant34;
2203           break;
2204         case 35:
2205           v = Variant35;
2206           break;
2207         case 36:
2208           v = Variant36;
2209           break;
2210         case 37:
2211           v = VariantShogi;
2212           break;
2213         case 38:
2214           v = VariantXiangqi;
2215           break;
2216         case 39:
2217           v = VariantCourier;
2218           break;
2219         case 40:
2220           v = VariantGothic;
2221           break;
2222         case 41:
2223           v = VariantCapablanca;
2224           break;
2225         case 42:
2226           v = VariantKnightmate;
2227           break;
2228         case 43:
2229           v = VariantFairy;
2230           break;
2231         case 44:
2232           v = VariantCylinder;
2233           break;
2234         case 45:
2235           v = VariantFalcon;
2236           break;
2237         case 46:
2238           v = VariantCapaRandom;
2239           break;
2240         case 47:
2241           v = VariantBerolina;
2242           break;
2243         case 48:
2244           v = VariantJanus;
2245           break;
2246         case 49:
2247           v = VariantSuper;
2248           break;
2249         case 50:
2250           v = VariantGreat;
2251           break;
2252         case -1:
2253           /* Found "wild" or "w" in the string but no number;
2254              must assume it's normal chess. */
2255           v = VariantNormal;
2256           break;
2257         default:
2258           len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2259           if( (len >= MSG_SIZ) && appData.debugMode )
2260             fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2261
2262           DisplayError(buf, 0);
2263           v = VariantUnknown;
2264           break;
2265         }
2266       }
2267     }
2268     if (appData.debugMode) {
2269       fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
2270               e, wnum, VariantName(v));
2271     }
2272     return v;
2273 }
2274
2275 static int leftover_start = 0, leftover_len = 0;
2276 char star_match[STAR_MATCH_N][MSG_SIZ];
2277
2278 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2279    advance *index beyond it, and set leftover_start to the new value of
2280    *index; else return FALSE.  If pattern contains the character '*', it
2281    matches any sequence of characters not containing '\r', '\n', or the
2282    character following the '*' (if any), and the matched sequence(s) are
2283    copied into star_match.
2284    */
2285 int
2286 looking_at ( char *buf, int *index, char *pattern)
2287 {
2288     char *bufp = &buf[*index], *patternp = pattern;
2289     int star_count = 0;
2290     char *matchp = star_match[0];
2291
2292     for (;;) {
2293         if (*patternp == NULLCHAR) {
2294             *index = leftover_start = bufp - buf;
2295             *matchp = NULLCHAR;
2296             return TRUE;
2297         }
2298         if (*bufp == NULLCHAR) return FALSE;
2299         if (*patternp == '*') {
2300             if (*bufp == *(patternp + 1)) {
2301                 *matchp = NULLCHAR;
2302                 matchp = star_match[++star_count];
2303                 patternp += 2;
2304                 bufp++;
2305                 continue;
2306             } else if (*bufp == '\n' || *bufp == '\r') {
2307                 patternp++;
2308                 if (*patternp == NULLCHAR)
2309                   continue;
2310                 else
2311                   return FALSE;
2312             } else {
2313                 *matchp++ = *bufp++;
2314                 continue;
2315             }
2316         }
2317         if (*patternp != *bufp) return FALSE;
2318         patternp++;
2319         bufp++;
2320     }
2321 }
2322
2323 void
2324 SendToPlayer (char *data, int length)
2325 {
2326     int error, outCount;
2327     outCount = OutputToProcess(NoProc, data, length, &error);
2328     if (outCount < length) {
2329         DisplayFatalError(_("Error writing to display"), error, 1);
2330     }
2331 }
2332
2333 void
2334 PackHolding (char packed[], char *holding)
2335 {
2336     char *p = holding;
2337     char *q = packed;
2338     int runlength = 0;
2339     int curr = 9999;
2340     do {
2341         if (*p == curr) {
2342             runlength++;
2343         } else {
2344             switch (runlength) {
2345               case 0:
2346                 break;
2347               case 1:
2348                 *q++ = curr;
2349                 break;
2350               case 2:
2351                 *q++ = curr;
2352                 *q++ = curr;
2353                 break;
2354               default:
2355                 sprintf(q, "%d", runlength);
2356                 while (*q) q++;
2357                 *q++ = curr;
2358                 break;
2359             }
2360             runlength = 1;
2361             curr = *p;
2362         }
2363     } while (*p++);
2364     *q = NULLCHAR;
2365 }
2366
2367 /* Telnet protocol requests from the front end */
2368 void
2369 TelnetRequest (unsigned char ddww, unsigned char option)
2370 {
2371     unsigned char msg[3];
2372     int outCount, outError;
2373
2374     if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2375
2376     if (appData.debugMode) {
2377         char buf1[8], buf2[8], *ddwwStr, *optionStr;
2378         switch (ddww) {
2379           case TN_DO:
2380             ddwwStr = "DO";
2381             break;
2382           case TN_DONT:
2383             ddwwStr = "DONT";
2384             break;
2385           case TN_WILL:
2386             ddwwStr = "WILL";
2387             break;
2388           case TN_WONT:
2389             ddwwStr = "WONT";
2390             break;
2391           default:
2392             ddwwStr = buf1;
2393             snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2394             break;
2395         }
2396         switch (option) {
2397           case TN_ECHO:
2398             optionStr = "ECHO";
2399             break;
2400           default:
2401             optionStr = buf2;
2402             snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2403             break;
2404         }
2405         fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2406     }
2407     msg[0] = TN_IAC;
2408     msg[1] = ddww;
2409     msg[2] = option;
2410     outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2411     if (outCount < 3) {
2412         DisplayFatalError(_("Error writing to ICS"), outError, 1);
2413     }
2414 }
2415
2416 void
2417 DoEcho ()
2418 {
2419     if (!appData.icsActive) return;
2420     TelnetRequest(TN_DO, TN_ECHO);
2421 }
2422
2423 void
2424 DontEcho ()
2425 {
2426     if (!appData.icsActive) return;
2427     TelnetRequest(TN_DONT, TN_ECHO);
2428 }
2429
2430 void
2431 CopyHoldings (Board board, char *holdings, ChessSquare lowestPiece)
2432 {
2433     /* put the holdings sent to us by the server on the board holdings area */
2434     int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2435     char p;
2436     ChessSquare piece;
2437
2438     if(gameInfo.holdingsWidth < 2)  return;
2439     if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2440         return; // prevent overwriting by pre-board holdings
2441
2442     if( (int)lowestPiece >= BlackPawn ) {
2443         holdingsColumn = 0;
2444         countsColumn = 1;
2445         holdingsStartRow = BOARD_HEIGHT-1;
2446         direction = -1;
2447     } else {
2448         holdingsColumn = BOARD_WIDTH-1;
2449         countsColumn = BOARD_WIDTH-2;
2450         holdingsStartRow = 0;
2451         direction = 1;
2452     }
2453
2454     for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2455         board[i][holdingsColumn] = EmptySquare;
2456         board[i][countsColumn]   = (ChessSquare) 0;
2457     }
2458     while( (p=*holdings++) != NULLCHAR ) {
2459         piece = CharToPiece( ToUpper(p) );
2460         if(piece == EmptySquare) continue;
2461         /*j = (int) piece - (int) WhitePawn;*/
2462         j = PieceToNumber(piece);
2463         if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2464         if(j < 0) continue;               /* should not happen */
2465         piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2466         board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2467         board[holdingsStartRow+j*direction][countsColumn]++;
2468     }
2469 }
2470
2471
2472 void
2473 VariantSwitch (Board board, VariantClass newVariant)
2474 {
2475    int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2476    static Board oldBoard;
2477
2478    startedFromPositionFile = FALSE;
2479    if(gameInfo.variant == newVariant) return;
2480
2481    /* [HGM] This routine is called each time an assignment is made to
2482     * gameInfo.variant during a game, to make sure the board sizes
2483     * are set to match the new variant. If that means adding or deleting
2484     * holdings, we shift the playing board accordingly
2485     * This kludge is needed because in ICS observe mode, we get boards
2486     * of an ongoing game without knowing the variant, and learn about the
2487     * latter only later. This can be because of the move list we requested,
2488     * in which case the game history is refilled from the beginning anyway,
2489     * but also when receiving holdings of a crazyhouse game. In the latter
2490     * case we want to add those holdings to the already received position.
2491     */
2492
2493
2494    if (appData.debugMode) {
2495      fprintf(debugFP, "Switch board from %s to %s\n",
2496              VariantName(gameInfo.variant), VariantName(newVariant));
2497      setbuf(debugFP, NULL);
2498    }
2499    shuffleOpenings = 0;       /* [HGM] shuffle */
2500    gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2501    switch(newVariant)
2502      {
2503      case VariantShogi:
2504        newWidth = 9;  newHeight = 9;
2505        gameInfo.holdingsSize = 7;
2506      case VariantBughouse:
2507      case VariantCrazyhouse:
2508        newHoldingsWidth = 2; break;
2509      case VariantGreat:
2510        newWidth = 10;
2511      case VariantSuper:
2512        newHoldingsWidth = 2;
2513        gameInfo.holdingsSize = 8;
2514        break;
2515      case VariantGothic:
2516      case VariantCapablanca:
2517      case VariantCapaRandom:
2518        newWidth = 10;
2519      default:
2520        newHoldingsWidth = gameInfo.holdingsSize = 0;
2521      };
2522
2523    if(newWidth  != gameInfo.boardWidth  ||
2524       newHeight != gameInfo.boardHeight ||
2525       newHoldingsWidth != gameInfo.holdingsWidth ) {
2526
2527      /* shift position to new playing area, if needed */
2528      if(newHoldingsWidth > gameInfo.holdingsWidth) {
2529        for(i=0; i<BOARD_HEIGHT; i++)
2530          for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2531            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2532              board[i][j];
2533        for(i=0; i<newHeight; i++) {
2534          board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2535          board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2536        }
2537      } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2538        for(i=0; i<BOARD_HEIGHT; i++)
2539          for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2540            board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2541              board[i][j];
2542      }
2543      board[HOLDINGS_SET] = 0;
2544      gameInfo.boardWidth  = newWidth;
2545      gameInfo.boardHeight = newHeight;
2546      gameInfo.holdingsWidth = newHoldingsWidth;
2547      gameInfo.variant = newVariant;
2548      InitDrawingSizes(-2, 0);
2549    } else gameInfo.variant = newVariant;
2550    CopyBoard(oldBoard, board);   // remember correctly formatted board
2551      InitPosition(FALSE);          /* this sets up board[0], but also other stuff        */
2552    DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2553 }
2554
2555 static int loggedOn = FALSE;
2556
2557 /*-- Game start info cache: --*/
2558 int gs_gamenum;
2559 char gs_kind[MSG_SIZ];
2560 static char player1Name[128] = "";
2561 static char player2Name[128] = "";
2562 static char cont_seq[] = "\n\\   ";
2563 static int player1Rating = -1;
2564 static int player2Rating = -1;
2565 /*----------------------------*/
2566
2567 ColorClass curColor = ColorNormal;
2568 int suppressKibitz = 0;
2569
2570 // [HGM] seekgraph
2571 Boolean soughtPending = FALSE;
2572 Boolean seekGraphUp;
2573 #define MAX_SEEK_ADS 200
2574 #define SQUARE 0x80
2575 char *seekAdList[MAX_SEEK_ADS];
2576 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2577 float tcList[MAX_SEEK_ADS];
2578 char colorList[MAX_SEEK_ADS];
2579 int nrOfSeekAds = 0;
2580 int minRating = 1010, maxRating = 2800;
2581 int hMargin = 10, vMargin = 20, h, w;
2582 extern int squareSize, lineGap;
2583
2584 void
2585 PlotSeekAd (int i)
2586 {
2587         int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2588         xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2589         if(r < minRating+100 && r >=0 ) r = minRating+100;
2590         if(r > maxRating) r = maxRating;
2591         if(tc < 1.f) tc = 1.f;
2592         if(tc > 95.f) tc = 95.f;
2593         x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2594         y = ((double)r - minRating)/(maxRating - minRating)
2595             * (h-vMargin-squareSize/8-1) + vMargin;
2596         if(ratingList[i] < 0) y = vMargin + squareSize/4;
2597         if(strstr(seekAdList[i], " u ")) color = 1;
2598         if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2599            !strstr(seekAdList[i], "bullet") &&
2600            !strstr(seekAdList[i], "blitz") &&
2601            !strstr(seekAdList[i], "standard") ) color = 2;
2602         if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2603         DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2604 }
2605
2606 void
2607 PlotSingleSeekAd (int i)
2608 {
2609         PlotSeekAd(i);
2610 }
2611
2612 void
2613 AddAd (char *handle, char *rating, int base, int inc,  char rated, char *type, int nr, Boolean plot)
2614 {
2615         char buf[MSG_SIZ], *ext = "";
2616         VariantClass v = StringToVariant(type);
2617         if(strstr(type, "wild")) {
2618             ext = type + 4; // append wild number
2619             if(v == VariantFischeRandom) type = "chess960"; else
2620             if(v == VariantLoadable) type = "setup"; else
2621             type = VariantName(v);
2622         }
2623         snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2624         if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2625             if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2626             ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2627             sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2628             tcList[nrOfSeekAds] = base + (2./3.)*inc;
2629             seekNrList[nrOfSeekAds] = nr;
2630             zList[nrOfSeekAds] = 0;
2631             seekAdList[nrOfSeekAds++] = StrSave(buf);
2632             if(plot) PlotSingleSeekAd(nrOfSeekAds-1);
2633         }
2634 }
2635
2636 void
2637 EraseSeekDot (int i)
2638 {
2639     int x = xList[i], y = yList[i], d=squareSize/4, k;
2640     DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2641     if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2642     // now replot every dot that overlapped
2643     for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2644         int xx = xList[k], yy = yList[k];
2645         if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2646             DrawSeekDot(xx, yy, colorList[k]);
2647     }
2648 }
2649
2650 void
2651 RemoveSeekAd (int nr)
2652 {
2653         int i;
2654         for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2655             EraseSeekDot(i);
2656             if(seekAdList[i]) free(seekAdList[i]);
2657             seekAdList[i] = seekAdList[--nrOfSeekAds];
2658             seekNrList[i] = seekNrList[nrOfSeekAds];
2659             ratingList[i] = ratingList[nrOfSeekAds];
2660             colorList[i]  = colorList[nrOfSeekAds];
2661             tcList[i] = tcList[nrOfSeekAds];
2662             xList[i]  = xList[nrOfSeekAds];
2663             yList[i]  = yList[nrOfSeekAds];
2664             zList[i]  = zList[nrOfSeekAds];
2665             seekAdList[nrOfSeekAds] = NULL;
2666             break;
2667         }
2668 }
2669
2670 Boolean
2671 MatchSoughtLine (char *line)
2672 {
2673     char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2674     int nr, base, inc, u=0; char dummy;
2675
2676     if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2677        sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2678        (u=1) &&
2679        (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2680         sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7)  ) {
2681         // match: compact and save the line
2682         AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2683         return TRUE;
2684     }
2685     return FALSE;
2686 }
2687
2688 int
2689 DrawSeekGraph ()
2690 {
2691     int i;
2692     if(!seekGraphUp) return FALSE;
2693     h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2694     w = BOARD_WIDTH  * (squareSize + lineGap) + lineGap;
2695
2696     DrawSeekBackground(0, 0, w, h);
2697     DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2698     DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2699     for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2700         int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2701         yy = h-1-yy;
2702         DrawSeekAxis(hMargin-5, yy, hMargin+5*(i%500==0), yy); // rating ticks
2703         if(i%500 == 0) {
2704             char buf[MSG_SIZ];
2705             snprintf(buf, MSG_SIZ, "%d", i);
2706             DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2707         }
2708     }
2709     DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2710     for(i=1; i<100; i+=(i<10?1:5)) {
2711         int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2712         DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2713         if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2714             char buf[MSG_SIZ];
2715             snprintf(buf, MSG_SIZ, "%d", i);
2716             DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2717         }
2718     }
2719     for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2720     return TRUE;
2721 }
2722
2723 int
2724 SeekGraphClick (ClickType click, int x, int y, int moving)
2725 {
2726     static int lastDown = 0, displayed = 0, lastSecond;
2727     if(y < 0) return FALSE;
2728     if(!(appData.seekGraph && appData.icsActive && loggedOn &&
2729         (gameMode == BeginningOfGame || gameMode == IcsIdle))) {
2730         if(!seekGraphUp) return FALSE;
2731         seekGraphUp = FALSE; // seek graph is up when it shouldn't be: take it down
2732         DrawPosition(TRUE, NULL);
2733         return TRUE;
2734     }
2735     if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2736         if(click == Release || moving) return FALSE;
2737         nrOfSeekAds = 0;
2738         soughtPending = TRUE;
2739         SendToICS(ics_prefix);
2740         SendToICS("sought\n"); // should this be "sought all"?
2741     } else { // issue challenge based on clicked ad
2742         int dist = 10000; int i, closest = 0, second = 0;
2743         for(i=0; i<nrOfSeekAds; i++) {
2744             int d = (x-xList[i])*(x-xList[i]) +  (y-yList[i])*(y-yList[i]) + zList[i];
2745             if(d < dist) { dist = d; closest = i; }
2746             second += (d - zList[i] < 120); // count in-range ads
2747             if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2748         }
2749         if(dist < 120) {
2750             char buf[MSG_SIZ];
2751             second = (second > 1);
2752             if(displayed != closest || second != lastSecond) {
2753                 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2754                 lastSecond = second; displayed = closest;
2755             }
2756             if(click == Press) {
2757                 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2758                 lastDown = closest;
2759                 return TRUE;
2760             } // on press 'hit', only show info
2761             if(moving == 2) return TRUE; // ignore right up-clicks on dot
2762             snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2763             SendToICS(ics_prefix);
2764             SendToICS(buf);
2765             return TRUE; // let incoming board of started game pop down the graph
2766         } else if(click == Release) { // release 'miss' is ignored
2767             zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2768             if(moving == 2) { // right up-click
2769                 nrOfSeekAds = 0; // refresh graph
2770                 soughtPending = TRUE;
2771                 SendToICS(ics_prefix);
2772                 SendToICS("sought\n"); // should this be "sought all"?
2773             }
2774             return TRUE;
2775         } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2776         // press miss or release hit 'pop down' seek graph
2777         seekGraphUp = FALSE;
2778         DrawPosition(TRUE, NULL);
2779     }
2780     return TRUE;
2781 }
2782
2783 void
2784 read_from_ics (InputSourceRef isr, VOIDSTAR closure, char *data, int count, int error)
2785 {
2786 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2787 #define STARTED_NONE 0
2788 #define STARTED_MOVES 1
2789 #define STARTED_BOARD 2
2790 #define STARTED_OBSERVE 3
2791 #define STARTED_HOLDINGS 4
2792 #define STARTED_CHATTER 5
2793 #define STARTED_COMMENT 6
2794 #define STARTED_MOVES_NOHIDE 7
2795
2796     static int started = STARTED_NONE;
2797     static char parse[20000];
2798     static int parse_pos = 0;
2799     static char buf[BUF_SIZE + 1];
2800     static int firstTime = TRUE, intfSet = FALSE;
2801     static ColorClass prevColor = ColorNormal;
2802     static int savingComment = FALSE;
2803     static int cmatch = 0; // continuation sequence match
2804     char *bp;
2805     char str[MSG_SIZ];
2806     int i, oldi;
2807     int buf_len;
2808     int next_out;
2809     int tkind;
2810     int backup;    /* [DM] For zippy color lines */
2811     char *p;
2812     char talker[MSG_SIZ]; // [HGM] chat
2813     int channel;
2814
2815     connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2816
2817     if (appData.debugMode) {
2818       if (!error) {
2819         fprintf(debugFP, "<ICS: ");
2820         show_bytes(debugFP, data, count);
2821         fprintf(debugFP, "\n");
2822       }
2823     }
2824
2825     if (appData.debugMode) { int f = forwardMostMove;
2826         fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2827                 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2828                 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2829     }
2830     if (count > 0) {
2831         /* If last read ended with a partial line that we couldn't parse,
2832            prepend it to the new read and try again. */
2833         if (leftover_len > 0) {
2834             for (i=0; i<leftover_len; i++)
2835               buf[i] = buf[leftover_start + i];
2836         }
2837
2838     /* copy new characters into the buffer */
2839     bp = buf + leftover_len;
2840     buf_len=leftover_len;
2841     for (i=0; i<count; i++)
2842     {
2843         // ignore these
2844         if (data[i] == '\r')
2845             continue;
2846
2847         // join lines split by ICS?
2848         if (!appData.noJoin)
2849         {
2850             /*
2851                 Joining just consists of finding matches against the
2852                 continuation sequence, and discarding that sequence
2853                 if found instead of copying it.  So, until a match
2854                 fails, there's nothing to do since it might be the
2855                 complete sequence, and thus, something we don't want
2856                 copied.
2857             */
2858             if (data[i] == cont_seq[cmatch])
2859             {
2860                 cmatch++;
2861                 if (cmatch == strlen(cont_seq))
2862                 {
2863                     cmatch = 0; // complete match.  just reset the counter
2864
2865                     /*
2866                         it's possible for the ICS to not include the space
2867                         at the end of the last word, making our [correct]
2868                         join operation fuse two separate words.  the server
2869                         does this when the space occurs at the width setting.
2870                     */
2871                     if (!buf_len || buf[buf_len-1] != ' ')
2872                     {
2873                         *bp++ = ' ';
2874                         buf_len++;
2875                     }
2876                 }
2877                 continue;
2878             }
2879             else if (cmatch)
2880             {
2881                 /*
2882                     match failed, so we have to copy what matched before
2883                     falling through and copying this character.  In reality,
2884                     this will only ever be just the newline character, but
2885                     it doesn't hurt to be precise.
2886                 */
2887                 strncpy(bp, cont_seq, cmatch);
2888                 bp += cmatch;
2889                 buf_len += cmatch;
2890                 cmatch = 0;
2891             }
2892         }
2893
2894         // copy this char
2895         *bp++ = data[i];
2896         buf_len++;
2897     }
2898
2899         buf[buf_len] = NULLCHAR;
2900 //      next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2901         next_out = 0;
2902         leftover_start = 0;
2903
2904         i = 0;
2905         while (i < buf_len) {
2906             /* Deal with part of the TELNET option negotiation
2907                protocol.  We refuse to do anything beyond the
2908                defaults, except that we allow the WILL ECHO option,
2909                which ICS uses to turn off password echoing when we are
2910                directly connected to it.  We reject this option
2911                if localLineEditing mode is on (always on in xboard)
2912                and we are talking to port 23, which might be a real
2913                telnet server that will try to keep WILL ECHO on permanently.
2914              */
2915             if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2916                 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2917                 unsigned char option;
2918                 oldi = i;
2919                 switch ((unsigned char) buf[++i]) {
2920                   case TN_WILL:
2921                     if (appData.debugMode)
2922                       fprintf(debugFP, "\n<WILL ");
2923                     switch (option = (unsigned char) buf[++i]) {
2924                       case TN_ECHO:
2925                         if (appData.debugMode)
2926                           fprintf(debugFP, "ECHO ");
2927                         /* Reply only if this is a change, according
2928                            to the protocol rules. */
2929                         if (remoteEchoOption) break;
2930                         if (appData.localLineEditing &&
2931                             atoi(appData.icsPort) == TN_PORT) {
2932                             TelnetRequest(TN_DONT, TN_ECHO);
2933                         } else {
2934                             EchoOff();
2935                             TelnetRequest(TN_DO, TN_ECHO);
2936                             remoteEchoOption = TRUE;
2937                         }
2938                         break;
2939                       default:
2940                         if (appData.debugMode)
2941                           fprintf(debugFP, "%d ", option);
2942                         /* Whatever this is, we don't want it. */
2943                         TelnetRequest(TN_DONT, option);
2944                         break;
2945                     }
2946                     break;
2947                   case TN_WONT:
2948                     if (appData.debugMode)
2949                       fprintf(debugFP, "\n<WONT ");
2950                     switch (option = (unsigned char) buf[++i]) {
2951                       case TN_ECHO:
2952                         if (appData.debugMode)
2953                           fprintf(debugFP, "ECHO ");
2954                         /* Reply only if this is a change, according
2955                            to the protocol rules. */
2956                         if (!remoteEchoOption) break;
2957                         EchoOn();
2958                         TelnetRequest(TN_DONT, TN_ECHO);
2959                         remoteEchoOption = FALSE;
2960                         break;
2961                       default:
2962                         if (appData.debugMode)
2963                           fprintf(debugFP, "%d ", (unsigned char) option);
2964                         /* Whatever this is, it must already be turned
2965                            off, because we never agree to turn on
2966                            anything non-default, so according to the
2967                            protocol rules, we don't reply. */
2968                         break;
2969                     }
2970                     break;
2971                   case TN_DO:
2972                     if (appData.debugMode)
2973                       fprintf(debugFP, "\n<DO ");
2974                     switch (option = (unsigned char) buf[++i]) {
2975                       default:
2976                         /* Whatever this is, we refuse to do it. */
2977                         if (appData.debugMode)
2978                           fprintf(debugFP, "%d ", option);
2979                         TelnetRequest(TN_WONT, option);
2980                         break;
2981                     }
2982                     break;
2983                   case TN_DONT:
2984                     if (appData.debugMode)
2985                       fprintf(debugFP, "\n<DONT ");
2986                     switch (option = (unsigned char) buf[++i]) {
2987                       default:
2988                         if (appData.debugMode)
2989                           fprintf(debugFP, "%d ", option);
2990                         /* Whatever this is, we are already not doing
2991                            it, because we never agree to do anything
2992                            non-default, so according to the protocol
2993                            rules, we don't reply. */
2994                         break;
2995                     }
2996                     break;
2997                   case TN_IAC:
2998                     if (appData.debugMode)
2999                       fprintf(debugFP, "\n<IAC ");
3000                     /* Doubled IAC; pass it through */
3001                     i--;
3002                     break;
3003                   default:
3004                     if (appData.debugMode)
3005                       fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
3006                     /* Drop all other telnet commands on the floor */
3007                     break;
3008                 }
3009                 if (oldi > next_out)
3010                   SendToPlayer(&buf[next_out], oldi - next_out);
3011                 if (++i > next_out)
3012                   next_out = i;
3013                 continue;
3014             }
3015
3016             /* OK, this at least will *usually* work */
3017             if (!loggedOn && looking_at(buf, &i, "ics%")) {
3018                 loggedOn = TRUE;
3019             }
3020
3021             if (loggedOn && !intfSet) {
3022                 if (ics_type == ICS_ICC) {
3023                   snprintf(str, MSG_SIZ,
3024                           "/set-quietly interface %s\n/set-quietly style 12\n",
3025                           programVersion);
3026                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3027                       strcat(str, "/set-2 51 1\n/set seek 1\n");
3028                 } else if (ics_type == ICS_CHESSNET) {
3029                   snprintf(str, MSG_SIZ, "/style 12\n");
3030                 } else {
3031                   safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
3032                   strcat(str, programVersion);
3033                   strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
3034                   if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
3035                       strcat(str, "$iset seekremove 1\n$set seek 1\n");
3036 #ifdef WIN32
3037                   strcat(str, "$iset nohighlight 1\n");
3038 #endif
3039                   strcat(str, "$iset lock 1\n$style 12\n");
3040                 }
3041                 SendToICS(str);
3042                 NotifyFrontendLogin();
3043                 intfSet = TRUE;
3044             }
3045
3046             if (started == STARTED_COMMENT) {
3047                 /* Accumulate characters in comment */
3048                 parse[parse_pos++] = buf[i];
3049                 if (buf[i] == '\n') {
3050                     parse[parse_pos] = NULLCHAR;
3051                     if(chattingPartner>=0) {
3052                         char mess[MSG_SIZ];
3053                         snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
3054                         OutputChatMessage(chattingPartner, mess);
3055                         chattingPartner = -1;
3056                         next_out = i+1; // [HGM] suppress printing in ICS window
3057                     } else
3058                     if(!suppressKibitz) // [HGM] kibitz
3059                         AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
3060                     else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
3061                         int nrDigit = 0, nrAlph = 0, j;
3062                         if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
3063                         { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
3064                         parse[parse_pos] = NULLCHAR;
3065                         // try to be smart: if it does not look like search info, it should go to
3066                         // ICS interaction window after all, not to engine-output window.
3067                         for(j=0; j<parse_pos; j++) { // count letters and digits
3068                             nrDigit += (parse[j] >= '0' && parse[j] <= '9');
3069                             nrAlph  += (parse[j] >= 'a' && parse[j] <= 'z');
3070                             nrAlph  += (parse[j] >= 'A' && parse[j] <= 'Z');
3071                         }
3072                         if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
3073                             int depth=0; float score;
3074                             if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
3075                                 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
3076                                 pvInfoList[forwardMostMove-1].depth = depth;
3077                                 pvInfoList[forwardMostMove-1].score = 100*score;
3078                             }
3079                             OutputKibitz(suppressKibitz, parse);
3080                         } else {
3081                             char tmp[MSG_SIZ];
3082                             if(gameMode == IcsObserving) // restore original ICS messages
3083                               /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3084                               snprintf(tmp, MSG_SIZ, "%s kibitzes: %s", star_match[0], parse);
3085                             else
3086                             /* TRANSLATORS: to 'kibitz' is to send a message to all players and the game observers */
3087                             snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
3088                             SendToPlayer(tmp, strlen(tmp));
3089                         }
3090                         next_out = i+1; // [HGM] suppress printing in ICS window
3091                     }
3092                     started = STARTED_NONE;
3093                 } else {
3094                     /* Don't match patterns against characters in comment */
3095                     i++;
3096                     continue;
3097                 }
3098             }
3099             if (started == STARTED_CHATTER) {
3100                 if (buf[i] != '\n') {
3101                     /* Don't match patterns against characters in chatter */
3102                     i++;
3103                     continue;
3104                 }
3105                 started = STARTED_NONE;
3106                 if(suppressKibitz) next_out = i+1;
3107             }
3108
3109             /* Kludge to deal with rcmd protocol */
3110             if (firstTime && looking_at(buf, &i, "\001*")) {
3111                 DisplayFatalError(&buf[1], 0, 1);
3112                 continue;
3113             } else {
3114                 firstTime = FALSE;
3115             }
3116
3117             if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
3118                 ics_type = ICS_ICC;
3119                 ics_prefix = "/";
3120                 if (appData.debugMode)
3121                   fprintf(debugFP, "ics_type %d\n", ics_type);
3122                 continue;
3123             }
3124             if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
3125                 ics_type = ICS_FICS;
3126                 ics_prefix = "$";
3127                 if (appData.debugMode)
3128                   fprintf(debugFP, "ics_type %d\n", ics_type);
3129                 continue;
3130             }
3131             if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3132                 ics_type = ICS_CHESSNET;
3133                 ics_prefix = "/";
3134                 if (appData.debugMode)
3135                   fprintf(debugFP, "ics_type %d\n", ics_type);
3136                 continue;
3137             }
3138
3139             if (!loggedOn &&
3140                 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3141                  looking_at(buf, &i, "Logging you in as \"*\"") ||
3142                  looking_at(buf, &i, "will be \"*\""))) {
3143               safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3144               continue;
3145             }
3146
3147             if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3148               char buf[MSG_SIZ];
3149               snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3150               DisplayIcsInteractionTitle(buf);
3151               have_set_title = TRUE;
3152             }
3153
3154             /* skip finger notes */
3155             if (started == STARTED_NONE &&
3156                 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3157                  (buf[i] == '1' && buf[i+1] == '0')) &&
3158                 buf[i+2] == ':' && buf[i+3] == ' ') {
3159               started = STARTED_CHATTER;
3160               i += 3;
3161               continue;
3162             }
3163
3164             oldi = i;
3165             // [HGM] seekgraph: recognize sought lines and end-of-sought message
3166             if(appData.seekGraph) {
3167                 if(soughtPending && MatchSoughtLine(buf+i)) {
3168                     i = strstr(buf+i, "rated") - buf;
3169                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3170                     next_out = leftover_start = i;
3171                     started = STARTED_CHATTER;
3172                     suppressKibitz = TRUE;
3173                     continue;
3174                 }
3175                 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3176                         && looking_at(buf, &i, "* ads displayed")) {
3177                     soughtPending = FALSE;
3178                     seekGraphUp = TRUE;
3179                     DrawSeekGraph();
3180                     continue;
3181                 }
3182                 if(appData.autoRefresh) {
3183                     if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3184                         int s = (ics_type == ICS_ICC); // ICC format differs
3185                         if(seekGraphUp)
3186                         AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3187                               star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3188                         looking_at(buf, &i, "*% "); // eat prompt
3189                         if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3190                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3191                         next_out = i; // suppress
3192                         continue;
3193                     }
3194                     if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3195                         char *p = star_match[0];
3196                         while(*p) {
3197                             if(seekGraphUp) RemoveSeekAd(atoi(p));
3198                             while(*p && *p++ != ' '); // next
3199                         }
3200                         looking_at(buf, &i, "*% "); // eat prompt
3201                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3202                         next_out = i;
3203                         continue;
3204                     }
3205                 }
3206             }
3207
3208             /* skip formula vars */
3209             if (started == STARTED_NONE &&
3210                 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3211               started = STARTED_CHATTER;
3212               i += 3;
3213               continue;
3214             }
3215
3216             // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3217             if (appData.autoKibitz && started == STARTED_NONE &&
3218                 !appData.icsEngineAnalyze &&                     // [HGM] [DM] ICS analyze
3219                 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3220                 if((looking_at(buf, &i, "\n* kibitzes: ") || looking_at(buf, &i, "\n* whispers: ") ||
3221                     looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3222                    (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3223                     StrStr(star_match[0], gameInfo.black) == star_match[0]   )) { // kibitz of self or opponent
3224                         suppressKibitz = TRUE;
3225                         if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3226                         next_out = i;
3227                         if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3228                                 && (gameMode == IcsPlayingWhite)) ||
3229                            (StrStr(star_match[0], gameInfo.black) == star_match[0]
3230                                 && (gameMode == IcsPlayingBlack))   ) // opponent kibitz
3231                             started = STARTED_CHATTER; // own kibitz we simply discard
3232                         else {
3233                             started = STARTED_COMMENT; // make sure it will be collected in parse[]
3234                             parse_pos = 0; parse[0] = NULLCHAR;
3235                             savingComment = TRUE;
3236                             suppressKibitz = gameMode != IcsObserving ? 2 :
3237                                 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3238                         }
3239                         continue;
3240                 } else
3241                 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3242                     looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3243                          && atoi(star_match[0])) {
3244                     // suppress the acknowledgements of our own autoKibitz
3245                     char *p;
3246                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3247                     if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3248                     SendToPlayer(star_match[0], strlen(star_match[0]));
3249                     if(looking_at(buf, &i, "*% ")) // eat prompt
3250                         suppressKibitz = FALSE;
3251                     next_out = i;
3252                     continue;
3253                 }
3254             } // [HGM] kibitz: end of patch
3255
3256             if(looking_at(buf, &i, "* rating adjustment: * --> *\n")) continue;
3257
3258             // [HGM] chat: intercept tells by users for which we have an open chat window
3259             channel = -1;
3260             if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3261                                            looking_at(buf, &i, "* whispers:") ||
3262                                            looking_at(buf, &i, "* kibitzes:") ||
3263                                            looking_at(buf, &i, "* shouts:") ||
3264                                            looking_at(buf, &i, "* c-shouts:") ||
3265                                            looking_at(buf, &i, "--> * ") ||
3266                                            looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3267                                            looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3268                                            looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3269                                            looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3270                 int p;
3271                 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3272                 chattingPartner = -1;
3273
3274                 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3275                 for(p=0; p<MAX_CHAT; p++) {
3276                     if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3277                     talker[0] = '['; strcat(talker, "] ");
3278                     Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3279                     chattingPartner = p; break;
3280                     }
3281                 } else
3282                 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3283                 for(p=0; p<MAX_CHAT; p++) {
3284                     if(!strcmp("kibitzes", chatPartner[p])) {
3285                         talker[0] = '['; strcat(talker, "] ");
3286                         chattingPartner = p; break;
3287                     }
3288                 } else
3289                 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3290                 for(p=0; p<MAX_CHAT; p++) {
3291                     if(!strcmp("whispers", chatPartner[p])) {
3292                         talker[0] = '['; strcat(talker, "] ");
3293                         chattingPartner = p; break;
3294                     }
3295                 } else
3296                 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3297                   if(buf[i-8] == '-' && buf[i-3] == 't')
3298                   for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3299                     if(!strcmp("c-shouts", chatPartner[p])) {
3300                         talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3301                         chattingPartner = p; break;
3302                     }
3303                   }
3304                   if(chattingPartner < 0)
3305                   for(p=0; p<MAX_CHAT; p++) {
3306                     if(!strcmp("shouts", chatPartner[p])) {
3307                         if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3308                         else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3309                         else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3310                         chattingPartner = p; break;
3311                     }
3312                   }
3313                 }
3314                 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3315                 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3316                     talker[0] = 0; Colorize(ColorTell, FALSE);
3317                     chattingPartner = p; break;
3318                 }
3319                 if(chattingPartner<0) i = oldi; else {
3320                     Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3321                     if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3322                     if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3323                     started = STARTED_COMMENT;
3324                     parse_pos = 0; parse[0] = NULLCHAR;
3325                     savingComment = 3 + chattingPartner; // counts as TRUE
3326                     suppressKibitz = TRUE;
3327                     continue;
3328                 }
3329             } // [HGM] chat: end of patch
3330
3331           backup = i;
3332             if (appData.zippyTalk || appData.zippyPlay) {
3333                 /* [DM] Backup address for color zippy lines */
3334 #if ZIPPY
3335                if (loggedOn == TRUE)
3336                        if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3337                           (appData.zippyPlay && ZippyMatch(buf, &backup)));
3338 #endif
3339             } // [DM] 'else { ' deleted
3340                 if (
3341                     /* Regular tells and says */
3342                     (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3343                     looking_at(buf, &i, "* (your partner) tells you: ") ||
3344                     looking_at(buf, &i, "* says: ") ||
3345                     /* Don't color "message" or "messages" output */
3346                     (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3347                     looking_at(buf, &i, "*. * at *:*: ") ||
3348                     looking_at(buf, &i, "--* (*:*): ") ||
3349                     /* Message notifications (same color as tells) */
3350                     looking_at(buf, &i, "* has left a message ") ||
3351                     looking_at(buf, &i, "* just sent you a message:\n") ||
3352                     /* Whispers and kibitzes */
3353                     (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3354                     looking_at(buf, &i, "* kibitzes: ") ||
3355                     /* Channel tells */
3356                     (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3357
3358                   if (tkind == 1 && strchr(star_match[0], ':')) {
3359                       /* Avoid "tells you:" spoofs in channels */
3360                      tkind = 3;
3361                   }
3362                   if (star_match[0][0] == NULLCHAR ||
3363                       strchr(star_match[0], ' ') ||
3364                       (tkind == 3 && strchr(star_match[1], ' '))) {
3365                     /* Reject bogus matches */
3366                     i = oldi;
3367                   } else {
3368                     if (appData.colorize) {
3369                       if (oldi > next_out) {
3370                         SendToPlayer(&buf[next_out], oldi - next_out);
3371                         next_out = oldi;
3372                       }
3373                       switch (tkind) {
3374                       case 1:
3375                         Colorize(ColorTell, FALSE);
3376                         curColor = ColorTell;
3377                         break;
3378                       case 2:
3379                         Colorize(ColorKibitz, FALSE);
3380                         curColor = ColorKibitz;
3381                         break;
3382                       case 3:
3383                         p = strrchr(star_match[1], '(');
3384                         if (p == NULL) {
3385                           p = star_match[1];
3386                         } else {
3387                           p++;
3388                         }
3389                         if (atoi(p) == 1) {
3390                           Colorize(ColorChannel1, FALSE);
3391                           curColor = ColorChannel1;
3392                         } else {
3393                           Colorize(ColorChannel, FALSE);
3394                           curColor = ColorChannel;
3395                         }
3396                         break;
3397                       case 5:
3398                         curColor = ColorNormal;
3399                         break;
3400                       }
3401                     }
3402                     if (started == STARTED_NONE && appData.autoComment &&
3403                         (gameMode == IcsObserving ||
3404                          gameMode == IcsPlayingWhite ||
3405                          gameMode == IcsPlayingBlack)) {
3406                       parse_pos = i - oldi;
3407                       memcpy(parse, &buf[oldi], parse_pos);
3408                       parse[parse_pos] = NULLCHAR;
3409                       started = STARTED_COMMENT;
3410                       savingComment = TRUE;
3411                     } else {
3412                       started = STARTED_CHATTER;
3413                       savingComment = FALSE;
3414                     }
3415                     loggedOn = TRUE;
3416                     continue;
3417                   }
3418                 }
3419
3420                 if (looking_at(buf, &i, "* s-shouts: ") ||