2 * backend.c -- Common back end for X and Windows NT versions of
3 * XBoard $Id: backend.c,v 2.6 2003/11/28 09:37:36 mann Exp $
5 * Copyright 1991 by Digital Equipment Corporation, Maynard, Massachusetts.
6 * Enhancements Copyright 1992-2001 Free Software Foundation, Inc.
8 * The following terms apply to Digital Equipment Corporation's copyright
10 * ------------------------------------------------------------------------
13 * Permission to use, copy, modify, and distribute this software and its
14 * documentation for any purpose and without fee is hereby granted,
15 * provided that the above copyright notice appear in all copies and that
16 * both that copyright notice and this permission notice appear in
17 * supporting documentation, and that the name of Digital not be
18 * used in advertising or publicity pertaining to distribution of the
19 * software without specific, written prior permission.
21 * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
22 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
23 * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
24 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
25 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
26 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
28 * ------------------------------------------------------------------------
30 * The following terms apply to the enhanced version of XBoard distributed
31 * by the Free Software Foundation:
32 * ------------------------------------------------------------------------
33 * This program is free software; you can redistribute it and/or modify
34 * it under the terms of the GNU General Public License as published by
35 * the Free Software Foundation; either version 2 of the License, or
36 * (at your option) any later version.
38 * This program is distributed in the hope that it will be useful,
39 * but WITHOUT ANY WARRANTY; without even the implied warranty of
40 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
41 * GNU General Public License for more details.
43 * You should have received a copy of the GNU General Public License
44 * along with this program; if not, write to the Free Software
45 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
46 * ------------------------------------------------------------------------
48 * See the file ChangeLog for a revision history. */
50 /* [AS] Also useful here for debugging */
54 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
68 #include <sys/types.h>
75 #else /* not STDC_HEADERS */
78 # else /* not HAVE_STRING_H */
80 # endif /* not HAVE_STRING_H */
81 #endif /* not STDC_HEADERS */
84 # include <sys/fcntl.h>
85 #else /* not HAVE_SYS_FCNTL_H */
88 # endif /* HAVE_FCNTL_H */
89 #endif /* not HAVE_SYS_FCNTL_H */
91 #if TIME_WITH_SYS_TIME
92 # include <sys/time.h>
96 # include <sys/time.h>
102 #if defined(_amigados) && !defined(__GNUC__)
107 extern int gettimeofday(struct timeval *, struct timezone *);
115 #include "frontend.h"
122 #include "backendz.h"
124 /* A point in time */
126 long sec; /* Assuming this is >= 32 bits */
127 int ms; /* Assuming this is >= 16 bits */
130 /* Search stats from chessprogram */
132 char movelist[2*MSG_SIZ]; /* Last PV we were sent */
133 int depth; /* Current search depth */
134 int nr_moves; /* Total nr of root moves */
135 int moves_left; /* Moves remaining to be searched */
136 char move_name[MOVE_LEN]; /* Current move being searched, if provided */
137 unsigned long nodes; /* # of nodes searched */
138 int time; /* Search time (centiseconds) */
139 int score; /* Score (centipawns) */
140 int got_only_move; /* If last msg was "(only move)" */
141 int got_fail; /* 0 - nothing, 1 - got "--", 2 - got "++" */
142 int ok_to_send; /* handshaking between send & recv */
143 int line_is_book; /* 1 if movelist is book moves */
144 int seen_stat; /* 1 if we've seen the stat01: line */
147 int establish P((void));
148 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
149 char *buf, int count, int error));
150 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
151 char *buf, int count, int error));
152 void SendToICS P((char *s));
153 void SendToICSDelayed P((char *s, long msdelay));
154 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY,
156 void InitPosition P((int redraw));
157 void HandleMachineMove P((char *message, ChessProgramState *cps));
158 int AutoPlayOneMove P((void));
159 int LoadGameOneMove P((ChessMove readAhead));
160 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
161 int LoadPositionFromFile P((char *filename, int n, char *title));
162 int SavePositionToFile P((char *filename));
163 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
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 void 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 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
172 void EditPositionDone P((void));
173 void PrintOpponents P((FILE *fp));
174 void PrintPosition P((FILE *fp, int move));
175 void StartChessProgram P((ChessProgramState *cps));
176 void SendToProgram P((char *message, ChessProgramState *cps));
177 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
178 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
179 char *buf, int count, int error));
180 void SendTimeControl P((ChessProgramState *cps,
181 int mps, long tc, int inc, int sd, int st));
182 char *TimeControlTagValue P((void));
183 void Attention P((ChessProgramState *cps));
184 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
185 void ResurrectChessProgram P((void));
186 void DisplayComment P((int moveNumber, char *text));
187 void DisplayMove P((int moveNumber));
188 void DisplayAnalysis P((void));
190 void ParseGameHistory P((char *game));
191 void ParseBoard12 P((char *string));
192 void StartClocks P((void));
193 void SwitchClocks P((void));
194 void StopClocks P((void));
195 void ResetClocks P((void));
196 char *PGNDate P((void));
197 void SetGameInfo P((void));
198 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
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 void GetTimeMark P((TimeMark *));
208 long SubtractTimeMarks P((TimeMark *, TimeMark *));
209 int CheckFlags P((void));
210 long NextTickLength P((long));
211 void CheckTimeControl P((void));
212 void show_bytes P((FILE *, char *, int));
213 int string_to_rating P((char *str));
214 void ParseFeatures P((char* args, ChessProgramState *cps));
215 void InitBackEnd3 P((void));
216 void FeatureDone P((ChessProgramState* cps, int val));
217 void InitChessProgram P((ChessProgramState *cps));
219 void GetInfoFromComment( int, char * );
221 extern int tinyLayout, smallLayout;
222 static ChessProgramStats programStats;
224 /* States for ics_getting_history */
226 #define H_REQUESTED 1
227 #define H_GOT_REQ_HEADER 2
228 #define H_GOT_UNREQ_HEADER 3
229 #define H_GETTING_MOVES 4
230 #define H_GOT_UNWANTED_HEADER 5
232 /* whosays values for GameEnds */
239 /* Maximum number of games in a cmail message */
240 #define CMAIL_MAX_GAMES 20
242 /* Different types of move when calling RegisterMove */
244 #define CMAIL_RESIGN 1
246 #define CMAIL_ACCEPT 3
248 /* Different types of result to remember for each game */
249 #define CMAIL_NOT_RESULT 0
250 #define CMAIL_OLD_RESULT 1
251 #define CMAIL_NEW_RESULT 2
253 /* Telnet protocol constants */
264 static char * safeStrCpy( char * dst, const char * src, size_t count )
266 assert( dst != NULL );
267 assert( src != NULL );
270 strncpy( dst, src, count );
271 dst[ count-1 ] = '\0';
275 static char * safeStrCat( char * dst, const char * src, size_t count )
279 assert( dst != NULL );
280 assert( src != NULL );
283 dst_len = strlen(dst);
285 assert( count > dst_len ); /* Buffer size must be greater than current length */
287 safeStrCpy( dst + dst_len, src, count - dst_len );
292 /* Fake up flags for now, as we aren't keeping track of castling
297 int flags = F_ALL_CASTLE_OK;
298 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
299 switch (gameInfo.variant) {
301 case VariantGiveaway:
302 flags |= F_IGNORE_CHECK;
303 flags &= ~F_ALL_CASTLE_OK;
306 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
308 case VariantKriegspiel:
309 flags |= F_KRIEGSPIEL_CAPTURE;
311 case VariantNoCastle:
312 flags &= ~F_ALL_CASTLE_OK;
320 FILE *gameFileFP, *debugFP;
323 [AS] Note: sometimes, the sscanf() function is used to parse the input
324 into a fixed-size buffer. Because of this, we must be prepared to
325 receive strings as long as the size of the input buffer, which is currently
326 set to 4K for Windows and 8K for the rest.
327 So, we must either allocate sufficiently large buffers here, or
328 reduce the size of the input buffer in the input reading part.
331 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
332 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
333 char thinkOutput1[MSG_SIZ*10];
335 ChessProgramState first, second;
337 /* premove variables */
340 int premoveFromX = 0;
341 int premoveFromY = 0;
342 int premovePromoChar = 0;
344 Boolean alarmSounded;
345 /* end premove variables */
347 #define ICS_GENERIC 0
350 #define ICS_CHESSNET 3 /* not really supported */
351 int ics_type = ICS_GENERIC;
352 char *ics_prefix = "$";
354 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
355 int pauseExamForwardMostMove = 0;
356 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
357 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
358 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
359 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
360 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
361 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
362 int whiteFlag = FALSE, blackFlag = FALSE;
363 int userOfferedDraw = FALSE;
364 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
365 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
366 int cmailMoveType[CMAIL_MAX_GAMES];
367 long ics_clock_paused = 0;
368 ProcRef icsPR = NoProc, cmailPR = NoProc;
369 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
370 GameMode gameMode = BeginningOfGame;
371 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
372 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
373 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
374 int hiddenThinkOutputState = 0; /* [AS] */
375 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
376 int adjudicateLossPlies = 6;
377 char white_holding[64], black_holding[64];
378 TimeMark lastNodeCountTime;
379 long lastNodeCount=0;
380 int have_sent_ICS_logon = 0;
382 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement;
383 long timeControl_2; /* [AS] Allow separate time controls */
384 long timeRemaining[2][MAX_MOVES];
386 TimeMark programStartTime;
387 char ics_handle[MSG_SIZ];
388 int have_set_title = 0;
390 /* animateTraining preserves the state of appData.animate
391 * when Training mode is activated. This allows the
392 * response to be animated when appData.animate == TRUE and
393 * appData.animateDragging == TRUE.
395 Boolean animateTraining;
401 Board boards[MAX_MOVES];
402 Board initialPosition = {
403 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
404 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
405 { WhitePawn, WhitePawn, WhitePawn, WhitePawn,
406 WhitePawn, WhitePawn, WhitePawn, WhitePawn },
407 { EmptySquare, EmptySquare, EmptySquare, EmptySquare,
408 EmptySquare, EmptySquare, EmptySquare, EmptySquare },
409 { EmptySquare, EmptySquare, EmptySquare, EmptySquare,
410 EmptySquare, EmptySquare, EmptySquare, EmptySquare },
411 { EmptySquare, EmptySquare, EmptySquare, EmptySquare,
412 EmptySquare, EmptySquare, EmptySquare, EmptySquare },
413 { EmptySquare, EmptySquare, EmptySquare, EmptySquare,
414 EmptySquare, EmptySquare, EmptySquare, EmptySquare },
415 { BlackPawn, BlackPawn, BlackPawn, BlackPawn,
416 BlackPawn, BlackPawn, BlackPawn, BlackPawn },
417 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
418 BlackKing, BlackBishop, BlackKnight, BlackRook }
420 Board twoKingsPosition = {
421 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
422 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
423 { WhitePawn, WhitePawn, WhitePawn, WhitePawn,
424 WhitePawn, WhitePawn, WhitePawn, WhitePawn },
425 { EmptySquare, EmptySquare, EmptySquare, EmptySquare,
426 EmptySquare, EmptySquare, EmptySquare, EmptySquare },
427 { EmptySquare, EmptySquare, EmptySquare, EmptySquare,
428 EmptySquare, EmptySquare, EmptySquare, EmptySquare },
429 { EmptySquare, EmptySquare, EmptySquare, EmptySquare,
430 EmptySquare, EmptySquare, EmptySquare, EmptySquare },
431 { EmptySquare, EmptySquare, EmptySquare, EmptySquare,
432 EmptySquare, EmptySquare, EmptySquare, EmptySquare },
433 { BlackPawn, BlackPawn, BlackPawn, BlackPawn,
434 BlackPawn, BlackPawn, BlackPawn, BlackPawn },
435 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
436 BlackKing, BlackKing, BlackKnight, BlackRook }
440 /* Convert str to a rating. Checks for special cases of "----",
441 "++++", etc. Also strips ()'s */
443 string_to_rating(str)
446 while(*str && !isdigit(*str)) ++str;
448 return 0; /* One of the special "no rating" cases */
456 /* Init programStats */
457 programStats.movelist[0] = 0;
458 programStats.depth = 0;
459 programStats.nr_moves = 0;
460 programStats.moves_left = 0;
461 programStats.nodes = 0;
462 programStats.time = 100;
463 programStats.score = 0;
464 programStats.got_only_move = 0;
465 programStats.got_fail = 0;
466 programStats.line_is_book = 0;
472 int matched, min, sec;
474 GetTimeMark(&programStartTime);
477 programStats.ok_to_send = 1;
478 programStats.seen_stat = 0;
481 * Initialize game list
487 * Internet chess server status
489 if (appData.icsActive) {
490 appData.matchMode = FALSE;
491 appData.matchGames = 0;
493 appData.noChessProgram = !appData.zippyPlay;
495 appData.zippyPlay = FALSE;
496 appData.zippyTalk = FALSE;
497 appData.noChessProgram = TRUE;
499 if (*appData.icsHelper != NULLCHAR) {
500 appData.useTelnet = TRUE;
501 appData.telnetProgram = appData.icsHelper;
504 appData.zippyTalk = appData.zippyPlay = FALSE;
507 /* [AS] Initialize pv info list */
511 for( i=0; i<MAX_MOVES; i++ ) {
512 pvInfoList[i].depth = 0;
517 * Parse timeControl resource
519 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
520 appData.movesPerSession)) {
522 sprintf(buf, "bad timeControl option %s", appData.timeControl);
523 DisplayFatalError(buf, 0, 2);
527 * Parse searchTime resource
529 if (*appData.searchTime != NULLCHAR) {
530 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
532 searchTime = min * 60;
533 } else if (matched == 2) {
534 searchTime = min * 60 + sec;
537 sprintf(buf, "bad searchTime option %s", appData.searchTime);
538 DisplayFatalError(buf, 0, 2);
542 /* [AS] Adjudication threshold */
543 adjudicateLossThreshold = appData.adjudicateLossThreshold;
545 first.which = "first";
546 second.which = "second";
547 first.maybeThinking = second.maybeThinking = FALSE;
548 first.pr = second.pr = NoProc;
549 first.isr = second.isr = NULL;
550 first.sendTime = second.sendTime = 2;
551 first.sendDrawOffers = 1;
552 if (appData.firstPlaysBlack) {
553 first.twoMachinesColor = "black\n";
554 second.twoMachinesColor = "white\n";
556 first.twoMachinesColor = "white\n";
557 second.twoMachinesColor = "black\n";
559 first.program = appData.firstChessProgram;
560 second.program = appData.secondChessProgram;
561 first.host = appData.firstHost;
562 second.host = appData.secondHost;
563 first.dir = appData.firstDirectory;
564 second.dir = appData.secondDirectory;
565 first.other = &second;
566 second.other = &first;
567 first.initString = appData.initString;
568 second.initString = appData.secondInitString;
569 first.computerString = appData.firstComputerString;
570 second.computerString = appData.secondComputerString;
571 first.useSigint = second.useSigint = TRUE;
572 first.useSigterm = second.useSigterm = TRUE;
573 first.reuse = appData.reuseFirst;
574 second.reuse = appData.reuseSecond;
575 first.useSetboard = second.useSetboard = FALSE;
576 first.useSAN = second.useSAN = FALSE;
577 first.usePing = second.usePing = FALSE;
578 first.lastPing = second.lastPing = 0;
579 first.lastPong = second.lastPong = 0;
580 first.usePlayother = second.usePlayother = FALSE;
581 first.useColors = second.useColors = TRUE;
582 first.useUsermove = second.useUsermove = FALSE;
583 first.sendICS = second.sendICS = FALSE;
584 first.sendName = second.sendName = appData.icsActive;
585 first.sdKludge = second.sdKludge = FALSE;
586 first.stKludge = second.stKludge = FALSE;
587 TidyProgramName(first.program, first.host, first.tidy);
588 TidyProgramName(second.program, second.host, second.tidy);
589 first.matchWins = second.matchWins = 0;
590 strcpy(first.variants, appData.variant);
591 strcpy(second.variants, appData.variant);
592 first.analysisSupport = second.analysisSupport = 2; /* detect */
593 first.analyzing = second.analyzing = FALSE;
594 first.initDone = second.initDone = FALSE;
596 /* New features added by Tord: */
597 first.useFEN960 = FALSE; second.useFEN960 = FALSE;
598 first.useOOCastle = TRUE; second.useOOCastle = TRUE;
599 /* End of new features added by Tord. */
600 first.scoreIsAbsolute = appData.firstScoreIsAbsolute; /* [AS] */
601 second.scoreIsAbsolute = appData.secondScoreIsAbsolute; /* [AS] */
603 if (appData.firstProtocolVersion > PROTOVER ||
604 appData.firstProtocolVersion < 1) {
606 sprintf(buf, "protocol version %d not supported",
607 appData.firstProtocolVersion);
608 DisplayFatalError(buf, 0, 2);
610 first.protocolVersion = appData.firstProtocolVersion;
613 if (appData.secondProtocolVersion > PROTOVER ||
614 appData.secondProtocolVersion < 1) {
616 sprintf(buf, "protocol version %d not supported",
617 appData.secondProtocolVersion);
618 DisplayFatalError(buf, 0, 2);
620 second.protocolVersion = appData.secondProtocolVersion;
623 if (appData.icsActive) {
624 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
625 } else if (*appData.searchTime != NULLCHAR || appData.noChessProgram) {
626 appData.clockMode = FALSE;
627 first.sendTime = second.sendTime = 0;
631 /* Override some settings from environment variables, for backward
632 compatibility. Unfortunately it's not feasible to have the env
633 vars just set defaults, at least in xboard. Ugh.
635 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
640 if (appData.noChessProgram) {
641 programVersion = (char*) malloc(5 + strlen(PRODUCT) + strlen(VERSION)
642 + strlen(PATCHLEVEL));
643 sprintf(programVersion, "%s %s.%s", PRODUCT, VERSION, PATCHLEVEL);
647 while (*q != ' ' && *q != NULLCHAR) q++;
649 while (p > first.program && *(p-1) != '/') p--;
650 programVersion = (char*) malloc(8 + strlen(PRODUCT) + strlen(VERSION)
651 + strlen(PATCHLEVEL) + (q - p));
652 sprintf(programVersion, "%s %s.%s + ", PRODUCT, VERSION, PATCHLEVEL);
653 strncat(programVersion, p, q - p);
656 if (!appData.icsActive) {
658 /* Check for variants that are supported only in ICS mode,
659 or not at all. Some that are accepted here nevertheless
660 have bugs; see comments below.
662 VariantClass variant = StringToVariant(appData.variant);
664 case VariantBughouse: /* need four players and two boards */
665 case VariantKriegspiel: /* need to hide pieces and move details */
666 /* case VariantFischeRandom: (Fabien: moved below) */
667 sprintf(buf, "Variant %s supported only in ICS mode", appData.variant);
668 DisplayFatalError(buf, 0, 2);
672 case VariantLoadable:
682 sprintf(buf, "Unknown variant name %s", appData.variant);
683 DisplayFatalError(buf, 0, 2);
686 case VariantNormal: /* definitely works! */
687 case VariantWildCastle: /* pieces not automatically shuffled */
688 case VariantNoCastle: /* pieces not automatically shuffled */
689 case VariantFischeRandom: /* Fabien: pieces not automatically shuffled */
690 case VariantCrazyhouse: /* holdings not shown,
691 offboard interposition not understood */
692 case VariantLosers: /* should work except for win condition,
693 and doesn't know captures are mandatory */
694 case VariantSuicide: /* should work except for win condition,
695 and doesn't know captures are mandatory */
696 case VariantGiveaway: /* should work except for win condition,
697 and doesn't know captures are mandatory */
698 case VariantTwoKings: /* should work */
699 case VariantAtomic: /* should work except for win condition */
700 case Variant3Check: /* should work except for win condition */
701 case VariantShatranj: /* might work if TestLegality is off */
707 int NextIntegerFromString( char ** str, long * value )
712 while( *s == ' ' || *s == '\t' ) {
718 if( *s >= '0' && *s <= '9' ) {
719 while( *s >= '0' && *s <= '9' ) {
720 *value = *value * 10 + (*s - '0');
732 int NextTimeControlFromString( char ** str, long * value )
735 int result = NextIntegerFromString( str, &temp );
738 *value = temp * 60; /* Minutes */
741 result = NextIntegerFromString( str, &temp );
742 *value += temp; /* Seconds */
749 int GetTimeControlForWhite()
751 int result = timeControl;
756 int GetTimeControlForBlack()
758 int result = timeControl;
760 if( timeControl_2 > 0 ) {
761 result = timeControl_2;
768 ParseTimeControl(tc, ti, mps)
774 int matched, min, sec;
776 matched = sscanf(tc, "%d:%d", &min, &sec);
778 timeControl = min * 60 * 1000;
779 } else if (matched == 2) {
780 timeControl = (min * 60 + sec) * 1000;
788 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
793 /* Parse second time control */
796 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
804 timeControl_2 = tc2 * 1000;
814 timeControl = tc1 * 1000;
818 timeIncrement = ti * 1000; /* convert to ms */
822 movesPerSession = mps;
830 if (appData.debugMode) {
831 fprintf(debugFP, "%s\n", programVersion);
834 if (appData.matchGames > 0) {
835 appData.matchMode = TRUE;
836 } else if (appData.matchMode) {
837 appData.matchGames = 1;
840 if (appData.noChessProgram || first.protocolVersion == 1) {
843 /* kludge: allow timeout for initial "feature" commands */
845 DisplayMessage("", "Starting chess program");
846 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
851 InitBackEnd3 P((void))
853 GameMode initialMode;
857 InitChessProgram(&first);
859 if (appData.icsActive) {
862 if (*appData.icsCommPort != NULLCHAR) {
863 sprintf(buf, "Could not open comm port %s",
864 appData.icsCommPort);
866 sprintf(buf, "Could not connect to host %s, port %s",
867 appData.icsHost, appData.icsPort);
869 DisplayFatalError(buf, err, 1);
874 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
876 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
877 } else if (appData.noChessProgram) {
883 if (*appData.cmailGameName != NULLCHAR) {
885 OpenLoopback(&cmailPR);
887 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
891 DisplayMessage("", "");
892 if (StrCaseCmp(appData.initialMode, "") == 0) {
893 initialMode = BeginningOfGame;
894 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
895 initialMode = TwoMachinesPlay;
896 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
897 initialMode = AnalyzeFile;
898 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
899 initialMode = AnalyzeMode;
900 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
901 initialMode = MachinePlaysWhite;
902 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
903 initialMode = MachinePlaysBlack;
904 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
905 initialMode = EditGame;
906 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
907 initialMode = EditPosition;
908 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
909 initialMode = Training;
911 sprintf(buf, "Unknown initialMode %s", appData.initialMode);
912 DisplayFatalError(buf, 0, 2);
916 if (appData.matchMode) {
917 /* Set up machine vs. machine match */
918 if (appData.noChessProgram) {
919 DisplayFatalError("Can't have a match with no chess programs",
925 if (*appData.loadGameFile != NULLCHAR) {
926 if (!LoadGameFromFile(appData.loadGameFile,
927 appData.loadGameIndex,
928 appData.loadGameFile, FALSE)) {
929 DisplayFatalError("Bad game file", 0, 1);
932 } else if (*appData.loadPositionFile != NULLCHAR) {
933 if (!LoadPositionFromFile(appData.loadPositionFile,
934 appData.loadPositionIndex,
935 appData.loadPositionFile)) {
936 DisplayFatalError("Bad position file", 0, 1);
941 } else if (*appData.cmailGameName != NULLCHAR) {
942 /* Set up cmail mode */
943 ReloadCmailMsgEvent(TRUE);
945 /* Set up other modes */
946 if (initialMode == AnalyzeFile) {
947 if (*appData.loadGameFile == NULLCHAR) {
948 DisplayFatalError("AnalyzeFile mode requires a game file", 0, 1);
952 if (*appData.loadGameFile != NULLCHAR) {
953 (void) LoadGameFromFile(appData.loadGameFile,
954 appData.loadGameIndex,
955 appData.loadGameFile, TRUE);
956 } else if (*appData.loadPositionFile != NULLCHAR) {
957 (void) LoadPositionFromFile(appData.loadPositionFile,
958 appData.loadPositionIndex,
959 appData.loadPositionFile);
961 if (initialMode == AnalyzeMode) {
962 if (appData.noChessProgram) {
963 DisplayFatalError("Analysis mode requires a chess engine", 0, 2);
966 if (appData.icsActive) {
967 DisplayFatalError("Analysis mode does not work with ICS mode",0,2);
971 } else if (initialMode == AnalyzeFile) {
972 ShowThinkingEvent(TRUE);
974 AnalysisPeriodicEvent(1);
975 } else if (initialMode == MachinePlaysWhite) {
976 if (appData.noChessProgram) {
977 DisplayFatalError("MachineWhite mode requires a chess engine",
981 if (appData.icsActive) {
982 DisplayFatalError("MachineWhite mode does not work with ICS mode",
987 } else if (initialMode == MachinePlaysBlack) {
988 if (appData.noChessProgram) {
989 DisplayFatalError("MachineBlack mode requires a chess engine",
993 if (appData.icsActive) {
994 DisplayFatalError("MachineBlack mode does not work with ICS mode",
999 } else if (initialMode == TwoMachinesPlay) {
1000 if (appData.noChessProgram) {
1001 DisplayFatalError("TwoMachines mode requires a chess engine",
1005 if (appData.icsActive) {
1006 DisplayFatalError("TwoMachines mode does not work with ICS mode",
1011 } else if (initialMode == EditGame) {
1013 } else if (initialMode == EditPosition) {
1014 EditPositionEvent();
1015 } else if (initialMode == Training) {
1016 if (*appData.loadGameFile == NULLCHAR) {
1017 DisplayFatalError("Training mode requires a game file", 0, 2);
1026 * Establish will establish a contact to a remote host.port.
1027 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1028 * used to talk to the host.
1029 * Returns 0 if okay, error code if not.
1036 if (*appData.icsCommPort != NULLCHAR) {
1037 /* Talk to the host through a serial comm port */
1038 return OpenCommPort(appData.icsCommPort, &icsPR);
1040 } else if (*appData.gateway != NULLCHAR) {
1041 if (*appData.remoteShell == NULLCHAR) {
1042 /* Use the rcmd protocol to run telnet program on a gateway host */
1043 sprintf(buf, "%s %s %s",
1044 appData.telnetProgram, appData.icsHost, appData.icsPort);
1045 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1048 /* Use the rsh program to run telnet program on a gateway host */
1049 if (*appData.remoteUser == NULLCHAR) {
1050 sprintf(buf, "%s %s %s %s %s", appData.remoteShell,
1051 appData.gateway, appData.telnetProgram,
1052 appData.icsHost, appData.icsPort);
1054 sprintf(buf, "%s %s -l %s %s %s %s",
1055 appData.remoteShell, appData.gateway,
1056 appData.remoteUser, appData.telnetProgram,
1057 appData.icsHost, appData.icsPort);
1059 return StartChildProcess(buf, "", &icsPR);
1062 } else if (appData.useTelnet) {
1063 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1066 /* TCP socket interface differs somewhat between
1067 Unix and NT; handle details in the front end.
1069 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1074 show_bytes(fp, buf, count)
1080 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1081 fprintf(fp, "\\%03o", *buf & 0xff);
1090 /* Returns an errno value */
1092 OutputMaybeTelnet(pr, message, count, outError)
1098 char buf[8192], *p, *q, *buflim;
1099 int left, newcount, outcount;
1101 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1102 *appData.gateway != NULLCHAR) {
1103 if (appData.debugMode) {
1104 fprintf(debugFP, ">ICS: ");
1105 show_bytes(debugFP, message, count);
1106 fprintf(debugFP, "\n");
1108 return OutputToProcess(pr, message, count, outError);
1111 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1118 if (appData.debugMode) {
1119 fprintf(debugFP, ">ICS: ");
1120 show_bytes(debugFP, buf, newcount);
1121 fprintf(debugFP, "\n");
1123 outcount = OutputToProcess(pr, buf, newcount, outError);
1124 if (outcount < newcount) return -1; /* to be sure */
1131 } else if (((unsigned char) *p) == TN_IAC) {
1132 *q++ = (char) TN_IAC;
1139 if (appData.debugMode) {
1140 fprintf(debugFP, ">ICS: ");
1141 show_bytes(debugFP, buf, newcount);
1142 fprintf(debugFP, "\n");
1144 outcount = OutputToProcess(pr, buf, newcount, outError);
1145 if (outcount < newcount) return -1; /* to be sure */
1150 read_from_player(isr, closure, message, count, error)
1157 int outError, outCount;
1158 static int gotEof = 0;
1160 /* Pass data read from player on to ICS */
1163 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1164 if (outCount < count) {
1165 DisplayFatalError("Error writing to ICS", outError, 1);
1167 } else if (count < 0) {
1168 RemoveInputSource(isr);
1169 DisplayFatalError("Error reading from keyboard", error, 1);
1170 } else if (gotEof++ > 0) {
1171 RemoveInputSource(isr);
1172 DisplayFatalError("Got end of file from keyboard", 0, 0);
1180 int count, outCount, outError;
1182 if (icsPR == NULL) return;
1185 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1186 if (outCount < count) {
1187 DisplayFatalError("Error writing to ICS", outError, 1);
1191 /* This is used for sending logon scripts to the ICS. Sending
1192 without a delay causes problems when using timestamp on ICC
1193 (at least on my machine). */
1195 SendToICSDelayed(s,msdelay)
1199 int count, outCount, outError;
1201 if (icsPR == NULL) return;
1204 if (appData.debugMode) {
1205 fprintf(debugFP, ">ICS: ");
1206 show_bytes(debugFP, s, count);
1207 fprintf(debugFP, "\n");
1209 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1211 if (outCount < count) {
1212 DisplayFatalError("Error writing to ICS", outError, 1);
1217 /* Remove all highlighting escape sequences in s
1218 Also deletes any suffix starting with '('
1221 StripHighlightAndTitle(s)
1224 static char retbuf[MSG_SIZ];
1227 while (*s != NULLCHAR) {
1228 while (*s == '\033') {
1229 while (*s != NULLCHAR && !isalpha(*s)) s++;
1230 if (*s != NULLCHAR) s++;
1232 while (*s != NULLCHAR && *s != '\033') {
1233 if (*s == '(' || *s == '[') {
1244 /* Remove all highlighting escape sequences in s */
1249 static char retbuf[MSG_SIZ];
1252 while (*s != NULLCHAR) {
1253 while (*s == '\033') {
1254 while (*s != NULLCHAR && !isalpha(*s)) s++;
1255 if (*s != NULLCHAR) s++;
1257 while (*s != NULLCHAR && *s != '\033') {
1265 char *variantNames[] = VARIANT_NAMES;
1270 return variantNames[v];
1274 /* Identify a variant from the strings the chess servers use or the
1275 PGN Variant tag names we use. */
1282 VariantClass v = VariantNormal;
1283 int i, found = FALSE;
1288 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1289 if (StrCaseStr(e, variantNames[i])) {
1290 v = (VariantClass) i;
1297 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1298 || StrCaseStr(e, "wild/fr")) {
1299 v = VariantFischeRandom;
1300 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1301 (i = 1, p = StrCaseStr(e, "w"))) {
1303 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1310 case 0: /* FICS only, actually */
1312 /* Castling legal even if K starts on d-file */
1313 v = VariantWildCastle;
1318 /* Castling illegal even if K & R happen to start in
1319 normal positions. */
1320 v = VariantNoCastle;
1333 /* Castling legal iff K & R start in normal positions */
1339 /* Special wilds for position setup; unclear what to do here */
1340 v = VariantLoadable;
1343 /* Bizarre ICC game */
1344 v = VariantTwoKings;
1347 v = VariantKriegspiel;
1353 v = VariantFischeRandom;
1356 v = VariantCrazyhouse;
1359 v = VariantBughouse;
1365 /* Not quite the same as FICS suicide! */
1366 v = VariantGiveaway;
1372 v = VariantShatranj;
1375 /* Temporary names for future ICC types. The name *will* change in
1376 the next xboard/WinBoard release after ICC defines it. */
1403 /* Found "wild" or "w" in the string but no number;
1404 must assume it's normal chess. */
1408 sprintf(buf, "Unknown wild type %d", wnum);
1409 DisplayError(buf, 0);
1415 if (appData.debugMode) {
1416 fprintf(debugFP, "recognized '%s' (%d) as variant %s\n",
1417 e, wnum, VariantName(v));
1422 static int leftover_start = 0, leftover_len = 0;
1423 char star_match[STAR_MATCH_N][MSG_SIZ];
1425 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
1426 advance *index beyond it, and set leftover_start to the new value of
1427 *index; else return FALSE. If pattern contains the character '*', it
1428 matches any sequence of characters not containing '\r', '\n', or the
1429 character following the '*' (if any), and the matched sequence(s) are
1430 copied into star_match.
1433 looking_at(buf, index, pattern)
1438 char *bufp = &buf[*index], *patternp = pattern;
1440 char *matchp = star_match[0];
1443 if (*patternp == NULLCHAR) {
1444 *index = leftover_start = bufp - buf;
1448 if (*bufp == NULLCHAR) return FALSE;
1449 if (*patternp == '*') {
1450 if (*bufp == *(patternp + 1)) {
1452 matchp = star_match[++star_count];
1456 } else if (*bufp == '\n' || *bufp == '\r') {
1458 if (*patternp == NULLCHAR)
1463 *matchp++ = *bufp++;
1467 if (*patternp != *bufp) return FALSE;
1474 SendToPlayer(data, length)
1478 int error, outCount;
1479 outCount = OutputToProcess(NoProc, data, length, &error);
1480 if (outCount < length) {
1481 DisplayFatalError("Error writing to display", error, 1);
1486 PackHolding(packed, holding)
1498 switch (runlength) {
1509 sprintf(q, "%d", runlength);
1521 /* Telnet protocol requests from the front end */
1523 TelnetRequest(ddww, option)
1524 unsigned char ddww, option;
1526 unsigned char msg[3];
1527 int outCount, outError;
1529 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
1531 if (appData.debugMode) {
1532 char buf1[8], buf2[8], *ddwwStr, *optionStr;
1548 sprintf(buf1, "%d", ddww);
1557 sprintf(buf2, "%d", option);
1560 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
1565 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
1567 DisplayFatalError("Error writing to ICS", outError, 1);
1574 if (!appData.icsActive) return;
1575 TelnetRequest(TN_DO, TN_ECHO);
1581 if (!appData.icsActive) return;
1582 TelnetRequest(TN_DONT, TN_ECHO);
1585 static int loggedOn = FALSE;
1587 /*-- Game start info cache: --*/
1589 char gs_kind[MSG_SIZ];
1590 static char player1Name[128] = "";
1591 static char player2Name[128] = "";
1592 static int player1Rating = -1;
1593 static int player2Rating = -1;
1594 /*----------------------------*/
1597 read_from_ics(isr, closure, data, count, error)
1604 #define BUF_SIZE 8192
1605 #define STARTED_NONE 0
1606 #define STARTED_MOVES 1
1607 #define STARTED_BOARD 2
1608 #define STARTED_OBSERVE 3
1609 #define STARTED_HOLDINGS 4
1610 #define STARTED_CHATTER 5
1611 #define STARTED_COMMENT 6
1612 #define STARTED_MOVES_NOHIDE 7
1614 static int started = STARTED_NONE;
1615 static char parse[20000];
1616 static int parse_pos = 0;
1617 static char buf[BUF_SIZE + 1];
1618 static int firstTime = TRUE, intfSet = FALSE;
1619 static ColorClass curColor = ColorNormal;
1620 static ColorClass prevColor = ColorNormal;
1621 static int savingComment = FALSE;
1630 if (appData.debugMode) {
1632 fprintf(debugFP, "<ICS: ");
1633 show_bytes(debugFP, data, count);
1634 fprintf(debugFP, "\n");
1640 /* If last read ended with a partial line that we couldn't parse,
1641 prepend it to the new read and try again. */
1642 if (leftover_len > 0) {
1643 for (i=0; i<leftover_len; i++)
1644 buf[i] = buf[leftover_start + i];
1647 /* Copy in new characters, removing nulls and \r's */
1648 buf_len = leftover_len;
1649 for (i = 0; i < count; i++) {
1650 if (data[i] != NULLCHAR && data[i] != '\r')
1651 buf[buf_len++] = data[i];
1654 buf[buf_len] = NULLCHAR;
1655 next_out = leftover_len;
1659 while (i < buf_len) {
1660 /* Deal with part of the TELNET option negotiation
1661 protocol. We refuse to do anything beyond the
1662 defaults, except that we allow the WILL ECHO option,
1663 which ICS uses to turn off password echoing when we are
1664 directly connected to it. We reject this option
1665 if localLineEditing mode is on (always on in xboard)
1666 and we are talking to port 23, which might be a real
1667 telnet server that will try to keep WILL ECHO on permanently.
1669 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
1670 static int remoteEchoOption = FALSE; /* telnet ECHO option */
1671 unsigned char option;
1673 switch ((unsigned char) buf[++i]) {
1675 if (appData.debugMode)
1676 fprintf(debugFP, "\n<WILL ");
1677 switch (option = (unsigned char) buf[++i]) {
1679 if (appData.debugMode)
1680 fprintf(debugFP, "ECHO ");
1681 /* Reply only if this is a change, according
1682 to the protocol rules. */
1683 if (remoteEchoOption) break;
1684 if (appData.localLineEditing &&
1685 atoi(appData.icsPort) == TN_PORT) {
1686 TelnetRequest(TN_DONT, TN_ECHO);
1689 TelnetRequest(TN_DO, TN_ECHO);
1690 remoteEchoOption = TRUE;
1694 if (appData.debugMode)
1695 fprintf(debugFP, "%d ", option);
1696 /* Whatever this is, we don't want it. */
1697 TelnetRequest(TN_DONT, option);
1702 if (appData.debugMode)
1703 fprintf(debugFP, "\n<WONT ");
1704 switch (option = (unsigned char) buf[++i]) {
1706 if (appData.debugMode)
1707 fprintf(debugFP, "ECHO ");
1708 /* Reply only if this is a change, according
1709 to the protocol rules. */
1710 if (!remoteEchoOption) break;
1712 TelnetRequest(TN_DONT, TN_ECHO);
1713 remoteEchoOption = FALSE;
1716 if (appData.debugMode)
1717 fprintf(debugFP, "%d ", (unsigned char) option);
1718 /* Whatever this is, it must already be turned
1719 off, because we never agree to turn on
1720 anything non-default, so according to the
1721 protocol rules, we don't reply. */
1726 if (appData.debugMode)
1727 fprintf(debugFP, "\n<DO ");
1728 switch (option = (unsigned char) buf[++i]) {
1730 /* Whatever this is, we refuse to do it. */
1731 if (appData.debugMode)
1732 fprintf(debugFP, "%d ", option);
1733 TelnetRequest(TN_WONT, option);
1738 if (appData.debugMode)
1739 fprintf(debugFP, "\n<DONT ");
1740 switch (option = (unsigned char) buf[++i]) {
1742 if (appData.debugMode)
1743 fprintf(debugFP, "%d ", option);
1744 /* Whatever this is, we are already not doing
1745 it, because we never agree to do anything
1746 non-default, so according to the protocol
1747 rules, we don't reply. */
1752 if (appData.debugMode)
1753 fprintf(debugFP, "\n<IAC ");
1754 /* Doubled IAC; pass it through */
1758 if (appData.debugMode)
1759 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
1760 /* Drop all other telnet commands on the floor */
1763 if (oldi > next_out)
1764 SendToPlayer(&buf[next_out], oldi - next_out);
1770 /* OK, this at least will *usually* work */
1771 if (!loggedOn && looking_at(buf, &i, "ics%")) {
1775 if (loggedOn && !intfSet) {
1776 if (ics_type == ICS_ICC) {
1778 "/set-quietly interface %s\n/set-quietly style 12\n",
1781 } else if (ics_type == ICS_CHESSNET) {
1782 sprintf(str, "/style 12\n");
1784 strcpy(str, "alias $ @\n$set interface ");
1785 strcat(str, programVersion);
1786 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
1788 strcat(str, "$iset nohighlight 1\n");
1790 strcat(str, "$iset lock 1\n$style 12\n");
1796 if (started == STARTED_COMMENT) {
1797 /* Accumulate characters in comment */
1798 parse[parse_pos++] = buf[i];
1799 if (buf[i] == '\n') {
1800 parse[parse_pos] = NULLCHAR;
1801 AppendComment(forwardMostMove, StripHighlight(parse));
1802 started = STARTED_NONE;
1804 /* Don't match patterns against characters in chatter */
1809 if (started == STARTED_CHATTER) {
1810 if (buf[i] != '\n') {
1811 /* Don't match patterns against characters in chatter */
1815 started = STARTED_NONE;
1818 /* Kludge to deal with rcmd protocol */
1819 if (firstTime && looking_at(buf, &i, "\001*")) {
1820 DisplayFatalError(&buf[1], 0, 1);
1826 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
1829 if (appData.debugMode)
1830 fprintf(debugFP, "ics_type %d\n", ics_type);
1833 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
1834 ics_type = ICS_FICS;
1836 if (appData.debugMode)
1837 fprintf(debugFP, "ics_type %d\n", ics_type);
1840 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
1841 ics_type = ICS_CHESSNET;
1843 if (appData.debugMode)
1844 fprintf(debugFP, "ics_type %d\n", ics_type);
1849 (looking_at(buf, &i, "\"*\" is *a registered name") ||
1850 looking_at(buf, &i, "Logging you in as \"*\"") ||
1851 looking_at(buf, &i, "will be \"*\""))) {
1852 strcpy(ics_handle, star_match[0]);
1856 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
1858 sprintf(buf, "%s@%s", ics_handle, appData.icsHost);
1859 DisplayIcsInteractionTitle(buf);
1860 have_set_title = TRUE;
1863 /* skip finger notes */
1864 if (started == STARTED_NONE &&
1865 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
1866 (buf[i] == '1' && buf[i+1] == '0')) &&
1867 buf[i+2] == ':' && buf[i+3] == ' ') {
1868 started = STARTED_CHATTER;
1873 /* skip formula vars */
1874 if (started == STARTED_NONE &&
1875 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
1876 started = STARTED_CHATTER;
1882 if (appData.zippyTalk || appData.zippyPlay) {
1884 if (ZippyControl(buf, &i) ||
1885 ZippyConverse(buf, &i) ||
1886 (appData.zippyPlay && ZippyMatch(buf, &i))) {
1892 if (/* Don't color "message" or "messages" output */
1893 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
1894 looking_at(buf, &i, "*. * at *:*: ") ||
1895 looking_at(buf, &i, "--* (*:*): ") ||
1896 /* Regular tells and says */
1897 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
1898 looking_at(buf, &i, "* (your partner) tells you: ") ||
1899 looking_at(buf, &i, "* says: ") ||
1900 /* Message notifications (same color as tells) */
1901 looking_at(buf, &i, "* has left a message ") ||
1902 looking_at(buf, &i, "* just sent you a message:\n") ||
1903 /* Whispers and kibitzes */
1904 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
1905 looking_at(buf, &i, "* kibitzes: ") ||
1907 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
1909 if (tkind == 1 && strchr(star_match[0], ':')) {
1910 /* Avoid "tells you:" spoofs in channels */
1913 if (star_match[0][0] == NULLCHAR ||
1914 strchr(star_match[0], ' ') ||
1915 (tkind == 3 && strchr(star_match[1], ' '))) {
1916 /* Reject bogus matches */
1919 if (appData.colorize) {
1920 if (oldi > next_out) {
1921 SendToPlayer(&buf[next_out], oldi - next_out);
1926 Colorize(ColorTell, FALSE);
1927 curColor = ColorTell;
1930 Colorize(ColorKibitz, FALSE);
1931 curColor = ColorKibitz;
1934 p = strrchr(star_match[1], '(');
1941 Colorize(ColorChannel1, FALSE);
1942 curColor = ColorChannel1;
1944 Colorize(ColorChannel, FALSE);
1945 curColor = ColorChannel;
1949 curColor = ColorNormal;
1953 if (started == STARTED_NONE && appData.autoComment &&
1954 (gameMode == IcsObserving ||
1955 gameMode == IcsPlayingWhite ||
1956 gameMode == IcsPlayingBlack)) {
1957 parse_pos = i - oldi;
1958 memcpy(parse, &buf[oldi], parse_pos);
1959 parse[parse_pos] = NULLCHAR;
1960 started = STARTED_COMMENT;
1961 savingComment = TRUE;
1963 started = STARTED_CHATTER;
1964 savingComment = FALSE;
1971 if (looking_at(buf, &i, "* s-shouts: ") ||
1972 looking_at(buf, &i, "* c-shouts: ")) {
1973 if (appData.colorize) {
1974 if (oldi > next_out) {
1975 SendToPlayer(&buf[next_out], oldi - next_out);
1978 Colorize(ColorSShout, FALSE);
1979 curColor = ColorSShout;
1982 started = STARTED_CHATTER;
1986 if (looking_at(buf, &i, "--->")) {
1991 if (looking_at(buf, &i, "* shouts: ") ||
1992 looking_at(buf, &i, "--> ")) {
1993 if (appData.colorize) {
1994 if (oldi > next_out) {
1995 SendToPlayer(&buf[next_out], oldi - next_out);
1998 Colorize(ColorShout, FALSE);
1999 curColor = ColorShout;
2002 started = STARTED_CHATTER;
2006 if (looking_at( buf, &i, "Challenge:")) {
2007 if (appData.colorize) {
2008 if (oldi > next_out) {
2009 SendToPlayer(&buf[next_out], oldi - next_out);
2012 Colorize(ColorChallenge, FALSE);
2013 curColor = ColorChallenge;
2019 if (looking_at(buf, &i, "* offers you") ||
2020 looking_at(buf, &i, "* offers to be") ||
2021 looking_at(buf, &i, "* would like to") ||
2022 looking_at(buf, &i, "* requests to") ||
2023 looking_at(buf, &i, "Your opponent offers") ||
2024 looking_at(buf, &i, "Your opponent requests")) {
2026 if (appData.colorize) {
2027 if (oldi > next_out) {
2028 SendToPlayer(&buf[next_out], oldi - next_out);
2031 Colorize(ColorRequest, FALSE);
2032 curColor = ColorRequest;
2037 if (looking_at(buf, &i, "* (*) seeking")) {
2038 if (appData.colorize) {
2039 if (oldi > next_out) {
2040 SendToPlayer(&buf[next_out], oldi - next_out);
2043 Colorize(ColorSeek, FALSE);
2044 curColor = ColorSeek;
2050 if (looking_at(buf, &i, "\\ ")) {
2051 if (prevColor != ColorNormal) {
2052 if (oldi > next_out) {
2053 SendToPlayer(&buf[next_out], oldi - next_out);
2056 Colorize(prevColor, TRUE);
2057 curColor = prevColor;
2059 if (savingComment) {
2060 parse_pos = i - oldi;
2061 memcpy(parse, &buf[oldi], parse_pos);
2062 parse[parse_pos] = NULLCHAR;
2063 started = STARTED_COMMENT;
2065 started = STARTED_CHATTER;
2070 if (looking_at(buf, &i, "Black Strength :") ||
2071 looking_at(buf, &i, "<<< style 10 board >>>") ||
2072 looking_at(buf, &i, "<10>") ||
2073 looking_at(buf, &i, "#@#")) {
2074 /* Wrong board style */
2076 SendToICS(ics_prefix);
2077 SendToICS("set style 12\n");
2078 SendToICS(ics_prefix);
2079 SendToICS("refresh\n");
2083 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
2085 have_sent_ICS_logon = 1;
2089 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
2090 (looking_at(buf, &i, "\n<12> ") ||
2091 looking_at(buf, &i, "<12> "))) {
2093 if (oldi > next_out) {
2094 SendToPlayer(&buf[next_out], oldi - next_out);
2097 started = STARTED_BOARD;
2102 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
2103 looking_at(buf, &i, "<b1> ")) {
2104 if (oldi > next_out) {
2105 SendToPlayer(&buf[next_out], oldi - next_out);
2108 started = STARTED_HOLDINGS;
2113 if (looking_at(buf, &i, "* *vs. * *--- *")) {
2115 /* Header for a move list -- first line */
2117 switch (ics_getting_history) {
2121 case BeginningOfGame:
2122 /* User typed "moves" or "oldmoves" while we
2123 were idle. Pretend we asked for these
2124 moves and soak them up so user can step
2125 through them and/or save them.
2128 gameMode = IcsObserving;
2131 ics_getting_history = H_GOT_UNREQ_HEADER;
2133 case EditGame: /*?*/
2134 case EditPosition: /*?*/
2135 /* Should above feature work in these modes too? */
2136 /* For now it doesn't */
2137 ics_getting_history = H_GOT_UNWANTED_HEADER;
2140 ics_getting_history = H_GOT_UNWANTED_HEADER;
2145 /* Is this the right one? */
2146 if (gameInfo.white && gameInfo.black &&
2147 strcmp(gameInfo.white, star_match[0]) == 0 &&
2148 strcmp(gameInfo.black, star_match[2]) == 0) {
2150 ics_getting_history = H_GOT_REQ_HEADER;
2153 case H_GOT_REQ_HEADER:
2154 case H_GOT_UNREQ_HEADER:
2155 case H_GOT_UNWANTED_HEADER:
2156 case H_GETTING_MOVES:
2157 /* Should not happen */
2158 DisplayError("Error gathering move list: two headers", 0);
2159 ics_getting_history = H_FALSE;
2163 /* Save player ratings into gameInfo if needed */
2164 if ((ics_getting_history == H_GOT_REQ_HEADER ||
2165 ics_getting_history == H_GOT_UNREQ_HEADER) &&
2166 (gameInfo.whiteRating == -1 ||
2167 gameInfo.blackRating == -1)) {
2169 gameInfo.whiteRating = string_to_rating(star_match[1]);
2170 gameInfo.blackRating = string_to_rating(star_match[3]);
2171 if (appData.debugMode)
2172 fprintf(debugFP, "Ratings from header: W %d, B %d\n",
2173 gameInfo.whiteRating, gameInfo.blackRating);
2178 if (looking_at(buf, &i,
2179 "* * match, initial time: * minute*, increment: * second")) {
2180 /* Header for a move list -- second line */
2181 /* Initial board will follow if this is a wild game */
2183 if (gameInfo.event != NULL) free(gameInfo.event);
2184 sprintf(str, "ICS %s %s match", star_match[0], star_match[1]);
2185 gameInfo.event = StrSave(str);
2186 gameInfo.variant = StringToVariant(gameInfo.event);
2190 if (looking_at(buf, &i, "Move ")) {
2191 /* Beginning of a move list */
2192 switch (ics_getting_history) {
2194 /* Normally should not happen */
2195 /* Maybe user hit reset while we were parsing */
2198 /* Happens if we are ignoring a move list that is not
2199 * the one we just requested. Common if the user
2200 * tries to observe two games without turning off
2203 case H_GETTING_MOVES:
2204 /* Should not happen */
2205 DisplayError("Error gathering move list: nested", 0);
2206 ics_getting_history = H_FALSE;
2208 case H_GOT_REQ_HEADER:
2209 ics_getting_history = H_GETTING_MOVES;
2210 started = STARTED_MOVES;
2212 if (oldi > next_out) {
2213 SendToPlayer(&buf[next_out], oldi - next_out);
2216 case H_GOT_UNREQ_HEADER:
2217 ics_getting_history = H_GETTING_MOVES;
2218 started = STARTED_MOVES_NOHIDE;
2221 case H_GOT_UNWANTED_HEADER:
2222 ics_getting_history = H_FALSE;
2228 if (looking_at(buf, &i, "% ") ||
2229 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
2230 && looking_at(buf, &i, "}*"))) {
2231 savingComment = FALSE;
2234 case STARTED_MOVES_NOHIDE:
2235 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
2236 parse[parse_pos + i - oldi] = NULLCHAR;
2237 ParseGameHistory(parse);
2239 if (appData.zippyPlay && first.initDone) {
2240 FeedMovesToProgram(&first, forwardMostMove);
2241 if (gameMode == IcsPlayingWhite) {
2242 if (WhiteOnMove(forwardMostMove)) {
2243 if (first.sendTime) {
2244 if (first.useColors) {
2245 SendToProgram("black\n", &first);
2247 SendTimeRemaining(&first, TRUE);
2249 if (first.useColors) {
2250 SendToProgram("white\ngo\n", &first);
2252 SendToProgram("go\n", &first);
2254 first.maybeThinking = TRUE;
2256 if (first.usePlayother) {
2257 if (first.sendTime) {
2258 SendTimeRemaining(&first, TRUE);
2260 SendToProgram("playother\n", &first);
2266 } else if (gameMode == IcsPlayingBlack) {
2267 if (!WhiteOnMove(forwardMostMove)) {
2268 if (first.sendTime) {
2269 if (first.useColors) {
2270 SendToProgram("white\n", &first);
2272 SendTimeRemaining(&first, FALSE);
2274 if (first.useColors) {
2275 SendToProgram("black\ngo\n", &first);
2277 SendToProgram("go\n", &first);
2279 first.maybeThinking = TRUE;
2281 if (first.usePlayother) {
2282 if (first.sendTime) {
2283 SendTimeRemaining(&first, FALSE);
2285 SendToProgram("playother\n", &first);
2294 if (gameMode == IcsObserving && ics_gamenum == -1) {
2295 /* Moves came from oldmoves or moves command
2296 while we weren't doing anything else.
2298 currentMove = forwardMostMove;
2299 ClearHighlights();/*!!could figure this out*/
2300 flipView = appData.flipView;
2301 DrawPosition(FALSE, boards[currentMove]);
2302 DisplayBothClocks();
2303 sprintf(str, "%s vs. %s",
2304 gameInfo.white, gameInfo.black);
2308 /* Moves were history of an active game */
2309 if (gameInfo.resultDetails != NULL) {
2310 free(gameInfo.resultDetails);
2311 gameInfo.resultDetails = NULL;
2314 HistorySet(parseList, backwardMostMove,
2315 forwardMostMove, currentMove-1);
2316 DisplayMove(currentMove - 1);
2317 if (started == STARTED_MOVES) next_out = i;
2318 started = STARTED_NONE;
2319 ics_getting_history = H_FALSE;
2322 case STARTED_OBSERVE:
2323 started = STARTED_NONE;
2324 SendToICS(ics_prefix);
2325 SendToICS("refresh\n");
2334 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
2335 started == STARTED_HOLDINGS ||
2336 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
2337 /* Accumulate characters in move list or board */
2338 parse[parse_pos++] = buf[i];
2341 /* Start of game messages. Mostly we detect start of game
2342 when the first board image arrives. On some versions
2343 of the ICS, though, we need to do a "refresh" after starting
2344 to observe in order to get the current board right away. */
2345 if (looking_at(buf, &i, "Adding game * to observation list")) {
2346 started = STARTED_OBSERVE;
2350 /* Handle auto-observe */
2351 if (appData.autoObserve &&
2352 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
2353 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
2355 /* Choose the player that was highlighted, if any. */
2356 if (star_match[0][0] == '\033' ||
2357 star_match[1][0] != '\033') {
2358 player = star_match[0];
2360 player = star_match[2];
2362 sprintf(str, "%sobserve %s\n",
2363 ics_prefix, StripHighlightAndTitle(player));
2366 /* Save ratings from notify string */
2367 strcpy(player1Name, star_match[0]);
2368 player1Rating = string_to_rating(star_match[1]);
2369 strcpy(player2Name, star_match[2]);
2370 player2Rating = string_to_rating(star_match[3]);
2372 if (appData.debugMode)
2374 "Ratings from 'Game notification:' %s %d, %s %d\n",
2375 player1Name, player1Rating,
2376 player2Name, player2Rating);
2381 /* Deal with automatic examine mode after a game,
2382 and with IcsObserving -> IcsExamining transition */
2383 if (looking_at(buf, &i, "Entering examine mode for game *") ||
2384 looking_at(buf, &i, "has made you an examiner of game *")) {
2386 int gamenum = atoi(star_match[0]);
2387 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
2388 gamenum == ics_gamenum) {
2389 /* We were already playing or observing this game;
2390 no need to refetch history */
2391 gameMode = IcsExamining;
2393 pauseExamForwardMostMove = forwardMostMove;
2394 } else if (currentMove < forwardMostMove) {
2395 ForwardInner(forwardMostMove);
2398 /* I don't think this case really can happen */
2399 SendToICS(ics_prefix);
2400 SendToICS("refresh\n");
2405 /* Error messages */
2406 if (ics_user_moved) {
2407 if (looking_at(buf, &i, "Illegal move") ||
2408 looking_at(buf, &i, "Not a legal move") ||
2409 looking_at(buf, &i, "Your king is in check") ||
2410 looking_at(buf, &i, "It isn't your turn") ||
2411 looking_at(buf, &i, "It is not your move")) {
2414 if (forwardMostMove > backwardMostMove) {
2415 currentMove = --forwardMostMove;
2416 DisplayMove(currentMove - 1); /* before DMError */
2417 DisplayMoveError("Illegal move (rejected by ICS)");
2418 DrawPosition(FALSE, boards[currentMove]);
2420 DisplayBothClocks();
2426 if (looking_at(buf, &i, "still have time") ||
2427 looking_at(buf, &i, "not out of time") ||
2428 looking_at(buf, &i, "either player is out of time") ||
2429 looking_at(buf, &i, "has timeseal; checking")) {
2430 /* We must have called his flag a little too soon */
2431 whiteFlag = blackFlag = FALSE;
2435 if (looking_at(buf, &i, "added * seconds to") ||
2436 looking_at(buf, &i, "seconds were added to")) {
2437 /* Update the clocks */
2438 SendToICS(ics_prefix);
2439 SendToICS("refresh\n");
2443 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
2444 ics_clock_paused = TRUE;
2449 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
2450 ics_clock_paused = FALSE;
2455 /* Grab player ratings from the Creating: message.
2456 Note we have to check for the special case when
2457 the ICS inserts things like [white] or [black]. */
2458 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
2459 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
2461 0 player 1 name (not necessarily white)
2463 2 empty, white, or black (IGNORED)
2464 3 player 2 name (not necessarily black)
2467 The names/ratings are sorted out when the game
2468 actually starts (below).
2470 strcpy(player1Name, StripHighlightAndTitle(star_match[0]));
2471 player1Rating = string_to_rating(star_match[1]);
2472 strcpy(player2Name, StripHighlightAndTitle(star_match[3]));
2473 player2Rating = string_to_rating(star_match[4]);
2475 if (appData.debugMode)
2477 "Ratings from 'Creating:' %s %d, %s %d\n",
2478 player1Name, player1Rating,
2479 player2Name, player2Rating);
2484 /* Improved generic start/end-of-game messages */
2485 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
2486 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
2487 /* If tkind == 0: */
2488 /* star_match[0] is the game number */
2489 /* [1] is the white player's name */
2490 /* [2] is the black player's name */
2491 /* For end-of-game: */
2492 /* [3] is the reason for the game end */
2493 /* [4] is a PGN end game-token, preceded by " " */
2494 /* For start-of-game: */
2495 /* [3] begins with "Creating" or "Continuing" */
2496 /* [4] is " *" or empty (don't care). */
2497 int gamenum = atoi(star_match[0]);
2498 char *whitename, *blackname, *why, *endtoken;
2499 ChessMove endtype = (ChessMove) 0;
2502 whitename = star_match[1];
2503 blackname = star_match[2];
2504 why = star_match[3];
2505 endtoken = star_match[4];
2507 whitename = star_match[1];
2508 blackname = star_match[3];
2509 why = star_match[5];
2510 endtoken = star_match[6];
2513 /* Game start messages */
2514 if (strncmp(why, "Creating ", 9) == 0 ||
2515 strncmp(why, "Continuing ", 11) == 0) {
2516 gs_gamenum = gamenum;
2517 strcpy(gs_kind, strchr(why, ' ') + 1);
2519 if (appData.zippyPlay) {
2520 ZippyGameStart(whitename, blackname);
2526 /* Game end messages */
2527 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
2528 ics_gamenum != gamenum) {
2531 while (endtoken[0] == ' ') endtoken++;
2532 switch (endtoken[0]) {
2535 endtype = GameUnfinished;
2538 endtype = BlackWins;
2541 if (endtoken[1] == '/')
2542 endtype = GameIsDrawn;
2544 endtype = WhiteWins;
2547 GameEnds(endtype, why, GE_ICS);
2549 if (appData.zippyPlay && first.initDone) {
2550 ZippyGameEnd(endtype, why);
2551 if (first.pr == NULL) {
2552 /* Start the next process early so that we'll
2553 be ready for the next challenge */
2554 StartChessProgram(&first);
2556 /* Send "new" early, in case this command takes
2557 a long time to finish, so that we'll be ready
2558 for the next challenge. */
2565 if (looking_at(buf, &i, "Removing game * from observation") ||
2566 looking_at(buf, &i, "no longer observing game *") ||
2567 looking_at(buf, &i, "Game * (*) has no examiners")) {
2568 if (gameMode == IcsObserving &&
2569 atoi(star_match[0]) == ics_gamenum)
2574 ics_user_moved = FALSE;
2579 if (looking_at(buf, &i, "no longer examining game *")) {
2580 if (gameMode == IcsExamining &&
2581 atoi(star_match[0]) == ics_gamenum)
2585 ics_user_moved = FALSE;
2590 /* Advance leftover_start past any newlines we find,
2591 so only partial lines can get reparsed */
2592 if (looking_at(buf, &i, "\n")) {
2593 prevColor = curColor;
2594 if (curColor != ColorNormal) {
2595 if (oldi > next_out) {
2596 SendToPlayer(&buf[next_out], oldi - next_out);
2599 Colorize(ColorNormal, FALSE);
2600 curColor = ColorNormal;
2602 if (started == STARTED_BOARD) {
2603 started = STARTED_NONE;
2604 parse[parse_pos] = NULLCHAR;
2605 ParseBoard12(parse);
2608 /* Send premove here */
2609 if (appData.premove) {
2611 if (currentMove == 0 &&
2612 gameMode == IcsPlayingWhite &&
2613 appData.premoveWhite) {
2614 sprintf(str, "%s%s\n", ics_prefix,
2615 appData.premoveWhiteText);
2616 if (appData.debugMode)
2617 fprintf(debugFP, "Sending premove:\n");
2619 } else if (currentMove == 1 &&
2620 gameMode == IcsPlayingBlack &&
2621 appData.premoveBlack) {
2622 sprintf(str, "%s%s\n", ics_prefix,
2623 appData.premoveBlackText);
2624 if (appData.debugMode)
2625 fprintf(debugFP, "Sending premove:\n");
2627 } else if (gotPremove) {
2629 ClearPremoveHighlights();
2630 if (appData.debugMode)
2631 fprintf(debugFP, "Sending premove:\n");
2632 UserMoveEvent(premoveFromX, premoveFromY,
2633 premoveToX, premoveToY,
2638 /* Usually suppress following prompt */
2639 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
2640 if (looking_at(buf, &i, "*% ")) {
2641 savingComment = FALSE;
2645 } else if (started == STARTED_HOLDINGS) {
2647 char new_piece[MSG_SIZ];
2648 started = STARTED_NONE;
2649 parse[parse_pos] = NULLCHAR;
2650 if (appData.debugMode)
2651 fprintf(debugFP, "Parsing holdings: %s\n", parse);
2652 if (sscanf(parse, " game %d", &gamenum) == 1 &&
2653 gamenum == ics_gamenum) {
2654 if (gameInfo.variant == VariantNormal) {
2655 gameInfo.variant = VariantCrazyhouse; /*temp guess*/
2656 /* Get a move list just to see the header, which
2657 will tell us whether this is really bug or zh */
2658 if (ics_getting_history == H_FALSE) {
2659 ics_getting_history = H_REQUESTED;
2660 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
2664 new_piece[0] = NULLCHAR;
2665 sscanf(parse, "game %d white [%s black [%s <- %s",
2666 &gamenum, white_holding, black_holding,
2668 white_holding[strlen(white_holding)-1] = NULLCHAR;
2669 black_holding[strlen(black_holding)-1] = NULLCHAR;
2671 if (appData.zippyPlay && first.initDone) {
2672 ZippyHoldings(white_holding, black_holding,
2676 if (tinyLayout || smallLayout) {
2677 char wh[16], bh[16];
2678 PackHolding(wh, white_holding);
2679 PackHolding(bh, black_holding);
2680 sprintf(str, "[%s-%s] %s-%s", wh, bh,
2681 gameInfo.white, gameInfo.black);
2683 sprintf(str, "%s [%s] vs. %s [%s]",
2684 gameInfo.white, white_holding,
2685 gameInfo.black, black_holding);
2687 DrawPosition(FALSE, NULL);
2690 /* Suppress following prompt */
2691 if (looking_at(buf, &i, "*% ")) {
2692 savingComment = FALSE;
2699 i++; /* skip unparsed character and loop back */
2702 if (started != STARTED_MOVES && started != STARTED_BOARD &&
2703 started != STARTED_HOLDINGS && i > next_out) {
2704 SendToPlayer(&buf[next_out], i - next_out);
2708 leftover_len = buf_len - leftover_start;
2709 /* if buffer ends with something we couldn't parse,
2710 reparse it after appending the next read */
2712 } else if (count == 0) {
2713 RemoveInputSource(isr);
2714 DisplayFatalError("Connection closed by ICS", 0, 0);
2716 DisplayFatalError("Error reading from ICS", error, 1);
2721 /* Board style 12 looks like this:
2723 <12> r-b---k- pp----pp ---bP--- ---p---- q------- ------P- P--Q--BP -----R-K W -1 0 0 0 0 0 0 paf MaxII 0 2 12 21 25 234 174 24 Q/d7-a4 (0:06) Qxa4 0 0
2725 * The "<12> " is stripped before it gets to this routine. The two
2726 * trailing 0's (flip state and clock ticking) are later addition, and
2727 * some chess servers may not have them, or may have only the first.
2728 * Additional trailing fields may be added in the future.
2731 #define PATTERN "%72c%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
2733 #define RELATION_OBSERVING_PLAYED 0
2734 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
2735 #define RELATION_PLAYING_MYMOVE 1
2736 #define RELATION_PLAYING_NOTMYMOVE -1
2737 #define RELATION_EXAMINING 2
2738 #define RELATION_ISOLATED_BOARD -3
2739 #define RELATION_STARTING_POSITION -4 /* FICS only */
2742 ParseBoard12(string)
2745 GameMode newGameMode;
2746 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0;
2747 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time;
2748 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
2749 char to_play, board_chars[72];
2750 char move_str[500], str[500], elapsed_time[500];
2751 char black[32], white[32];
2753 int prevMove = currentMove;
2756 int fromX, fromY, toX, toY;
2759 fromX = fromY = toX = toY = -1;
2763 if (appData.debugMode)
2764 fprintf(debugFP, "Parsing board: %s\n", string);
2766 move_str[0] = NULLCHAR;
2767 elapsed_time[0] = NULLCHAR;
2768 n = sscanf(string, PATTERN, board_chars, &to_play, &double_push,
2769 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
2770 &gamenum, white, black, &relation, &basetime, &increment,
2771 &white_stren, &black_stren, &white_time, &black_time,
2772 &moveNum, str, elapsed_time, move_str, &ics_flip,
2776 sprintf(str, "Failed to parse board string:\n\"%s\"", string);
2777 DisplayError(str, 0);
2781 /* Convert the move number to internal form */
2782 moveNum = (moveNum - 1) * 2;
2783 if (to_play == 'B') moveNum++;
2784 if (moveNum >= MAX_MOVES) {
2785 DisplayFatalError("Game too long; increase MAX_MOVES and recompile",
2791 case RELATION_OBSERVING_PLAYED:
2792 case RELATION_OBSERVING_STATIC:
2793 if (gamenum == -1) {
2794 /* Old ICC buglet */
2795 relation = RELATION_OBSERVING_STATIC;
2797 newGameMode = IcsObserving;
2799 case RELATION_PLAYING_MYMOVE:
2800 case RELATION_PLAYING_NOTMYMOVE:
2802 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
2803 IcsPlayingWhite : IcsPlayingBlack;
2805 case RELATION_EXAMINING:
2806 newGameMode = IcsExamining;
2808 case RELATION_ISOLATED_BOARD:
2810 /* Just display this board. If user was doing something else,
2811 we will forget about it until the next board comes. */
2812 newGameMode = IcsIdle;
2814 case RELATION_STARTING_POSITION:
2815 newGameMode = gameMode;
2819 /* Modify behavior for initial board display on move listing
2822 switch (ics_getting_history) {
2826 case H_GOT_REQ_HEADER:
2827 case H_GOT_UNREQ_HEADER:
2828 /* This is the initial position of the current game */
2829 gamenum = ics_gamenum;
2830 moveNum = 0; /* old ICS bug workaround */
2831 if (to_play == 'B') {
2832 startedFromSetupPosition = TRUE;
2833 blackPlaysFirst = TRUE;
2835 if (forwardMostMove == 0) forwardMostMove = 1;
2836 if (backwardMostMove == 0) backwardMostMove = 1;
2837 if (currentMove == 0) currentMove = 1;
2839 newGameMode = gameMode;
2840 relation = RELATION_STARTING_POSITION; /* ICC needs this */
2842 case H_GOT_UNWANTED_HEADER:
2843 /* This is an initial board that we don't want */
2845 case H_GETTING_MOVES:
2846 /* Should not happen */
2847 DisplayError("Error gathering move list: extra board", 0);
2848 ics_getting_history = H_FALSE;
2852 /* Take action if this is the first board of a new game, or of a
2853 different game than is currently being displayed. */
2854 if (gamenum != ics_gamenum || newGameMode != gameMode ||
2855 relation == RELATION_ISOLATED_BOARD) {
2857 /* Forget the old game and get the history (if any) of the new one */
2858 if (gameMode != BeginningOfGame) {
2862 if (appData.autoRaiseBoard) BoardToTop();
2864 if (gamenum == -1) {
2865 newGameMode = IcsIdle;
2866 } else if (moveNum > 0 && newGameMode != IcsIdle &&
2867 appData.getMoveList) {
2868 /* Need to get game history */
2869 ics_getting_history = H_REQUESTED;
2870 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
2874 /* Initially flip the board to have black on the bottom if playing
2875 black or if the ICS flip flag is set, but let the user change
2876 it with the Flip View button. */
2877 flipView = appData.autoFlipView ?
2878 (newGameMode == IcsPlayingBlack) || ics_flip :
2881 /* Done with values from previous mode; copy in new ones */
2882 gameMode = newGameMode;
2884 ics_gamenum = gamenum;
2885 if (gamenum == gs_gamenum) {
2886 int klen = strlen(gs_kind);
2887 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
2888 sprintf(str, "ICS %s", gs_kind);
2889 gameInfo.event = StrSave(str);
2891 gameInfo.event = StrSave("ICS game");
2893 gameInfo.site = StrSave(appData.icsHost);
2894 gameInfo.date = PGNDate();
2895 gameInfo.round = StrSave("-");
2896 gameInfo.white = StrSave(white);
2897 gameInfo.black = StrSave(black);
2898 timeControl = basetime * 60 * 1000;
2900 timeIncrement = increment * 1000;
2901 movesPerSession = 0;
2902 gameInfo.timeControl = TimeControlTagValue();
2903 gameInfo.variant = StringToVariant(gameInfo.event);
2904 gameInfo.outOfBook = NULL;
2906 /* Do we have the ratings? */
2907 if (strcmp(player1Name, white) == 0 &&
2908 strcmp(player2Name, black) == 0) {
2909 if (appData.debugMode)
2910 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
2911 player1Rating, player2Rating);
2912 gameInfo.whiteRating = player1Rating;
2913 gameInfo.blackRating = player2Rating;
2914 } else if (strcmp(player2Name, white) == 0 &&
2915 strcmp(player1Name, black) == 0) {
2916 if (appData.debugMode)
2917 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
2918 player2Rating, player1Rating);
2919 gameInfo.whiteRating = player2Rating;
2920 gameInfo.blackRating = player1Rating;
2922 player1Name[0] = player2Name[0] = NULLCHAR;
2924 /* Silence shouts if requested */
2925 if (appData.quietPlay &&
2926 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
2927 SendToICS(ics_prefix);
2928 SendToICS("set shout 0\n");
2932 /* Deal with midgame name changes */
2934 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
2935 if (gameInfo.white) free(gameInfo.white);
2936 gameInfo.white = StrSave(white);
2938 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
2939 if (gameInfo.black) free(gameInfo.black);
2940 gameInfo.black = StrSave(black);
2944 /* Throw away game result if anything actually changes in examine mode */
2945 if (gameMode == IcsExamining && !newGame) {
2946 gameInfo.result = GameUnfinished;
2947 if (gameInfo.resultDetails != NULL) {
2948 free(gameInfo.resultDetails);
2949 gameInfo.resultDetails = NULL;
2953 /* In pausing && IcsExamining mode, we ignore boards coming
2954 in if they are in a different variation than we are. */
2955 if (pauseExamInvalid) return;
2956 if (pausing && gameMode == IcsExamining) {
2957 if (moveNum <= pauseExamForwardMostMove) {
2958 pauseExamInvalid = TRUE;
2959 forwardMostMove = pauseExamForwardMostMove;
2964 /* Parse the board */
2965 for (k = 0; k < 8; k++)
2966 for (j = 0; j < 8; j++)
2967 board[k][j] = CharToPiece(board_chars[(7-k)*9 + j]);
2968 CopyBoard(boards[moveNum], board);
2970 startedFromSetupPosition =
2971 !CompareBoards(board, initialPosition);
2974 if (ics_getting_history == H_GOT_REQ_HEADER ||
2975 ics_getting_history == H_GOT_UNREQ_HEADER) {
2976 /* This was an initial position from a move list, not
2977 the current position */
2981 /* Update currentMove and known move number limits */
2982 newMove = newGame || moveNum > forwardMostMove;
2984 forwardMostMove = backwardMostMove = currentMove = moveNum;
2985 if (gameMode == IcsExamining && moveNum == 0) {
2986 /* Workaround for ICS limitation: we are not told the wild
2987 type when starting to examine a game. But if we ask for
2988 the move list, the move list header will tell us */
2989 ics_getting_history = H_REQUESTED;
2990 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
2993 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
2994 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
2995 forwardMostMove = moveNum;
2996 if (!pausing || currentMove > forwardMostMove)
2997 currentMove = forwardMostMove;
2999 /* New part of history that is not contiguous with old part */
3000 if (pausing && gameMode == IcsExamining) {
3001 pauseExamInvalid = TRUE;
3002 forwardMostMove = pauseExamForwardMostMove;
3005 forwardMostMove = backwardMostMove = currentMove = moveNum;
3006 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
3007 ics_getting_history = H_REQUESTED;
3008 sprintf(str, "%smoves %d\n", ics_prefix, gamenum);
3013 /* Update the clocks */
3014 if (strchr(elapsed_time, '.')) {
3016 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
3017 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
3019 /* Time is in seconds */
3020 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
3021 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
3026 if (appData.zippyPlay && newGame &&
3027 gameMode != IcsObserving && gameMode != IcsIdle &&
3028 gameMode != IcsExamining)
3029 ZippyFirstBoard(moveNum, basetime, increment);
3032 /* Put the move on the move list, first converting
3033 to canonical algebraic form. */
3035 if (moveNum <= backwardMostMove) {
3036 /* We don't know what the board looked like before
3038 strcpy(parseList[moveNum - 1], move_str);
3039 strcat(parseList[moveNum - 1], " ");
3040 strcat(parseList[moveNum - 1], elapsed_time);
3041 moveList[moveNum - 1][0] = NULLCHAR;
3042 } else if (ParseOneMove(move_str, moveNum - 1, &moveType,
3043 &fromX, &fromY, &toX, &toY, &promoChar)) {
3044 (void) CoordsToAlgebraic(boards[moveNum - 1],
3045 PosFlags(moveNum - 1), EP_UNKNOWN,
3046 fromY, fromX, toY, toX, promoChar,
3047 parseList[moveNum-1]);
3048 switch (MateTest(boards[moveNum], PosFlags(moveNum), EP_UNKNOWN)){
3054 strcat(parseList[moveNum - 1], "+");
3057 strcat(parseList[moveNum - 1], "#");
3060 strcat(parseList[moveNum - 1], " ");
3061 strcat(parseList[moveNum - 1], elapsed_time);
3062 /* currentMoveString is set as a side-effect of ParseOneMove */
3063 strcpy(moveList[moveNum - 1], currentMoveString);
3064 strcat(moveList[moveNum - 1], "\n");
3065 } else if (strcmp(move_str, "none") == 0) {
3066 /* Again, we don't know what the board looked like;
3067 this is really the start of the game. */
3068 parseList[moveNum - 1][0] = NULLCHAR;
3069 moveList[moveNum - 1][0] = NULLCHAR;
3070 backwardMostMove = moveNum;
3071 startedFromSetupPosition = TRUE;
3072 fromX = fromY = toX = toY = -1;
3074 /* Move from ICS was illegal!? Punt. */
3076 if (appData.testLegality && appData.debugMode) {
3077 sprintf(str, "Illegal move \"%s\" from ICS", move_str);
3078 DisplayError(str, 0);
3081 strcpy(parseList[moveNum - 1], move_str);
3082 strcat(parseList[moveNum - 1], " ");
3083 strcat(parseList[moveNum - 1], elapsed_time);
3084 moveList[moveNum - 1][0] = NULLCHAR;
3085 fromX = fromY = toX = toY = -1;
3089 /* Send move to chess program (BEFORE animating it). */
3090 if (appData.zippyPlay && !newGame && newMove &&
3091 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
3093 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
3094 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
3095 if (moveList[moveNum - 1][0] == NULLCHAR) {
3096 sprintf(str, "Couldn't parse move \"%s\" from ICS",
3098 DisplayError(str, 0);
3100 if (first.sendTime) {
3101 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
3103 SendMoveToProgram(moveNum - 1, &first);
3106 if (first.useColors) {
3107 SendToProgram(gameMode == IcsPlayingWhite ?
3109 "black\ngo\n", &first);
3111 SendToProgram("go\n", &first);
3113 first.maybeThinking = TRUE;
3116 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
3117 if (moveList[moveNum - 1][0] == NULLCHAR) {
3118 sprintf(str, "Couldn't parse move \"%s\" from ICS", move_str);
3119 DisplayError(str, 0);
3121 SendMoveToProgram(moveNum - 1, &first);
3128 if (moveNum > 0 && !gotPremove) {
3129 /* If move comes from a remote source, animate it. If it
3130 isn't remote, it will have already been animated. */
3131 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
3132 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
3134 if (!pausing && appData.highlightLastMove) {
3135 SetHighlights(fromX, fromY, toX, toY);
3139 /* Start the clocks */
3140 whiteFlag = blackFlag = FALSE;
3141 appData.clockMode = !(basetime == 0 && increment == 0);
3143 ics_clock_paused = TRUE;
3145 } else if (ticking == 1) {
3146 ics_clock_paused = FALSE;
3148 if (gameMode == IcsIdle ||
3149 relation == RELATION_OBSERVING_STATIC ||
3150 relation == RELATION_EXAMINING ||
3152 DisplayBothClocks();
3156 /* Display opponents and material strengths */
3157 if (gameInfo.variant != VariantBughouse &&
3158 gameInfo.variant != VariantCrazyhouse) {
3159 if (tinyLayout || smallLayout) {
3160 sprintf(str, "%s(%d) %s(%d) {%d %d}",
3161 gameInfo.white, white_stren, gameInfo.black, black_stren,
3162 basetime, increment);
3164 sprintf(str, "%s (%d) vs. %s (%d) {%d %d}",
3165 gameInfo.white, white_stren, gameInfo.black, black_stren,
3166 basetime, increment);
3172 /* Display the board */
3175 if (appData.premove)
3177 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
3178 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
3179 ClearPremoveHighlights();
3181 DrawPosition(FALSE, boards[currentMove]);
3182 DisplayMove(moveNum - 1);
3183 if (appData.ringBellAfterMoves && !ics_user_moved)
3187 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
3194 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
3195 ics_getting_history = H_REQUESTED;
3196 sprintf(buf, "%smoves %d\n", ics_prefix, ics_gamenum);
3202 AnalysisPeriodicEvent(force)
3205 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
3206 && !force) || !appData.periodicUpdates)
3209 /* Send . command to Crafty to collect stats */
3210 SendToProgram(".\n", &first);
3212 /* Don't send another until we get a response (this makes
3213 us stop sending to old Crafty's which don't understand
3214 the "." command (sending illegal cmds resets node count & time,
3215 which looks bad)) */
3216 programStats.ok_to_send = 0;
3220 SendMoveToProgram(moveNum, cps)
3222 ChessProgramState *cps;
3225 if (cps->useUsermove) {
3226 SendToProgram("usermove ", cps);
3230 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
3231 int len = space - parseList[moveNum];
3232 memcpy(buf, parseList[moveNum], len);
3234 buf[len] = NULLCHAR;
3236 sprintf(buf, "%s\n", parseList[moveNum]);
3238 SendToProgram(buf, cps);
3240 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
3241 * the engine. It would be nice to have a better way to identify castle
3243 if(gameInfo.variant == VariantFischeRandom && cps->useOOCastle) {
3244 int fromX = moveList[moveNum][0] - 'a';
3245 int fromY = moveList[moveNum][1] - '1';
3246 int toX = moveList[moveNum][2] - 'a';
3247 int toY = moveList[moveNum][3] - '1';
3248 if((boards[currentMove][fromY][fromX] == WhiteKing
3249 && boards[currentMove][toY][toX] == WhiteRook)
3250 || (boards[currentMove][fromY][fromX] == BlackKing
3251 && boards[currentMove][toY][toX] == BlackRook)) {
3252 if(toX > fromX) SendToProgram("O-O\n", cps);
3253 else SendToProgram("O-O-O\n", cps);
3255 else SendToProgram(moveList[moveNum], cps);
3257 else SendToProgram(moveList[moveNum], cps);
3258 /* End of additions by Tord */
3263 SendMoveToICS(moveType, fromX, fromY, toX, toY)
3265 int fromX, fromY, toX, toY;
3267 char user_move[MSG_SIZ];
3271 sprintf(user_move, "say Internal error; bad moveType %d (%d,%d-%d,%d)",
3272 (int)moveType, fromX, fromY, toX, toY);
3273 DisplayError(user_move + strlen("say "), 0);
3275 case WhiteKingSideCastle:
3276 case BlackKingSideCastle:
3277 case WhiteQueenSideCastleWild:
3278 case BlackQueenSideCastleWild:
3280 case WhiteHSideCastleFR:
3281 case BlackHSideCastleFR:
3283 sprintf(user_move, "o-o\n");
3285 case WhiteQueenSideCastle:
3286 case BlackQueenSideCastle:
3287 case WhiteKingSideCastleWild:
3288 case BlackKingSideCastleWild:
3290 case WhiteASideCastleFR:
3291 case BlackASideCastleFR:
3293 sprintf(user_move, "o-o-o\n");
3295 case WhitePromotionQueen:
3296 case BlackPromotionQueen:
3297 case WhitePromotionRook:
3298 case BlackPromotionRook:
3299 case WhitePromotionBishop:
3300 case BlackPromotionBishop:
3301 case WhitePromotionKnight:
3302 case BlackPromotionKnight:
3303 case WhitePromotionKing:
3304 case BlackPromotionKing:
3305 sprintf(user_move, "%c%c%c%c=%c\n",
3306 'a' + fromX, '1' + fromY, 'a' + toX, '1' + toY,
3307 PieceToChar(PromoPiece(moveType)));
3311 sprintf(user_move, "%c@%c%c\n",
3312 ToUpper(PieceToChar((ChessSquare) fromX)),
3313 'a' + toX, '1' + toY);
3316 case WhiteCapturesEnPassant:
3317 case BlackCapturesEnPassant:
3318 case IllegalMove: /* could be a variant we don't quite understand */
3319 sprintf(user_move, "%c%c%c%c\n",
3320 'a' + fromX, '1' + fromY, 'a' + toX, '1' + toY);
3323 SendToICS(user_move);
3327 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
3332 if (rf == DROP_RANK) {
3333 sprintf(move, "%c@%c%c\n",
3334 ToUpper(PieceToChar((ChessSquare) ff)), 'a' + ft, '1' + rt);
3336 if (promoChar == 'x' || promoChar == NULLCHAR) {
3337 sprintf(move, "%c%c%c%c\n",
3338 'a' + ff, '1' + rf, 'a' + ft, '1' + rt);
3340 sprintf(move, "%c%c%c%c%c\n",
3341 'a' + ff, '1' + rf, 'a' + ft, '1' + rt, promoChar);
3347 ProcessICSInitScript(f)
3352 while (fgets(buf, MSG_SIZ, f)) {
3353 SendToICSDelayed(buf,(long)appData.msLoginDelay);
3360 /* Parser for moves from gnuchess, ICS, or user typein box */
3362 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
3365 ChessMove *moveType;
3366 int *fromX, *fromY, *toX, *toY;
3369 *moveType = yylexstr(moveNum, move);
3370 switch (*moveType) {
3371 case WhitePromotionQueen:
3372 case BlackPromotionQueen:
3373 case WhitePromotionRook:
3374 case BlackPromotionRook:
3375 case WhitePromotionBishop:
3376 case BlackPromotionBishop:
3377 case WhitePromotionKnight:
3378 case BlackPromotionKnight:
3379 case WhitePromotionKing:
3380 case BlackPromotionKing:
3382 case WhiteCapturesEnPassant:
3383 case BlackCapturesEnPassant:
3384 case WhiteKingSideCastle:
3385 case WhiteQueenSideCastle:
3386 case BlackKingSideCastle:
3387 case BlackQueenSideCastle:
3388 case WhiteKingSideCastleWild:
3389 case WhiteQueenSideCastleWild:
3390 case BlackKingSideCastleWild:
3391 case BlackQueenSideCastleWild:
3392 /* Code added by Tord: */
3393 case WhiteHSideCastleFR:
3394 case WhiteASideCastleFR:
3395 case BlackHSideCastleFR:
3396 case BlackASideCastleFR:
3397 /* End of code added by Tord */
3398 case IllegalMove: /* bug or odd chess variant */
3399 *fromX = currentMoveString[0] - 'a';
3400 *fromY = currentMoveString[1] - '1';
3401 *toX = currentMoveString[2] - 'a';
3402 *toY = currentMoveString[3] - '1';
3403 *promoChar = currentMoveString[4];
3404 if (*fromX < 0 || *fromX > 7 || *fromY < 0 || *fromY > 7 ||
3405 *toX < 0 || *toX > 7 || *toY < 0 || *toY > 7) {
3406 *fromX = *fromY = *toX = *toY = 0;
3409 if (appData.testLegality) {
3410 return (*moveType != IllegalMove);
3412 return !(fromX == fromY && toX == toY);
3417 *fromX = *moveType == WhiteDrop ?
3418 (int) CharToPiece(ToUpper(currentMoveString[0])) :
3419 (int) CharToPiece(ToLower(currentMoveString[0]));
3421 *toX = currentMoveString[2] - 'a';
3422 *toY = currentMoveString[3] - '1';
3423 *promoChar = NULLCHAR;
3427 case ImpossibleMove:
3428 case (ChessMove) 0: /* end of file */
3438 *fromX = *fromY = *toX = *toY = 0;
3439 *promoChar = NULLCHAR;
3444 /* [AS] FRC game initialization */
3445 static int FindEmptySquare( Board board, int n )
3450 while( board[0][i] != EmptySquare ) i++;
3460 static void ShuffleFRC( Board board )
3466 for( i=0; i<8; i++ ) {
3467 board[0][i] = EmptySquare;
3470 board[0][(rand() % 4)*2 ] = WhiteBishop; /* On dark square */
3471 board[0][(rand() % 4)*2+1] = WhiteBishop; /* On lite square */
3472 board[0][FindEmptySquare(board, rand() % 6)] = WhiteQueen;
3473 board[0][FindEmptySquare(board, rand() % 5)] = WhiteKnight;
3474 board[0][FindEmptySquare(board, rand() % 4)] = WhiteKnight;
3475 board[0][FindEmptySquare(board, 0)] = WhiteRook;
3476 board[0][FindEmptySquare(board, 0)] = WhiteKing;
3477 board[0][FindEmptySquare(board, 0)] = WhiteRook;
3479 for( i=0; i<8; i++ ) {
3480 board[7][i] = board[0][i] + BlackPawn - WhitePawn;
3484 static unsigned char FRC_KnightTable[10] = {
3485 0x00, 0x01, 0x02, 0x03, 0x11, 0x12, 0x13, 0x22, 0x23, 0x33
3488 static void SetupFRC( Board board, int pos_index )
3491 unsigned char knights;
3493 /* Bring the position index into a safe range (just in case...) */
3494 if( pos_index < 0 ) pos_index = 0;
3498 /* Clear the board */
3499 for( i=0; i<8; i++ ) {
3500 board[0][i] = EmptySquare;
3503 /* Place bishops and queen */
3504 board[0][ (pos_index % 4)*2 + 1 ] = WhiteBishop; /* On lite square */
3507 board[0][ (pos_index % 4)*2 ] = WhiteBishop; /* On dark square */
3510 board[0][ FindEmptySquare(board, pos_index % 6) ] = WhiteQueen;
3514 knights = FRC_KnightTable[ pos_index ];
3516 board[0][ FindEmptySquare(board, knights / 16) ] = WhiteKnight;
3517 board[0][ FindEmptySquare(board, knights % 16) ] = WhiteKnight;
3519 /* Place rooks and king */
3520 board[0][ FindEmptySquare(board, 0) ] = WhiteRook;
3521 board[0][ FindEmptySquare(board, 0) ] = WhiteKing;
3522 board[0][ FindEmptySquare(board, 0) ] = WhiteRook;
3524 /* Mirror piece placement for black */
3525 for( i=0; i<8; i++ ) {
3526 board[7][i] = board[0][i] + BlackPawn - WhitePawn;
3531 InitPosition(redraw)
3534 currentMove = forwardMostMove = backwardMostMove = 0;
3536 /* [AS] Initialize pv info list */
3540 for( i=0; i<MAX_MOVES; i++ ) {
3541 pvInfoList[i].depth = 0;
3545 switch (gameInfo.variant) {
3547 CopyBoard(boards[0], initialPosition);
3549 case VariantTwoKings:
3550 CopyBoard(boards[0], twoKingsPosition);
3551 startedFromSetupPosition = TRUE;
3553 case VariantWildCastle:
3554 CopyBoard(boards[0], initialPosition);
3555 /* !!?shuffle with kings guaranteed to be on d or e file */
3557 case VariantNoCastle:
3558 CopyBoard(boards[0], initialPosition);
3559 /* !!?unconstrained back-rank shuffle */
3561 case VariantFischeRandom:
3562 CopyBoard(boards[0], initialPosition);
3563 if( appData.defaultFrcPosition < 0 ) {
3564 ShuffleFRC( boards[0] );
3567 SetupFRC( boards[0], appData.defaultFrcPosition );
3573 DrawPosition(TRUE, boards[currentMove]);
3577 SendBoard(cps, moveNum)
3578 ChessProgramState *cps;
3581 char message[MSG_SIZ];
3583 if (cps->useSetboard) {
3584 char* fen = PositionToFEN(moveNum, cps->useFEN960);
3585 sprintf(message, "setboard %s\n", fen);
3586 SendToProgram(message, cps);
3592 /* Kludge to set black to move, avoiding the troublesome and now
3593 * deprecated "black" command.
3595 if (!WhiteOnMove(moveNum)) SendToProgram("a2a3\n", cps);
3597 SendToProgram("edit\n", cps);
3598 SendToProgram("#\n", cps);
3599 for (i = BOARD_SIZE - 1; i >= 0; i--) {
3600 bp = &boards[moveNum][i][0];
3601 for (j = 0; j < BOARD_SIZE; j++, bp++) {
3602 if ((int) *bp < (int) BlackPawn) {
3603 sprintf(message, "%c%c%c\n", PieceToChar(*bp),
3605 SendToProgram(message, cps);
3610 SendToProgram("c\n", cps);
3611 for (i = BOARD_SIZE - 1; i >= 0; i--) {
3612 bp = &boards[moveNum][i][0];
3613 for (j = 0; j < BOARD_SIZE; j++, bp++) {
3614 if (((int) *bp != (int) EmptySquare)
3615 && ((int) *bp >= (int) BlackPawn)) {
3616 sprintf(message, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
3618 SendToProgram(message, cps);
3623 SendToProgram(".\n", cps);
3628 IsPromotion(fromX, fromY, toX, toY)
3629 int fromX, fromY, toX, toY;
3631 return gameMode != EditPosition &&
3632 fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0 &&
3633 ((boards[currentMove][fromY][fromX] == WhitePawn && toY == 7) ||
3634 (boards[currentMove][fromY][fromX] == BlackPawn && toY == 0));
3639 PieceForSquare (x, y)
3643 if (x < 0 || x >= BOARD_SIZE || y < 0 || y >= BOARD_SIZE)
3646 return boards[currentMove][y][x];
3650 OKToStartUserMove(x, y)
3653 ChessSquare from_piece;
3656 if (matchMode) return FALSE;
3657 if (gameMode == EditPosition) return TRUE;
3659 if (x >= 0 && y >= 0)
3660 from_piece = boards[currentMove][y][x];
3662 from_piece = EmptySquare;
3664 if (from_piece == EmptySquare) return FALSE;
3666 white_piece = (int)from_piece >= (int)WhitePawn &&
3667 (int)from_piece <= (int)WhiteKing;
3670 case PlayFromGameFile:
3672 case TwoMachinesPlay:
3680 case MachinePlaysWhite:
3681 case IcsPlayingBlack:
3682 if (appData.zippyPlay) return FALSE;
3684 DisplayMoveError("You are playing Black");
3689 case MachinePlaysBlack:
3690 case IcsPlayingWhite:
3691 if (appData.zippyPlay) return FALSE;
3693 DisplayMoveError("You are playing White");
3699 if (!white_piece && WhiteOnMove(currentMove)) {
3700 DisplayMoveError("It is White's turn");
3703 if (white_piece && !WhiteOnMove(currentMove)) {
3704 DisplayMoveError("It is Black's turn");
3707 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
3708 /* Editing correspondence game history */
3709 /* Could disallow this or prompt for confirmation */
3712 if (currentMove < forwardMostMove) {
3713 /* Discarding moves */
3714 /* Could prompt for confirmation here,
3715 but I don't think that's such a good idea */
3716 forwardMostMove = currentMove;
3720 case BeginningOfGame:
3721 if (appData.icsActive) return FALSE;
3722 if (!appData.noChessProgram) {
3724 DisplayMoveError("You are playing White");
3731 if (!white_piece && WhiteOnMove(currentMove)) {
3732 DisplayMoveError("It is White's turn");
3735 if (white_piece && !WhiteOnMove(currentMove)) {
3736 DisplayMoveError("It is Black's turn");
3745 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
3746 && gameMode != AnalyzeFile && gameMode != Training) {
3747 DisplayMoveError("Displayed position is not current");
3753 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
3754 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
3755 int lastLoadGameUseList = FALSE;
3756 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
3757 ChessMove lastLoadGameStart = (ChessMove) 0;
3761 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
3762 int fromX, fromY, toX, toY;
3767 if (fromX < 0 || fromY < 0) return;
3768 if ((fromX == toX) && (fromY == toY)) {
3772 /* Check if the user is playing in turn. This is complicated because we
3773 let the user "pick up" a piece before it is his turn. So the piece he
3774 tried to pick up may have been captured by the time he puts it down!
3775 Therefore we use the color the user is supposed to be playing in this
3776 test, not the color of the piece that is currently on the starting
3777 square---except in EditGame mode, where the user is playing both
3778 sides; fortunately there the capture race can't happen. (It can
3779 now happen in IcsExamining mode, but that's just too bad. The user
3780 will get a somewhat confusing message in that case.)
3784 case PlayFromGameFile:
3786 case TwoMachinesPlay:
3790 /* We switched into a game mode where moves are not accepted,
3791 perhaps while the mouse button was down. */
3794 case MachinePlaysWhite:
3795 /* User is moving for Black */
3796 if (WhiteOnMove(currentMove)) {
3797 DisplayMoveError("It is White's turn");
3802 case MachinePlaysBlack:
3803 /* User is moving for White */
3804 if (!WhiteOnMove(currentMove)) {
3805 DisplayMoveError("It is Black's turn");
3812 case BeginningOfGame:
3815 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
3816 (int) boards[currentMove][fromY][fromX] <= (int) BlackKing) {
3817 /* User is moving for Black */
3818 if (WhiteOnMove(currentMove)) {
3819 DisplayMoveError("It is White's turn");
3823 /* User is moving for White */
3824 if (!WhiteOnMove(currentMove)) {
3825 DisplayMoveError("It is Black's turn");
3831 case IcsPlayingBlack:
3832 /* User is moving for Black */
3833 if (WhiteOnMove(currentMove)) {
3834 if (!appData.premove) {
3835 DisplayMoveError("It is White's turn");
3836 } else if (toX >= 0 && toY >= 0) {
3839 premoveFromX = fromX;
3840 premoveFromY = fromY;
3841 premovePromoChar = promoChar;
3843 if (appData.debugMode)
3844 fprintf(debugFP, "Got premove: fromX %d,"
3845 "fromY %d, toX %d, toY %d\n",
3846 fromX, fromY, toX, toY);
3852 case IcsPlayingWhite:
3853 /* User is moving for White */
3854 if (!WhiteOnMove(currentMove)) {
3855 if (!appData.premove) {
3856 DisplayMoveError("It is Black's turn");
3857 } else if (toX >= 0 && toY >= 0) {
3860 premoveFromX = fromX;
3861 premoveFromY = fromY;
3862 premovePromoChar = promoChar;
3864 if (appData.debugMode)
3865 fprintf(debugFP, "Got premove: fromX %d,"
3866 "fromY %d, toX %d, toY %d\n",
3867 fromX, fromY, toX, toY);
3877 if (toX == -2 || toY == -2) {
3878 boards[0][fromY][fromX] = EmptySquare;
3879 DrawPosition(FALSE, boards[currentMove]);
3880 } else if (toX >= 0 && toY >= 0) {
3881 boards[0][toY][toX] = boards[0][fromY][fromX];
3882 boards[0][fromY][fromX] = EmptySquare;
3883 DrawPosition(FALSE, boards[currentMove]);
3888 if (toX < 0 || toY < 0) return;
3889 userOfferedDraw = FALSE;
3891 if (appData.testLegality) {
3892 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
3893 EP_UNKNOWN, fromY, fromX, toY, toX, promoChar);
3894 if (moveType == IllegalMove || moveType == ImpossibleMove) {
3895 DisplayMoveError("Illegal move");
3899 moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
3902 if (gameMode == Training) {
3903 /* compare the move played on the board to the next move in the
3904 * game. If they match, display the move and the opponent's response.
3905 * If they don't match, display an error message.
3909 CopyBoard(testBoard, boards[currentMove]);
3910 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
3912 if (CompareBoards(testBoard, boards[currentMove+1])) {
3913 ForwardInner(currentMove+1);
3915 /* Autoplay the opponent's response.
3916 * if appData.animate was TRUE when Training mode was entered,
3917 * the response will be animated.
3919 saveAnimate = appData.animate;
3920 appData.animate = animateTraining;
3921 ForwardInner(currentMove+1);
3922 appData.animate = saveAnimate;
3924 /* check for the end of the game */
3925 if (currentMove >= forwardMostMove) {
3926 gameMode = PlayFromGameFile;
3928 SetTrainingModeOff();