2 * backend.c -- Common back end for X and Windows NT versions of
4 * Copyright 1991 by Digital Equipment Corporation, Maynard,
7 * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8 * 2007, 2008, 2009, 2010, 2011 Free Software Foundation, Inc.
10 * Enhancements Copyright 2005 Alessandro Scotti
12 * The following terms apply to Digital Equipment Corporation's copyright
14 * ------------------------------------------------------------------------
17 * Permission to use, copy, modify, and distribute this software and its
18 * documentation for any purpose and without fee is hereby granted,
19 * provided that the above copyright notice appear in all copies and that
20 * both that copyright notice and this permission notice appear in
21 * supporting documentation, and that the name of Digital not be
22 * used in advertising or publicity pertaining to distribution of the
23 * software without specific, written prior permission.
25 * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27 * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
32 * ------------------------------------------------------------------------
34 * The following terms apply to the enhanced version of XBoard
35 * distributed by the Free Software Foundation:
36 * ------------------------------------------------------------------------
38 * GNU XBoard is free software: you can redistribute it and/or modify
39 * it under the terms of the GNU General Public License as published by
40 * the Free Software Foundation, either version 3 of the License, or (at
41 * your option) any later version.
43 * GNU XBoard is distributed in the hope that it will be useful, but
44 * WITHOUT ANY WARRANTY; without even the implied warranty of
45 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46 * General Public License for more details.
48 * You should have received a copy of the GNU General Public License
49 * along with this program. If not, see http://www.gnu.org/licenses/. *
51 *------------------------------------------------------------------------
52 ** See the file ChangeLog for a revision history. */
54 /* [AS] Also useful here for debugging */
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
60 int flock(int f, int code);
66 #define DoSleep( n ) if( (n) >= 0) sleep(n)
77 #include <sys/types.h>
86 #else /* not STDC_HEADERS */
89 # else /* not HAVE_STRING_H */
91 # endif /* not HAVE_STRING_H */
92 #endif /* not STDC_HEADERS */
95 # include <sys/fcntl.h>
96 #else /* not HAVE_SYS_FCNTL_H */
99 # endif /* HAVE_FCNTL_H */
100 #endif /* not HAVE_SYS_FCNTL_H */
102 #if TIME_WITH_SYS_TIME
103 # include <sys/time.h>
107 # include <sys/time.h>
113 #if defined(_amigados) && !defined(__GNUC__)
118 extern int gettimeofday(struct timeval *, struct timezone *);
126 #include "frontend.h"
133 #include "backendz.h"
137 # define _(s) gettext (s)
138 # define N_(s) gettext_noop (s)
139 # define T_(s) gettext(s)
152 /* A point in time */
154 long sec; /* Assuming this is >= 32 bits */
155 int ms; /* Assuming this is >= 16 bits */
158 int establish P((void));
159 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
160 char *buf, int count, int error));
161 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
162 char *buf, int count, int error));
163 void ics_printf P((char *format, ...));
164 void SendToICS P((char *s));
165 void SendToICSDelayed P((char *s, long msdelay));
166 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
167 void HandleMachineMove P((char *message, ChessProgramState *cps));
168 int AutoPlayOneMove P((void));
169 int LoadGameOneMove P((ChessMove readAhead));
170 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
171 int LoadPositionFromFile P((char *filename, int n, char *title));
172 int SavePositionToFile P((char *filename));
173 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
175 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
176 void ShowMove P((int fromX, int fromY, int toX, int toY));
177 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
178 /*char*/int promoChar));
179 void BackwardInner P((int target));
180 void ForwardInner P((int target));
181 int Adjudicate P((ChessProgramState *cps));
182 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
183 void EditPositionDone P((Boolean fakeRights));
184 void PrintOpponents P((FILE *fp));
185 void PrintPosition P((FILE *fp, int move));
186 void StartChessProgram P((ChessProgramState *cps));
187 void SendToProgram P((char *message, ChessProgramState *cps));
188 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
189 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
190 char *buf, int count, int error));
191 void SendTimeControl P((ChessProgramState *cps,
192 int mps, long tc, int inc, int sd, int st));
193 char *TimeControlTagValue P((void));
194 void Attention P((ChessProgramState *cps));
195 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
196 int ResurrectChessProgram P((void));
197 void DisplayComment P((int moveNumber, char *text));
198 void DisplayMove P((int moveNumber));
200 void ParseGameHistory P((char *game));
201 void ParseBoard12 P((char *string));
202 void KeepAlive P((void));
203 void StartClocks P((void));
204 void SwitchClocks P((int nr));
205 void StopClocks P((void));
206 void ResetClocks P((void));
207 char *PGNDate P((void));
208 void SetGameInfo P((void));
209 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
210 int RegisterMove P((void));
211 void MakeRegisteredMove P((void));
212 void TruncateGame P((void));
213 int looking_at P((char *, int *, char *));
214 void CopyPlayerNameIntoFileName P((char **, char *));
215 char *SavePart P((char *));
216 int SaveGameOldStyle P((FILE *));
217 int SaveGamePGN P((FILE *));
218 void GetTimeMark P((TimeMark *));
219 long SubtractTimeMarks P((TimeMark *, TimeMark *));
220 int CheckFlags P((void));
221 long NextTickLength P((long));
222 void CheckTimeControl P((void));
223 void show_bytes P((FILE *, char *, int));
224 int string_to_rating P((char *str));
225 void ParseFeatures P((char* args, ChessProgramState *cps));
226 void InitBackEnd3 P((void));
227 void FeatureDone P((ChessProgramState* cps, int val));
228 void InitChessProgram P((ChessProgramState *cps, int setup));
229 void OutputKibitz(int window, char *text);
230 int PerpetualChase(int first, int last);
231 int EngineOutputIsUp();
232 void InitDrawingSizes(int x, int y);
233 void NextMatchGame P((void));
234 int NextTourneyGame P((int nr, int *swap));
235 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
238 extern void ConsoleCreate();
241 ChessProgramState *WhitePlayer();
242 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
243 int VerifyDisplayMode P(());
245 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
246 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
247 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
248 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
249 void ics_update_width P((int new_width));
250 extern char installDir[MSG_SIZ];
251 VariantClass startVariant; /* [HGM] nicks: initial variant */
254 extern int tinyLayout, smallLayout;
255 ChessProgramStats programStats;
256 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
258 static int exiting = 0; /* [HGM] moved to top */
259 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
260 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
261 Board partnerBoard; /* [HGM] bughouse: for peeking at partner game */
262 int partnerHighlight[2];
263 Boolean partnerBoardValid = 0;
264 char partnerStatus[MSG_SIZ];
266 Boolean originalFlip;
267 Boolean twoBoards = 0;
268 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
269 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
270 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
271 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
272 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
273 int opponentKibitzes;
274 int lastSavedGame; /* [HGM] save: ID of game */
275 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
276 extern int chatCount;
278 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
279 char lastMsg[MSG_SIZ];
280 ChessSquare pieceSweep = EmptySquare;
281 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
282 int promoDefaultAltered;
284 /* States for ics_getting_history */
286 #define H_REQUESTED 1
287 #define H_GOT_REQ_HEADER 2
288 #define H_GOT_UNREQ_HEADER 3
289 #define H_GETTING_MOVES 4
290 #define H_GOT_UNWANTED_HEADER 5
292 /* whosays values for GameEnds */
301 /* Maximum number of games in a cmail message */
302 #define CMAIL_MAX_GAMES 20
304 /* Different types of move when calling RegisterMove */
306 #define CMAIL_RESIGN 1
308 #define CMAIL_ACCEPT 3
310 /* Different types of result to remember for each game */
311 #define CMAIL_NOT_RESULT 0
312 #define CMAIL_OLD_RESULT 1
313 #define CMAIL_NEW_RESULT 2
315 /* Telnet protocol constants */
326 safeStrCpy( char *dst, const char *src, size_t count )
329 assert( dst != NULL );
330 assert( src != NULL );
333 for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
334 if( i == count && dst[count-1] != NULLCHAR)
336 dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
337 if(appData.debugMode)
338 fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
344 /* Some compiler can't cast u64 to double
345 * This function do the job for us:
347 * We use the highest bit for cast, this only
348 * works if the highest bit is not
349 * in use (This should not happen)
351 * We used this for all compiler
354 u64ToDouble(u64 value)
357 u64 tmp = value & u64Const(0x7fffffffffffffff);
358 r = (double)(s64)tmp;
359 if (value & u64Const(0x8000000000000000))
360 r += 9.2233720368547758080e18; /* 2^63 */
364 /* Fake up flags for now, as we aren't keeping track of castling
365 availability yet. [HGM] Change of logic: the flag now only
366 indicates the type of castlings allowed by the rule of the game.
367 The actual rights themselves are maintained in the array
368 castlingRights, as part of the game history, and are not probed
374 int flags = F_ALL_CASTLE_OK;
375 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
376 switch (gameInfo.variant) {
378 flags &= ~F_ALL_CASTLE_OK;
379 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
380 flags |= F_IGNORE_CHECK;
382 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
385 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
387 case VariantKriegspiel:
388 flags |= F_KRIEGSPIEL_CAPTURE;
390 case VariantCapaRandom:
391 case VariantFischeRandom:
392 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
393 case VariantNoCastle:
394 case VariantShatranj:
397 flags &= ~F_ALL_CASTLE_OK;
405 FILE *gameFileFP, *debugFP;
408 [AS] Note: sometimes, the sscanf() function is used to parse the input
409 into a fixed-size buffer. Because of this, we must be prepared to
410 receive strings as long as the size of the input buffer, which is currently
411 set to 4K for Windows and 8K for the rest.
412 So, we must either allocate sufficiently large buffers here, or
413 reduce the size of the input buffer in the input reading part.
416 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
417 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
418 char thinkOutput1[MSG_SIZ*10];
420 ChessProgramState first, second;
422 /* premove variables */
425 int premoveFromX = 0;
426 int premoveFromY = 0;
427 int premovePromoChar = 0;
429 Boolean alarmSounded;
430 /* end premove variables */
432 char *ics_prefix = "$";
433 int ics_type = ICS_GENERIC;
435 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
436 int pauseExamForwardMostMove = 0;
437 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
438 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
439 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
440 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
441 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
442 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
443 int whiteFlag = FALSE, blackFlag = FALSE;
444 int userOfferedDraw = FALSE;
445 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
446 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
447 int cmailMoveType[CMAIL_MAX_GAMES];
448 long ics_clock_paused = 0;
449 ProcRef icsPR = NoProc, cmailPR = NoProc;
450 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
451 GameMode gameMode = BeginningOfGame;
452 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
453 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
454 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
455 int hiddenThinkOutputState = 0; /* [AS] */
456 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
457 int adjudicateLossPlies = 6;
458 char white_holding[64], black_holding[64];
459 TimeMark lastNodeCountTime;
460 long lastNodeCount=0;
461 int shiftKey; // [HGM] set by mouse handler
463 int have_sent_ICS_logon = 0;
465 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
466 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
467 long timeControl_2; /* [AS] Allow separate time controls */
468 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
469 long timeRemaining[2][MAX_MOVES];
470 int matchGame = 0, nextGame = 0, roundNr = 0;
471 Boolean waitingForGame = FALSE;
472 TimeMark programStartTime, pauseStart;
473 char ics_handle[MSG_SIZ];
474 int have_set_title = 0;
476 /* animateTraining preserves the state of appData.animate
477 * when Training mode is activated. This allows the
478 * response to be animated when appData.animate == TRUE and
479 * appData.animateDragging == TRUE.
481 Boolean animateTraining;
487 Board boards[MAX_MOVES];
488 /* [HGM] Following 7 needed for accurate legality tests: */
489 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
490 signed char initialRights[BOARD_FILES];
491 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
492 int initialRulePlies, FENrulePlies;
493 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
496 int mute; // mute all sounds
498 // [HGM] vari: next 12 to save and restore variations
499 #define MAX_VARIATIONS 10
500 int framePtr = MAX_MOVES-1; // points to free stack entry
502 int savedFirst[MAX_VARIATIONS];
503 int savedLast[MAX_VARIATIONS];
504 int savedFramePtr[MAX_VARIATIONS];
505 char *savedDetails[MAX_VARIATIONS];
506 ChessMove savedResult[MAX_VARIATIONS];
508 void PushTail P((int firstMove, int lastMove));
509 Boolean PopTail P((Boolean annotate));
510 void PushInner P((int firstMove, int lastMove));
511 void PopInner P((Boolean annotate));
512 void CleanupTail P((void));
514 ChessSquare FIDEArray[2][BOARD_FILES] = {
515 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
516 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
517 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
518 BlackKing, BlackBishop, BlackKnight, BlackRook }
521 ChessSquare twoKingsArray[2][BOARD_FILES] = {
522 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
523 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
524 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
525 BlackKing, BlackKing, BlackKnight, BlackRook }
528 ChessSquare KnightmateArray[2][BOARD_FILES] = {
529 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
530 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
531 { BlackRook, BlackMan, BlackBishop, BlackQueen,
532 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
535 ChessSquare SpartanArray[2][BOARD_FILES] = {
536 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
537 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
538 { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
539 BlackDragon, BlackKing, BlackAngel, BlackAlfil }
542 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
543 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
544 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
545 { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
546 BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
549 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
550 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
551 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
552 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
553 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
556 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
557 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
558 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
559 { BlackRook, BlackKnight, BlackMan, BlackFerz,
560 BlackKing, BlackMan, BlackKnight, BlackRook }
564 #if (BOARD_FILES>=10)
565 ChessSquare ShogiArray[2][BOARD_FILES] = {
566 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
567 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
568 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
569 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
572 ChessSquare XiangqiArray[2][BOARD_FILES] = {
573 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
574 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
575 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
576 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
579 ChessSquare CapablancaArray[2][BOARD_FILES] = {
580 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
581 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
582 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
583 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
586 ChessSquare GreatArray[2][BOARD_FILES] = {
587 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
588 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
589 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
590 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
593 ChessSquare JanusArray[2][BOARD_FILES] = {
594 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
595 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
596 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
597 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
601 ChessSquare GothicArray[2][BOARD_FILES] = {
602 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
603 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
604 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
605 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
608 #define GothicArray CapablancaArray
612 ChessSquare FalconArray[2][BOARD_FILES] = {
613 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
614 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
615 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
616 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
619 #define FalconArray CapablancaArray
622 #else // !(BOARD_FILES>=10)
623 #define XiangqiPosition FIDEArray
624 #define CapablancaArray FIDEArray
625 #define GothicArray FIDEArray
626 #define GreatArray FIDEArray
627 #endif // !(BOARD_FILES>=10)
629 #if (BOARD_FILES>=12)
630 ChessSquare CourierArray[2][BOARD_FILES] = {
631 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
632 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
633 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
634 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
636 #else // !(BOARD_FILES>=12)
637 #define CourierArray CapablancaArray
638 #endif // !(BOARD_FILES>=12)
641 Board initialPosition;
644 /* Convert str to a rating. Checks for special cases of "----",
646 "++++", etc. Also strips ()'s */
648 string_to_rating(str)
651 while(*str && !isdigit(*str)) ++str;
653 return 0; /* One of the special "no rating" cases */
661 /* Init programStats */
662 programStats.movelist[0] = 0;
663 programStats.depth = 0;
664 programStats.nr_moves = 0;
665 programStats.moves_left = 0;
666 programStats.nodes = 0;
667 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
668 programStats.score = 0;
669 programStats.got_only_move = 0;
670 programStats.got_fail = 0;
671 programStats.line_is_book = 0;
676 { // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
677 if (appData.firstPlaysBlack) {
678 first.twoMachinesColor = "black\n";
679 second.twoMachinesColor = "white\n";
681 first.twoMachinesColor = "white\n";
682 second.twoMachinesColor = "black\n";
685 first.other = &second;
686 second.other = &first;
689 if(appData.timeOddsMode) {
690 norm = appData.timeOdds[0];
691 if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
693 first.timeOdds = appData.timeOdds[0]/norm;
694 second.timeOdds = appData.timeOdds[1]/norm;
697 if(programVersion) free(programVersion);
698 if (appData.noChessProgram) {
699 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
700 sprintf(programVersion, "%s", PACKAGE_STRING);
702 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
703 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
704 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
709 UnloadEngine(ChessProgramState *cps)
711 /* Kill off first chess program */
712 if (cps->isr != NULL)
713 RemoveInputSource(cps->isr);
716 if (cps->pr != NoProc) {
718 DoSleep( appData.delayBeforeQuit );
719 SendToProgram("quit\n", cps);
720 DoSleep( appData.delayAfterQuit );
721 DestroyChildProcess(cps->pr, cps->useSigterm);
724 if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
728 ClearOptions(ChessProgramState *cps)
731 cps->nrOptions = cps->comboCnt = 0;
732 for(i=0; i<MAX_OPTIONS; i++) {
733 cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
734 cps->option[i].textValue = 0;
738 char *engineNames[] = {
744 InitEngine(ChessProgramState *cps, int n)
745 { // [HGM] all engine initialiation put in a function that does one engine
749 cps->which = engineNames[n];
750 cps->maybeThinking = FALSE;
754 cps->sendDrawOffers = 1;
756 cps->program = appData.chessProgram[n];
757 cps->host = appData.host[n];
758 cps->dir = appData.directory[n];
759 cps->initString = appData.engInitString[n];
760 cps->computerString = appData.computerString[n];
761 cps->useSigint = TRUE;
762 cps->useSigterm = TRUE;
763 cps->reuse = appData.reuse[n];
764 cps->nps = appData.NPS[n]; // [HGM] nps: copy nodes per second
765 cps->useSetboard = FALSE;
767 cps->usePing = FALSE;
770 cps->usePlayother = FALSE;
771 cps->useColors = TRUE;
772 cps->useUsermove = FALSE;
773 cps->sendICS = FALSE;
774 cps->sendName = appData.icsActive;
775 cps->sdKludge = FALSE;
776 cps->stKludge = FALSE;
777 TidyProgramName(cps->program, cps->host, cps->tidy);
779 safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
780 cps->analysisSupport = 2; /* detect */
781 cps->analyzing = FALSE;
782 cps->initDone = FALSE;
784 /* New features added by Tord: */
785 cps->useFEN960 = FALSE;
786 cps->useOOCastle = TRUE;
787 /* End of new features added by Tord. */
788 cps->fenOverride = appData.fenOverride[n];
790 /* [HGM] time odds: set factor for each machine */
791 cps->timeOdds = appData.timeOdds[n];
793 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
794 cps->accumulateTC = appData.accumulateTC[n];
795 cps->maxNrOfSessions = 1;
799 cps->supportsNPS = UNKNOWN;
802 cps->optionSettings = appData.engOptions[n];
804 cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
805 cps->isUCI = appData.isUCI[n]; /* [AS] */
806 cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
808 if (appData.protocolVersion[n] > PROTOVER
809 || appData.protocolVersion[n] < 1)
814 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
815 appData.protocolVersion[n]);
816 if( (len > MSG_SIZ) && appData.debugMode )
817 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
819 DisplayFatalError(buf, 0, 2);
823 cps->protocolVersion = appData.protocolVersion[n];
826 InitEngineUCI( installDir, cps ); // [HGM] moved here from winboard.c, to make available in xboard
829 ChessProgramState *savCps;
835 if(WaitForEngine(savCps, LoadEngine)) return;
836 CommonEngineInit(); // recalculate time odds
837 if(gameInfo.variant != StringToVariant(appData.variant)) {
838 // we changed variant when loading the engine; this forces us to reset
839 Reset(TRUE, savCps != &first);
840 EditGameEvent(); // for consistency with other path, as Reset changes mode
842 InitChessProgram(savCps, FALSE);
843 SendToProgram("force\n", savCps);
844 DisplayMessage("", "");
845 if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
846 for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
852 ReplaceEngine(ChessProgramState *cps, int n)
856 appData.noChessProgram = FALSE;
857 appData.clockMode = TRUE;
859 if(n) return; // only startup first engine immediately; second can wait
860 savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
864 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
865 extern Boolean isUCI, hasBook, storeVariant, v1, addToList;
868 Load(ChessProgramState *cps, int i)
870 char *p, *q, buf[MSG_SIZ], command[MSG_SIZ];
871 if(engineLine[0]) { // an engine was selected from the combo box
872 snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
873 SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
874 ParseArgsFromString("-firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1");
875 ParseArgsFromString(buf);
877 ReplaceEngine(cps, i);
881 while(q = strchr(p, SLASH)) p = q+1;
882 if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
883 if(engineDir[0] != NULLCHAR)
884 appData.directory[i] = engineDir;
885 else if(p != engineName) { // derive directory from engine path, when not given
887 appData.directory[i] = strdup(engineName);
889 } else appData.directory[i] = ".";
891 snprintf(command, MSG_SIZ, "%s %s", p, params);
894 appData.chessProgram[i] = strdup(p);
895 appData.isUCI[i] = isUCI;
896 appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
897 appData.hasOwnBookUCI[i] = hasBook;
900 q = firstChessProgramNames;
901 if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
902 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "\"%s\" -fd \"%s\"%s%s%s%s%s\n", p, appData.directory[i],
903 v1 ? " -firstProtocolVersion 1" : "",
904 hasBook ? "" : " -fNoOwnBookUCI",
905 isUCI ? " -fUCI" : "",
906 storeVariant ? " -variant " : "",
907 storeVariant ? VariantName(gameInfo.variant) : "");
908 firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
909 snprintf(firstChessProgramNames, len, "%s%s", q, buf);
912 ReplaceEngine(cps, i);
918 int matched, min, sec;
920 * Parse timeControl resource
922 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
923 appData.movesPerSession)) {
925 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
926 DisplayFatalError(buf, 0, 2);
930 * Parse searchTime resource
932 if (*appData.searchTime != NULLCHAR) {
933 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
935 searchTime = min * 60;
936 } else if (matched == 2) {
937 searchTime = min * 60 + sec;
940 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
941 DisplayFatalError(buf, 0, 2);
950 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
951 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
953 GetTimeMark(&programStartTime);
954 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
955 pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
958 programStats.ok_to_send = 1;
959 programStats.seen_stat = 0;
962 * Initialize game list
968 * Internet chess server status
970 if (appData.icsActive) {
971 appData.matchMode = FALSE;
972 appData.matchGames = 0;
974 appData.noChessProgram = !appData.zippyPlay;
976 appData.zippyPlay = FALSE;
977 appData.zippyTalk = FALSE;
978 appData.noChessProgram = TRUE;
980 if (*appData.icsHelper != NULLCHAR) {
981 appData.useTelnet = TRUE;
982 appData.telnetProgram = appData.icsHelper;
985 appData.zippyTalk = appData.zippyPlay = FALSE;
988 /* [AS] Initialize pv info list [HGM] and game state */
992 for( i=0; i<=framePtr; i++ ) {
993 pvInfoList[i].depth = -1;
994 boards[i][EP_STATUS] = EP_NONE;
995 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1001 /* [AS] Adjudication threshold */
1002 adjudicateLossThreshold = appData.adjudicateLossThreshold;
1004 InitEngine(&first, 0);
1005 InitEngine(&second, 1);
1008 if (appData.icsActive) {
1009 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
1010 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1011 appData.clockMode = FALSE;
1012 first.sendTime = second.sendTime = 0;
1016 /* Override some settings from environment variables, for backward
1017 compatibility. Unfortunately it's not feasible to have the env
1018 vars just set defaults, at least in xboard. Ugh.
1020 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1025 if (!appData.icsActive) {
1029 /* Check for variants that are supported only in ICS mode,
1030 or not at all. Some that are accepted here nevertheless
1031 have bugs; see comments below.
1033 VariantClass variant = StringToVariant(appData.variant);
1035 case VariantBughouse: /* need four players and two boards */
1036 case VariantKriegspiel: /* need to hide pieces and move details */
1037 /* case VariantFischeRandom: (Fabien: moved below) */
1038 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1039 if( (len > MSG_SIZ) && appData.debugMode )
1040 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1042 DisplayFatalError(buf, 0, 2);
1045 case VariantUnknown:
1046 case VariantLoadable:
1056 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1057 if( (len > MSG_SIZ) && appData.debugMode )
1058 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1060 DisplayFatalError(buf, 0, 2);
1063 case VariantXiangqi: /* [HGM] repetition rules not implemented */
1064 case VariantFairy: /* [HGM] TestLegality definitely off! */
1065 case VariantGothic: /* [HGM] should work */
1066 case VariantCapablanca: /* [HGM] should work */
1067 case VariantCourier: /* [HGM] initial forced moves not implemented */
1068 case VariantShogi: /* [HGM] could still mate with pawn drop */
1069 case VariantKnightmate: /* [HGM] should work */
1070 case VariantCylinder: /* [HGM] untested */
1071 case VariantFalcon: /* [HGM] untested */
1072 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1073 offboard interposition not understood */
1074 case VariantNormal: /* definitely works! */
1075 case VariantWildCastle: /* pieces not automatically shuffled */
1076 case VariantNoCastle: /* pieces not automatically shuffled */
1077 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1078 case VariantLosers: /* should work except for win condition,
1079 and doesn't know captures are mandatory */
1080 case VariantSuicide: /* should work except for win condition,
1081 and doesn't know captures are mandatory */
1082 case VariantGiveaway: /* should work except for win condition,
1083 and doesn't know captures are mandatory */
1084 case VariantTwoKings: /* should work */
1085 case VariantAtomic: /* should work except for win condition */
1086 case Variant3Check: /* should work except for win condition */
1087 case VariantShatranj: /* should work except for all win conditions */
1088 case VariantMakruk: /* should work except for daw countdown */
1089 case VariantBerolina: /* might work if TestLegality is off */
1090 case VariantCapaRandom: /* should work */
1091 case VariantJanus: /* should work */
1092 case VariantSuper: /* experimental */
1093 case VariantGreat: /* experimental, requires legality testing to be off */
1094 case VariantSChess: /* S-Chess, should work */
1095 case VariantSpartan: /* should work */
1102 int NextIntegerFromString( char ** str, long * value )
1107 while( *s == ' ' || *s == '\t' ) {
1113 if( *s >= '0' && *s <= '9' ) {
1114 while( *s >= '0' && *s <= '9' ) {
1115 *value = *value * 10 + (*s - '0');
1127 int NextTimeControlFromString( char ** str, long * value )
1130 int result = NextIntegerFromString( str, &temp );
1133 *value = temp * 60; /* Minutes */
1134 if( **str == ':' ) {
1136 result = NextIntegerFromString( str, &temp );
1137 *value += temp; /* Seconds */
1144 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1145 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1146 int result = -1, type = 0; long temp, temp2;
1148 if(**str != ':') return -1; // old params remain in force!
1150 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1151 if( NextIntegerFromString( str, &temp ) ) return -1;
1152 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1155 /* time only: incremental or sudden-death time control */
1156 if(**str == '+') { /* increment follows; read it */
1158 if(**str == '!') type = *(*str)++; // Bronstein TC
1159 if(result = NextIntegerFromString( str, &temp2)) return -1;
1160 *inc = temp2 * 1000;
1161 if(**str == '.') { // read fraction of increment
1162 char *start = ++(*str);
1163 if(result = NextIntegerFromString( str, &temp2)) return -1;
1165 while(start++ < *str) temp2 /= 10;
1169 *moves = 0; *tc = temp * 1000; *incType = type;
1173 (*str)++; /* classical time control */
1174 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1185 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1186 { /* [HGM] get time to add from the multi-session time-control string */
1187 int incType, moves=1; /* kludge to force reading of first session */
1188 long time, increment;
1191 if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1192 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1194 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1195 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1196 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1197 if(movenr == -1) return time; /* last move before new session */
1198 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1199 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1200 if(!moves) return increment; /* current session is incremental */
1201 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1202 } while(movenr >= -1); /* try again for next session */
1204 return 0; // no new time quota on this move
1208 ParseTimeControl(tc, ti, mps)
1215 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1218 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1219 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1220 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1224 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1226 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1229 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1231 snprintf(buf, MSG_SIZ, ":%s", mytc);
1233 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1235 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1240 /* Parse second time control */
1243 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1251 timeControl_2 = tc2 * 1000;
1261 timeControl = tc1 * 1000;
1264 timeIncrement = ti * 1000; /* convert to ms */
1265 movesPerSession = 0;
1268 movesPerSession = mps;
1276 if (appData.debugMode) {
1277 fprintf(debugFP, "%s\n", programVersion);
1280 set_cont_sequence(appData.wrapContSeq);
1281 if (appData.matchGames > 0) {
1282 appData.matchMode = TRUE;
1283 } else if (appData.matchMode) {
1284 appData.matchGames = 1;
1286 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1287 appData.matchGames = appData.sameColorGames;
1288 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1289 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1290 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1293 if (appData.noChessProgram || first.protocolVersion == 1) {
1296 /* kludge: allow timeout for initial "feature" commands */
1298 DisplayMessage("", _("Starting chess program"));
1299 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1304 CalculateIndex(int index, int gameNr)
1305 { // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1307 if(index > 0) return index; // fixed nmber
1308 if(index == 0) return 1;
1309 res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1310 if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1315 LoadGameOrPosition(int gameNr)
1316 { // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1317 if (*appData.loadGameFile != NULLCHAR) {
1318 if (!LoadGameFromFile(appData.loadGameFile,
1319 CalculateIndex(appData.loadGameIndex, gameNr),
1320 appData.loadGameFile, FALSE)) {
1321 DisplayFatalError(_("Bad game file"), 0, 1);
1324 } else if (*appData.loadPositionFile != NULLCHAR) {
1325 if (!LoadPositionFromFile(appData.loadPositionFile,
1326 CalculateIndex(appData.loadPositionIndex, gameNr),
1327 appData.loadPositionFile)) {
1328 DisplayFatalError(_("Bad position file"), 0, 1);
1336 ReserveGame(int gameNr, char resChar)
1338 FILE *tf = fopen(appData.tourneyFile, "r+");
1339 char *p, *q, c, buf[MSG_SIZ];
1340 if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1341 safeStrCpy(buf, lastMsg, MSG_SIZ);
1342 DisplayMessage(_("Pick new game"), "");
1343 flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1344 ParseArgsFromFile(tf);
1345 p = q = appData.results;
1346 if(appData.debugMode) {
1347 char *r = appData.participants;
1348 fprintf(debugFP, "results = '%s'\n", p);
1349 while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1350 fprintf(debugFP, "\n");
1352 while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1354 q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1355 safeStrCpy(q, p, strlen(p) + 2);
1356 if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1357 if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1358 if(nextGame <= appData.matchGames && resChar != ' ') { // already reserve next game, if tourney not yet done
1359 if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1362 fseek(tf, -(strlen(p)+4), SEEK_END);
1364 if(c != '"') // depending on DOS or Unix line endings we can be one off
1365 fseek(tf, -(strlen(p)+2), SEEK_END);
1366 else fseek(tf, -(strlen(p)+3), SEEK_END);
1367 fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1368 DisplayMessage(buf, "");
1369 free(p); appData.results = q;
1370 if(nextGame <= appData.matchGames && resChar != ' ' &&
1371 (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1372 UnloadEngine(&first); // next game belongs to other pairing;
1373 UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1378 MatchEvent(int mode)
1379 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1381 if(matchMode) { // already in match mode: switch it off
1383 appData.matchGames = appData.tourneyFile[0] ? nextGame: matchGame; // kludge to let match terminate after next game.
1384 ModeHighlight(); // kludgey way to remove checkmark...
1387 // if(gameMode != BeginningOfGame) {
1388 // DisplayError(_("You can only start a match from the initial position."), 0);
1392 appData.matchGames = appData.defaultMatchGames;
1393 /* Set up machine vs. machine match */
1395 NextTourneyGame(0, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1396 if(appData.tourneyFile[0]) {
1398 if(nextGame > appData.matchGames) {
1400 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1401 DisplayError(buf, 0);
1402 appData.tourneyFile[0] = 0;
1406 if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically
1407 DisplayFatalError(_("Can't have a match with no chess programs"),
1412 matchGame = roundNr = 1;
1413 first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
\r
1418 InitBackEnd3 P((void))
1420 GameMode initialMode;
1424 InitChessProgram(&first, startedFromSetupPosition);
1426 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1427 free(programVersion);
1428 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1429 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1432 if (appData.icsActive) {
1434 /* [DM] Make a console window if needed [HGM] merged ifs */
1440 if (*appData.icsCommPort != NULLCHAR)
1441 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1442 appData.icsCommPort);
1444 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1445 appData.icsHost, appData.icsPort);
1447 if( (len > MSG_SIZ) && appData.debugMode )
1448 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1450 DisplayFatalError(buf, err, 1);
1455 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1457 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1458 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1459 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1460 } else if (appData.noChessProgram) {
1466 if (*appData.cmailGameName != NULLCHAR) {
1468 OpenLoopback(&cmailPR);
1470 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1474 DisplayMessage("", "");
1475 if (StrCaseCmp(appData.initialMode, "") == 0) {
1476 initialMode = BeginningOfGame;
1477 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1478 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1479 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1480 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1483 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1484 initialMode = TwoMachinesPlay;
1485 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1486 initialMode = AnalyzeFile;
1487 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1488 initialMode = AnalyzeMode;
1489 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1490 initialMode = MachinePlaysWhite;
1491 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1492 initialMode = MachinePlaysBlack;
1493 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1494 initialMode = EditGame;
1495 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1496 initialMode = EditPosition;
1497 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1498 initialMode = Training;
1500 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1501 if( (len > MSG_SIZ) && appData.debugMode )
1502 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1504 DisplayFatalError(buf, 0, 2);
1508 if (appData.matchMode) {
1509 if(appData.tourneyFile[0]) { // start tourney from command line
1511 if(f = fopen(appData.tourneyFile, "r")) {
1512 ParseArgsFromFile(f); // make sure tourney parmeters re known
1514 } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1517 } else if (*appData.cmailGameName != NULLCHAR) {
1518 /* Set up cmail mode */
1519 ReloadCmailMsgEvent(TRUE);
1521 /* Set up other modes */
1522 if (initialMode == AnalyzeFile) {
1523 if (*appData.loadGameFile == NULLCHAR) {
1524 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1528 if (*appData.loadGameFile != NULLCHAR) {
1529 (void) LoadGameFromFile(appData.loadGameFile,
1530 appData.loadGameIndex,
1531 appData.loadGameFile, TRUE);
1532 } else if (*appData.loadPositionFile != NULLCHAR) {
1533 (void) LoadPositionFromFile(appData.loadPositionFile,
1534 appData.loadPositionIndex,
1535 appData.loadPositionFile);
1536 /* [HGM] try to make self-starting even after FEN load */
1537 /* to allow automatic setup of fairy variants with wtm */
1538 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1539 gameMode = BeginningOfGame;
1540 setboardSpoiledMachineBlack = 1;
1542 /* [HGM] loadPos: make that every new game uses the setup */
1543 /* from file as long as we do not switch variant */
1544 if(!blackPlaysFirst) {
1545 startedFromPositionFile = TRUE;
1546 CopyBoard(filePosition, boards[0]);
1549 if (initialMode == AnalyzeMode) {
1550 if (appData.noChessProgram) {
1551 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1554 if (appData.icsActive) {
1555 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1559 } else if (initialMode == AnalyzeFile) {
1560 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1561 ShowThinkingEvent();
1563 AnalysisPeriodicEvent(1);
1564 } else if (initialMode == MachinePlaysWhite) {
1565 if (appData.noChessProgram) {
1566 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1570 if (appData.icsActive) {
1571 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1575 MachineWhiteEvent();
1576 } else if (initialMode == MachinePlaysBlack) {
1577 if (appData.noChessProgram) {
1578 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1582 if (appData.icsActive) {
1583 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1587 MachineBlackEvent();
1588 } else if (initialMode == TwoMachinesPlay) {
1589 if (appData.noChessProgram) {
1590 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1594 if (appData.icsActive) {
1595 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1600 } else if (initialMode == EditGame) {
1602 } else if (initialMode == EditPosition) {
1603 EditPositionEvent();
1604 } else if (initialMode == Training) {
1605 if (*appData.loadGameFile == NULLCHAR) {
1606 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1615 * Establish will establish a contact to a remote host.port.
1616 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1617 * used to talk to the host.
1618 * Returns 0 if okay, error code if not.
1625 if (*appData.icsCommPort != NULLCHAR) {
1626 /* Talk to the host through a serial comm port */
1627 return OpenCommPort(appData.icsCommPort, &icsPR);
1629 } else if (*appData.gateway != NULLCHAR) {
1630 if (*appData.remoteShell == NULLCHAR) {
1631 /* Use the rcmd protocol to run telnet program on a gateway host */
1632 snprintf(buf, sizeof(buf), "%s %s %s",
1633 appData.telnetProgram, appData.icsHost, appData.icsPort);
1634 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1637 /* Use the rsh program to run telnet program on a gateway host */
1638 if (*appData.remoteUser == NULLCHAR) {
1639 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1640 appData.gateway, appData.telnetProgram,
1641 appData.icsHost, appData.icsPort);
1643 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1644 appData.remoteShell, appData.gateway,
1645 appData.remoteUser, appData.telnetProgram,
1646 appData.icsHost, appData.icsPort);
1648 return StartChildProcess(buf, "", &icsPR);
1651 } else if (appData.useTelnet) {
1652 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1655 /* TCP socket interface differs somewhat between
1656 Unix and NT; handle details in the front end.
1658 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1662 void EscapeExpand(char *p, char *q)
1663 { // [HGM] initstring: routine to shape up string arguments
1664 while(*p++ = *q++) if(p[-1] == '\\')
1666 case 'n': p[-1] = '\n'; break;
1667 case 'r': p[-1] = '\r'; break;
1668 case 't': p[-1] = '\t'; break;
1669 case '\\': p[-1] = '\\'; break;
1670 case 0: *p = 0; return;
1671 default: p[-1] = q[-1]; break;
1676 show_bytes(fp, buf, count)
1682 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1683 fprintf(fp, "\\%03o", *buf & 0xff);
1692 /* Returns an errno value */
1694 OutputMaybeTelnet(pr, message, count, outError)
1700 char buf[8192], *p, *q, *buflim;
1701 int left, newcount, outcount;
1703 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1704 *appData.gateway != NULLCHAR) {
1705 if (appData.debugMode) {
1706 fprintf(debugFP, ">ICS: ");
1707 show_bytes(debugFP, message, count);
1708 fprintf(debugFP, "\n");
1710 return OutputToProcess(pr, message, count, outError);
1713 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1720 if (appData.debugMode) {
1721 fprintf(debugFP, ">ICS: ");
1722 show_bytes(debugFP, buf, newcount);
1723 fprintf(debugFP, "\n");
1725 outcount = OutputToProcess(pr, buf, newcount, outError);
1726 if (outcount < newcount) return -1; /* to be sure */
1733 } else if (((unsigned char) *p) == TN_IAC) {
1734 *q++ = (char) TN_IAC;
1741 if (appData.debugMode) {
1742 fprintf(debugFP, ">ICS: ");
1743 show_bytes(debugFP, buf, newcount);
1744 fprintf(debugFP, "\n");
1746 outcount = OutputToProcess(pr, buf, newcount, outError);
1747 if (outcount < newcount) return -1; /* to be sure */
1752 read_from_player(isr, closure, message, count, error)
1759 int outError, outCount;
1760 static int gotEof = 0;
1762 /* Pass data read from player on to ICS */
1765 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1766 if (outCount < count) {
1767 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1769 } else if (count < 0) {
1770 RemoveInputSource(isr);
1771 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1772 } else if (gotEof++ > 0) {
1773 RemoveInputSource(isr);
1774 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1780 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1781 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1782 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1783 SendToICS("date\n");
1784 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1787 /* added routine for printf style output to ics */
1788 void ics_printf(char *format, ...)
1790 char buffer[MSG_SIZ];
1793 va_start(args, format);
1794 vsnprintf(buffer, sizeof(buffer), format, args);
1795 buffer[sizeof(buffer)-1] = '\0';
1804 int count, outCount, outError;
1806 if (icsPR == NULL) return;
1809 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1810 if (outCount < count) {
1811 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1815 /* This is used for sending logon scripts to the ICS. Sending
1816 without a delay causes problems when using timestamp on ICC
1817 (at least on my machine). */
1819 SendToICSDelayed(s,msdelay)
1823 int count, outCount, outError;
1825 if (icsPR == NULL) return;
1828 if (appData.debugMode) {
1829 fprintf(debugFP, ">ICS: ");
1830 show_bytes(debugFP, s, count);
1831 fprintf(debugFP, "\n");
1833 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1835 if (outCount < count) {
1836 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1841 /* Remove all highlighting escape sequences in s
1842 Also deletes any suffix starting with '('
1845 StripHighlightAndTitle(s)
1848 static char retbuf[MSG_SIZ];
1851 while (*s != NULLCHAR) {
1852 while (*s == '\033') {
1853 while (*s != NULLCHAR && !isalpha(*s)) s++;
1854 if (*s != NULLCHAR) s++;
1856 while (*s != NULLCHAR && *s != '\033') {
1857 if (*s == '(' || *s == '[') {
1868 /* Remove all highlighting escape sequences in s */
1873 static char retbuf[MSG_SIZ];
1876 while (*s != NULLCHAR) {
1877 while (*s == '\033') {
1878 while (*s != NULLCHAR && !isalpha(*s)) s++;
1879 if (*s != NULLCHAR) s++;
1881 while (*s != NULLCHAR && *s != '\033') {
1889 char *variantNames[] = VARIANT_NAMES;
1894 return variantNames[v];
1898 /* Identify a variant from the strings the chess servers use or the
1899 PGN Variant tag names we use. */
1906 VariantClass v = VariantNormal;
1907 int i, found = FALSE;
1913 /* [HGM] skip over optional board-size prefixes */
1914 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1915 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1916 while( *e++ != '_');
1919 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1923 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1924 if (StrCaseStr(e, variantNames[i])) {
1925 v = (VariantClass) i;
1932 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1933 || StrCaseStr(e, "wild/fr")
1934 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1935 v = VariantFischeRandom;
1936 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1937 (i = 1, p = StrCaseStr(e, "w"))) {
1939 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1946 case 0: /* FICS only, actually */
1948 /* Castling legal even if K starts on d-file */
1949 v = VariantWildCastle;
1954 /* Castling illegal even if K & R happen to start in
1955 normal positions. */
1956 v = VariantNoCastle;
1969 /* Castling legal iff K & R start in normal positions */
1975 /* Special wilds for position setup; unclear what to do here */
1976 v = VariantLoadable;
1979 /* Bizarre ICC game */
1980 v = VariantTwoKings;
1983 v = VariantKriegspiel;
1989 v = VariantFischeRandom;
1992 v = VariantCrazyhouse;
1995 v = VariantBughouse;
2001 /* Not quite the same as FICS suicide! */
2002 v = VariantGiveaway;
2008 v = VariantShatranj;
2011 /* Temporary names for future ICC types. The name *will* change in
2012 the next xboard/WinBoard release after ICC defines it. */
2050 v = VariantCapablanca;
2053 v = VariantKnightmate;
2059 v = VariantCylinder;
2065 v = VariantCapaRandom;
2068 v = VariantBerolina;
2080 /* Found "wild" or "w" in the string but no number;
2081 must assume it's normal chess. */
2085 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2086 if( (len > MSG_SIZ) && appData.debugMode )
2087 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2089 DisplayError(buf, 0);
2095 if (appData.debugMode) {
2096 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2097 e, wnum, VariantName(v));
2102 static int leftover_start = 0, leftover_len = 0;
2103 char star_match[STAR_MATCH_N][MSG_SIZ];
2105 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2106 advance *index beyond it, and set leftover_start to the new value of
2107 *index; else return FALSE. If pattern contains the character '*', it
2108 matches any sequence of characters not containing '\r', '\n', or the
2109 character following the '*' (if any), and the matched sequence(s) are
2110 copied into star_match.
2113 looking_at(buf, index, pattern)
2118 char *bufp = &buf[*index], *patternp = pattern;
2120 char *matchp = star_match[0];
2123 if (*patternp == NULLCHAR) {
2124 *index = leftover_start = bufp - buf;
2128 if (*bufp == NULLCHAR) return FALSE;
2129 if (*patternp == '*') {
2130 if (*bufp == *(patternp + 1)) {
2132 matchp = star_match[++star_count];
2136 } else if (*bufp == '\n' || *bufp == '\r') {
2138 if (*patternp == NULLCHAR)
2143 *matchp++ = *bufp++;
2147 if (*patternp != *bufp) return FALSE;
2154 SendToPlayer(data, length)
2158 int error, outCount;
2159 outCount = OutputToProcess(NoProc, data, length, &error);
2160 if (outCount < length) {
2161 DisplayFatalError(_("Error writing to display"), error, 1);
2166 PackHolding(packed, holding)
2178 switch (runlength) {
2189 sprintf(q, "%d", runlength);
2201 /* Telnet protocol requests from the front end */
2203 TelnetRequest(ddww, option)
2204 unsigned char ddww, option;
2206 unsigned char msg[3];
2207 int outCount, outError;
2209 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2211 if (appData.debugMode) {
2212 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2228 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2237 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2240 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2245 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2247 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2254 if (!appData.icsActive) return;
2255 TelnetRequest(TN_DO, TN_ECHO);
2261 if (!appData.icsActive) return;
2262 TelnetRequest(TN_DONT, TN_ECHO);
2266 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2268 /* put the holdings sent to us by the server on the board holdings area */
2269 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2273 if(gameInfo.holdingsWidth < 2) return;
2274 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2275 return; // prevent overwriting by pre-board holdings
2277 if( (int)lowestPiece >= BlackPawn ) {
2280 holdingsStartRow = BOARD_HEIGHT-1;
2283 holdingsColumn = BOARD_WIDTH-1;
2284 countsColumn = BOARD_WIDTH-2;
2285 holdingsStartRow = 0;
2289 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2290 board[i][holdingsColumn] = EmptySquare;
2291 board[i][countsColumn] = (ChessSquare) 0;
2293 while( (p=*holdings++) != NULLCHAR ) {
2294 piece = CharToPiece( ToUpper(p) );
2295 if(piece == EmptySquare) continue;
2296 /*j = (int) piece - (int) WhitePawn;*/
2297 j = PieceToNumber(piece);
2298 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2299 if(j < 0) continue; /* should not happen */
2300 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2301 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2302 board[holdingsStartRow+j*direction][countsColumn]++;
2308 VariantSwitch(Board board, VariantClass newVariant)
2310 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2311 static Board oldBoard;
2313 startedFromPositionFile = FALSE;
2314 if(gameInfo.variant == newVariant) return;
2316 /* [HGM] This routine is called each time an assignment is made to
2317 * gameInfo.variant during a game, to make sure the board sizes
2318 * are set to match the new variant. If that means adding or deleting
2319 * holdings, we shift the playing board accordingly
2320 * This kludge is needed because in ICS observe mode, we get boards
2321 * of an ongoing game without knowing the variant, and learn about the
2322 * latter only later. This can be because of the move list we requested,
2323 * in which case the game history is refilled from the beginning anyway,
2324 * but also when receiving holdings of a crazyhouse game. In the latter
2325 * case we want to add those holdings to the already received position.
2329 if (appData.debugMode) {
2330 fprintf(debugFP, "Switch board from %s to %s\n",
2331 VariantName(gameInfo.variant), VariantName(newVariant));
2332 setbuf(debugFP, NULL);
2334 shuffleOpenings = 0; /* [HGM] shuffle */
2335 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2339 newWidth = 9; newHeight = 9;
2340 gameInfo.holdingsSize = 7;
2341 case VariantBughouse:
2342 case VariantCrazyhouse:
2343 newHoldingsWidth = 2; break;
2347 newHoldingsWidth = 2;
2348 gameInfo.holdingsSize = 8;
2351 case VariantCapablanca:
2352 case VariantCapaRandom:
2355 newHoldingsWidth = gameInfo.holdingsSize = 0;
2358 if(newWidth != gameInfo.boardWidth ||
2359 newHeight != gameInfo.boardHeight ||
2360 newHoldingsWidth != gameInfo.holdingsWidth ) {
2362 /* shift position to new playing area, if needed */
2363 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2364 for(i=0; i<BOARD_HEIGHT; i++)
2365 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2366 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2368 for(i=0; i<newHeight; i++) {
2369 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2370 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2372 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2373 for(i=0; i<BOARD_HEIGHT; i++)
2374 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2375 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2378 gameInfo.boardWidth = newWidth;
2379 gameInfo.boardHeight = newHeight;
2380 gameInfo.holdingsWidth = newHoldingsWidth;
2381 gameInfo.variant = newVariant;
2382 InitDrawingSizes(-2, 0);
2383 } else gameInfo.variant = newVariant;
2384 CopyBoard(oldBoard, board); // remember correctly formatted board
2385 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2386 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2389 static int loggedOn = FALSE;
2391 /*-- Game start info cache: --*/
2393 char gs_kind[MSG_SIZ];
2394 static char player1Name[128] = "";
2395 static char player2Name[128] = "";
2396 static char cont_seq[] = "\n\\ ";
2397 static int player1Rating = -1;
2398 static int player2Rating = -1;
2399 /*----------------------------*/
2401 ColorClass curColor = ColorNormal;
2402 int suppressKibitz = 0;
2405 Boolean soughtPending = FALSE;
2406 Boolean seekGraphUp;
2407 #define MAX_SEEK_ADS 200
2409 char *seekAdList[MAX_SEEK_ADS];
2410 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2411 float tcList[MAX_SEEK_ADS];
2412 char colorList[MAX_SEEK_ADS];
2413 int nrOfSeekAds = 0;
2414 int minRating = 1010, maxRating = 2800;
2415 int hMargin = 10, vMargin = 20, h, w;
2416 extern int squareSize, lineGap;
2421 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2422 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2423 if(r < minRating+100 && r >=0 ) r = minRating+100;
2424 if(r > maxRating) r = maxRating;
2425 if(tc < 1.) tc = 1.;
2426 if(tc > 95.) tc = 95.;
2427 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2428 y = ((double)r - minRating)/(maxRating - minRating)
2429 * (h-vMargin-squareSize/8-1) + vMargin;
2430 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2431 if(strstr(seekAdList[i], " u ")) color = 1;
2432 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2433 !strstr(seekAdList[i], "bullet") &&
2434 !strstr(seekAdList[i], "blitz") &&
2435 !strstr(seekAdList[i], "standard") ) color = 2;
2436 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2437 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2441 AddAd(char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2443 char buf[MSG_SIZ], *ext = "";
2444 VariantClass v = StringToVariant(type);
2445 if(strstr(type, "wild")) {
2446 ext = type + 4; // append wild number
2447 if(v == VariantFischeRandom) type = "chess960"; else
2448 if(v == VariantLoadable) type = "setup"; else
2449 type = VariantName(v);
2451 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2452 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2453 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2454 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2455 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2456 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2457 seekNrList[nrOfSeekAds] = nr;
2458 zList[nrOfSeekAds] = 0;
2459 seekAdList[nrOfSeekAds++] = StrSave(buf);
2460 if(plot) PlotSeekAd(nrOfSeekAds-1);
2467 int x = xList[i], y = yList[i], d=squareSize/4, k;
2468 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2469 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2470 // now replot every dot that overlapped
2471 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2472 int xx = xList[k], yy = yList[k];
2473 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2474 DrawSeekDot(xx, yy, colorList[k]);
2479 RemoveSeekAd(int nr)
2482 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2484 if(seekAdList[i]) free(seekAdList[i]);
2485 seekAdList[i] = seekAdList[--nrOfSeekAds];
2486 seekNrList[i] = seekNrList[nrOfSeekAds];
2487 ratingList[i] = ratingList[nrOfSeekAds];
2488 colorList[i] = colorList[nrOfSeekAds];
2489 tcList[i] = tcList[nrOfSeekAds];
2490 xList[i] = xList[nrOfSeekAds];
2491 yList[i] = yList[nrOfSeekAds];
2492 zList[i] = zList[nrOfSeekAds];
2493 seekAdList[nrOfSeekAds] = NULL;
2499 MatchSoughtLine(char *line)
2501 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2502 int nr, base, inc, u=0; char dummy;
2504 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2505 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2507 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2508 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2509 // match: compact and save the line
2510 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2520 if(!seekGraphUp) return FALSE;
2521 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2522 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2524 DrawSeekBackground(0, 0, w, h);
2525 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2526 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2527 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2528 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2530 DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2533 snprintf(buf, MSG_SIZ, "%d", i);
2534 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2537 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2538 for(i=1; i<100; i+=(i<10?1:5)) {
2539 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2540 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2541 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2543 snprintf(buf, MSG_SIZ, "%d", i);
2544 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2547 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2551 int SeekGraphClick(ClickType click, int x, int y, int moving)
2553 static int lastDown = 0, displayed = 0, lastSecond;
2554 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2555 if(click == Release || moving) return FALSE;
2557 soughtPending = TRUE;
2558 SendToICS(ics_prefix);
2559 SendToICS("sought\n"); // should this be "sought all"?
2560 } else { // issue challenge based on clicked ad
2561 int dist = 10000; int i, closest = 0, second = 0;
2562 for(i=0; i<nrOfSeekAds; i++) {
2563 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2564 if(d < dist) { dist = d; closest = i; }
2565 second += (d - zList[i] < 120); // count in-range ads
2566 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2570 second = (second > 1);
2571 if(displayed != closest || second != lastSecond) {
2572 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2573 lastSecond = second; displayed = closest;
2575 if(click == Press) {
2576 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2579 } // on press 'hit', only show info
2580 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2581 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2582 SendToICS(ics_prefix);
2584 return TRUE; // let incoming board of started game pop down the graph
2585 } else if(click == Release) { // release 'miss' is ignored
2586 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2587 if(moving == 2) { // right up-click
2588 nrOfSeekAds = 0; // refresh graph
2589 soughtPending = TRUE;
2590 SendToICS(ics_prefix);
2591 SendToICS("sought\n"); // should this be "sought all"?
2594 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2595 // press miss or release hit 'pop down' seek graph
2596 seekGraphUp = FALSE;
2597 DrawPosition(TRUE, NULL);
2603 read_from_ics(isr, closure, data, count, error)
2610 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2611 #define STARTED_NONE 0
2612 #define STARTED_MOVES 1
2613 #define STARTED_BOARD 2
2614 #define STARTED_OBSERVE 3
2615 #define STARTED_HOLDINGS 4
2616 #define STARTED_CHATTER 5
2617 #define STARTED_COMMENT 6
2618 #define STARTED_MOVES_NOHIDE 7
2620 static int started = STARTED_NONE;
2621 static char parse[20000];
2622 static int parse_pos = 0;
2623 static char buf[BUF_SIZE + 1];
2624 static int firstTime = TRUE, intfSet = FALSE;
2625 static ColorClass prevColor = ColorNormal;
2626 static int savingComment = FALSE;
2627 static int cmatch = 0; // continuation sequence match
2634 int backup; /* [DM] For zippy color lines */
2636 char talker[MSG_SIZ]; // [HGM] chat
2639 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2641 if (appData.debugMode) {
2643 fprintf(debugFP, "<ICS: ");
2644 show_bytes(debugFP, data, count);
2645 fprintf(debugFP, "\n");
2649 if (appData.debugMode) { int f = forwardMostMove;
2650 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2651 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2652 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2655 /* If last read ended with a partial line that we couldn't parse,
2656 prepend it to the new read and try again. */
2657 if (leftover_len > 0) {
2658 for (i=0; i<leftover_len; i++)
2659 buf[i] = buf[leftover_start + i];
2662 /* copy new characters into the buffer */
2663 bp = buf + leftover_len;
2664 buf_len=leftover_len;
2665 for (i=0; i<count; i++)
2668 if (data[i] == '\r')
2671 // join lines split by ICS?
2672 if (!appData.noJoin)
2675 Joining just consists of finding matches against the
2676 continuation sequence, and discarding that sequence
2677 if found instead of copying it. So, until a match
2678 fails, there's nothing to do since it might be the
2679 complete sequence, and thus, something we don't want
2682 if (data[i] == cont_seq[cmatch])
2685 if (cmatch == strlen(cont_seq))
2687 cmatch = 0; // complete match. just reset the counter
2690 it's possible for the ICS to not include the space
2691 at the end of the last word, making our [correct]
2692 join operation fuse two separate words. the server
2693 does this when the space occurs at the width setting.
2695 if (!buf_len || buf[buf_len-1] != ' ')
2706 match failed, so we have to copy what matched before
2707 falling through and copying this character. In reality,
2708 this will only ever be just the newline character, but
2709 it doesn't hurt to be precise.
2711 strncpy(bp, cont_seq, cmatch);
2723 buf[buf_len] = NULLCHAR;
2724 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2729 while (i < buf_len) {
2730 /* Deal with part of the TELNET option negotiation
2731 protocol. We refuse to do anything beyond the
2732 defaults, except that we allow the WILL ECHO option,
2733 which ICS uses to turn off password echoing when we are
2734 directly connected to it. We reject this option
2735 if localLineEditing mode is on (always on in xboard)
2736 and we are talking to port 23, which might be a real
2737 telnet server that will try to keep WILL ECHO on permanently.
2739 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2740 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2741 unsigned char option;
2743 switch ((unsigned char) buf[++i]) {
2745 if (appData.debugMode)
2746 fprintf(debugFP, "\n<WILL ");
2747 switch (option = (unsigned char) buf[++i]) {
2749 if (appData.debugMode)
2750 fprintf(debugFP, "ECHO ");
2751 /* Reply only if this is a change, according
2752 to the protocol rules. */
2753 if (remoteEchoOption) break;
2754 if (appData.localLineEditing &&
2755 atoi(appData.icsPort) == TN_PORT) {
2756 TelnetRequest(TN_DONT, TN_ECHO);
2759 TelnetRequest(TN_DO, TN_ECHO);
2760 remoteEchoOption = TRUE;
2764 if (appData.debugMode)
2765 fprintf(debugFP, "%d ", option);
2766 /* Whatever this is, we don't want it. */
2767 TelnetRequest(TN_DONT, option);
2772 if (appData.debugMode)
2773 fprintf(debugFP, "\n<WONT ");
2774 switch (option = (unsigned char) buf[++i]) {
2776 if (appData.debugMode)
2777 fprintf(debugFP, "ECHO ");
2778 /* Reply only if this is a change, according
2779 to the protocol rules. */
2780 if (!remoteEchoOption) break;
2782 TelnetRequest(TN_DONT, TN_ECHO);
2783 remoteEchoOption = FALSE;
2786 if (appData.debugMode)
2787 fprintf(debugFP, "%d ", (unsigned char) option);
2788 /* Whatever this is, it must already be turned
2789 off, because we never agree to turn on
2790 anything non-default, so according to the
2791 protocol rules, we don't reply. */
2796 if (appData.debugMode)
2797 fprintf(debugFP, "\n<DO ");
2798 switch (option = (unsigned char) buf[++i]) {
2800 /* Whatever this is, we refuse to do it. */
2801 if (appData.debugMode)
2802 fprintf(debugFP, "%d ", option);
2803 TelnetRequest(TN_WONT, option);
2808 if (appData.debugMode)
2809 fprintf(debugFP, "\n<DONT ");
2810 switch (option = (unsigned char) buf[++i]) {
2812 if (appData.debugMode)
2813 fprintf(debugFP, "%d ", option);
2814 /* Whatever this is, we are already not doing
2815 it, because we never agree to do anything
2816 non-default, so according to the protocol
2817 rules, we don't reply. */
2822 if (appData.debugMode)
2823 fprintf(debugFP, "\n<IAC ");
2824 /* Doubled IAC; pass it through */
2828 if (appData.debugMode)
2829 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2830 /* Drop all other telnet commands on the floor */
2833 if (oldi > next_out)
2834 SendToPlayer(&buf[next_out], oldi - next_out);
2840 /* OK, this at least will *usually* work */
2841 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2845 if (loggedOn && !intfSet) {
2846 if (ics_type == ICS_ICC) {
2847 snprintf(str, MSG_SIZ,
2848 "/set-quietly interface %s\n/set-quietly style 12\n",
2850 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2851 strcat(str, "/set-2 51 1\n/set seek 1\n");
2852 } else if (ics_type == ICS_CHESSNET) {
2853 snprintf(str, MSG_SIZ, "/style 12\n");
2855 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2856 strcat(str, programVersion);
2857 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2858 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2859 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2861 strcat(str, "$iset nohighlight 1\n");
2863 strcat(str, "$iset lock 1\n$style 12\n");
2866 NotifyFrontendLogin();
2870 if (started == STARTED_COMMENT) {
2871 /* Accumulate characters in comment */
2872 parse[parse_pos++] = buf[i];
2873 if (buf[i] == '\n') {
2874 parse[parse_pos] = NULLCHAR;
2875 if(chattingPartner>=0) {
2877 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2878 OutputChatMessage(chattingPartner, mess);
2879 chattingPartner = -1;
2880 next_out = i+1; // [HGM] suppress printing in ICS window
2882 if(!suppressKibitz) // [HGM] kibitz
2883 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2884 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2885 int nrDigit = 0, nrAlph = 0, j;
2886 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2887 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2888 parse[parse_pos] = NULLCHAR;
2889 // try to be smart: if it does not look like search info, it should go to
2890 // ICS interaction window after all, not to engine-output window.
2891 for(j=0; j<parse_pos; j++) { // count letters and digits
2892 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2893 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2894 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2896 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2897 int depth=0; float score;
2898 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2899 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2900 pvInfoList[forwardMostMove-1].depth = depth;
2901 pvInfoList[forwardMostMove-1].score = 100*score;
2903 OutputKibitz(suppressKibitz, parse);
2906 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2907 SendToPlayer(tmp, strlen(tmp));
2909 next_out = i+1; // [HGM] suppress printing in ICS window
2911 started = STARTED_NONE;
2913 /* Don't match patterns against characters in comment */
2918 if (started == STARTED_CHATTER) {
2919 if (buf[i] != '\n') {
2920 /* Don't match patterns against characters in chatter */
2924 started = STARTED_NONE;
2925 if(suppressKibitz) next_out = i+1;
2928 /* Kludge to deal with rcmd protocol */
2929 if (firstTime && looking_at(buf, &i, "\001*")) {
2930 DisplayFatalError(&buf[1], 0, 1);
2936 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2939 if (appData.debugMode)
2940 fprintf(debugFP, "ics_type %d\n", ics_type);
2943 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2944 ics_type = ICS_FICS;
2946 if (appData.debugMode)
2947 fprintf(debugFP, "ics_type %d\n", ics_type);
2950 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2951 ics_type = ICS_CHESSNET;
2953 if (appData.debugMode)
2954 fprintf(debugFP, "ics_type %d\n", ics_type);
2959 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2960 looking_at(buf, &i, "Logging you in as \"*\"") ||
2961 looking_at(buf, &i, "will be \"*\""))) {
2962 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2966 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2968 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2969 DisplayIcsInteractionTitle(buf);
2970 have_set_title = TRUE;
2973 /* skip finger notes */
2974 if (started == STARTED_NONE &&
2975 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2976 (buf[i] == '1' && buf[i+1] == '0')) &&
2977 buf[i+2] == ':' && buf[i+3] == ' ') {
2978 started = STARTED_CHATTER;
2984 // [HGM] seekgraph: recognize sought lines and end-of-sought message
2985 if(appData.seekGraph) {
2986 if(soughtPending && MatchSoughtLine(buf+i)) {
2987 i = strstr(buf+i, "rated") - buf;
2988 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2989 next_out = leftover_start = i;
2990 started = STARTED_CHATTER;
2991 suppressKibitz = TRUE;
2994 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2995 && looking_at(buf, &i, "* ads displayed")) {
2996 soughtPending = FALSE;
3001 if(appData.autoRefresh) {
3002 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3003 int s = (ics_type == ICS_ICC); // ICC format differs
3005 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3006 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3007 looking_at(buf, &i, "*% "); // eat prompt
3008 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3009 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3010 next_out = i; // suppress
3013 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3014 char *p = star_match[0];
3016 if(seekGraphUp) RemoveSeekAd(atoi(p));
3017 while(*p && *p++ != ' '); // next
3019 looking_at(buf, &i, "*% "); // eat prompt
3020 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3027 /* skip formula vars */
3028 if (started == STARTED_NONE &&
3029 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3030 started = STARTED_CHATTER;
3035 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3036 if (appData.autoKibitz && started == STARTED_NONE &&
3037 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3038 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3039 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3040 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3041 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3042 suppressKibitz = TRUE;
3043 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3045 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3046 && (gameMode == IcsPlayingWhite)) ||
3047 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3048 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3049 started = STARTED_CHATTER; // own kibitz we simply discard
3051 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3052 parse_pos = 0; parse[0] = NULLCHAR;
3053 savingComment = TRUE;
3054 suppressKibitz = gameMode != IcsObserving ? 2 :
3055 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3059 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3060 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3061 && atoi(star_match[0])) {
3062 // suppress the acknowledgements of our own autoKibitz
3064 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3065 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3066 SendToPlayer(star_match[0], strlen(star_match[0]));
3067 if(looking_at(buf, &i, "*% ")) // eat prompt
3068 suppressKibitz = FALSE;
3072 } // [HGM] kibitz: end of patch
3074 // [HGM] chat: intercept tells by users for which we have an open chat window
3076 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3077 looking_at(buf, &i, "* whispers:") ||
3078 looking_at(buf, &i, "* kibitzes:") ||
3079 looking_at(buf, &i, "* shouts:") ||
3080 looking_at(buf, &i, "* c-shouts:") ||
3081 looking_at(buf, &i, "--> * ") ||
3082 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3083 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3084 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3085 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3087 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3088 chattingPartner = -1;
3090 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3091 for(p=0; p<MAX_CHAT; p++) {
3092 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3093 talker[0] = '['; strcat(talker, "] ");
3094 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3095 chattingPartner = p; break;
3098 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3099 for(p=0; p<MAX_CHAT; p++) {
3100 if(!strcmp("kibitzes", chatPartner[p])) {
3101 talker[0] = '['; strcat(talker, "] ");
3102 chattingPartner = p; break;
3105 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3106 for(p=0; p<MAX_CHAT; p++) {
3107 if(!strcmp("whispers", chatPartner[p])) {
3108 talker[0] = '['; strcat(talker, "] ");
3109 chattingPartner = p; break;
3112 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3113 if(buf[i-8] == '-' && buf[i-3] == 't')
3114 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3115 if(!strcmp("c-shouts", chatPartner[p])) {
3116 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3117 chattingPartner = p; break;
3120 if(chattingPartner < 0)
3121 for(p=0; p<MAX_CHAT; p++) {
3122 if(!strcmp("shouts", chatPartner[p])) {
3123 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3124 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3125 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3126 chattingPartner = p; break;
3130 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3131 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3132 talker[0] = 0; Colorize(ColorTell, FALSE);
3133 chattingPartner = p; break;
3135 if(chattingPartner<0) i = oldi; else {
3136 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3137 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3138 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3139 started = STARTED_COMMENT;
3140 parse_pos = 0; parse[0] = NULLCHAR;
3141 savingComment = 3 + chattingPartner; // counts as TRUE
3142 suppressKibitz = TRUE;
3145 } // [HGM] chat: end of patch
3148 if (appData.zippyTalk || appData.zippyPlay) {
3149 /* [DM] Backup address for color zippy lines */
3151 if (loggedOn == TRUE)
3152 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3153 (appData.zippyPlay && ZippyMatch(buf, &backup)));
3155 } // [DM] 'else { ' deleted
3157 /* Regular tells and says */
3158 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3159 looking_at(buf, &i, "* (your partner) tells you: ") ||
3160 looking_at(buf, &i, "* says: ") ||
3161 /* Don't color "message" or "messages" output */
3162 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3163 looking_at(buf, &i, "*. * at *:*: ") ||
3164 looking_at(buf, &i, "--* (*:*): ") ||
3165 /* Message notifications (same color as tells) */
3166 looking_at(buf, &i, "* has left a message ") ||
3167 looking_at(buf, &i, "* just sent you a message:\n") ||
3168 /* Whispers and kibitzes */
3169 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3170 looking_at(buf, &i, "* kibitzes: ") ||
3172 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3174 if (tkind == 1 && strchr(star_match[0], ':')) {
3175 /* Avoid "tells you:" spoofs in channels */
3178 if (star_match[0][0] == NULLCHAR ||
3179 strchr(star_match[0], ' ') ||
3180 (tkind == 3 && strchr(star_match[1], ' '))) {
3181 /* Reject bogus matches */
3184 if (appData.colorize) {
3185 if (oldi > next_out) {
3186 SendToPlayer(&buf[next_out], oldi - next_out);
3191 Colorize(ColorTell, FALSE);
3192 curColor = ColorTell;
3195 Colorize(ColorKibitz, FALSE);
3196 curColor = ColorKibitz;
3199 p = strrchr(star_match[1], '(');
3206 Colorize(ColorChannel1, FALSE);
3207 curColor = ColorChannel1;
3209 Colorize(ColorChannel, FALSE);
3210 curColor = ColorChannel;
3214 curColor = ColorNormal;
3218 if (started == STARTED_NONE && appData.autoComment &&
3219 (gameMode == IcsObserving ||
3220 gameMode == IcsPlayingWhite ||
3221 gameMode == IcsPlayingBlack)) {
3222 parse_pos = i - oldi;
3223 memcpy(parse, &buf[oldi], parse_pos);
3224 parse[parse_pos] = NULLCHAR;
3225 started = STARTED_COMMENT;
3226 savingComment = TRUE;
3228 started = STARTED_CHATTER;
3229 savingComment = FALSE;
3236 if (looking_at(buf, &i, "* s-shouts: ") ||
3237 looking_at(buf, &i, "* c-shouts: ")) {
3238 if (appData.colorize) {
3239 if (oldi > next_out) {
3240 SendToPlayer(&buf[next_out], oldi - next_out);
3243 Colorize(ColorSShout, FALSE);
3244 curColor = ColorSShout;
3247 started = STARTED_CHATTER;
3251 if (looking_at(buf, &i, "--->")) {
3256 if (looking_at(buf, &i, "* shouts: ") ||
3257 looking_at(buf, &i, "--> ")) {
3258 if (appData.colorize) {
3259 if (oldi > next_out) {
3260 SendToPlayer(&buf[next_out], oldi - next_out);
3263 Colorize(ColorShout, FALSE);
3264 curColor = ColorShout;
3267 started = STARTED_CHATTER;
3271 if (looking_at( buf, &i, "Challenge:")) {
3272 if (appData.colorize) {
3273 if (oldi > next_out) {
3274 SendToPlayer(&buf[next_out], oldi - next_out);
3277 Colorize(ColorChallenge, FALSE);
3278 curColor = ColorChallenge;
3284 if (looking_at(buf, &i, "* offers you") ||
3285 looking_at(buf, &i, "* offers to be") ||
3286 looking_at(buf, &i, "* would like to") ||
3287 looking_at(buf, &i, "* requests to") ||
3288 looking_at(buf, &i, "Your opponent offers") ||
3289 looking_at(buf, &i, "Your opponent requests")) {
3291 if (appData.colorize) {
3292 if (oldi > next_out) {
3293 SendToPlayer(&buf[next_out], oldi - next_out);
3296 Colorize(ColorRequest, FALSE);
3297 curColor = ColorRequest;
3302 if (looking_at(buf, &i, "* (*) seeking")) {
3303 if (appData.colorize) {
3304 if (oldi > next_out) {
3305 SendToPlayer(&buf[next_out], oldi - next_out);
3308 Colorize(ColorSeek, FALSE);
3309 curColor = ColorSeek;
3314 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3316 if (looking_at(buf, &i, "\\ ")) {
3317 if (prevColor != ColorNormal) {
3318 if (oldi > next_out) {
3319 SendToPlayer(&buf[next_out], oldi - next_out);
3322 Colorize(prevColor, TRUE);
3323 curColor = prevColor;
3325 if (savingComment) {
3326 parse_pos = i - oldi;
3327 memcpy(parse, &buf[oldi], parse_pos);
3328 parse[parse_pos] = NULLCHAR;
3329 started = STARTED_COMMENT;
3330 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3331 chattingPartner = savingComment - 3; // kludge to remember the box
3333 started = STARTED_CHATTER;
3338 if (looking_at(buf, &i, "Black Strength :") ||
3339 looking_at(buf, &i, "<<< style 10 board >>>") ||
3340 looking_at(buf, &i, "<10>") ||
3341 looking_at(buf, &i, "#@#")) {
3342 /* Wrong board style */
3344 SendToICS(ics_prefix);
3345 SendToICS("set style 12\n");
3346 SendToICS(ics_prefix);
3347 SendToICS("refresh\n");
3351 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3353 have_sent_ICS_logon = 1;
3357 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3358 (looking_at(buf, &i, "\n<12> ") ||
3359 looking_at(buf, &i, "<12> "))) {
3361 if (oldi > next_out) {
3362 SendToPlayer(&buf[next_out], oldi - next_out);
3365 started = STARTED_BOARD;
3370 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3371 looking_at(buf, &i, "<b1> ")) {
3372 if (oldi > next_out) {
3373 SendToPlayer(&buf[next_out], oldi - next_out);
3376 started = STARTED_HOLDINGS;
3381 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3383 /* Header for a move list -- first line */
3385 switch (ics_getting_history) {
3389 case BeginningOfGame:
3390 /* User typed "moves" or "oldmoves" while we
3391 were idle. Pretend we asked for these
3392 moves and soak them up so user can step
3393 through them and/or save them.
3396 gameMode = IcsObserving;
3399 ics_getting_history = H_GOT_UNREQ_HEADER;
3401 case EditGame: /*?*/
3402 case EditPosition: /*?*/
3403 /* Should above feature work in these modes too? */
3404 /* For now it doesn't */
3405 ics_getting_history = H_GOT_UNWANTED_HEADER;
3408 ics_getting_history = H_GOT_UNWANTED_HEADER;
3413 /* Is this the right one? */
3414 if (gameInfo.white && gameInfo.black &&
3415 strcmp(gameInfo.white, star_match[0]) == 0 &&
3416 strcmp(gameInfo.black, star_match[2]) == 0) {
3418 ics_getting_history = H_GOT_REQ_HEADER;
3421 case H_GOT_REQ_HEADER:
3422 case H_GOT_UNREQ_HEADER:
3423 case H_GOT_UNWANTED_HEADER:
3424 case H_GETTING_MOVES:
3425 /* Should not happen */
3426 DisplayError(_("Error gathering move list: two headers"), 0);
3427 ics_getting_history = H_FALSE;
3431 /* Save player ratings into gameInfo if needed */
3432 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3433 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3434 (gameInfo.whiteRating == -1 ||
3435 gameInfo.blackRating == -1)) {
3437 gameInfo.whiteRating = string_to_rating(star_match[1]);
3438 gameInfo.blackRating = string_to_rating(star_match[3]);
3439 if (appData.debugMode)
3440 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3441 gameInfo.whiteRating, gameInfo.blackRating);
3446 if (looking_at(buf, &i,
3447 "* * match, initial time: * minute*, increment: * second")) {
3448 /* Header for a move list -- second line */
3449 /* Initial board will follow if this is a wild game */
3450 if (gameInfo.event != NULL) free(gameInfo.event);
3451 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3452 gameInfo.event = StrSave(str);
3453 /* [HGM] we switched variant. Translate boards if needed. */
3454 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3458 if (looking_at(buf, &i, "Move ")) {
3459 /* Beginning of a move list */
3460 switch (ics_getting_history) {
3462 /* Normally should not happen */
3463 /* Maybe user hit reset while we were parsing */
3466 /* Happens if we are ignoring a move list that is not
3467 * the one we just requested. Common if the user
3468 * tries to observe two games without turning off
3471 case H_GETTING_MOVES:
3472 /* Should not happen */
3473 DisplayError(_("Error gathering move list: nested"), 0);
3474 ics_getting_history = H_FALSE;
3476 case H_GOT_REQ_HEADER:
3477 ics_getting_history = H_GETTING_MOVES;
3478 started = STARTED_MOVES;
3480 if (oldi > next_out) {
3481 SendToPlayer(&buf[next_out], oldi - next_out);
3484 case H_GOT_UNREQ_HEADER:
3485 ics_getting_history = H_GETTING_MOVES;
3486 started = STARTED_MOVES_NOHIDE;
3489 case H_GOT_UNWANTED_HEADER:
3490 ics_getting_history = H_FALSE;
3496 if (looking_at(buf, &i, "% ") ||
3497 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3498 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3499 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3500 soughtPending = FALSE;
3504 if(suppressKibitz) next_out = i;
3505 savingComment = FALSE;
3509 case STARTED_MOVES_NOHIDE:
3510 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3511 parse[parse_pos + i - oldi] = NULLCHAR;
3512 ParseGameHistory(parse);
3514 if (appData.zippyPlay && first.initDone) {
3515 FeedMovesToProgram(&first, forwardMostMove);
3516 if (gameMode == IcsPlayingWhite) {
3517 if (WhiteOnMove(forwardMostMove)) {
3518 if (first.sendTime) {
3519 if (first.useColors) {
3520 SendToProgram("black\n", &first);
3522 SendTimeRemaining(&first, TRUE);
3524 if (first.useColors) {
3525 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3527 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3528 first.maybeThinking = TRUE;
3530 if (first.usePlayother) {
3531 if (first.sendTime) {
3532 SendTimeRemaining(&first, TRUE);
3534 SendToProgram("playother\n", &first);
3540 } else if (gameMode == IcsPlayingBlack) {
3541 if (!WhiteOnMove(forwardMostMove)) {
3542 if (first.sendTime) {
3543 if (first.useColors) {
3544 SendToProgram("white\n", &first);
3546 SendTimeRemaining(&first, FALSE);
3548 if (first.useColors) {
3549 SendToProgram("black\n", &first);
3551 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3552 first.maybeThinking = TRUE;
3554 if (first.usePlayother) {
3555 if (first.sendTime) {
3556 SendTimeRemaining(&first, FALSE);
3558 SendToProgram("playother\n", &first);
3567 if (gameMode == IcsObserving && ics_gamenum == -1) {
3568 /* Moves came from oldmoves or moves command
3569 while we weren't doing anything else.
3571 currentMove = forwardMostMove;
3572 ClearHighlights();/*!!could figure this out*/
3573 flipView = appData.flipView;
3574 DrawPosition(TRUE, boards[currentMove]);
3575 DisplayBothClocks();
3576 snprintf(str, MSG_SIZ, "%s vs. %s",
3577 gameInfo.white, gameInfo.black);
3581 /* Moves were history of an active game */
3582 if (gameInfo.resultDetails != NULL) {
3583 free(gameInfo.resultDetails);
3584 gameInfo.resultDetails = NULL;
3587 HistorySet(parseList, backwardMostMove,
3588 forwardMostMove, currentMove-1);
3589 DisplayMove(currentMove - 1);
3590 if (started == STARTED_MOVES) next_out = i;
3591 started = STARTED_NONE;
3592 ics_getting_history = H_FALSE;
3595 case STARTED_OBSERVE:
3596 started = STARTED_NONE;
3597 SendToICS(ics_prefix);
3598 SendToICS("refresh\n");
3604 if(bookHit) { // [HGM] book: simulate book reply
3605 static char bookMove[MSG_SIZ]; // a bit generous?
3607 programStats.nodes = programStats.depth = programStats.time =
3608 programStats.score = programStats.got_only_move = 0;
3609 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3611 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3612 strcat(bookMove, bookHit);
3613 HandleMachineMove(bookMove, &first);
3618 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3619 started == STARTED_HOLDINGS ||
3620 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3621 /* Accumulate characters in move list or board */
3622 parse[parse_pos++] = buf[i];
3625 /* Start of game messages. Mostly we detect start of game
3626 when the first board image arrives. On some versions
3627 of the ICS, though, we need to do a "refresh" after starting
3628 to observe in order to get the current board right away. */
3629 if (looking_at(buf, &i, "Adding game * to observation list")) {
3630 started = STARTED_OBSERVE;
3634 /* Handle auto-observe */
3635 if (appData.autoObserve &&
3636 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3637 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3639 /* Choose the player that was highlighted, if any. */
3640 if (star_match[0][0] == '\033' ||
3641 star_match[1][0] != '\033') {
3642 player = star_match[0];
3644 player = star_match[2];
3646 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3647 ics_prefix, StripHighlightAndTitle(player));
3650 /* Save ratings from notify string */
3651 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3652 player1Rating = string_to_rating(star_match[1]);
3653 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3654 player2Rating = string_to_rating(star_match[3]);
3656 if (appData.debugMode)
3658 "Ratings from 'Game notification:' %s %d, %s %d\n",
3659 player1Name, player1Rating,
3660 player2Name, player2Rating);
3665 /* Deal with automatic examine mode after a game,
3666 and with IcsObserving -> IcsExamining transition */
3667 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3668 looking_at(buf, &i, "has made you an examiner of game *")) {
3670 int gamenum = atoi(star_match[0]);
3671 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3672 gamenum == ics_gamenum) {
3673 /* We were already playing or observing this game;
3674 no need to refetch history */
3675 gameMode = IcsExamining;
3677 pauseExamForwardMostMove = forwardMostMove;
3678 } else if (currentMove < forwardMostMove) {
3679 ForwardInner(forwardMostMove);
3682 /* I don't think this case really can happen */
3683 SendToICS(ics_prefix);
3684 SendToICS("refresh\n");
3689 /* Error messages */
3690 // if (ics_user_moved) {
3691 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3692 if (looking_at(buf, &i, "Illegal move") ||
3693 looking_at(buf, &i, "Not a legal move") ||
3694 looking_at(buf, &i, "Your king is in check") ||
3695 looking_at(buf, &i, "It isn't your turn") ||
3696 looking_at(buf, &i, "It is not your move")) {
3698 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3699 currentMove = forwardMostMove-1;
3700 DisplayMove(currentMove - 1); /* before DMError */
3701 DrawPosition(FALSE, boards[currentMove]);
3702 SwitchClocks(forwardMostMove-1); // [HGM] race
3703 DisplayBothClocks();
3705 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3711 if (looking_at(buf, &i, "still have time") ||
3712 looking_at(buf, &i, "not out of time") ||
3713 looking_at(buf, &i, "either player is out of time") ||
3714 looking_at(buf, &i, "has timeseal; checking")) {
3715 /* We must have called his flag a little too soon */
3716 whiteFlag = blackFlag = FALSE;
3720 if (looking_at(buf, &i, "added * seconds to") ||
3721 looking_at(buf, &i, "seconds were added to")) {
3722 /* Update the clocks */
3723 SendToICS(ics_prefix);
3724 SendToICS("refresh\n");
3728 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3729 ics_clock_paused = TRUE;
3734 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3735 ics_clock_paused = FALSE;
3740 /* Grab player ratings from the Creating: message.
3741 Note we have to check for the special case when
3742 the ICS inserts things like [white] or [black]. */
3743 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3744 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3746 0 player 1 name (not necessarily white)
3748 2 empty, white, or black (IGNORED)
3749 3 player 2 name (not necessarily black)
3752 The names/ratings are sorted out when the game
3753 actually starts (below).
3755 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3756 player1Rating = string_to_rating(star_match[1]);
3757 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3758 player2Rating = string_to_rating(star_match[4]);
3760 if (appData.debugMode)
3762 "Ratings from 'Creating:' %s %d, %s %d\n",
3763 player1Name, player1Rating,
3764 player2Name, player2Rating);
3769 /* Improved generic start/end-of-game messages */
3770 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3771 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3772 /* If tkind == 0: */
3773 /* star_match[0] is the game number */
3774 /* [1] is the white player's name */
3775 /* [2] is the black player's name */
3776 /* For end-of-game: */
3777 /* [3] is the reason for the game end */
3778 /* [4] is a PGN end game-token, preceded by " " */
3779 /* For start-of-game: */
3780 /* [3] begins with "Creating" or "Continuing" */
3781 /* [4] is " *" or empty (don't care). */
3782 int gamenum = atoi(star_match[0]);
3783 char *whitename, *blackname, *why, *endtoken;
3784 ChessMove endtype = EndOfFile;
3787 whitename = star_match[1];
3788 blackname = star_match[2];
3789 why = star_match[3];
3790 endtoken = star_match[4];
3792 whitename = star_match[1];
3793 blackname = star_match[3];
3794 why = star_match[5];
3795 endtoken = star_match[6];
3798 /* Game start messages */
3799 if (strncmp(why, "Creating ", 9) == 0 ||
3800 strncmp(why, "Continuing ", 11) == 0) {
3801 gs_gamenum = gamenum;
3802 safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3803 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3805 if (appData.zippyPlay) {
3806 ZippyGameStart(whitename, blackname);
3809 partnerBoardValid = FALSE; // [HGM] bughouse
3813 /* Game end messages */
3814 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3815 ics_gamenum != gamenum) {
3818 while (endtoken[0] == ' ') endtoken++;
3819 switch (endtoken[0]) {
3822 endtype = GameUnfinished;
3825 endtype = BlackWins;
3828 if (endtoken[1] == '/')
3829 endtype = GameIsDrawn;
3831 endtype = WhiteWins;
3834 GameEnds(endtype, why, GE_ICS);
3836 if (appData.zippyPlay && first.initDone) {
3837 ZippyGameEnd(endtype, why);
3838 if (first.pr == NULL) {
3839 /* Start the next process early so that we'll
3840 be ready for the next challenge */
3841 StartChessProgram(&first);
3843 /* Send "new" early, in case this command takes
3844 a long time to finish, so that we'll be ready
3845 for the next challenge. */
3846 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3850 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3854 if (looking_at(buf, &i, "Removing game * from observation") ||
3855 looking_at(buf, &i, "no longer observing game *") ||
3856 looking_at(buf, &i, "Game * (*) has no examiners")) {
3857 if (gameMode == IcsObserving &&
3858 atoi(star_match[0]) == ics_gamenum)
3860 /* icsEngineAnalyze */
3861 if (appData.icsEngineAnalyze) {
3868 ics_user_moved = FALSE;
3873 if (looking_at(buf, &i, "no longer examining game *")) {
3874 if (gameMode == IcsExamining &&
3875 atoi(star_match[0]) == ics_gamenum)
3879 ics_user_moved = FALSE;
3884 /* Advance leftover_start past any newlines we find,
3885 so only partial lines can get reparsed */
3886 if (looking_at(buf, &i, "\n")) {
3887 prevColor = curColor;
3888 if (curColor != ColorNormal) {
3889 if (oldi > next_out) {
3890 SendToPlayer(&buf[next_out], oldi - next_out);
3893 Colorize(ColorNormal, FALSE);
3894 curColor = ColorNormal;
3896 if (started == STARTED_BOARD) {
3897 started = STARTED_NONE;
3898 parse[parse_pos] = NULLCHAR;
3899 ParseBoard12(parse);
3902 /* Send premove here */
3903 if (appData.premove) {
3905 if (currentMove == 0 &&
3906 gameMode == IcsPlayingWhite &&
3907 appData.premoveWhite) {
3908 snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3909 if (appData.debugMode)
3910 fprintf(debugFP, "Sending premove:\n");
3912 } else if (currentMove == 1 &&
3913 gameMode == IcsPlayingBlack &&
3914 appData.premoveBlack) {
3915 snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3916 if (appData.debugMode)
3917 fprintf(debugFP, "Sending premove:\n");
3919 } else if (gotPremove) {
3921 ClearPremoveHighlights();
3922 if (appData.debugMode)
3923 fprintf(debugFP, "Sending premove:\n");
3924 UserMoveEvent(premoveFromX, premoveFromY,
3925 premoveToX, premoveToY,
3930 /* Usually suppress following prompt */
3931 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3932 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3933 if (looking_at(buf, &i, "*% ")) {
3934 savingComment = FALSE;
3939 } else if (started == STARTED_HOLDINGS) {
3941 char new_piece[MSG_SIZ];
3942 started = STARTED_NONE;
3943 parse[parse_pos] = NULLCHAR;
3944 if (appData.debugMode)
3945 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3946 parse, currentMove);
3947 if (sscanf(parse, " game %d", &gamenum) == 1) {
3948 if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3949 if (gameInfo.variant == VariantNormal) {
3950 /* [HGM] We seem to switch variant during a game!
3951 * Presumably no holdings were displayed, so we have
3952 * to move the position two files to the right to
3953 * create room for them!
3955 VariantClass newVariant;
3956 switch(gameInfo.boardWidth) { // base guess on board width
3957 case 9: newVariant = VariantShogi; break;
3958 case 10: newVariant = VariantGreat; break;
3959 default: newVariant = VariantCrazyhouse; break;
3961 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3962 /* Get a move list just to see the header, which
3963 will tell us whether this is really bug or zh */
3964 if (ics_getting_history == H_FALSE) {
3965 ics_getting_history = H_REQUESTED;
3966 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
3970 new_piece[0] = NULLCHAR;
3971 sscanf(parse, "game %d white [%s black [%s <- %s",
3972 &gamenum, white_holding, black_holding,
3974 white_holding[strlen(white_holding)-1] = NULLCHAR;
3975 black_holding[strlen(black_holding)-1] = NULLCHAR;
3976 /* [HGM] copy holdings to board holdings area */
3977 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3978 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3979 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3981 if (appData.zippyPlay && first.initDone) {
3982 ZippyHoldings(white_holding, black_holding,
3986 if (tinyLayout || smallLayout) {
3987 char wh[16], bh[16];
3988 PackHolding(wh, white_holding);
3989 PackHolding(bh, black_holding);
3990 snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
3991 gameInfo.white, gameInfo.black);
3993 snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
3994 gameInfo.white, white_holding,
3995 gameInfo.black, black_holding);
3997 if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3998 DrawPosition(FALSE, boards[currentMove]);
4000 } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4001 sscanf(parse, "game %d white [%s black [%s <- %s",
4002 &gamenum, white_holding, black_holding,
4004 white_holding[strlen(white_holding)-1] = NULLCHAR;
4005 black_holding[strlen(black_holding)-1] = NULLCHAR;
4006 /* [HGM] copy holdings to partner-board holdings area */
4007 CopyHoldings(partnerBoard, white_holding, WhitePawn);
4008 CopyHoldings(partnerBoard, black_holding, BlackPawn);
4009 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4010 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4011 if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4014 /* Suppress following prompt */
4015 if (looking_at(buf, &i, "*% ")) {
4016 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4017 savingComment = FALSE;
4025 i++; /* skip unparsed character and loop back */
4028 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4029 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4030 // SendToPlayer(&buf[next_out], i - next_out);
4031 started != STARTED_HOLDINGS && leftover_start > next_out) {
4032 SendToPlayer(&buf[next_out], leftover_start - next_out);
4036 leftover_len = buf_len - leftover_start;
4037 /* if buffer ends with something we couldn't parse,
4038 reparse it after appending the next read */
4040 } else if (count == 0) {
4041 RemoveInputSource(isr);
4042 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4044 DisplayFatalError(_("Error reading from ICS"), error, 1);
4049 /* Board style 12 looks like this:
4051 <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
4053 * The "<12> " is stripped before it gets to this routine. The two
4054 * trailing 0's (flip state and clock ticking) are later addition, and
4055 * some chess servers may not have them, or may have only the first.
4056 * Additional trailing fields may be added in the future.
4059 #define PATTERN "%c%d%d%d%d%d%d%d%s%s%d%d%d%d%d%d%d%d%s%s%s%d%d"
4061 #define RELATION_OBSERVING_PLAYED 0
4062 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
4063 #define RELATION_PLAYING_MYMOVE 1
4064 #define RELATION_PLAYING_NOTMYMOVE -1
4065 #define RELATION_EXAMINING 2
4066 #define RELATION_ISOLATED_BOARD -3
4067 #define RELATION_STARTING_POSITION -4 /* FICS only */
4070 ParseBoard12(string)
4073 GameMode newGameMode;
4074 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4075 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4076 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4077 char to_play, board_chars[200];
4078 char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4079 char black[32], white[32];
4081 int prevMove = currentMove;
4084 int fromX, fromY, toX, toY;
4086 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4087 char *bookHit = NULL; // [HGM] book
4088 Boolean weird = FALSE, reqFlag = FALSE;
4090 fromX = fromY = toX = toY = -1;
4094 if (appData.debugMode)
4095 fprintf(debugFP, _("Parsing board: %s\n"), string);
4097 move_str[0] = NULLCHAR;
4098 elapsed_time[0] = NULLCHAR;
4099 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4101 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4102 if(string[i] == ' ') { ranks++; files = 0; }
4104 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4107 for(j = 0; j <i; j++) board_chars[j] = string[j];
4108 board_chars[i] = '\0';
4111 n = sscanf(string, PATTERN, &to_play, &double_push,
4112 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4113 &gamenum, white, black, &relation, &basetime, &increment,
4114 &white_stren, &black_stren, &white_time, &black_time,
4115 &moveNum, str, elapsed_time, move_str, &ics_flip,
4119 snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4120 DisplayError(str, 0);
4124 /* Convert the move number to internal form */
4125 moveNum = (moveNum - 1) * 2;
4126 if (to_play == 'B') moveNum++;
4127 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4128 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4134 case RELATION_OBSERVING_PLAYED:
4135 case RELATION_OBSERVING_STATIC:
4136 if (gamenum == -1) {
4137 /* Old ICC buglet */
4138 relation = RELATION_OBSERVING_STATIC;
4140 newGameMode = IcsObserving;
4142 case RELATION_PLAYING_MYMOVE:
4143 case RELATION_PLAYING_NOTMYMOVE:
4145 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4146 IcsPlayingWhite : IcsPlayingBlack;
4148 case RELATION_EXAMINING:
4149 newGameMode = IcsExamining;
4151 case RELATION_ISOLATED_BOARD:
4153 /* Just display this board. If user was doing something else,
4154 we will forget about it until the next board comes. */
4155 newGameMode = IcsIdle;
4157 case RELATION_STARTING_POSITION:
4158 newGameMode = gameMode;
4162 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4163 && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4164 // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4166 for (k = 0; k < ranks; k++) {
4167 for (j = 0; j < files; j++)
4168 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4169 if(gameInfo.holdingsWidth > 1) {
4170 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4171 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4174 CopyBoard(partnerBoard, board);
4175 if(toSqr = strchr(str, '/')) { // extract highlights from long move
4176 partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4177 partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4178 } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4179 if(toSqr = strchr(str, '-')) {
4180 partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4181 partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4182 } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4183 if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4184 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4185 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4186 if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4187 snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4188 (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4189 DisplayMessage(partnerStatus, "");
4190 partnerBoardValid = TRUE;
4194 /* Modify behavior for initial board display on move listing
4197 switch (ics_getting_history) {
4201 case H_GOT_REQ_HEADER:
4202 case H_GOT_UNREQ_HEADER:
4203 /* This is the initial position of the current game */
4204 gamenum = ics_gamenum;
4205 moveNum = 0; /* old ICS bug workaround */
4206 if (to_play == 'B') {
4207 startedFromSetupPosition = TRUE;
4208 blackPlaysFirst = TRUE;
4210 if (forwardMostMove == 0) forwardMostMove = 1;
4211 if (backwardMostMove == 0) backwardMostMove = 1;
4212 if (currentMove == 0) currentMove = 1;
4214 newGameMode = gameMode;
4215 relation = RELATION_STARTING_POSITION; /* ICC needs this */
4217 case H_GOT_UNWANTED_HEADER:
4218 /* This is an initial board that we don't want */
4220 case H_GETTING_MOVES:
4221 /* Should not happen */
4222 DisplayError(_("Error gathering move list: extra board"), 0);
4223 ics_getting_history = H_FALSE;
4227 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4228 weird && (int)gameInfo.variant < (int)VariantShogi) {
4229 /* [HGM] We seem to have switched variant unexpectedly
4230 * Try to guess new variant from board size
4232 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4233 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4234 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4235 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4236 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
4237 if(!weird) newVariant = VariantNormal;
4238 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4239 /* Get a move list just to see the header, which
4240 will tell us whether this is really bug or zh */
4241 if (ics_getting_history == H_FALSE) {
4242 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4243 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4248 /* Take action if this is the first board of a new game, or of a
4249 different game than is currently being displayed. */
4250 if (gamenum != ics_gamenum || newGameMode != gameMode ||
4251 relation == RELATION_ISOLATED_BOARD) {
4253 /* Forget the old game and get the history (if any) of the new one */
4254 if (gameMode != BeginningOfGame) {
4258 if (appData.autoRaiseBoard) BoardToTop();
4260 if (gamenum == -1) {
4261 newGameMode = IcsIdle;
4262 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4263 appData.getMoveList && !reqFlag) {
4264 /* Need to get game history */
4265 ics_getting_history = H_REQUESTED;
4266 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4270 /* Initially flip the board to have black on the bottom if playing
4271 black or if the ICS flip flag is set, but let the user change
4272 it with the Flip View button. */
4273 flipView = appData.autoFlipView ?
4274 (newGameMode == IcsPlayingBlack) || ics_flip :
4277 /* Done with values from previous mode; copy in new ones */
4278 gameMode = newGameMode;
4280 ics_gamenum = gamenum;
4281 if (gamenum == gs_gamenum) {
4282 int klen = strlen(gs_kind);
4283 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4284 snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4285 gameInfo.event = StrSave(str);
4287 gameInfo.event = StrSave("ICS game");
4289 gameInfo.site = StrSave(appData.icsHost);
4290 gameInfo.date = PGNDate();
4291 gameInfo.round = StrSave("-");
4292 gameInfo.white = StrSave(white);
4293 gameInfo.black = StrSave(black);
4294 timeControl = basetime * 60 * 1000;
4296 timeIncrement = increment * 1000;
4297 movesPerSession = 0;
4298 gameInfo.timeControl = TimeControlTagValue();
4299 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4300 if (appData.debugMode) {
4301 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4302 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4303 setbuf(debugFP, NULL);
4306 gameInfo.outOfBook = NULL;
4308 /* Do we have the ratings? */
4309 if (strcmp(player1Name, white) == 0 &&
4310 strcmp(player2Name, black) == 0) {
4311 if (appData.debugMode)
4312 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4313 player1Rating, player2Rating);
4314 gameInfo.whiteRating = player1Rating;
4315 gameInfo.blackRating = player2Rating;
4316 } else if (strcmp(player2Name, white) == 0 &&
4317 strcmp(player1Name, black) == 0) {
4318 if (appData.debugMode)
4319 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4320 player2Rating, player1Rating);
4321 gameInfo.whiteRating = player2Rating;
4322 gameInfo.blackRating = player1Rating;
4324 player1Name[0] = player2Name[0] = NULLCHAR;
4326 /* Silence shouts if requested */
4327 if (appData.quietPlay &&
4328 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4329 SendToICS(ics_prefix);
4330 SendToICS("set shout 0\n");
4334 /* Deal with midgame name changes */
4336 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4337 if (gameInfo.white) free(gameInfo.white);
4338 gameInfo.white = StrSave(white);
4340 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4341 if (gameInfo.black) free(gameInfo.black);
4342 gameInfo.black = StrSave(black);
4346 /* Throw away game result if anything actually changes in examine mode */
4347 if (gameMode == IcsExamining && !newGame) {
4348 gameInfo.result = GameUnfinished;
4349 if (gameInfo.resultDetails != NULL) {
4350 free(gameInfo.resultDetails);
4351 gameInfo.resultDetails = NULL;
4355 /* In pausing && IcsExamining mode, we ignore boards coming
4356 in if they are in a different variation than we are. */
4357 if (pauseExamInvalid) return;
4358 if (pausing && gameMode == IcsExamining) {
4359 if (moveNum <= pauseExamForwardMostMove) {
4360 pauseExamInvalid = TRUE;
4361 forwardMostMove = pauseExamForwardMostMove;
4366 if (appData.debugMode) {
4367 fprintf(debugFP, "load %dx%d board\n", files, ranks);
4369 /* Parse the board */
4370 for (k = 0; k < ranks; k++) {
4371 for (j = 0; j < files; j++)
4372 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4373 if(gameInfo.holdingsWidth > 1) {
4374 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4375 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4378 CopyBoard(boards[moveNum], board);
4379 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4381 startedFromSetupPosition =
4382 !CompareBoards(board, initialPosition);
4383 if(startedFromSetupPosition)
4384 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4387 /* [HGM] Set castling rights. Take the outermost Rooks,
4388 to make it also work for FRC opening positions. Note that board12
4389 is really defective for later FRC positions, as it has no way to
4390 indicate which Rook can castle if they are on the same side of King.
4391 For the initial position we grant rights to the outermost Rooks,
4392 and remember thos rights, and we then copy them on positions
4393 later in an FRC game. This means WB might not recognize castlings with
4394 Rooks that have moved back to their original position as illegal,
4395 but in ICS mode that is not its job anyway.
4397 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4398 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4400 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4401 if(board[0][i] == WhiteRook) j = i;
4402 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4403 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4404 if(board[0][i] == WhiteRook) j = i;
4405 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4406 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4407 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4408 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4409 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4410 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4411 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4413 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4414 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4415 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4416 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4417 if(board[BOARD_HEIGHT-1][k] == bKing)
4418 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4419 if(gameInfo.variant == VariantTwoKings) {
4420 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4421 if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4422 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4425 r = boards[moveNum][CASTLING][0] = initialRights[0];
4426 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4427 r = boards[moveNum][CASTLING][1] = initialRights[1];
4428 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4429 r = boards[moveNum][CASTLING][3] = initialRights[3];
4430 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4431 r = boards[moveNum][CASTLING][4] = initialRights[4];
4432 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4433 /* wildcastle kludge: always assume King has rights */
4434 r = boards[moveNum][CASTLING][2] = initialRights[2];
4435 r = boards[moveNum][CASTLING][5] = initialRights[5];
4437 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4438 boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4441 if (ics_getting_history == H_GOT_REQ_HEADER ||
4442 ics_getting_history == H_GOT_UNREQ_HEADER) {
4443 /* This was an initial position from a move list, not
4444 the current position */
4448 /* Update currentMove and known move number limits */
4449 newMove = newGame || moveNum > forwardMostMove;
4452 forwardMostMove = backwardMostMove = currentMove = moveNum;
4453 if (gameMode == IcsExamining && moveNum == 0) {
4454 /* Workaround for ICS limitation: we are not told the wild
4455 type when starting to examine a game. But if we ask for
4456 the move list, the move list header will tell us */
4457 ics_getting_history = H_REQUESTED;
4458 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4461 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4462 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4464 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4465 /* [HGM] applied this also to an engine that is silently watching */
4466 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4467 (gameMode == IcsObserving || gameMode == IcsExamining) &&
4468 gameInfo.variant == currentlyInitializedVariant) {
4469 takeback = forwardMostMove - moveNum;
4470 for (i = 0; i < takeback; i++) {
4471 if (appData.debugMode) fprintf(debugFP, "take back move\n");
4472 SendToProgram("undo\n", &first);
4477 forwardMostMove = moveNum;
4478 if (!pausing || currentMove > forwardMostMove)
4479 currentMove = forwardMostMove;
4481 /* New part of history that is not contiguous with old part */
4482 if (pausing && gameMode == IcsExamining) {
4483 pauseExamInvalid = TRUE;
4484 forwardMostMove = pauseExamForwardMostMove;
4487 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4489 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4490 // [HGM] when we will receive the move list we now request, it will be
4491 // fed to the engine from the first move on. So if the engine is not
4492 // in the initial position now, bring it there.
4493 InitChessProgram(&first, 0);
4496 ics_getting_history = H_REQUESTED;
4497 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4500 forwardMostMove = backwardMostMove = currentMove = moveNum;
4503 /* Update the clocks */
4504 if (strchr(elapsed_time, '.')) {
4506 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4507 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4509 /* Time is in seconds */
4510 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4511 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4516 if (appData.zippyPlay && newGame &&
4517 gameMode != IcsObserving && gameMode != IcsIdle &&
4518 gameMode != IcsExamining)
4519 ZippyFirstBoard(moveNum, basetime, increment);
4522 /* Put the move on the move list, first converting
4523 to canonical algebraic form. */
4525 if (appData.debugMode) {
4526 if (appData.debugMode) { int f = forwardMostMove;
4527 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4528 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4529 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4531 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4532 fprintf(debugFP, "moveNum = %d\n", moveNum);
4533 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4534 setbuf(debugFP, NULL);
4536 if (moveNum <= backwardMostMove) {
4537 /* We don't know what the board looked like before
4539 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4540 strcat(parseList[moveNum - 1], " ");
4541 strcat(parseList[moveNum - 1], elapsed_time);
4542 moveList[moveNum - 1][0] = NULLCHAR;
4543 } else if (strcmp(move_str, "none") == 0) {
4544 // [HGM] long SAN: swapped order; test for 'none' before parsing move
4545 /* Again, we don't know what the board looked like;
4546 this is really the start of the game. */
4547 parseList[moveNum - 1][0] = NULLCHAR;
4548 moveList[moveNum - 1][0] = NULLCHAR;
4549 backwardMostMove = moveNum;
4550 startedFromSetupPosition = TRUE;
4551 fromX = fromY = toX = toY = -1;
4553 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4554 // So we parse the long-algebraic move string in stead of the SAN move
4555 int valid; char buf[MSG_SIZ], *prom;
4557 if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4558 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4559 // str looks something like "Q/a1-a2"; kill the slash
4561 snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4562 else safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4563 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4564 strcat(buf, prom); // long move lacks promo specification!
4565 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4566 if(appData.debugMode)
4567 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4568 safeStrCpy(move_str, buf, MSG_SIZ);
4570 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4571 &fromX, &fromY, &toX, &toY, &promoChar)
4572 || ParseOneMove(buf, moveNum - 1, &moveType,
4573 &fromX, &fromY, &toX, &toY, &promoChar);
4574 // end of long SAN patch
4576 (void) CoordsToAlgebraic(boards[moveNum - 1],
4577 PosFlags(moveNum - 1),
4578 fromY, fromX, toY, toX, promoChar,
4579 parseList[moveNum-1]);
4580 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4586 if(gameInfo.variant != VariantShogi)
4587 strcat(parseList[moveNum - 1], "+");
4590 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4591 strcat(parseList[moveNum - 1], "#");
4594 strcat(parseList[moveNum - 1], " ");
4595 strcat(parseList[moveNum - 1], elapsed_time);
4596 /* currentMoveString is set as a side-effect of ParseOneMove */
4597 if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4598 safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4599 strcat(moveList[moveNum - 1], "\n");
4601 if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper
4602 && gameInfo.variant != VariantGreat) // inherit info that ICS does not give from previous board
4603 for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4604 ChessSquare old, new = boards[moveNum][k][j];
4605 if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4606 old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4607 if(old == new) continue;
4608 if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4609 else if(new == WhiteWazir || new == BlackWazir) {
4610 if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4611 boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4612 else boards[moveNum][k][j] = old; // preserve type of Gold
4613 } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4614 boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4617 /* Move from ICS was illegal!? Punt. */
4618 if (appData.debugMode) {
4619 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4620 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4622 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4623 strcat(parseList[moveNum - 1], " ");
4624 strcat(parseList[moveNum - 1], elapsed_time);
4625 moveList[moveNum - 1][0] = NULLCHAR;
4626 fromX = fromY = toX = toY = -1;
4629 if (appData.debugMode) {
4630 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4631 setbuf(debugFP, NULL);
4635 /* Send move to chess program (BEFORE animating it). */
4636 if (appData.zippyPlay && !newGame && newMove &&
4637 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4639 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4640 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4641 if (moveList[moveNum - 1][0] == NULLCHAR) {
4642 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4644 DisplayError(str, 0);
4646 if (first.sendTime) {
4647 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4649 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4650 if (firstMove && !bookHit) {
4652 if (first.useColors) {
4653 SendToProgram(gameMode == IcsPlayingWhite ?
4655 "black\ngo\n", &first);
4657 SendToProgram("go\n", &first);
4659 first.maybeThinking = TRUE;
4662 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4663 if (moveList[moveNum - 1][0] == NULLCHAR) {
4664 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4665 DisplayError(str, 0);
4667 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4668 SendMoveToProgram(moveNum - 1, &first);
4675 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4676 /* If move comes from a remote source, animate it. If it
4677 isn't remote, it will have already been animated. */
4678 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4679 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4681 if (!pausing && appData.highlightLastMove) {
4682 SetHighlights(fromX, fromY, toX, toY);
4686 /* Start the clocks */
4687 whiteFlag = blackFlag = FALSE;
4688 appData.clockMode = !(basetime == 0 && increment == 0);
4690 ics_clock_paused = TRUE;
4692 } else if (ticking == 1) {
4693 ics_clock_paused = FALSE;
4695 if (gameMode == IcsIdle ||
4696 relation == RELATION_OBSERVING_STATIC ||
4697 relation == RELATION_EXAMINING ||
4699 DisplayBothClocks();
4703 /* Display opponents and material strengths */
4704 if (gameInfo.variant != VariantBughouse &&
4705 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4706 if (tinyLayout || smallLayout) {
4707 if(gameInfo.variant == VariantNormal)
4708 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4709 gameInfo.white, white_stren, gameInfo.black, black_stren,
4710 basetime, increment);
4712 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4713 gameInfo.white, white_stren, gameInfo.black, black_stren,
4714 basetime, increment, (int) gameInfo.variant);
4716 if(gameInfo.variant == VariantNormal)
4717 snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4718 gameInfo.white, white_stren, gameInfo.black, black_stren,
4719 basetime, increment);
4721 snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4722 gameInfo.white, white_stren, gameInfo.black, black_stren,
4723 basetime, increment, VariantName(gameInfo.variant));
4726 if (appData.debugMode) {
4727 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4732 /* Display the board */
4733 if (!pausing && !appData.noGUI) {
4735 if (appData.premove)
4737 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4738 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4739 ClearPremoveHighlights();
4741 j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4742 if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4743 DrawPosition(j, boards[currentMove]);
4745 DisplayMove(moveNum - 1);
4746 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4747 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4748 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4749 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4753 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4755 if(bookHit) { // [HGM] book: simulate book reply
4756 static char bookMove[MSG_SIZ]; // a bit generous?
4758 programStats.nodes = programStats.depth = programStats.time =
4759 programStats.score = programStats.got_only_move = 0;
4760 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4762 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4763 strcat(bookMove, bookHit);
4764 HandleMachineMove(bookMove, &first);
4773 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4774 ics_getting_history = H_REQUESTED;
4775 snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4781 AnalysisPeriodicEvent(force)
4784 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4785 && !force) || !appData.periodicUpdates)
4788 /* Send . command to Crafty to collect stats */
4789 SendToProgram(".\n", &first);
4791 /* Don't send another until we get a response (this makes
4792 us stop sending to old Crafty's which don't understand
4793 the "." command (sending illegal cmds resets node count & time,
4794 which looks bad)) */
4795 programStats.ok_to_send = 0;
4798 void ics_update_width(new_width)
4801 ics_printf("set width %d\n", new_width);
4805 SendMoveToProgram(moveNum, cps)
4807 ChessProgramState *cps;
4811 if (cps->useUsermove) {
4812 SendToProgram("usermove ", cps);
4816 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4817 int len = space - parseList[moveNum];
4818 memcpy(buf, parseList[moveNum], len);
4820 buf[len] = NULLCHAR;
4822 snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4824 SendToProgram(buf, cps);
4826 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4827 AlphaRank(moveList[moveNum], 4);
4828 SendToProgram(moveList[moveNum], cps);
4829 AlphaRank(moveList[moveNum], 4); // and back
4831 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4832 * the engine. It would be nice to have a better way to identify castle
4834 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4835 && cps->useOOCastle) {
4836 int fromX = moveList[moveNum][0] - AAA;
4837 int fromY = moveList[moveNum][1] - ONE;
4838 int toX = moveList[moveNum][2] - AAA;
4839 int toY = moveList[moveNum][3] - ONE;
4840 if((boards[moveNum][fromY][fromX] == WhiteKing
4841 && boards[moveNum][toY][toX] == WhiteRook)
4842 || (boards[moveNum][fromY][fromX] == BlackKing
4843 && boards[moveNum][toY][toX] == BlackRook)) {
4844 if(toX > fromX) SendToProgram("O-O\n", cps);
4845 else SendToProgram("O-O-O\n", cps);
4847 else SendToProgram(moveList[moveNum], cps);
4849 else SendToProgram(moveList[moveNum], cps);
4850 /* End of additions by Tord */
4853 /* [HGM] setting up the opening has brought engine in force mode! */
4854 /* Send 'go' if we are in a mode where machine should play. */
4855 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4856 (gameMode == TwoMachinesPlay ||
4858 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4860 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4861 SendToProgram("go\n", cps);
4862 if (appData.debugMode) {
4863 fprintf(debugFP, "(extra)\n");
4866 setboardSpoiledMachineBlack = 0;
4870 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4872 int fromX, fromY, toX, toY;
4875 char user_move[MSG_SIZ];
4879 snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4880 (int)moveType, fromX, fromY, toX, toY);
4881 DisplayError(user_move + strlen("say "), 0);
4883 case WhiteKingSideCastle:
4884 case BlackKingSideCastle:
4885 case WhiteQueenSideCastleWild:
4886 case BlackQueenSideCastleWild:
4888 case WhiteHSideCastleFR:
4889 case BlackHSideCastleFR:
4891 snprintf(user_move, MSG_SIZ, "o-o\n");
4893 case WhiteQueenSideCastle:
4894 case BlackQueenSideCastle:
4895 case WhiteKingSideCastleWild:
4896 case BlackKingSideCastleWild:
4898 case WhiteASideCastleFR:
4899 case BlackASideCastleFR:
4901 snprintf(user_move, MSG_SIZ, "o-o-o\n");
4903 case WhiteNonPromotion:
4904 case BlackNonPromotion:
4905 sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4907 case WhitePromotion:
4908 case BlackPromotion:
4909 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4910 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4911 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4912 PieceToChar(WhiteFerz));
4913 else if(gameInfo.variant == VariantGreat)
4914 snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4915 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4916 PieceToChar(WhiteMan));
4918 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4919 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4925 snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4926 ToUpper(PieceToChar((ChessSquare) fromX)),
4927 AAA + toX, ONE + toY);
4929 case IllegalMove: /* could be a variant we don't quite understand */
4930 if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4932 case WhiteCapturesEnPassant:
4933 case BlackCapturesEnPassant:
4934 snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4935 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4938 SendToICS(user_move);
4939 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4940 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4945 { // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4946 int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4947 static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4948 if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4949 DisplayError("You cannot do this while you are playing or observing", 0);
4952 if(gameMode != IcsExamining) { // is this ever not the case?
4953 char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4955 if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4956 snprintf(command,MSG_SIZ, "match %s", ics_handle);
4957 } else { // on FICS we must first go to general examine mode
4958 safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
4960 if(gameInfo.variant != VariantNormal) {
4961 // try figure out wild number, as xboard names are not always valid on ICS
4962 for(i=1; i<=36; i++) {
4963 snprintf(buf, MSG_SIZ, "wild/%d", i);
4964 if(StringToVariant(buf) == gameInfo.variant) break;
4966 if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
4967 else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
4968 else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
4969 } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4970 SendToICS(ics_prefix);
4972 if(startedFromSetupPosition || backwardMostMove != 0) {
4973 fen = PositionToFEN(backwardMostMove, NULL);
4974 if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4975 snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
4977 } else { // FICS: everything has to set by separate bsetup commands
4978 p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4979 snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
4981 if(!WhiteOnMove(backwardMostMove)) {
4982 SendToICS("bsetup tomove black\n");
4984 i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4985 snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
4987 i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4988 snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
4990 i = boards[backwardMostMove][EP_STATUS];
4991 if(i >= 0) { // set e.p.
4992 snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
4998 if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4999 SendToICS("bsetup done\n"); // switch to normal examining.
5001 for(i = backwardMostMove; i<last; i++) {
5003 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5006 SendToICS(ics_prefix);
5007 SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5011 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
5016 if (rf == DROP_RANK) {
5017 sprintf(move, "%c@%c%c\n",
5018 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5020 if (promoChar == 'x' || promoChar == NULLCHAR) {
5021 sprintf(move, "%c%c%c%c\n",
5022 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5024 sprintf(move, "%c%c%c%c%c\n",
5025 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5031 ProcessICSInitScript(f)
5036 while (fgets(buf, MSG_SIZ, f)) {
5037 SendToICSDelayed(buf,(long)appData.msLoginDelay);
5044 static int lastX, lastY, selectFlag, dragging;
5049 ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5050 if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5051 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5052 if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5053 if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5054 if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5057 if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5058 else if((int)promoSweep == -1) promoSweep = WhiteKing;
5059 else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5060 else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5062 } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5063 appData.testLegality && (promoSweep == king ||
5064 gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5065 ChangeDragPiece(promoSweep);
5068 int PromoScroll(int x, int y)
5072 if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5073 if(abs(x - lastX) < 15 && abs(y - lastY) < 15) return FALSE;
5074 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5075 if(!step) return FALSE;
5076 lastX = x; lastY = y;
5077 if((promoSweep < BlackPawn) == flipView) step = -step;
5078 if(step > 0) selectFlag = 1;
5079 if(!selectFlag) Sweep(step);
5086 ChessSquare piece = boards[currentMove][toY][toX];
5089 if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5090 if((int)pieceSweep == -1) pieceSweep = BlackKing;
5091 if(!step) step = -1;
5092 } while(PieceToChar(pieceSweep) == '.');
5093 boards[currentMove][toY][toX] = pieceSweep;
5094 DrawPosition(FALSE, boards[currentMove]);
5095 boards[currentMove][toY][toX] = piece;
5097 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5099 AlphaRank(char *move, int n)
5101 // char *p = move, c; int x, y;
5103 if (appData.debugMode) {
5104 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5108 move[2]>='0' && move[2]<='9' &&
5109 move[3]>='a' && move[3]<='x' ) {
5111 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5112 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5114 if(move[0]>='0' && move[0]<='9' &&
5115 move[1]>='a' && move[1]<='x' &&
5116 move[2]>='0' && move[2]<='9' &&
5117 move[3]>='a' && move[3]<='x' ) {
5118 /* input move, Shogi -> normal */
5119 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
5120 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5121 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5122 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5125 move[3]>='0' && move[3]<='9' &&
5126 move[2]>='a' && move[2]<='x' ) {
5128 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5129 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5132 move[0]>='a' && move[0]<='x' &&
5133 move[3]>='0' && move[3]<='9' &&
5134 move[2]>='a' && move[2]<='x' ) {
5135 /* output move, normal -> Shogi */
5136 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5137 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5138 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5139 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5140 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5142 if (appData.debugMode) {
5143 fprintf(debugFP, " out = '%s'\n", move);
5147 char yy_textstr[8000];
5149 /* Parser for moves from gnuchess, ICS, or user typein box */
5151 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
5154 ChessMove *moveType;
5155 int *fromX, *fromY, *toX, *toY;
5158 *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5160 switch (*moveType) {
5161 case WhitePromotion:
5162 case BlackPromotion:
5163 case WhiteNonPromotion:
5164 case BlackNonPromotion:
5166 case WhiteCapturesEnPassant:
5167 case BlackCapturesEnPassant:
5168 case WhiteKingSideCastle:
5169 case WhiteQueenSideCastle:
5170 case BlackKingSideCastle:
5171 case BlackQueenSideCastle:
5172 case WhiteKingSideCastleWild:
5173 case WhiteQueenSideCastleWild:
5174 case BlackKingSideCastleWild:
5175 case BlackQueenSideCastleWild:
5176 /* Code added by Tord: */
5177 case WhiteHSideCastleFR:
5178 case WhiteASideCastleFR:
5179 case BlackHSideCastleFR:
5180 case BlackASideCastleFR:
5181 /* End of code added by Tord */
5182 case IllegalMove: /* bug or odd chess variant */
5183 *fromX = currentMoveString[0] - AAA;
5184 *fromY = currentMoveString[1] - ONE;
5185 *toX = currentMoveString[2] - AAA;
5186 *toY = currentMoveString[3] - ONE;
5187 *promoChar = currentMoveString[4];
5188 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5189 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5190 if (appData.debugMode) {
5191 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5193 *fromX = *fromY = *toX = *toY = 0;
5196 if (appData.testLegality) {
5197 return (*moveType != IllegalMove);
5199 return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5200 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5205 *fromX = *moveType == WhiteDrop ?
5206 (int) CharToPiece(ToUpper(currentMoveString[0])) :
5207 (int) CharToPiece(ToLower(currentMoveString[0]));
5209 *toX = currentMoveString[2] - AAA;
5210 *toY = currentMoveString[3] - ONE;
5211 *promoChar = NULLCHAR;
5215 case ImpossibleMove:
5225 if (appData.debugMode) {
5226 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5229 *fromX = *fromY = *toX = *toY = 0;
5230 *promoChar = NULLCHAR;
5235 Boolean pushed = FALSE;
5238 ParsePV(char *pv, Boolean storeComments)
5239 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5240 int fromX, fromY, toX, toY; char promoChar;
5245 if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5246 PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5249 endPV = forwardMostMove;
5251 while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5252 if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5253 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5254 if(appData.debugMode){
5255 fprintf(debugFP,"parsePV: %d %c%c%c%c yy='%s'\nPV = '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, yy_textstr, pv);
5257 if(!valid && nr == 0 &&
5258 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5259 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5260 // Hande case where played move is different from leading PV move
5261 CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5262 CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5263 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5264 if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5265 endPV += 2; // if position different, keep this
5266 moveList[endPV-1][0] = fromX + AAA;
5267 moveList[endPV-1][1] = fromY + ONE;
5268 moveList[endPV-1][2] = toX + AAA;
5269 moveList[endPV-1][3] = toY + ONE;
5270 parseList[endPV-1][0] = NULLCHAR;
5271 safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5274 pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5275 if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5276 if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5277 if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5278 valid++; // allow comments in PV
5282 if(endPV+1 > framePtr) break; // no space, truncate
5285 CopyBoard(boards[endPV], boards[endPV-1]);
5286 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5287 moveList[endPV-1][0] = fromX + AAA;
5288 moveList[endPV-1][1] = fromY + ONE;
5289 moveList[endPV-1][2] = toX + AAA;
5290 moveList[endPV-1][3] = toY + ONE;
5291 moveList[endPV-1][4] = promoChar;
5292 moveList[endPV-1][5] = NULLCHAR;
5293 strncat(moveList[endPV-1], "\n", MOVE_LEN);
5295 CoordsToAlgebraic(boards[endPV - 1],
5296 PosFlags(endPV - 1),
5297 fromY, fromX, toY, toX, promoChar,
5298 parseList[endPV - 1]);
5300 parseList[endPV-1][0] = NULLCHAR;
5302 currentMove = endPV;
5303 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5304 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5305 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5306 DrawPosition(TRUE, boards[currentMove]);
5310 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5315 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5316 lastX = x; lastY = y;
5317 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5319 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5320 if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5322 do{ while(buf[index] && buf[index] != '\n') index++;
5323 } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5325 ParsePV(buf+startPV, FALSE);
5326 *start = startPV; *end = index-1;
5331 LoadPV(int x, int y)
5332 { // called on right mouse click to load PV
5333 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5334 lastX = x; lastY = y;
5335 ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
5342 if(endPV < 0) return;
5344 currentMove = forwardMostMove;
5345 if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game contnuation
5346 ClearPremoveHighlights();
5347 DrawPosition(TRUE, boards[currentMove]);
5351 MovePV(int x, int y, int h)
5352 { // step through PV based on mouse coordinates (called on mouse move)
5353 int margin = h>>3, step = 0;
5355 // we must somehow check if right button is still down (might be released off board!)
5356 if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5357 if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return;
5358 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5360 lastX = x; lastY = y;
5362 if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5363 if(endPV < 0) return;
5364 if(y < margin) step = 1; else
5365 if(y > h - margin) step = -1;
5366 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5367 currentMove += step;
5368 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5369 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5370 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5371 DrawPosition(FALSE, boards[currentMove]);
5375 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5376 // All positions will have equal probability, but the current method will not provide a unique
5377 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5383 int piecesLeft[(int)BlackPawn];
5384 int seed, nrOfShuffles;
5386 void GetPositionNumber()
5387 { // sets global variable seed
5390 seed = appData.defaultFrcPosition;
5391 if(seed < 0) { // randomize based on time for negative FRC position numbers
5392 for(i=0; i<50; i++) seed += random();
5393 seed = random() ^ random() >> 8 ^ random() << 8;
5394 if(seed<0) seed = -seed;
5398 int put(Board board, int pieceType, int rank, int n, int shade)
5399 // put the piece on the (n-1)-th empty squares of the given shade
5403 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5404 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5405 board[rank][i] = (ChessSquare) pieceType;
5406 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5408 piecesLeft[pieceType]--;
5416 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5417 // calculate where the next piece goes, (any empty square), and put it there
5421 i = seed % squaresLeft[shade];
5422 nrOfShuffles *= squaresLeft[shade];
5423 seed /= squaresLeft[shade];
5424 put(board, pieceType, rank, i, shade);
5427 void AddTwoPieces(Board board, int pieceType, int rank)
5428 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5430 int i, n=squaresLeft[ANY], j=n-1, k;
5432 k = n*(n-1)/2; // nr of possibilities, not counting permutations
5433 i = seed % k; // pick one
5436 while(i >= j) i -= j--;
5437 j = n - 1 - j; i += j;
5438 put(board, pieceType, rank, j, ANY);
5439 put(board, pieceType, rank, i, ANY);
5442 void SetUpShuffle(Board board, int number)
5446 GetPositionNumber(); nrOfShuffles = 1;
5448 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5449 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
5450 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5452 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5454 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5455 p = (int) board[0][i];
5456 if(p < (int) BlackPawn) piecesLeft[p] ++;
5457 board[0][i] = EmptySquare;
5460 if(PosFlags(0) & F_ALL_CASTLE_OK) {
5461 // shuffles restricted to allow normal castling put KRR first
5462 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5463 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5464 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5465 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5466 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5467 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5468 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5469 put(board, WhiteRook, 0, 0, ANY);
5470 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5473 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5474 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5475 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5476 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5477 while(piecesLeft[p] >= 2) {
5478 AddOnePiece(board, p, 0, LITE);
5479 AddOnePiece(board, p, 0, DARK);
5481 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5484 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5485 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5486 // but we leave King and Rooks for last, to possibly obey FRC restriction
5487 if(p == (int)WhiteRook) continue;
5488 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5489 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
5492 // now everything is placed, except perhaps King (Unicorn) and Rooks
5494 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5495 // Last King gets castling rights
5496 while(piecesLeft[(int)WhiteUnicorn]) {
5497 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5498 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5501 while(piecesLeft[(int)WhiteKing]) {
5502 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5503 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5508 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
5509 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5512 // Only Rooks can be left; simply place them all
5513 while(piecesLeft[(int)WhiteRook]) {
5514 i = put(board, WhiteRook, 0, 0, ANY);
5515 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5518 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
5520 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
5523 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5524 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5527 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5530 int SetCharTable( char *table, const char * map )
5531 /* [HGM] moved here from winboard.c because of its general usefulness */
5532 /* Basically a safe strcpy that uses the last character as King */
5534 int result = FALSE; int NrPieces;
5536 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5537 && NrPieces >= 12 && !(NrPieces&1)) {
5538 int i; /* [HGM] Accept even length from 12 to 34 */
5540 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5541 for( i=0; i<NrPieces/2-1; i++ ) {
5543 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5545 table[(int) WhiteKing] = map[NrPieces/2-1];
5546 table[(int) BlackKing] = map[NrPieces-1];
5554 void Prelude(Board board)
5555 { // [HGM] superchess: random selection of exo-pieces
5556 int i, j, k; ChessSquare p;
5557 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5559 GetPositionNumber(); // use FRC position number
5561 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5562 SetCharTable(pieceToChar, appData.pieceToCharTable);
5563 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5564 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5567 j = seed%4; seed /= 4;
5568 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5569 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5570 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5571 j = seed%3 + (seed%3 >= j); seed /= 3;
5572 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5573 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5574 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5575 j = seed%3; seed /= 3;
5576 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5577 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5578 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5579 j = seed%2 + (seed%2 >= j); seed /= 2;
5580 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5581 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5582 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5583 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
5584 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
5585 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5586 put(board, exoPieces[0], 0, 0, ANY);
5587 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5591 InitPosition(redraw)
5594 ChessSquare (* pieces)[BOARD_FILES];
5595 int i, j, pawnRow, overrule,
5596 oldx = gameInfo.boardWidth,
5597 oldy = gameInfo.boardHeight,
5598 oldh = gameInfo.holdingsWidth;
5601 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5603 /* [AS] Initialize pv info list [HGM] and game status */
5605 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5606 pvInfoList[i].depth = 0;
5607 boards[i][EP_STATUS] = EP_NONE;
5608 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5611 initialRulePlies = 0; /* 50-move counter start */
5613 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5614 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5618 /* [HGM] logic here is completely changed. In stead of full positions */
5619 /* the initialized data only consist of the two backranks. The switch */
5620 /* selects which one we will use, which is than copied to the Board */
5621 /* initialPosition, which for the rest is initialized by Pawns and */
5622 /* empty squares. This initial position is then copied to boards[0], */
5623 /* possibly after shuffling, so that it remains available. */
5625 gameInfo.holdingsWidth = 0; /* default board sizes */
5626 gameInfo.boardWidth = 8;
5627 gameInfo.boardHeight = 8;
5628 gameInfo.holdingsSize = 0;
5629 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5630 for(i=0; i<BOARD_FILES-2; i++)
5631 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5632 initialPosition[EP_STATUS] = EP_NONE;
5633 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5634 if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5635 SetCharTable(pieceNickName, appData.pieceNickNames);
5636 else SetCharTable(pieceNickName, "............");
5639 switch (gameInfo.variant) {
5640 case VariantFischeRandom:
5641 shuffleOpenings = TRUE;
5644 case VariantShatranj:
5645 pieces = ShatranjArray;
5646 nrCastlingRights = 0;
5647 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5650 pieces = makrukArray;
5651 nrCastlingRights = 0;
5652 startedFromSetupPosition = TRUE;
5653 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5655 case VariantTwoKings:
5656 pieces = twoKingsArray;
5658 case VariantCapaRandom:
5659 shuffleOpenings = TRUE;
5660 case VariantCapablanca:
5661 pieces = CapablancaArray;
5662 gameInfo.boardWidth = 10;
5663 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5666 pieces = GothicArray;
5667 gameInfo.boardWidth = 10;
5668 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5671 SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5672 gameInfo.holdingsSize = 7;
5675 pieces = JanusArray;
5676 gameInfo.boardWidth = 10;
5677 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5678 nrCastlingRights = 6;
5679 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5680 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5681 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5682 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5683 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5684 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5687 pieces = FalconArray;
5688 gameInfo.boardWidth = 10;
5689 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5691 case VariantXiangqi:
5692 pieces = XiangqiArray;
5693 gameInfo.boardWidth = 9;
5694 gameInfo.boardHeight = 10;
5695 nrCastlingRights = 0;
5696 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5699 pieces = ShogiArray;
5700 gameInfo.boardWidth = 9;
5701 gameInfo.boardHeight = 9;
5702 gameInfo.holdingsSize = 7;
5703 nrCastlingRights = 0;
5704 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5706 case VariantCourier:
5707 pieces = CourierArray;
5708 gameInfo.boardWidth = 12;
5709 nrCastlingRights = 0;
5710 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5712 case VariantKnightmate:
5713 pieces = KnightmateArray;
5714 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5716 case VariantSpartan:
5717 pieces = SpartanArray;
5718 SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5721 pieces = fairyArray;
5722 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5725 pieces = GreatArray;
5726 gameInfo.boardWidth = 10;
5727 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5728 gameInfo.holdingsSize = 8;
5732 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5733 gameInfo.holdingsSize = 8;
5734 startedFromSetupPosition = TRUE;
5736 case VariantCrazyhouse:
5737 case VariantBughouse:
5739 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5740 gameInfo.holdingsSize = 5;
5742 case VariantWildCastle:
5744 /* !!?shuffle with kings guaranteed to be on d or e file */
5745 shuffleOpenings = 1;
5747 case VariantNoCastle:
5749 nrCastlingRights = 0;
5750 /* !!?unconstrained back-rank shuffle */
5751 shuffleOpenings = 1;
5756 if(appData.NrFiles >= 0) {
5757 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5758 gameInfo.boardWidth = appData.NrFiles;
5760 if(appData.NrRanks >= 0) {
5761 gameInfo.boardHeight = appData.NrRanks;
5763 if(appData.holdingsSize >= 0) {
5764 i = appData.holdingsSize;
5765 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5766 gameInfo.holdingsSize = i;
5768 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5769 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5770 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5772 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5773 if(pawnRow < 1) pawnRow = 1;
5774 if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5776 /* User pieceToChar list overrules defaults */
5777 if(appData.pieceToCharTable != NULL)
5778 SetCharTable(pieceToChar, appData.pieceToCharTable);
5780 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5782 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5783 s = (ChessSquare) 0; /* account holding counts in guard band */
5784 for( i=0; i<BOARD_HEIGHT; i++ )
5785 initialPosition[i][j] = s;
5787 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5788 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5789 initialPosition[pawnRow][j] = WhitePawn;
5790 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5791 if(gameInfo.variant == VariantXiangqi) {
5793 initialPosition[pawnRow][j] =
5794 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5795 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5796 initialPosition[2][j] = WhiteCannon;
5797 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5801 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
5803 if( (gameInfo.variant == VariantShogi) && !overrule ) {
5806 initialPosition[1][j] = WhiteBishop;
5807 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5809 initialPosition[1][j] = WhiteRook;
5810 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5813 if( nrCastlingRights == -1) {
5814 /* [HGM] Build normal castling rights (must be done after board sizing!) */
5815 /* This sets default castling rights from none to normal corners */
5816 /* Variants with other castling rights must set them themselves above */
5817 nrCastlingRights = 6;
5819 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5820 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5821 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5822 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5823 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5824 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5827 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5828 if(gameInfo.variant == VariantGreat) { // promotion commoners
5829 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5830 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5831 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5832 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5834 if( gameInfo.variant == VariantSChess ) {
5835 initialPosition[1][0] = BlackMarshall;
5836 initialPosition[2][0] = BlackAngel;
5837 initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5838 initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5839 initialPosition[1][1] = initialPosition[2][1] =
5840 initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5842 if (appData.debugMode) {
5843 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5845 if(shuffleOpenings) {
5846 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5847 startedFromSetupPosition = TRUE;
5849 if(startedFromPositionFile) {
5850 /* [HGM] loadPos: use PositionFile for every new game */
5851 CopyBoard(initialPosition, filePosition);
5852 for(i=0; i<nrCastlingRights; i++)
5853 initialRights[i] = filePosition[CASTLING][i];
5854 startedFromSetupPosition = TRUE;
5857 CopyBoard(boards[0], initialPosition);
5859 if(oldx != gameInfo.boardWidth ||
5860 oldy != gameInfo.boardHeight ||
5861 oldv != gameInfo.variant ||
5862 oldh != gameInfo.holdingsWidth
5864 InitDrawingSizes(-2 ,0);
5866 oldv = gameInfo.variant;
5868 DrawPosition(TRUE, boards[currentMove]);
5872 SendBoard(cps, moveNum)
5873 ChessProgramState *cps;
5876 char message[MSG_SIZ];
5878 if (cps->useSetboard) {
5879 char* fen = PositionToFEN(moveNum, cps->fenOverride);
5880 snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5881 SendToProgram(message, cps);
5887 /* Kludge to set black to move, avoiding the troublesome and now
5888 * deprecated "black" command.
5890 if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
5891 SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
5893 SendToProgram("edit\n", cps);
5894 SendToProgram("#\n", cps);
5895 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5896 bp = &boards[moveNum][i][BOARD_LEFT];
5897 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5898 if ((int) *bp < (int) BlackPawn) {
5899 snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
5901 if(message[0] == '+' || message[0] == '~') {
5902 snprintf(message, MSG_SIZ,"%c%c%c+\n",
5903 PieceToChar((ChessSquare)(DEMOTED *bp)),
5906 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5907 message[1] = BOARD_RGHT - 1 - j + '1';
5908 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5910 SendToProgram(message, cps);
5915 SendToProgram("c\n", cps);
5916 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5917 bp = &boards[moveNum][i][BOARD_LEFT];
5918 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5919 if (((int) *bp != (int) EmptySquare)
5920 && ((int) *bp >= (int) BlackPawn)) {
5921 snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5923 if(message[0] == '+' || message[0] == '~') {
5924 snprintf(message, MSG_SIZ,"%c%c%c+\n",
5925 PieceToChar((ChessSquare)(DEMOTED *bp)),
5928 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5929 message[1] = BOARD_RGHT - 1 - j + '1';
5930 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5932 SendToProgram(message, cps);
5937 SendToProgram(".\n", cps);
5939 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5943 DefaultPromoChoice(int white)
5946 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5947 result = WhiteFerz; // no choice
5948 else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
5949 result= WhiteKing; // in Suicide Q is the last thing we want
5950 else if(gameInfo.variant == VariantSpartan)
5951 result = white ? WhiteQueen : WhiteAngel;
5952 else result = WhiteQueen;
5953 if(!white) result = WHITE_TO_BLACK result;
5957 static int autoQueen; // [HGM] oneclick
5960 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5962 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5963 /* [HGM] add Shogi promotions */
5964 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5969 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5970 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
5972 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5973 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5976 piece = boards[currentMove][fromY][fromX];
5977 if(gameInfo.variant == VariantShogi) {
5978 promotionZoneSize = BOARD_HEIGHT/3;
5979 highestPromotingPiece = (int)WhiteFerz;
5980 } else if(gameInfo.variant == VariantMakruk) {
5981 promotionZoneSize = 3;
5984 // Treat Lance as Pawn when it is not representing Amazon
5985 if(gameInfo.variant != VariantSuper) {
5986 if(piece == WhiteLance) piece = WhitePawn; else
5987 if(piece == BlackLance) piece = BlackPawn;
5990 // next weed out all moves that do not touch the promotion zone at all
5991 if((int)piece >= BlackPawn) {
5992 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5994 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5996 if( toY < BOARD_HEIGHT - promotionZoneSize &&
5997 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6000 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6002 // weed out mandatory Shogi promotions
6003 if(gameInfo.variant == VariantShogi) {
6004 if(piece >= BlackPawn) {
6005 if(toY == 0 && piece == BlackPawn ||
6006 toY == 0 && piece == BlackQueen ||
6007 toY <= 1 && piece == BlackKnight) {
6012 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6013 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6014 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6021 // weed out obviously illegal Pawn moves
6022 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
6023 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6024 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6025 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6026 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6027 // note we are not allowed to test for valid (non-)capture, due to premove
6030 // we either have a choice what to promote to, or (in Shogi) whether to promote
6031 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6032 *promoChoice = PieceToChar(BlackFerz); // no choice
6035 // no sense asking what we must promote to if it is going to explode...
6036 if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6037 *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6040 // give caller the default choice even if we will not make it
6041 *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6042 if(gameInfo.variant == VariantShogi) *promoChoice = '+';
6043 if(appData.sweepSelect && gameInfo.variant != VariantGreat
6044 && gameInfo.variant != VariantShogi
6045 && gameInfo.variant != VariantSuper) return FALSE;
6046 if(autoQueen) return FALSE; // predetermined
6048 // suppress promotion popup on illegal moves that are not premoves
6049 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6050 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
6051 if(appData.testLegality && !premove) {
6052 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6053 fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6054 if(moveType != WhitePromotion && moveType != BlackPromotion)
6062 InPalace(row, column)
6064 { /* [HGM] for Xiangqi */
6065 if( (row < 3 || row > BOARD_HEIGHT-4) &&
6066 column < (BOARD_WIDTH + 4)/2 &&
6067 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6072 PieceForSquare (x, y)
6076 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6079 return boards[currentMove][y][x];
6083 OKToStartUserMove(x, y)
6086 ChessSquare from_piece;
6089 if (matchMode) return FALSE;
6090 if (gameMode == EditPosition) return TRUE;
6092 if (x >= 0 && y >= 0)
6093 from_piece = boards[currentMove][y][x];
6095 from_piece = EmptySquare;
6097 if (from_piece == EmptySquare) return FALSE;
6099 white_piece = (int)from_piece >= (int)WhitePawn &&
6100 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6103 case PlayFromGameFile:
6105 case TwoMachinesPlay:
6113 case MachinePlaysWhite:
6114 case IcsPlayingBlack:
6115 if (appData.zippyPlay) return FALSE;
6117 DisplayMoveError(_("You are playing Black"));
6122 case MachinePlaysBlack:
6123 case IcsPlayingWhite:
6124 if (appData.zippyPlay) return FALSE;
6126 DisplayMoveError(_("You are playing White"));
6132 if (!white_piece && WhiteOnMove(currentMove)) {
6133 DisplayMoveError(_("It is White's turn"));
6136 if (white_piece && !WhiteOnMove(currentMove)) {
6137 DisplayMoveError(_("It is Black's turn"));
6140 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6141 /* Editing correspondence game history */
6142 /* Could disallow this or prompt for confirmation */
6147 case BeginningOfGame:
6148 if (appData.icsActive) return FALSE;
6149 if (!appData.noChessProgram) {
6151 DisplayMoveError(_("You are playing White"));
6158 if (!white_piece && WhiteOnMove(currentMove)) {
6159 DisplayMoveError(_("It is White's turn"));
6162 if (white_piece && !WhiteOnMove(currentMove)) {
6163 DisplayMoveError(_("It is Black's turn"));
6172 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6173 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6174 && gameMode != AnalyzeFile && gameMode != Training) {
6175 DisplayMoveError(_("Displayed position is not current"));
6182 OnlyMove(int *x, int *y, Boolean captures) {
6183 DisambiguateClosure cl;
6184 if (appData.zippyPlay) return FALSE;
6186 case MachinePlaysBlack:
6187 case IcsPlayingWhite:
6188 case BeginningOfGame:
6189 if(!WhiteOnMove(currentMove)) return FALSE;
6191 case MachinePlaysWhite:
6192 case IcsPlayingBlack:
6193 if(WhiteOnMove(currentMove)) return FALSE;
6200 cl.pieceIn = EmptySquare;
6205 cl.promoCharIn = NULLCHAR;
6206 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6207 if( cl.kind == NormalMove ||
6208 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6209 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6210 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6217 if(cl.kind != ImpossibleMove) return FALSE;
6218 cl.pieceIn = EmptySquare;
6223 cl.promoCharIn = NULLCHAR;
6224 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6225 if( cl.kind == NormalMove ||
6226 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6227 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6228 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6233 autoQueen = TRUE; // act as if autoQueen on when we click to-square
6239 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6240 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6241 int lastLoadGameUseList = FALSE;
6242 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6243 ChessMove lastLoadGameStart = EndOfFile;
6246 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6247 int fromX, fromY, toX, toY;
6251 ChessSquare pdown, pup;
6253 /* Check if the user is playing in turn. This is complicated because we
6254 let the user "pick up" a piece before it is his turn. So the piece he
6255 tried to pick up may have been captured by the time he puts it down!
6256 Therefore we use the color the user is supposed to be playing in this
6257 test, not the color of the piece that is currently on the starting
6258 square---except in EditGame mode, where the user is playing both
6259 sides; fortunately there the capture race can't happen. (It can
6260 now happen in IcsExamining mode, but that's just too bad. The user
6261 will get a somewhat confusing message in that case.)
6265 case PlayFromGameFile:
6267 case TwoMachinesPlay:
6271 /* We switched into a game mode where moves are not accepted,
6272 perhaps while the mouse button was down. */
6275 case MachinePlaysWhite:
6276 /* User is moving for Black */
6277 if (WhiteOnMove(currentMove)) {
6278 DisplayMoveError(_("It is White's turn"));
6283 case MachinePlaysBlack:
6284 /* User is moving for White */
6285 if (!WhiteOnMove(currentMove)) {
6286 DisplayMoveError(_("It is Black's turn"));
6293 case BeginningOfGame:
6296 if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6297 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6298 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6299 /* User is moving for Black */
6300 if (WhiteOnMove(currentMove)) {
6301 DisplayMoveError(_("It is White's turn"));
6305 /* User is moving for White */
6306 if (!WhiteOnMove(currentMove)) {
6307 DisplayMoveError(_("It is Black's turn"));
6313 case IcsPlayingBlack:
6314 /* User is moving for Black */
6315 if (WhiteOnMove(currentMove)) {
6316 if (!appData.premove) {
6317 DisplayMoveError(_("It is White's turn"));
6318 } else if (toX >= 0 && toY >= 0) {
6321 premoveFromX = fromX;
6322 premoveFromY = fromY;
6323 premovePromoChar = promoChar;
6325 if (appData.debugMode)
6326 fprintf(debugFP, "Got premove: fromX %d,"
6327 "fromY %d, toX %d, toY %d\n",
6328 fromX, fromY, toX, toY);
6334 case IcsPlayingWhite:
6335 /* User is moving for White */
6336 if (!WhiteOnMove(currentMove)) {
6337 if (!appData.premove) {
6338 DisplayMoveError(_("It is Black's turn"));
6339 } else if (toX >= 0 && toY >= 0) {
6342 premoveFromX = fromX;
6343 premoveFromY = fromY;
6344 premovePromoChar = promoChar;
6346 if (appData.debugMode)
6347 fprintf(debugFP, "Got premove: fromX %d,"
6348 "fromY %d, toX %d, toY %d\n",
6349 fromX, fromY, toX, toY);
6359 /* EditPosition, empty square, or different color piece;
6360 click-click move is possible */
6361 if (toX == -2 || toY == -2) {
6362 boards[0][fromY][fromX] = EmptySquare;
6363 DrawPosition(FALSE, boards[currentMove]);
6365 } else if (toX >= 0 && toY >= 0) {
6366 boards[0][toY][toX] = boards[0][fromY][fromX];
6367 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6368 if(boards[0][fromY][0] != EmptySquare) {
6369 if(boards[0][fromY][1]) boards[0][fromY][1]--;
6370 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
6373 if(fromX == BOARD_RGHT+1) {
6374 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6375 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6376 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6379 boards[0][fromY][fromX] = EmptySquare;
6380 DrawPosition(FALSE, boards[currentMove]);
6386 if(toX < 0 || toY < 0) return;
6387 pdown = boards[currentMove][fromY][fromX];
6388 pup = boards[currentMove][toY][toX];
6390 /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6391 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6392 if( pup != EmptySquare ) return;
6393 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6394 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6395 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6396 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6397 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6398 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6399 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6403 /* [HGM] always test for legality, to get promotion info */
6404 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6405 fromY, fromX, toY, toX, promoChar);
6406 /* [HGM] but possibly ignore an IllegalMove result */
6407 if (appData.testLegality) {
6408 if (moveType == IllegalMove || moveType == ImpossibleMove) {
6409 DisplayMoveError(_("Illegal move"));
6414 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6417 /* Common tail of UserMoveEvent and DropMenuEvent */
6419 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6421 int fromX, fromY, toX, toY;
6422 /*char*/int promoChar;
6426 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6427 // [HGM] superchess: suppress promotions to non-available piece
6428 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6429 if(WhiteOnMove(currentMove)) {
6430 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6432 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6436 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6437 move type in caller when we know the move is a legal promotion */
6438 if(moveType == NormalMove && promoChar)
6439 moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6441 /* [HGM] <popupFix> The following if has been moved here from
6442 UserMoveEvent(). Because it seemed to belong here (why not allow
6443 piece drops in training games?), and because it can only be
6444 performed after it is known to what we promote. */
6445 if (gameMode == Training) {
6446 /* compare the move played on the board to the next move in the
6447 * game. If they match, display the move and the opponent's response.
6448 * If they don't match, display an error message.
6452 CopyBoard(testBoard, boards[currentMove]);
6453 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6455 if (CompareBoards(testBoard, boards[currentMove+1])) {
6456 ForwardInner(currentMove+1);
6458 /* Autoplay the opponent's response.
6459 * if appData.animate was TRUE when Training mode was entered,
6460 * the response will be animated.
6462 saveAnimate = appData.animate;
6463 appData.animate = animateTraining;
6464 ForwardInner(currentMove+1);
6465 appData.animate = saveAnimate;
6467 /* check for the end of the game */
6468 if (currentMove >= forwardMostMove) {
6469 gameMode = PlayFromGameFile;
6471 SetTrainingModeOff();
6472 DisplayInformation(_("End of game"));
6475 DisplayError(_("Incorrect move"), 0);
6480 /* Ok, now we know that the move is good, so we can kill
6481 the previous line in Analysis Mode */
6482 if ((gameMode == AnalyzeMode || gameMode == EditGame)
6483 && currentMove < forwardMostMove) {
6484 if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6485 else forwardMostMove = currentMove;
6488 /* If we need the chess program but it's dead, restart it */
6489 ResurrectChessProgram();
6491 /* A user move restarts a paused game*/
6495 thinkOutput[0] = NULLCHAR;
6497 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6499 if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6500 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6504 if (gameMode == BeginningOfGame) {
6505 if (appData.noChessProgram) {
6506 gameMode = EditGame;
6510 gameMode = MachinePlaysBlack;
6513 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6515 if (first.sendName) {
6516 snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6517 SendToProgram(buf, &first);
6524 /* Relay move to ICS or chess engine */
6525 if (appData.icsActive) {
6526 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6527 gameMode == IcsExamining) {
6528 if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6529 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6531 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6533 // also send plain move, in case ICS does not understand atomic claims
6534 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6538 if (first.sendTime && (gameMode == BeginningOfGame ||
6539 gameMode == MachinePlaysWhite ||
6540 gameMode == MachinePlaysBlack)) {
6541 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6543 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6544 // [HGM] book: if program might be playing, let it use book
6545 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6546 first.maybeThinking = TRUE;
6547 } else SendMoveToProgram(forwardMostMove-1, &first);
6548 if (currentMove == cmailOldMove + 1) {
6549 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6553 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6557 if(appData.testLegality)
6558 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6564 if (WhiteOnMove(currentMove)) {
6565 GameEnds(BlackWins, "Black mates", GE_PLAYER);
6567 GameEnds(WhiteWins, "White mates", GE_PLAYER);
6571 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6576 case MachinePlaysBlack:
6577 case MachinePlaysWhite:
6578 /* disable certain menu options while machine is thinking */
6579 SetMachineThinkingEnables();
6586 userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6587 promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6589 if(bookHit) { // [HGM] book: simulate book reply
6590 static char bookMove[MSG_SIZ]; // a bit generous?
6592 programStats.nodes = programStats.depth = programStats.time =
6593 programStats.score = programStats.got_only_move = 0;
6594 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6596 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6597 strcat(bookMove, bookHit);
6598 HandleMachineMove(bookMove, &first);
6604 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6611 typedef char Markers[BOARD_RANKS][BOARD_FILES];
6612 Markers *m = (Markers *) closure;
6613 if(rf == fromY && ff == fromX)
6614 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6615 || kind == WhiteCapturesEnPassant
6616 || kind == BlackCapturesEnPassant);
6617 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6621 MarkTargetSquares(int clear)
6624 if(!appData.markers || !appData.highlightDragging ||
6625 !appData.testLegality || gameMode == EditPosition) return;
6627 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6630 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6631 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6632 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6634 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6637 DrawPosition(TRUE, NULL);
6641 Explode(Board board, int fromX, int fromY, int toX, int toY)
6643 if(gameInfo.variant == VariantAtomic &&
6644 (board[toY][toX] != EmptySquare || // capture?
6645 toX != fromX && (board[fromY][fromX] == WhitePawn || // e.p. ?
6646 board[fromY][fromX] == BlackPawn )
6648 AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6654 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6656 int CanPromote(ChessSquare piece, int y)
6658 if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6659 // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6660 if(gameInfo.variant == VariantShogi || gameInfo.variant == VariantXiangqi ||
6661 gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat ||
6662 gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6663 gameInfo.variant == VariantMakruk) return FALSE;
6664 return (piece == BlackPawn && y == 1 ||
6665 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6666 piece == BlackLance && y == 1 ||
6667 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6670 void LeftClick(ClickType clickType, int xPix, int yPix)
6673 Boolean saveAnimate;
6674 static int second = 0, promotionChoice = 0, clearFlag = 0;
6675 char promoChoice = NULLCHAR;
6678 if(appData.seekGraph && appData.icsActive && loggedOn &&
6679 (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6680 SeekGraphClick(clickType, xPix, yPix, 0);
6684 if (clickType == Press) ErrorPopDown();
6685 MarkTargetSquares(1);
6687 x = EventToSquare(xPix, BOARD_WIDTH);
6688 y = EventToSquare(yPix, BOARD_HEIGHT);
6689 if (!flipView && y >= 0) {
6690 y = BOARD_HEIGHT - 1 - y;
6692 if (flipView && x >= 0) {
6693 x = BOARD_WIDTH - 1 - x;
6696 if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6697 defaultPromoChoice = promoSweep;
6698 promoSweep = EmptySquare; // terminate sweep
6699 promoDefaultAltered = TRUE;
6700 if(!selectFlag) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6703 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6704 if(clickType == Release) return; // ignore upclick of click-click destination
6705 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6706 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6707 if(gameInfo.holdingsWidth &&
6708 (WhiteOnMove(currentMove)
6709 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6710 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6711 // click in right holdings, for determining promotion piece
6712 ChessSquare p = boards[currentMove][y][x];
6713 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6714 if(p != EmptySquare) {
6715 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6720 DrawPosition(FALSE, boards[currentMove]);
6724 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6725 if(clickType == Press
6726 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6727 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6728 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6731 if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6732 fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6734 if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6735 int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6736 gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6737 defaultPromoChoice = DefaultPromoChoice(side);
6740 autoQueen = appData.alwaysPromoteToQueen;
6744 gatingPiece = EmptySquare;
6745 if (clickType != Press) {
6746 if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6747 DragPieceEnd(xPix, yPix); dragging = 0;
6748 DrawPosition(FALSE, NULL);
6752 fromX = x; fromY = y;
6753 if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6754 // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6755 appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6757 if (OKToStartUserMove(fromX, fromY)) {
6759 MarkTargetSquares(0);
6760 DragPieceBegin(xPix, yPix); dragging = 1;
6761 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6762 promoSweep = defaultPromoChoice;
6763 selectFlag = 0; lastX = xPix; lastY = yPix;
6764 Sweep(0); // Pawn that is going to promote: preview promotion piece
6765 DisplayMessage("", _("Pull pawn backwards to under-promote"));
6767 if (appData.highlightDragging) {
6768 SetHighlights(fromX, fromY, -1, -1);
6770 } else fromX = fromY = -1;
6776 if (clickType == Press && gameMode != EditPosition) {
6781 // ignore off-board to clicks
6782 if(y < 0 || x < 0) return;
6784 /* Check if clicking again on the same color piece */
6785 fromP = boards[currentMove][fromY][fromX];
6786 toP = boards[currentMove][y][x];
6787 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6788 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6789 WhitePawn <= toP && toP <= WhiteKing &&
6790 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6791 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6792 (BlackPawn <= fromP && fromP <= BlackKing &&
6793 BlackPawn <= toP && toP <= BlackKing &&
6794 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6795 !(fromP == BlackKing && toP == BlackRook && frc))) {
6796 /* Clicked again on same color piece -- changed his mind */
6797 second = (x == fromX && y == fromY);
6798 promoDefaultAltered = FALSE;
6799 if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6800 if (appData.highlightDragging) {
6801 SetHighlights(x, y, -1, -1);
6805 if (OKToStartUserMove(x, y)) {
6806 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6807 (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6808 y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6809 gatingPiece = boards[currentMove][fromY][fromX];
6810 else gatingPiece = EmptySquare;
6812 fromY = y; dragging = 1;
6813 MarkTargetSquares(0);
6814 DragPieceBegin(xPix, yPix);
6815 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6816 promoSweep = defaultPromoChoice;
6817 selectFlag = 0; lastX = xPix; lastY = yPix;
6818 Sweep(0); // Pawn that is going to promote: preview promotion piece
6822 if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6825 // ignore clicks on holdings
6826 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6829 if (clickType == Release && x == fromX && y == fromY) {
6830 DragPieceEnd(xPix, yPix); dragging = 0;
6832 // a deferred attempt to click-click move an empty square on top of a piece
6833 boards[currentMove][y][x] = EmptySquare;
6835 DrawPosition(FALSE, boards[currentMove]);
6836 fromX = fromY = -1; clearFlag = 0;
6839 if (appData.animateDragging) {
6840 /* Undo animation damage if any */
6841 DrawPosition(FALSE, NULL);
6844 /* Second up/down in same square; just abort move */
6847 gatingPiece = EmptySquare;
6850 ClearPremoveHighlights();
6852 /* First upclick in same square; start click-click mode */
6853 SetHighlights(x, y, -1, -1);
6860 /* we now have a different from- and (possibly off-board) to-square */
6861 /* Completed move */
6864 saveAnimate = appData.animate;
6865 if (clickType == Press) {
6866 if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
6867 // must be Edit Position mode with empty-square selected
6868 fromX = x; fromY = y; DragPieceBegin(xPix, yPix); dragging = 1; // consider this a new attempt to drag
6869 if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
6872 /* Finish clickclick move */
6873 if (appData.animate || appData.highlightLastMove) {
6874 SetHighlights(fromX, fromY, toX, toY);
6879 /* Finish drag move */
6880 if (appData.highlightLastMove) {
6881 SetHighlights(fromX, fromY, toX, toY);
6885 DragPieceEnd(xPix, yPix); dragging = 0;
6886 /* Don't animate move and drag both */
6887 appData.animate = FALSE;
6890 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6891 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6892 ChessSquare piece = boards[currentMove][fromY][fromX];
6893 if(gameMode == EditPosition && piece != EmptySquare &&
6894 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6897 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6898 n = PieceToNumber(piece - (int)BlackPawn);
6899 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6900 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6901 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6903 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6904 n = PieceToNumber(piece);
6905 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6906 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6907 boards[currentMove][n][BOARD_WIDTH-2]++;
6909 boards[currentMove][fromY][fromX] = EmptySquare;
6913 DrawPosition(TRUE, boards[currentMove]);
6917 // off-board moves should not be highlighted
6918 if(x < 0 || y < 0) ClearHighlights();
6920 if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
6922 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6923 SetHighlights(fromX, fromY, toX, toY);
6924 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6925 // [HGM] super: promotion to captured piece selected from holdings
6926 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6927 promotionChoice = TRUE;
6928 // kludge follows to temporarily execute move on display, without promoting yet
6929 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6930 boards[currentMove][toY][toX] = p;
6931 DrawPosition(FALSE, boards[currentMove]);
6932 boards[currentMove][fromY][fromX] = p; // take back, but display stays
6933 boards[currentMove][toY][toX] = q;
6934 DisplayMessage("Click in holdings to choose piece", "");
6939 int oldMove = currentMove;
6940 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6941 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6942 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6943 if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
6944 Explode(boards[currentMove-1], fromX, fromY, toX, toY))
6945 DrawPosition(TRUE, boards[currentMove]);
6948 appData.animate = saveAnimate;
6949 if (appData.animate || appData.animateDragging) {
6950 /* Undo animation damage if needed */
6951 DrawPosition(FALSE, NULL);
6955 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6956 { // front-end-free part taken out of PieceMenuPopup
6957 int whichMenu; int xSqr, ySqr;
6959 if(seekGraphUp) { // [HGM] seekgraph
6960 if(action == Press) SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6961 if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6965 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6966 && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6967 if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6968 if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6969 if(action == Press) {
6970 originalFlip = flipView;
6971 flipView = !flipView; // temporarily flip board to see game from partners perspective
6972 DrawPosition(TRUE, partnerBoard);
6973 DisplayMessage(partnerStatus, "");
6975 } else if(action == Release) {
6976 flipView = originalFlip;
6977 DrawPosition(TRUE, boards[currentMove]);
6983 xSqr = EventToSquare(x, BOARD_WIDTH);
6984 ySqr = EventToSquare(y, BOARD_HEIGHT);
6985 if (action == Release) {
6986 if(pieceSweep != EmptySquare) {
6987 EditPositionMenuEvent(pieceSweep, toX, toY);
6988 pieceSweep = EmptySquare;
6989 } else UnLoadPV(); // [HGM] pv
6991 if (action != Press) return -2; // return code to be ignored
6994 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
\r
6996 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
\r
6997 if (xSqr < 0 || ySqr < 0) return -1;
6998 if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
6999 pieceSweep = shiftKey ? BlackPawn : WhitePawn; // [HGM] sweep: prepare selecting piece by mouse sweep
7000 toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7001 if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7005 if(!appData.icsEngineAnalyze) return -1;
7006 case IcsPlayingWhite:
7007 case IcsPlayingBlack:
7008 if(!appData.zippyPlay) goto noZip;
7011 case MachinePlaysWhite:
7012 case MachinePlaysBlack:
7013 case TwoMachinesPlay: // [HGM] pv: use for showing PV
7014 if (!appData.dropMenu) {
7016 return 2; // flag front-end to grab mouse events
7018 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7019 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7022 if (xSqr < 0 || ySqr < 0) return -1;
7023 if (!appData.dropMenu || appData.testLegality &&
7024 gameInfo.variant != VariantBughouse &&
7025 gameInfo.variant != VariantCrazyhouse) return -1;
7026 whichMenu = 1; // drop menu
7032 if (((*fromX = xSqr) < 0) ||
7033 ((*fromY = ySqr) < 0)) {
7034 *fromX = *fromY = -1;
7038 *fromX = BOARD_WIDTH - 1 - *fromX;
7040 *fromY = BOARD_HEIGHT - 1 - *fromY;
7045 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
7047 // char * hint = lastHint;
7048 FrontEndProgramStats stats;
7050 stats.which = cps == &first ? 0 : 1;
7051 stats.depth = cpstats->depth;
7052 stats.nodes = cpstats->nodes;
7053 stats.score = cpstats->score;
7054 stats.time = cpstats->time;
7055 stats.pv = cpstats->movelist;
7056 stats.hint = lastHint;
7057 stats.an_move_index = 0;
7058 stats.an_move_count = 0;
7060 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7061 stats.hint = cpstats->move_name;
7062 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7063 stats.an_move_count = cpstats->nr_moves;
7066 if(stats.pv && stats.pv[0]) safeStrCpy(lastPV[stats.which], stats.pv, sizeof(lastPV[stats.which])/sizeof(lastPV[stats.which][0])); // [HGM] pv: remember last PV of each
7068 SetProgramStats( &stats );
7071 #define MAXPLAYERS 500
7074 TourneyStandings(int display)
7076 int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7077 int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7078 char result, *p, *names[MAXPLAYERS];
7080 names[0] = p = strdup(appData.participants);
7081 while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7083 for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7085 while(result = appData.results[nr]) {
7086 color = Pairing(nr, nPlayers, &w, &b, &dummy);
7087 if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7088 wScore = bScore = 0;
7090 case '+': wScore = 2; break;
7091 case '-': bScore = 2; break;
7092 case '=': wScore = bScore = 1; break;
7094 case '*': return strdup("busy"); // tourney not finished
7102 if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7103 for(w=0; w<nPlayers; w++) {
7105 for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7106 ranking[w] = b; points[w] = bScore; score[b] = -2;
7108 p = malloc(nPlayers*34+1);
7109 for(w=0; w<nPlayers && w<display; w++)
7110 sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7116 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7117 { // count all piece types
7119 *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7120 for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7121 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7124 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7125 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7126 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7127 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7128 p == BlackBishop || p == BlackFerz || p == BlackAlfil )
7129 *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7134 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
7136 int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7137 int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7139 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7140 if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7141 if(myPawns == 2 && nMine == 3) // KPP
7142 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7143 if(myPawns == 1 && nMine == 2) // KP
7144 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7145 if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7146 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7147 if(myPawns) return FALSE;
7148 if(pCnt[WhiteRook+side])
7149 return pCnt[BlackRook-side] ||
7150 pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7151 pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7152 pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7153 if(pCnt[WhiteCannon+side]) {
7154 if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7155 return majorDefense || pCnt[BlackAlfil-side] >= 2;
7157 if(pCnt[WhiteKnight+side])
7158 return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7163 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7165 VariantClass v = gameInfo.variant;
7167 if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7168 if(v == VariantShatranj) return TRUE; // always winnable through baring
7169 if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7170 if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7172 if(v == VariantXiangqi) {
7173 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7175 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7176 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7177 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7178 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7179 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7180 if(stale) // we have at least one last-rank P plus perhaps C
7181 return majors // KPKX
7182 || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7184 return pCnt[WhiteFerz+side] // KCAK
7185 || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7186 || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7187 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7189 } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7190 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7192 if(nMine == 1) return FALSE; // bare King
7193 if(nBishops && bisColor == 3) return TRUE; // There must be a second B/A/F, which can either block (his) or attack (mine) the escape square
7194 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7195 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7196 // by now we have King + 1 piece (or multiple Bishops on the same color)
7197 if(pCnt[WhiteKnight+side])
7198 return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7199 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7200 || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7202 return (pCnt[BlackKnight-side]); // KBKN, KFKN
7203 if(pCnt[WhiteAlfil+side])
7204 return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7205 if(pCnt[WhiteWazir+side])
7206 return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7213 Adjudicate(ChessProgramState *cps)
7214 { // [HGM] some adjudications useful with buggy engines
7215 // [HGM] adjudicate: made into separate routine, which now can be called after every move
7216 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7217 // Actually ending the game is now based on the additional internal condition canAdjudicate.
7218 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7219 int k, count = 0; static int bare = 1;
7220 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7221 Boolean canAdjudicate = !appData.icsActive;
7223 // most tests only when we understand the game, i.e. legality-checking on
7224 if( appData.testLegality )
7225 { /* [HGM] Some more adjudications for obstinate engines */
7226 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7227 static int moveCount = 6;
7229 char *reason = NULL;
7231 /* Count what is on board. */
7232 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7234 /* Some material-based adjudications that have to be made before stalemate test */
7235 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7236 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7237 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7238 if(canAdjudicate && appData.checkMates) {
7240 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7241 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7242 "Xboard adjudication: King destroyed", GE_XBOARD );
7247 /* Bare King in Shatranj (loses) or Losers (wins) */
7248 if( nrW == 1 || nrB == 1) {
7249 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7250 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
7251 if(canAdjudicate && appData.checkMates) {
7253 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7254 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7255 "Xboard adjudication: Bare king", GE_XBOARD );
7259 if( gameInfo.variant == VariantShatranj && --bare < 0)
7261 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7262 if(canAdjudicate && appData.checkMates) {
7263 /* but only adjudicate if adjudication enabled */
7265 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7266 GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7267 "Xboard adjudication: Bare king", GE_XBOARD );
7274 // don't wait for engine to announce game end if we can judge ourselves
7275 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7277 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7278 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
7279 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7280 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7283 reason = "Xboard adjudication: 3rd check";
7284 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7294 reason = "Xboard adjudication: Stalemate";
7295 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7296 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
7297 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7298 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
7299 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7300 boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7301 ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7302 EP_CHECKMATE : EP_WINS);
7303 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7304 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7308 reason = "Xboard adjudication: Checkmate";
7309 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7313 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7315 result = GameIsDrawn; break;
7317 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7319 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7323 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7325 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7326 GameEnds( result, reason, GE_XBOARD );
7330 /* Next absolutely insufficient mating material. */
7331 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7332 !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7333 { /* includes KBK, KNK, KK of KBKB with like Bishops */
7335 /* always flag draws, for judging claims */
7336 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7338 if(canAdjudicate && appData.materialDraws) {
7339 /* but only adjudicate them if adjudication enabled */
7340 if(engineOpponent) {
7341 SendToProgram("force\n", engineOpponent); // suppress reply
7342 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7344 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7349 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7350 if(gameInfo.variant == VariantXiangqi ?
7351 SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7353 ( nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7354 || nr[WhiteQueen] && nr[BlackQueen]==1 /* KQKQ */
7355 || nr[WhiteKnight]==2 || nr[BlackKnight]==2 /* KNNK */
7356 || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7358 if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7359 { /* if the first 3 moves do not show a tactical win, declare draw */
7360 if(engineOpponent) {
7361 SendToProgram("force\n", engineOpponent); // suppress reply
7362 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7364 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7367 } else moveCount = 6;
7369 if (appData.debugMode) { int i;
7370 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7371 forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7372 appData.drawRepeats);
7373 for( i=forwardMostMove; i>=backwardMostMove; i-- )
7374 fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7378 // Repetition draws and 50-move rule can be applied independently of legality testing
7380 /* Check for rep-draws */
7382 for(k = forwardMostMove-2;
7383 k>=backwardMostMove && k>=forwardMostMove-100 &&
7384 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7385 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7388 if(CompareBoards(boards[k], boards[forwardMostMove])) {
7389 /* compare castling rights */
7390 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7391 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7392 rights++; /* King lost rights, while rook still had them */
7393 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7394 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7395 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7396 rights++; /* but at least one rook lost them */
7398 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7399 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7401 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7402 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7403 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7406 if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7407 && appData.drawRepeats > 1) {
7408 /* adjudicate after user-specified nr of repeats */
7409 int result = GameIsDrawn;
7410 char *details = "XBoard adjudication: repetition draw";
7411 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7412 // [HGM] xiangqi: check for forbidden perpetuals
7413 int m, ourPerpetual = 1, hisPerpetual = 1;
7414 for(m=forwardMostMove; m>k; m-=2) {
7415 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7416 ourPerpetual = 0; // the current mover did not always check
7417 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7418 hisPerpetual = 0; // the opponent did not always check
7420 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7421 ourPerpetual, hisPerpetual);
7422 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7423 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7424 details = "Xboard adjudication: perpetual checking";
7426 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7427 break; // (or we would have caught him before). Abort repetition-checking loop.
7429 // Now check for perpetual chases
7430 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7431 hisPerpetual = PerpetualChase(k, forwardMostMove);
7432 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7433 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7434 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7435 details = "Xboard adjudication: perpetual chasing";
7437 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
7438 break; // Abort repetition-checking loop.
7440 // if neither of us is checking or chasing all the time, or both are, it is draw
7442 if(engineOpponent) {
7443 SendToProgram("force\n", engineOpponent); // suppress reply
7444 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7446 GameEnds( result, details, GE_XBOARD );
7449 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7450 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7454 /* Now we test for 50-move draws. Determine ply count */
7455 count = forwardMostMove;
7456 /* look for last irreversble move */
7457 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7459 /* if we hit starting position, add initial plies */
7460 if( count == backwardMostMove )
7461 count -= initialRulePlies;
7462 count = forwardMostMove - count;
7463 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7464 // adjust reversible move counter for checks in Xiangqi
7465 int i = forwardMostMove - count, inCheck = 0, lastCheck;
7466 if(i < backwardMostMove) i = backwardMostMove;
7467 while(i <= forwardMostMove) {
7468 lastCheck = inCheck; // check evasion does not count
7469 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7470 if(inCheck || lastCheck) count--; // check does not count
7475 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7476 /* this is used to judge if draw claims are legal */
7477 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7478 if(engineOpponent) {
7479 SendToProgram("force\n", engineOpponent); // suppress reply
7480 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7482 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7486 /* if draw offer is pending, treat it as a draw claim
7487 * when draw condition present, to allow engines a way to
7488 * claim draws before making their move to avoid a race
7489 * condition occurring after their move
7491 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7493 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7494 p = "Draw claim: 50-move rule";
7495 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7496 p = "Draw claim: 3-fold repetition";
7497 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7498 p = "Draw claim: insufficient mating material";
7499 if( p != NULL && canAdjudicate) {
7500 if(engineOpponent) {
7501 SendToProgram("force\n", engineOpponent); // suppress reply
7502 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7504 GameEnds( GameIsDrawn, p, GE_XBOARD );
7509 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7510 if(engineOpponent) {
7511 SendToProgram("force\n", engineOpponent); // suppress reply
7512 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7514 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7520 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7521 { // [HGM] book: this routine intercepts moves to simulate book replies
7522 char *bookHit = NULL;
7524 //first determine if the incoming move brings opponent into his book
7525 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7526 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7527 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7528 if(bookHit != NULL && !cps->bookSuspend) {
7529 // make sure opponent is not going to reply after receiving move to book position
7530 SendToProgram("force\n", cps);
7531 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7533 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7534 // now arrange restart after book miss
7536 // after a book hit we never send 'go', and the code after the call to this routine
7537 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7539 snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
7540 SendToProgram(buf, cps);
7541 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7542 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7543 SendToProgram("go\n", cps);
7544 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7545 } else { // 'go' might be sent based on 'firstMove' after this routine returns
7546 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7547 SendToProgram("go\n", cps);
7548 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7550 return bookHit; // notify caller of hit, so it can take action to send move to opponent
7554 ChessProgramState *savedState;
7555 void DeferredBookMove(void)
7557 if(savedState->lastPing != savedState->lastPong)
7558 ScheduleDelayedEvent(DeferredBookMove, 10);
7560 HandleMachineMove(savedMessage, savedState);
7564 HandleMachineMove(message, cps)
7566 ChessProgramState *cps;
7568 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7569 char realname[MSG_SIZ];
7570 int fromX, fromY, toX, toY;
7579 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7581 * Kludge to ignore BEL characters
7583 while (*message == '\007') message++;
7586 * [HGM] engine debug message: ignore lines starting with '#' character
7588 if(cps->debug && *message == '#') return;
7591 * Look for book output
7593 if (cps == &first && bookRequested) {
7594 if (message[0] == '\t' || message[0] == ' ') {
7595 /* Part of the book output is here; append it */
7596 strcat(bookOutput, message);
7597 strcat(bookOutput, " \n");
7599 } else if (bookOutput[0] != NULLCHAR) {
7600 /* All of book output has arrived; display it */
7601 char *p = bookOutput;
7602 while (*p != NULLCHAR) {
7603 if (*p == '\t') *p = ' ';
7606 DisplayInformation(bookOutput);
7607 bookRequested = FALSE;
7608 /* Fall through to parse the current output */
7613 * Look for machine move.
7615 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7616 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7618 /* This method is only useful on engines that support ping */
7619 if (cps->lastPing != cps->lastPong) {
7620 if (gameMode == BeginningOfGame) {
7621 /* Extra move from before last new; ignore */
7622 if (appData.debugMode) {
7623 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7626 if (appData.debugMode) {
7627 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7628 cps->which, gameMode);
7631 SendToProgram("undo\n", cps);
7637 case BeginningOfGame:
7638 /* Extra move from before last reset; ignore */
7639 if (appData.debugMode) {
7640 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7647 /* Extra move after we tried to stop. The mode test is
7648 not a reliable way of detecting this problem, but it's
7649 the best we can do on engines that don't support ping.
7651 if (appData.debugMode) {
7652 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7653 cps->which, gameMode);
7655 SendToProgram("undo\n", cps);
7658 case MachinePlaysWhite:
7659 case IcsPlayingWhite:
7660 machineWhite = TRUE;
7663 case MachinePlaysBlack:
7664 case IcsPlayingBlack:
7665 machineWhite = FALSE;
7668 case TwoMachinesPlay:
7669 machineWhite = (cps->twoMachinesColor[0] == 'w');
7672 if (WhiteOnMove(forwardMostMove) != machineWhite) {
7673 if (appData.debugMode) {
7675 "Ignoring move out of turn by %s, gameMode %d"
7676 ", forwardMost %d\n",
7677 cps->which, gameMode, forwardMostMove);
7682 if (appData.debugMode) { int f = forwardMostMove;
7683 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7684 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7685 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7687 if(cps->alphaRank) AlphaRank(machineMove, 4);
7688 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7689 &fromX, &fromY, &toX, &toY, &promoChar)) {
7690 /* Machine move could not be parsed; ignore it. */
7691 snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7692 machineMove, _(cps->which));
7693 DisplayError(buf1, 0);
7694 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7695 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7696 if (gameMode == TwoMachinesPlay) {
7697 GameEnds(machineWhite ? BlackWins : WhiteWins,
7703 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7704 /* So we have to redo legality test with true e.p. status here, */
7705 /* to make sure an illegal e.p. capture does not slip through, */
7706 /* to cause a forfeit on a justified illegal-move complaint */
7707 /* of the opponent. */
7708 if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7710 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7711 fromY, fromX, toY, toX, promoChar);
7712 if (appData.debugMode) {
7714 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7715 boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7716 fprintf(debugFP, "castling rights\n");
7718 if(moveType == IllegalMove) {
7719 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7720 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7721 GameEnds(machineWhite ? BlackWins : WhiteWins,
7724 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7725 /* [HGM] Kludge to handle engines that send FRC-style castling
7726 when they shouldn't (like TSCP-Gothic) */
7728 case WhiteASideCastleFR:
7729 case BlackASideCastleFR:
7731 currentMoveString[2]++;
7733 case WhiteHSideCastleFR:
7734 case BlackHSideCastleFR:
7736 currentMoveString[2]--;
7738 default: ; // nothing to do, but suppresses warning of pedantic compilers
7741 hintRequested = FALSE;
7742 lastHint[0] = NULLCHAR;
7743 bookRequested = FALSE;
7744 /* Program may be pondering now */
7745 cps->maybeThinking = TRUE;
7746 if (cps->sendTime == 2) cps->sendTime = 1;
7747 if (cps->offeredDraw) cps->offeredDraw--;
7749 /* [AS] Save move info*/
7750 pvInfoList[ forwardMostMove ].score = programStats.score;
7751 pvInfoList[ forwardMostMove ].depth = programStats.depth;
7752 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
7754 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7756 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7757 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7760 while( count < adjudicateLossPlies ) {
7761 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7764 score = -score; /* Flip score for winning side */
7767 if( score > adjudicateLossThreshold ) {
7774 if( count >= adjudicateLossPlies ) {
7775 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7777 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7778 "Xboard adjudication",
7785 if(Adjudicate(cps)) {
7786 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7787 return; // [HGM] adjudicate: for all automatic game ends
7791 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7793 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7794 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7796 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7798 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7800 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7801 char buf[3*MSG_SIZ];
7803 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7804 programStats.score / 100.,
7806 programStats.time / 100.,
7807 (unsigned int)programStats.nodes,
7808 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7809 programStats.movelist);
7811 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7816 /* [AS] Clear stats for next move */
7817 ClearProgramStats();
7818 thinkOutput[0] = NULLCHAR;
7819 hiddenThinkOutputState = 0;
7822 if (gameMode == TwoMachinesPlay) {
7823 /* [HGM] relaying draw offers moved to after reception of move */
7824 /* and interpreting offer as claim if it brings draw condition */
7825 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7826 SendToProgram("draw\n", cps->other);
7828 if (cps->other->sendTime) {
7829 SendTimeRemaining(cps->other,
7830 cps->other->twoMachinesColor[0] == 'w');
7832 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7833 if (firstMove && !bookHit) {
7835 if (cps->other->useColors) {
7836 SendToProgram(cps->other->twoMachinesColor, cps->other);
7838 SendToProgram("go\n", cps->other);
7840 cps->other->maybeThinking = TRUE;
7843 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7845 if (!pausing && appData.ringBellAfterMoves) {
7850 * Reenable menu items that were disabled while
7851 * machine was thinking
7853 if (gameMode != TwoMachinesPlay)
7854 SetUserThinkingEnables();
7856 // [HGM] book: after book hit opponent has received move and is now in force mode
7857 // force the book reply into it, and then fake that it outputted this move by jumping
7858 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7860 static char bookMove[MSG_SIZ]; // a bit generous?
7862 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7863 strcat(bookMove, bookHit);
7866 programStats.nodes = programStats.depth = programStats.time =
7867 programStats.score = programStats.got_only_move = 0;
7868 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7870 if(cps->lastPing != cps->lastPong) {
7871 savedMessage = message; // args for deferred call
7873 ScheduleDelayedEvent(DeferredBookMove, 10);
7882 /* Set special modes for chess engines. Later something general
7883 * could be added here; for now there is just one kludge feature,
7884 * needed because Crafty 15.10 and earlier don't ignore SIGINT
7885 * when "xboard" is given as an interactive command.
7887 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7888 cps->useSigint = FALSE;
7889 cps->useSigterm = FALSE;
7891 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7892 ParseFeatures(message+8, cps);
7893 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7896 if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
7897 int dummy, s=6; char buf[MSG_SIZ];
7898 if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
7899 if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
7900 ParseFEN(boards[0], &dummy, message+s);
7901 DrawPosition(TRUE, boards[0]);
7902 startedFromSetupPosition = TRUE;
7905 /* [HGM] Allow engine to set up a position. Don't ask me why one would
7906 * want this, I was asked to put it in, and obliged.
7908 if (!strncmp(message, "setboard ", 9)) {
7909 Board initial_position;
7911 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7913 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7914 DisplayError(_("Bad FEN received from engine"), 0);
7918 CopyBoard(boards[0], initial_position);
7919 initialRulePlies = FENrulePlies;
7920 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7921 else gameMode = MachinePlaysBlack;
7922 DrawPosition(FALSE, boards[currentMove]);
7928 * Look for communication commands
7930 if (!strncmp(message, "telluser ", 9)) {
7931 if(message[9] == '\\' && message[10] == '\\')
7932 EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
7933 DisplayNote(message + 9);
7936 if (!strncmp(message, "tellusererror ", 14)) {
7938 if(message[14] == '\\' && message[15] == '\\')
7939 EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
7940 DisplayError(message + 14, 0);
7943 if (!strncmp(message, "tellopponent ", 13)) {
7944 if (appData.icsActive) {
7946 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7950 DisplayNote(message + 13);
7954 if (!strncmp(message, "tellothers ", 11)) {
7955 if (appData.icsActive) {
7957 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7963 if (!strncmp(message, "tellall ", 8)) {
7964 if (appData.icsActive) {
7966 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7970 DisplayNote(message + 8);
7974 if (strncmp(message, "warning", 7) == 0) {
7975 /* Undocumented feature, use tellusererror in new code */
7976 DisplayError(message, 0);
7979 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7980 safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
7981 strcat(realname, " query");
7982 AskQuestion(realname, buf2, buf1, cps->pr);
7985 /* Commands from the engine directly to ICS. We don't allow these to be
7986 * sent until we are logged on. Crafty kibitzes have been known to
7987 * interfere with the login process.
7990 if (!strncmp(message, "tellics ", 8)) {
7991 SendToICS(message + 8);
7995 if (!strncmp(message, "tellicsnoalias ", 15)) {
7996 SendToICS(ics_prefix);
7997 SendToICS(message + 15);
8001 /* The following are for backward compatibility only */
8002 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8003 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8004 SendToICS(ics_prefix);
8010 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8014 * If the move is illegal, cancel it and redraw the board.
8015 * Also deal with other error cases. Matching is rather loose
8016 * here to accommodate engines written before the spec.
8018 if (strncmp(message + 1, "llegal move", 11) == 0 ||
8019 strncmp(message, "Error", 5) == 0) {
8020 if (StrStr(message, "name") ||
8021 StrStr(message, "rating") || StrStr(message, "?") ||
8022 StrStr(message, "result") || StrStr(message, "board") ||
8023 StrStr(message, "bk") || StrStr(message, "computer") ||
8024 StrStr(message, "variant") || StrStr(message, "hint") ||
8025 StrStr(message, "random") || StrStr(message, "depth") ||
8026 StrStr(message, "accepted")) {
8029 if (StrStr(message, "protover")) {
8030 /* Program is responding to input, so it's apparently done
8031 initializing, and this error message indicates it is
8032 protocol version 1. So we don't need to wait any longer
8033 for it to initialize and send feature commands. */
8034 FeatureDone(cps, 1);
8035 cps->protocolVersion = 1;
8038 cps->maybeThinking = FALSE;
8040 if (StrStr(message, "draw")) {
8041 /* Program doesn't have "draw" command */
8042 cps->sendDrawOffers = 0;
8045 if (cps->sendTime != 1 &&
8046 (StrStr(message, "time") || StrStr(message, "otim"))) {
8047 /* Program apparently doesn't have "time" or "otim" command */
8051 if (StrStr(message, "analyze")) {
8052 cps->analysisSupport = FALSE;
8053 cps->analyzing = FALSE;
8055 snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8056 DisplayError(buf2, 0);
8059 if (StrStr(message, "(no matching move)st")) {
8060 /* Special kludge for GNU Chess 4 only */
8061 cps->stKludge = TRUE;
8062 SendTimeControl(cps, movesPerSession, timeControl,
8063 timeIncrement, appData.searchDepth,
8067 if (StrStr(message, "(no matching move)sd")) {
8068 /* Special kludge for GNU Chess 4 only */
8069 cps->sdKludge = TRUE;
8070 SendTimeControl(cps, movesPerSession, timeControl,
8071 timeIncrement, appData.searchDepth,
8075 if (!StrStr(message, "llegal")) {
8078 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8079 gameMode == IcsIdle) return;
8080 if (forwardMostMove <= backwardMostMove) return;
8081 if (pausing) PauseEvent();
8082 if(appData.forceIllegal) {
8083 // [HGM] illegal: machine refused move; force position after move into it
8084 SendToProgram("force\n", cps);
8085 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8086 // we have a real problem now, as SendBoard will use the a2a3 kludge
8087 // when black is to move, while there might be nothing on a2 or black
8088 // might already have the move. So send the board as if white has the move.
8089 // But first we must change the stm of the engine, as it refused the last move
8090 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8091 if(WhiteOnMove(forwardMostMove)) {
8092 SendToProgram("a7a6\n", cps); // for the engine black still had the move
8093 SendBoard(cps, forwardMostMove); // kludgeless board
8095 SendToProgram("a2a3\n", cps); // for the engine white still had the move
8096 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8097 SendBoard(cps, forwardMostMove+1); // kludgeless board
8099 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8100 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8101 gameMode == TwoMachinesPlay)
8102 SendToProgram("go\n", cps);
8105 if (gameMode == PlayFromGameFile) {
8106 /* Stop reading this game file */
8107 gameMode = EditGame;
8110 /* [HGM] illegal-move claim should forfeit game when Xboard */
8111 /* only passes fully legal moves */
8112 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8113 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8114 "False illegal-move claim", GE_XBOARD );
8115 return; // do not take back move we tested as valid
8117 currentMove = forwardMostMove-1;
8118 DisplayMove(currentMove-1); /* before DisplayMoveError */
8119 SwitchClocks(forwardMostMove-1); // [HGM] race
8120 DisplayBothClocks();
8121 snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8122 parseList[currentMove], _(cps->which));
8123 DisplayMoveError(buf1);
8124 DrawPosition(FALSE, boards[currentMove]);
8127 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8128 /* Program has a broken "time" command that
8129 outputs a string not ending in newline.
8135 * If chess program startup fails, exit with an error message.
8136 * Attempts to recover here are futile.
8138 if ((StrStr(message, "unknown host") != NULL)
8139 || (StrStr(message, "No remote directory") != NULL)
8140 || (StrStr(message, "not found") != NULL)
8141 || (StrStr(message, "No such file") != NULL)
8142 || (StrStr(message, "can't alloc") != NULL)
8143 || (StrStr(message, "Permission denied") != NULL)) {
8145 cps->maybeThinking = FALSE;
8146 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8147 _(cps->which), cps->program, cps->host, message);
8148 RemoveInputSource(cps->isr);
8149 if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8150 if(cps == &first) appData.noChessProgram = TRUE;
8151 DisplayError(buf1, 0);
8157 * Look for hint output
8159 if (sscanf(message, "Hint: %s", buf1) == 1) {
8160 if (cps == &first && hintRequested) {
8161 hintRequested = FALSE;
8162 if (ParseOneMove(buf1, forwardMostMove, &moveType,
8163 &fromX, &fromY, &toX, &toY, &promoChar)) {
8164 (void) CoordsToAlgebraic(boards[forwardMostMove],
8165 PosFlags(forwardMostMove),
8166 fromY, fromX, toY, toX, promoChar, buf1);
8167 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8168 DisplayInformation(buf2);
8170 /* Hint move could not be parsed!? */
8171 snprintf(buf2, sizeof(buf2),
8172 _("Illegal hint move \"%s\"\nfrom %s chess program"),
8173 buf1, _(cps->which));
8174 DisplayError(buf2, 0);
8177 safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8183 * Ignore other messages if game is not in progress
8185 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8186 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8189 * look for win, lose, draw, or draw offer
8191 if (strncmp(message, "1-0", 3) == 0) {
8192 char *p, *q, *r = "";
8193 p = strchr(message, '{');
8201 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8203 } else if (strncmp(message, "0-1", 3) == 0) {
8204 char *p, *q, *r = "";
8205 p = strchr(message, '{');
8213 /* Kludge for Arasan 4.1 bug */
8214 if (strcmp(r, "Black resigns") == 0) {
8215 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8218 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8220 } else if (strncmp(message, "1/2", 3) == 0) {
8221 char *p, *q, *r = "";
8222 p = strchr(message, '{');
8231 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8234 } else if (strncmp(message, "White resign", 12) == 0) {
8235 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8237 } else if (strncmp(message, "Black resign", 12) == 0) {
8238 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8240 } else if (strncmp(message, "White matches", 13) == 0 ||
8241 strncmp(message, "Black matches", 13) == 0 ) {
8242 /* [HGM] ignore GNUShogi noises */
8244 } else if (strncmp(message, "White", 5) == 0 &&
8245 message[5] != '(' &&
8246 StrStr(message, "Black") == NULL) {
8247 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8249 } else if (strncmp(message, "Black", 5) == 0 &&
8250 message[5] != '(') {
8251 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8253 } else if (strcmp(message, "resign") == 0 ||
8254 strcmp(message, "computer resigns") == 0) {
8256 case MachinePlaysBlack:
8257 case IcsPlayingBlack:
8258 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8260 case MachinePlaysWhite:
8261 case IcsPlayingWhite:
8262 GameEnds(BlackWins, "White resigns", GE_ENGINE);
8264 case TwoMachinesPlay:
8265 if (cps->twoMachinesColor[0] == 'w')
8266 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8268 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8275 } else if (strncmp(message, "opponent mates", 14) == 0) {
8277 case MachinePlaysBlack:
8278 case IcsPlayingBlack:
8279 GameEnds(WhiteWins, "White mates", GE_ENGINE);
8281 case MachinePlaysWhite:
8282 case IcsPlayingWhite:
8283 GameEnds(BlackWins, "Black mates", GE_ENGINE);
8285 case TwoMachinesPlay:
8286 if (cps->twoMachinesColor[0] == 'w')
8287 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8289 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8296 } else if (strncmp(message, "computer mates", 14) == 0) {
8298 case MachinePlaysBlack:
8299 case IcsPlayingBlack:
8300 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8302 case MachinePlaysWhite:
8303 case IcsPlayingWhite:
8304 GameEnds(WhiteWins, "White mates", GE_ENGINE);
8306 case TwoMachinesPlay:
8307 if (cps->twoMachinesColor[0] == 'w')
8308 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8310 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8317 } else if (strncmp(message, "checkmate", 9) == 0) {
8318 if (WhiteOnMove(forwardMostMove)) {
8319 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8321 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8324 } else if (strstr(message, "Draw") != NULL ||
8325 strstr(message, "game is a draw") != NULL) {
8326 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8328 } else if (strstr(message, "offer") != NULL &&
8329 strstr(message, "draw") != NULL) {
8331 if (appData.zippyPlay && first.initDone) {
8332 /* Relay offer to ICS */
8333 SendToICS(ics_prefix);
8334 SendToICS("draw\n");
8337 cps->offeredDraw = 2; /* valid until this engine moves twice */
8338 if (gameMode == TwoMachinesPlay) {
8339 if (cps->other->offeredDraw) {
8340 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8341 /* [HGM] in two-machine mode we delay relaying draw offer */
8342 /* until after we also have move, to see if it is really claim */
8344 } else if (gameMode == MachinePlaysWhite ||
8345 gameMode == MachinePlaysBlack) {
8346 if (userOfferedDraw) {
8347 DisplayInformation(_("Machine accepts your draw offer"));
8348 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8350 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8357 * Look for thinking output
8359 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8360 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8362 int plylev, mvleft, mvtot, curscore, time;
8363 char mvname[MOVE_LEN];
8367 int prefixHint = FALSE;
8368 mvname[0] = NULLCHAR;
8371 case MachinePlaysBlack:
8372 case IcsPlayingBlack:
8373 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8375 case MachinePlaysWhite:
8376 case IcsPlayingWhite:
8377 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8382 case IcsObserving: /* [DM] icsEngineAnalyze */
8383 if (!appData.icsEngineAnalyze) ignore = TRUE;
8385 case TwoMachinesPlay:
8386 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8396 ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8398 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8399 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8401 if (plyext != ' ' && plyext != '\t') {
8405 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8406 if( cps->scoreIsAbsolute &&
8407 ( gameMode == MachinePlaysBlack ||
8408 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8409 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
8410 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8411 !WhiteOnMove(currentMove)
8414 curscore = -curscore;
8418 tempStats.depth = plylev;
8419 tempStats.nodes = nodes;
8420 tempStats.time = time;
8421 tempStats.score = curscore;
8422 tempStats.got_only_move = 0;
8424 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8427 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
8428 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8429 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8430 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8431 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8432 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8433 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8434 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8437 /* Buffer overflow protection */
8438 if (buf1[0] != NULLCHAR) {
8439 if (strlen(buf1) >= sizeof(tempStats.movelist)
8440 && appData.debugMode) {
8442 "PV is too long; using the first %u bytes.\n",
8443 (unsigned) sizeof(tempStats.movelist) - 1);
8446 safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8448 sprintf(tempStats.movelist, " no PV\n");
8451 if (tempStats.seen_stat) {
8452 tempStats.ok_to_send = 1;
8455 if (strchr(tempStats.movelist, '(') != NULL) {
8456 tempStats.line_is_book = 1;
8457 tempStats.nr_moves = 0;
8458 tempStats.moves_left = 0;
8460 tempStats.line_is_book = 0;
8463 if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8464 programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8466 SendProgramStatsToFrontend( cps, &tempStats );
8469 [AS] Protect the thinkOutput buffer from overflow... this
8470 is only useful if buf1 hasn't overflowed first!
8472 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8474 (gameMode == TwoMachinesPlay ?
8475 ToUpper(cps->twoMachinesColor[0]) : ' '),
8476 ((double) curscore) / 100.0,
8477 prefixHint ? lastHint : "",
8478 prefixHint ? " " : "" );
8480 if( buf1[0] != NULLCHAR ) {
8481 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8483 if( strlen(buf1) > max_len ) {
8484 if( appData.debugMode) {
8485 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8487 buf1[max_len+1] = '\0';
8490 strcat( thinkOutput, buf1 );
8493 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8494 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8495 DisplayMove(currentMove - 1);
8499 } else if ((p=StrStr(message, "(only move)")) != NULL) {
8500 /* crafty (9.25+) says "(only move) <move>"
8501 * if there is only 1 legal move
8503 sscanf(p, "(only move) %s", buf1);
8504 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8505 sprintf(programStats.movelist, "%s (only move)", buf1);
8506 programStats.depth = 1;
8507 programStats.nr_moves = 1;
8508 programStats.moves_left = 1;
8509 programStats.nodes = 1;
8510 programStats.time = 1;
8511 programStats.got_only_move = 1;
8513 /* Not really, but we also use this member to
8514 mean "line isn't going to change" (Crafty
8515 isn't searching, so stats won't change) */
8516 programStats.line_is_book = 1;
8518 SendProgramStatsToFrontend( cps, &programStats );
8520 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8521 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8522 DisplayMove(currentMove - 1);
8525 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8526 &time, &nodes, &plylev, &mvleft,
8527 &mvtot, mvname) >= 5) {
8528 /* The stat01: line is from Crafty (9.29+) in response
8529 to the "." command */
8530 programStats.seen_stat = 1;
8531 cps->maybeThinking = TRUE;
8533 if (programStats.got_only_move || !appData.periodicUpdates)
8536 programStats.depth = plylev;
8537 programStats.time = time;
8538 programStats.nodes = nodes;
8539 programStats.moves_left = mvleft;
8540 programStats.nr_moves = mvtot;
8541 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8542 programStats.ok_to_send = 1;
8543 programStats.movelist[0] = '\0';
8545 SendProgramStatsToFrontend( cps, &programStats );
8549 } else if (strncmp(message,"++",2) == 0) {
8550 /* Crafty 9.29+ outputs this */
8551 programStats.got_fail = 2;
8554 } else if (strncmp(message,"--",2) == 0) {
8555 /* Crafty 9.29+ outputs this */
8556 programStats.got_fail = 1;
8559 } else if (thinkOutput[0] != NULLCHAR &&
8560 strncmp(message, " ", 4) == 0) {
8561 unsigned message_len;
8564 while (*p && *p == ' ') p++;
8566 message_len = strlen( p );
8568 /* [AS] Avoid buffer overflow */
8569 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8570 strcat(thinkOutput, " ");
8571 strcat(thinkOutput, p);
8574 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8575 strcat(programStats.movelist, " ");
8576 strcat(programStats.movelist, p);
8579 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8580 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8581 DisplayMove(currentMove - 1);
8589 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8590 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8592 ChessProgramStats cpstats;
8594 if (plyext != ' ' && plyext != '\t') {
8598 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8599 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8600 curscore = -curscore;
8603 cpstats.depth = plylev;
8604 cpstats.nodes = nodes;
8605 cpstats.time = time;
8606 cpstats.score = curscore;
8607 cpstats.got_only_move = 0;
8608 cpstats.movelist[0] = '\0';
8610 if (buf1[0] != NULLCHAR) {
8611 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8614 cpstats.ok_to_send = 0;
8615 cpstats.line_is_book = 0;
8616 cpstats.nr_moves = 0;
8617 cpstats.moves_left = 0;
8619 SendProgramStatsToFrontend( cps, &cpstats );
8626 /* Parse a game score from the character string "game", and
8627 record it as the history of the current game. The game
8628 score is NOT assumed to start from the standard position.
8629 The display is not updated in any way.
8632 ParseGameHistory(game)
8636 int fromX, fromY, toX, toY, boardIndex;
8641 if (appData.debugMode)
8642 fprintf(debugFP, "Parsing game history: %s\n", game);
8644 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8645 gameInfo.site = StrSave(appData.icsHost);
8646 gameInfo.date = PGNDate();
8647 gameInfo.round = StrSave("-");
8649 /* Parse out names of players */
8650 while (*game == ' ') game++;
8652 while (*game != ' ') *p++ = *game++;
8654 gameInfo.white = StrSave(buf);
8655 while (*game == ' ') game++;
8657 while (*game != ' ' && *game != '\n') *p++ = *game++;
8659 gameInfo.black = StrSave(buf);
8662 boardIndex = blackPlaysFirst ? 1 : 0;
8665 yyboardindex = boardIndex;
8666 moveType = (ChessMove) Myylex();
8668 case IllegalMove: /* maybe suicide chess, etc. */
8669 if (appData.debugMode) {
8670 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8671 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8672 setbuf(debugFP, NULL);
8674 case WhitePromotion:
8675 case BlackPromotion:
8676 case WhiteNonPromotion:
8677 case BlackNonPromotion:
8679 case WhiteCapturesEnPassant:
8680 case BlackCapturesEnPassant:
8681 case WhiteKingSideCastle:
8682 case WhiteQueenSideCastle:
8683 case BlackKingSideCastle:
8684 case BlackQueenSideCastle:
8685 case WhiteKingSideCastleWild:
8686 case WhiteQueenSideCastleWild:
8687 case BlackKingSideCastleWild:
8688 case BlackQueenSideCastleWild:
8690 case WhiteHSideCastleFR:
8691 case WhiteASideCastleFR:
8692 case BlackHSideCastleFR:
8693 case BlackASideCastleFR:
8695 fromX = currentMoveString[0] - AAA;
8696 fromY = currentMoveString[1] - ONE;
8697 toX = currentMoveString[2] - AAA;
8698 toY = currentMoveString[3] - ONE;
8699 promoChar = currentMoveString[4];
8703 fromX = moveType == WhiteDrop ?
8704 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8705 (int) CharToPiece(ToLower(currentMoveString[0]));
8707 toX = currentMoveString[2] - AAA;
8708 toY = currentMoveString[3] - ONE;
8709 promoChar = NULLCHAR;
8713 snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8714 if (appData.debugMode) {
8715 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8716 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8717 setbuf(debugFP, NULL);
8719 DisplayError(buf, 0);
8721 case ImpossibleMove:
8723 snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8724 if (appData.debugMode) {
8725 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8726 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8727 setbuf(debugFP, NULL);
8729 DisplayError(buf, 0);
8732 if (boardIndex < backwardMostMove) {
8733 /* Oops, gap. How did that happen? */
8734 DisplayError(_("Gap in move list"), 0);
8737 backwardMostMove = blackPlaysFirst ? 1 : 0;
8738 if (boardIndex > forwardMostMove) {
8739 forwardMostMove = boardIndex;
8743 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8744 strcat(parseList[boardIndex-1], " ");
8745 strcat(parseList[boardIndex-1], yy_text);
8757 case GameUnfinished:
8758 if (gameMode == IcsExamining) {
8759 if (boardIndex < backwardMostMove) {
8760 /* Oops, gap. How did that happen? */
8763 backwardMostMove = blackPlaysFirst ? 1 : 0;
8766 gameInfo.result = moveType;
8767 p = strchr(yy_text, '{');
8768 if (p == NULL) p = strchr(yy_text, '(');
8771 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8773 q = strchr(p, *p == '{' ? '}' : ')');
8774 if (q != NULL) *q = NULLCHAR;
8777 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8778 gameInfo.resultDetails = StrSave(p);
8781 if (boardIndex >= forwardMostMove &&
8782 !(gameMode == IcsObserving && ics_gamenum == -1)) {
8783 backwardMostMove = blackPlaysFirst ? 1 : 0;
8786 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8787 fromY, fromX, toY, toX, promoChar,
8788 parseList[boardIndex]);
8789 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8790 /* currentMoveString is set as a side-effect of yylex */
8791 safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8792 strcat(moveList[boardIndex], "\n");
8794 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8795 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8801 if(gameInfo.variant != VariantShogi)
8802 strcat(parseList[boardIndex - 1], "+");
8806 strcat(parseList[boardIndex - 1], "#");
8813 /* Apply a move to the given board */
8815 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8816 int fromX, fromY, toX, toY;
8820 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8821 int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8823 /* [HGM] compute & store e.p. status and castling rights for new position */
8824 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8826 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8827 oldEP = (signed char)board[EP_STATUS];
8828 board[EP_STATUS] = EP_NONE;
8830 if( board[toY][toX] != EmptySquare )
8831 board[EP_STATUS] = EP_CAPTURE;
8833 if (fromY == DROP_RANK) {
8835 piece = board[toY][toX] = (ChessSquare) fromX;
8839 if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
8840 if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
8841 board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
8843 if( board[fromY][fromX] == WhitePawn ) {
8844 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8845 board[EP_STATUS] = EP_PAWN_MOVE;
8847 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
8848 gameInfo.variant != VariantBerolina || toX < fromX)
8849 board[EP_STATUS] = toX | berolina;
8850 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8851 gameInfo.variant != VariantBerolina || toX > fromX)
8852 board[EP_STATUS] = toX;
8855 if( board[fromY][fromX] == BlackPawn ) {
8856 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8857 board[EP_STATUS] = EP_PAWN_MOVE;
8858 if( toY-fromY== -2) {
8859 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
8860 gameInfo.variant != VariantBerolina || toX < fromX)
8861 board[EP_STATUS] = toX | berolina;
8862 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8863 gameInfo.variant != VariantBerolina || toX > fromX)
8864 board[EP_STATUS] = toX;
8868 for(i=0; i<nrCastlingRights; i++) {
8869 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8870 board[CASTLING][i] == toX && castlingRank[i] == toY
8871 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8874 if (fromX == toX && fromY == toY) return;
8876 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8877 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8878 if(gameInfo.variant == VariantKnightmate)
8879 king += (int) WhiteUnicorn - (int) WhiteKing;
8881 /* Code added by Tord: */
8882 /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
8883 if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
8884 board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
8885 board[fromY][fromX] = EmptySquare;
8886 board[toY][toX] = EmptySquare;
8887 if((toX > fromX) != (piece == WhiteRook)) {
8888 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8890 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8892 } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
8893 board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
8894 board[fromY][fromX] = EmptySquare;
8895 board[toY][toX] = EmptySquare;
8896 if((toX > fromX) != (piece == BlackRook)) {
8897 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8899 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8901 /* End of code added by Tord */
8903 } else if (board[fromY][fromX] == king
8904 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8905 && toY == fromY && toX > fromX+1) {
8906 board[fromY][fromX] = EmptySquare;
8907 board[toY][toX] = king;
8908 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8909 board[fromY][BOARD_RGHT-1] = EmptySquare;
8910 } else if (board[fromY][fromX] == king
8911 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8912 && toY == fromY && toX < fromX-1) {
8913 board[fromY][fromX] = EmptySquare;
8914 board[toY][toX] = king;
8915 board[toY][toX+1] = board[fromY][BOARD_LEFT];
8916 board[fromY][BOARD_LEFT] = EmptySquare;
8917 } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
8918 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8919 && toY >= BOARD_HEIGHT-promoRank
8921 /* white pawn promotion */
8922 board[toY][toX] = CharToPiece(ToUpper(promoChar));
8923 if (board[toY][toX] == EmptySquare) {
8924 board[toY][toX] = WhiteQueen;
8926 if(gameInfo.variant==VariantBughouse ||
8927 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8928 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8929 board[fromY][fromX] = EmptySquare;
8930 } else if ((fromY == BOARD_HEIGHT-4)
8932 && gameInfo.variant != VariantXiangqi
8933 && gameInfo.variant != VariantBerolina
8934 && (board[fromY][fromX] == WhitePawn)
8935 && (board[toY][toX] == EmptySquare)) {
8936 board[fromY][fromX] = EmptySquare;
8937 board[toY][toX] = WhitePawn;
8938 captured = board[toY - 1][toX];
8939 board[toY - 1][toX] = EmptySquare;
8940 } else if ((fromY == BOARD_HEIGHT-4)
8942 && gameInfo.variant == VariantBerolina
8943 && (board[fromY][fromX] == WhitePawn)
8944 && (board[toY][toX] == EmptySquare)) {
8945 board[fromY][fromX] = EmptySquare;
8946 board[toY][toX] = WhitePawn;
8947 if(oldEP & EP_BEROLIN_A) {
8948 captured = board[fromY][fromX-1];
8949 board[fromY][fromX-1] = EmptySquare;
8950 }else{ captured = board[fromY][fromX+1];
8951 board[fromY][fromX+1] = EmptySquare;
8953 } else if (board[fromY][fromX] == king
8954 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8955 && toY == fromY && toX > fromX+1) {
8956 board[fromY][fromX] = EmptySquare;
8957 board[toY][toX] = king;
8958 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8959 board[fromY][BOARD_RGHT-1] = EmptySquare;
8960 } else if (board[fromY][fromX] == king
8961 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8962 && toY == fromY && toX < fromX-1) {
8963 board[fromY][fromX] = EmptySquare;
8964 board[toY][toX] = king;
8965 board[toY][toX+1] = board[fromY][BOARD_LEFT];
8966 board[fromY][BOARD_LEFT] = EmptySquare;
8967 } else if (fromY == 7 && fromX == 3
8968 && board[fromY][fromX] == BlackKing
8969 && toY == 7 && toX == 5) {
8970 board[fromY][fromX] = EmptySquare;
8971 board[toY][toX] = BlackKing;
8972 board[fromY][7] = EmptySquare;
8973 board[toY][4] = BlackRook;
8974 } else if (fromY == 7 && fromX == 3
8975 && board[fromY][fromX] == BlackKing
8976 && toY == 7 && toX == 1) {
8977 board[fromY][fromX] = EmptySquare;
8978 board[toY][toX] = BlackKing;
8979 board[fromY][0] = EmptySquare;
8980 board[toY][2] = BlackRook;
8981 } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
8982 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8985 /* black pawn promotion */
8986 board[toY][toX] = CharToPiece(ToLower(promoChar));
8987 if (board[toY][toX] == EmptySquare) {
8988 board[toY][toX] = BlackQueen;
8990 if(gameInfo.variant==VariantBughouse ||
8991 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8992 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8993 board[fromY][fromX] = EmptySquare;
8994 } else if ((fromY == 3)
8996 && gameInfo.variant != VariantXiangqi
8997 && gameInfo.variant != VariantBerolina
8998 && (board[fromY][fromX] == BlackPawn)
8999 && (board[toY][toX] == EmptySquare)) {
9000 board[fromY][fromX] = EmptySquare;
9001 board[toY][toX] = BlackPawn;
9002 captured = board[toY + 1][toX];
9003 board[toY + 1][toX] = EmptySquare;
9004 } else if ((fromY == 3)
9006 && gameInfo.variant == VariantBerolina
9007 && (board[fromY][fromX] == BlackPawn)
9008 && (board[toY][toX] == EmptySquare)) {
9009 board[fromY][fromX] = EmptySquare;
9010 board[toY][toX] = BlackPawn;
9011 if(oldEP & EP_BEROLIN_A) {
9012 captured = board[fromY][fromX-1];
9013 board[fromY][fromX-1] = EmptySquare;
9014 }else{ captured = board[fromY][fromX+1];
9015 board[fromY][fromX+1] = EmptySquare;
9018 board[toY][toX] = board[fromY][fromX];
9019 board[fromY][fromX] = EmptySquare;
9023 if (gameInfo.holdingsWidth != 0) {
9025 /* !!A lot more code needs to be written to support holdings */
9026 /* [HGM] OK, so I have written it. Holdings are stored in the */
9027 /* penultimate board files, so they are automaticlly stored */
9028 /* in the game history. */
9029 if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9030 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9031 /* Delete from holdings, by decreasing count */
9032 /* and erasing image if necessary */
9033 p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9034 if(p < (int) BlackPawn) { /* white drop */
9035 p -= (int)WhitePawn;
9036 p = PieceToNumber((ChessSquare)p);
9037 if(p >= gameInfo.holdingsSize) p = 0;
9038 if(--board[p][BOARD_WIDTH-2] <= 0)
9039 board[p][BOARD_WIDTH-1] = EmptySquare;
9040 if((int)board[p][BOARD_WIDTH-2] < 0)
9041 board[p][BOARD_WIDTH-2] = 0;
9042 } else { /* black drop */
9043 p -= (int)BlackPawn;
9044 p = PieceToNumber((ChessSquare)p);
9045 if(p >= gameInfo.holdingsSize) p = 0;
9046 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9047 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9048 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9049 board[BOARD_HEIGHT-1-p][1] = 0;
9052 if (captured != EmptySquare && gameInfo.holdingsSize > 0
9053 && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess ) {
9054 /* [HGM] holdings: Add to holdings, if holdings exist */
9055 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
9056 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9057 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9060 if (p >= (int) BlackPawn) {
9061 p -= (int)BlackPawn;
9062 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9063 /* in Shogi restore piece to its original first */
9064 captured = (ChessSquare) (DEMOTED captured);
9067 p = PieceToNumber((ChessSquare)p);
9068 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9069 board[p][BOARD_WIDTH-2]++;
9070 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9072 p -= (int)WhitePawn;
9073 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9074 captured = (ChessSquare) (DEMOTED captured);
9077 p = PieceToNumber((ChessSquare)p);
9078 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9079 board[BOARD_HEIGHT-1-p][1]++;
9080 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9083 } else if (gameInfo.variant == VariantAtomic) {
9084 if (captured != EmptySquare) {
9086 for (y = toY-1; y <= toY+1; y++) {
9087 for (x = toX-1; x <= toX+1; x++) {
9088 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9089 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9090 board[y][x] = EmptySquare;
9094 board[toY][toX] = EmptySquare;
9097 if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9098 board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9100 if(promoChar == '+') {
9101 /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9102 board[toY][toX] = (ChessSquare) (PROMOTED piece);
9103 } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9104 board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9106 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9107 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9108 // [HGM] superchess: take promotion piece out of holdings
9109 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9110 if((int)piece < (int)BlackPawn) { // determine stm from piece color
9111 if(!--board[k][BOARD_WIDTH-2])
9112 board[k][BOARD_WIDTH-1] = EmptySquare;
9114 if(!--board[BOARD_HEIGHT-1-k][1])
9115 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9121 /* Updates forwardMostMove */
9123 MakeMove(fromX, fromY, toX, toY, promoChar)
9124 int fromX, fromY, toX, toY;
9127 // forwardMostMove++; // [HGM] bare: moved downstream
9129 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9130 int timeLeft; static int lastLoadFlag=0; int king, piece;
9131 piece = boards[forwardMostMove][fromY][fromX];
9132 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9133 if(gameInfo.variant == VariantKnightmate)
9134 king += (int) WhiteUnicorn - (int) WhiteKing;
9135 if(forwardMostMove == 0) {
9137 fprintf(serverMoves, "%s;", second.tidy);
9138 fprintf(serverMoves, "%s;", first.tidy);
9139 if(!blackPlaysFirst)
9140 fprintf(serverMoves, "%s;", second.tidy);
9141 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9142 lastLoadFlag = loadFlag;
9144 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9145 // print castling suffix
9146 if( toY == fromY && piece == king ) {
9148 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9150 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9153 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9154 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
9155 boards[forwardMostMove][toY][toX] == EmptySquare
9156 && fromX != toX && fromY != toY)
9157 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9159 if(promoChar != NULLCHAR)
9160 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
9162 fprintf(serverMoves, "/%d/%d",
9163 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9164 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9165 else timeLeft = blackTimeRemaining/1000;
9166 fprintf(serverMoves, "/%d", timeLeft);
9168 fflush(serverMoves);
9171 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
9172 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
9176 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9177 if (commentList[forwardMostMove+1] != NULL) {
9178 free(commentList[forwardMostMove+1]);
9179 commentList[forwardMostMove+1] = NULL;
9181 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9182 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9183 // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9184 SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9185 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9186 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9187 gameInfo.result = GameUnfinished;
9188 if (gameInfo.resultDetails != NULL) {
9189 free(gameInfo.resultDetails);
9190 gameInfo.resultDetails = NULL;
9192 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9193 moveList[forwardMostMove - 1]);
9194 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
9195 PosFlags(forwardMostMove - 1),
9196 fromY, fromX, toY, toX, promoChar,
9197 parseList[forwardMostMove - 1]);
9198 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9204 if(gameInfo.variant != VariantShogi)
9205 strcat(parseList[forwardMostMove - 1], "+");
9209 strcat(parseList[forwardMostMove - 1], "#");
9212 if (appData.debugMode) {
9213 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9218 /* Updates currentMove if not pausing */
9220 ShowMove(fromX, fromY, toX, toY)
9222 int instant = (gameMode == PlayFromGameFile) ?
9223 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9224 if(appData.noGUI) return;
9225 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9227 if (forwardMostMove == currentMove + 1) {
9228 AnimateMove(boards[forwardMostMove - 1],
9229 fromX, fromY, toX, toY);
9231 if (appData.highlightLastMove) {
9232 SetHighlights(fromX, fromY, toX, toY);
9235 currentMove = forwardMostMove;
9238 if (instant) return;
9240 DisplayMove(currentMove - 1);
9241 DrawPosition(FALSE, boards[currentMove]);
9242 DisplayBothClocks();
9243 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9246 void SendEgtPath(ChessProgramState *cps)
9247 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9248 char buf[MSG_SIZ], name[MSG_SIZ], *p;
9250 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9253 char c, *q = name+1, *r, *s;
9255 name[0] = ','; // extract next format name from feature and copy with prefixed ','
9256 while(*p && *p != ',') *q++ = *p++;
9258 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9259 strcmp(name, ",nalimov:") == 0 ) {
9260 // take nalimov path from the menu-changeable option first, if it is defined
9261 snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9262 SendToProgram(buf,cps); // send egtbpath command for nalimov
9264 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9265 (s = StrStr(appData.egtFormats, name)) != NULL) {
9266 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9267 s = r = StrStr(s, ":") + 1; // beginning of path info
9268 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9269 c = *r; *r = 0; // temporarily null-terminate path info
9270 *--q = 0; // strip of trailig ':' from name
9271 snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9273 SendToProgram(buf,cps); // send egtbpath command for this format
9275 if(*p == ',') p++; // read away comma to position for next format name
9280 InitChessProgram(cps, setup)
9281 ChessProgramState *cps;
9282 int setup; /* [HGM] needed to setup FRC opening position */
9284 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9285 if (appData.noChessProgram) return;
9286 hintRequested = FALSE;
9287 bookRequested = FALSE;
9289 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9290 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9291 if(cps->memSize) { /* [HGM] memory */
9292 snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9293 SendToProgram(buf, cps);
9295 SendEgtPath(cps); /* [HGM] EGT */
9296 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9297 snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9298 SendToProgram(buf, cps);
9301 SendToProgram(cps->initString, cps);
9302 if (gameInfo.variant != VariantNormal &&
9303 gameInfo.variant != VariantLoadable
9304 /* [HGM] also send variant if board size non-standard */
9305 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9307 char *v = VariantName(gameInfo.variant);
9308 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9309 /* [HGM] in protocol 1 we have to assume all variants valid */
9310 snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9311 DisplayFatalError(buf, 0, 1);
9315 /* [HGM] make prefix for non-standard board size. Awkward testing... */
9316 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9317 if( gameInfo.variant == VariantXiangqi )
9318 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9319 if( gameInfo.variant == VariantShogi )
9320 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9321 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9322 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9323 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9324 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9325 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9326 if( gameInfo.variant == VariantCourier )
9327 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9328 if( gameInfo.variant == VariantSuper )
9329 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9330 if( gameInfo.variant == VariantGreat )
9331 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9332 if( gameInfo.variant == VariantSChess )
9333 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9336 snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9337 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9338 /* [HGM] varsize: try first if this defiant size variant is specifically known */
9339 if(StrStr(cps->variants, b) == NULL) {
9340 // specific sized variant not known, check if general sizing allowed
9341 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9342 if(StrStr(cps->variants, "boardsize") == NULL) {
9343 snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9344 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9345 DisplayFatalError(buf, 0, 1);
9348 /* [HGM] here we really should compare with the maximum supported board size */
9351 } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9352 snprintf(buf, MSG_SIZ, "variant %s\n", b);
9353 SendToProgram(buf, cps);
9355 currentlyInitializedVariant = gameInfo.variant;
9357 /* [HGM] send opening position in FRC to first engine */
9359 SendToProgram("force\n", cps);
9361 /* engine is now in force mode! Set flag to wake it up after first move. */
9362 setboardSpoiledMachineBlack = 1;
9366 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9367 SendToProgram(buf, cps);
9369 cps->maybeThinking = FALSE;
9370 cps->offeredDraw = 0;
9371 if (!appData.icsActive) {
9372 SendTimeControl(cps, movesPerSession, timeControl,
9373 timeIncrement, appData.searchDepth,
9376 if (appData.showThinking
9377 // [HGM] thinking: four options require thinking output to be sent
9378 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9380 SendToProgram("post\n", cps);
9382 SendToProgram("hard\n", cps);
9383 if (!appData.ponderNextMove) {
9384 /* Warning: "easy" is a toggle in GNU Chess, so don't send
9385 it without being sure what state we are in first. "hard"
9386 is not a toggle, so that one is OK.
9388 SendToProgram("easy\n", cps);
9391 snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9392 SendToProgram(buf, cps);
9394 cps->initDone = TRUE;
9399 StartChessProgram(cps)
9400 ChessProgramState *cps;
9405 if (appData.noChessProgram) return;
9406 cps->initDone = FALSE;
9408 if (strcmp(cps->host, "localhost") == 0) {
9409 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9410 } else if (*appData.remoteShell == NULLCHAR) {
9411 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9413 if (*appData.remoteUser == NULLCHAR) {
9414 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9417 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9418 cps->host, appData.remoteUser, cps->program);
9420 err = StartChildProcess(buf, "", &cps->pr);
9424 snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9425 DisplayFatalError(buf, err, 1);
9431 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9432 if (cps->protocolVersion > 1) {
9433 snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9434 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9435 cps->comboCnt = 0; // and values of combo boxes
9436 SendToProgram(buf, cps);
9438 SendToProgram("xboard\n", cps);
9443 TwoMachinesEventIfReady P((void))
9445 static int curMess = 0;
9446 if (first.lastPing != first.lastPong) {
9447 if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9448 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9451 if (second.lastPing != second.lastPong) {
9452 if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9453 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9456 DisplayMessage("", ""); curMess = 0;
9462 CreateTourney(char *name)
9465 if(name[0] == NULLCHAR) return 0;
9466 f = fopen(appData.tourneyFile, "r");
9467 if(f) { // file exists
9468 ParseArgsFromFile(f); // parse it
9470 f = fopen(appData.tourneyFile, "w");
9471 if(f == NULL) { DisplayError("Could not write on tourney file", 0); return 0; } else {
9472 // create a file with tournament description
9473 fprintf(f, "-participants {%s}\n", appData.participants);
9474 fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9475 fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9476 fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9477 fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9478 fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9479 fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9480 fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9481 fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9482 fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9483 fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9484 fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9486 fprintf(f, "-searchTime \"%s\"\n", appData.searchTime);
9488 fprintf(f, "-mps %d\n", appData.movesPerSession);
9489 fprintf(f, "-tc %s\n", appData.timeControl);
9490 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9492 fprintf(f, "-results \"\"\n");
9496 appData.noChessProgram = FALSE;
9497 appData.clockMode = TRUE;
9502 #define MAXENGINES 1000
9503 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9505 void NamesToList(char *names, char **engineList, char **engineMnemonic)
9507 char buf[MSG_SIZ], *p, *q;
9511 while(*p && *p != '\n') *q++ = *p++;
9513 if(engineList[i]) free(engineList[i]);
9514 engineList[i] = strdup(buf);
9516 TidyProgramName(engineList[i], "localhost", buf);
9517 if(engineMnemonic[i]) free(engineMnemonic[i]);
9518 if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9520 sscanf(q + 8, "%s", buf + strlen(buf));
9523 engineMnemonic[i] = strdup(buf);
9525 if(i > MAXENGINES - 2) break;
9527 engineList[i] = NULL;
9530 // following implemented as macro to avoid type limitations
9531 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9533 void SwapEngines(int n)
9534 { // swap settings for first engine and other engine (so far only some selected options)
9539 SWAP(chessProgram, p)
9541 SWAP(hasOwnBookUCI, h)
9542 SWAP(protocolVersion, h)
9544 SWAP(scoreIsAbsolute, h)
9550 SetPlayer(int player)
9551 { // [HGM] find the engine line of the partcipant given by number, and parse its options.
9553 char buf[MSG_SIZ], *engineName, *p = appData.participants;
9554 static char resetOptions[] = "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 -firstOptions \"\" "
9555 "-firstNeedsNoncompliantFEN false -firstNPS -1";
9556 for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9557 engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9558 for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9560 snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9561 ParseArgsFromString(resetOptions);
9562 ParseArgsFromString(buf);
9568 Pairing(int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9569 { // determine players from game number
9570 int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle, pairingsPerRound;
9572 if(appData.tourneyType == 0) {
9573 roundsPerCycle = (nPlayers - 1) | 1;
9574 pairingsPerRound = nPlayers / 2;
9575 } else if(appData.tourneyType > 0) {
9576 roundsPerCycle = nPlayers - appData.tourneyType;
9577 pairingsPerRound = appData.tourneyType;
9579 gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9580 gamesPerCycle = gamesPerRound * roundsPerCycle;
9581 appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9582 curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9583 curRound = nr / gamesPerRound; nr %= gamesPerRound;
9584 curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9585 matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9586 roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9588 if(appData.cycleSync) *syncInterval = gamesPerCycle;
9589 if(appData.roundSync) *syncInterval = gamesPerRound;
9591 if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9593 if(appData.tourneyType == 0) {
9594 if(curPairing == (nPlayers-1)/2 ) {
9595 *whitePlayer = curRound;
9596 *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9598 *whitePlayer = curRound - pairingsPerRound + curPairing;
9599 if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9600 *blackPlayer = curRound + pairingsPerRound - curPairing;
9601 if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9603 } else if(appData.tourneyType > 0) {
9604 *whitePlayer = curPairing;
9605 *blackPlayer = curRound + appData.tourneyType;
9608 // take care of white/black alternation per round.
9609 // For cycles and games this is already taken care of by default, derived from matchGame!
9610 return curRound & 1;
9614 NextTourneyGame(int nr, int *swapColors)
9615 { // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
9617 int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers=0;
9619 if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
9620 tf = fopen(appData.tourneyFile, "r");
9621 if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
9622 ParseArgsFromFile(tf); fclose(tf);
9623 InitTimeControls(); // TC might be altered from tourney file
9625 p = appData.participants;
9626 while(p = strchr(p, '\n')) p++, nPlayers++; // count participants
9627 *swapColors = Pairing(nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
9630 p = q = appData.results;
9631 while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
9632 if(firstBusy/syncInterval < (nextGame/syncInterval)) {
9633 DisplayMessage(_("Waiting for other game(s)"),"");
9634 waitingForGame = TRUE;
9635 ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
9638 waitingForGame = FALSE;
9641 if(first.pr != NoProc) return 1; // engines already loaded
9643 // redefine engines, engine dir, etc.
9644 NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
9645 SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
9647 SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
9648 SwapEngines(1); // and make that valid for second engine by swapping
9649 InitEngine(&first, 0); // initialize ChessProgramStates based on new settings.
9650 InitEngine(&second, 1);
9651 CommonEngineInit(); // after this TwoMachinesEvent will create correct engine processes
9657 { // performs game initialization that does not invoke engines, and then tries to start the game
9658 int firstWhite, swapColors = 0;
9659 if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
9660 firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
9661 firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
9662 first.twoMachinesColor = firstWhite ? "white\n" : "black\n"; // perform actual color assignement
9663 second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
9664 appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
9665 Reset(FALSE, first.pr != NoProc);
9666 appData.noChessProgram = FALSE;
9667 if(!LoadGameOrPosition(matchGame)) return; // setup game; abort when bad game/pos file
9671 void UserAdjudicationEvent( int result )
9673 ChessMove gameResult = GameIsDrawn;
9676 gameResult = WhiteWins;
9678 else if( result < 0 ) {
9679 gameResult = BlackWins;
9682 if( gameMode == TwoMachinesPlay ) {
9683 GameEnds( gameResult, "User adjudication", GE_XBOARD );
9688 // [HGM] save: calculate checksum of game to make games easily identifiable
9689 int StringCheckSum(char *s)
9692 if(s==NULL) return 0;
9693 while(*s) i = i*259 + *s++;
9700 for(i=backwardMostMove; i<forwardMostMove; i++) {
9701 sum += pvInfoList[i].depth;
9702 sum += StringCheckSum(parseList[i]);
9703 sum += StringCheckSum(commentList[i]);
9706 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9707 return sum + StringCheckSum(commentList[i]);
9708 } // end of save patch
9711 GameEnds(result, resultDetails, whosays)
9713 char *resultDetails;
9716 GameMode nextGameMode;
9718 char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
9720 if(endingGame) return; /* [HGM] crash: forbid recursion */
9722 if(twoBoards) { // [HGM] dual: switch back to one board
9723 twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9724 DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9726 if (appData.debugMode) {
9727 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9728 result, resultDetails ? resultDetails : "(null)", whosays);
9731 fromX = fromY = -1; // [HGM] abort any move the user is entering.
9733 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9734 /* If we are playing on ICS, the server decides when the
9735 game is over, but the engine can offer to draw, claim
9739 if (appData.zippyPlay && first.initDone) {
9740 if (result == GameIsDrawn) {
9741 /* In case draw still needs to be claimed */
9742 SendToICS(ics_prefix);
9743 SendToICS("draw\n");
9744 } else if (StrCaseStr(resultDetails, "resign")) {
9745 SendToICS(ics_prefix);
9746 SendToICS("resign\n");
9750 endingGame = 0; /* [HGM] crash */
9754 /* If we're loading the game from a file, stop */
9755 if (whosays == GE_FILE) {
9756 (void) StopLoadGameTimer();
9760 /* Cancel draw offers */
9761 first.offeredDraw = second.offeredDraw = 0;
9763 /* If this is an ICS game, only ICS can really say it's done;
9764 if not, anyone can. */
9765 isIcsGame = (gameMode == IcsPlayingWhite ||
9766 gameMode == IcsPlayingBlack ||
9767 gameMode == IcsObserving ||
9768 gameMode == IcsExamining);
9770 if (!isIcsGame || whosays == GE_ICS) {
9771 /* OK -- not an ICS game, or ICS said it was done */
9773 if (!isIcsGame && !appData.noChessProgram)
9774 SetUserThinkingEnables();
9776 /* [HGM] if a machine claims the game end we verify this claim */
9777 if(gameMode == TwoMachinesPlay && appData.testClaims) {
9778 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9780 ChessMove trueResult = (ChessMove) -1;
9782 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
9783 first.twoMachinesColor[0] :
9784 second.twoMachinesColor[0] ;
9786 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9787 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9788 /* [HGM] verify: engine mate claims accepted if they were flagged */
9789 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9791 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9792 /* [HGM] verify: engine mate claims accepted if they were flagged */
9793 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9795 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9796 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9799 // now verify win claims, but not in drop games, as we don't understand those yet
9800 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9801 || gameInfo.variant == VariantGreat) &&
9802 (result == WhiteWins && claimer == 'w' ||
9803 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
9804 if (appData.debugMode) {
9805 fprintf(debugFP, "result=%d sp=%d move=%d\n",
9806 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9808 if(result != trueResult) {
9809 snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9810 result = claimer == 'w' ? BlackWins : WhiteWins;
9811 resultDetails = buf;
9814 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9815 && (forwardMostMove <= backwardMostMove ||
9816 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9817 (claimer=='b')==(forwardMostMove&1))
9819 /* [HGM] verify: draws that were not flagged are false claims */
9820 snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
9821 result = claimer == 'w' ? BlackWins : WhiteWins;
9822 resultDetails = buf;
9824 /* (Claiming a loss is accepted no questions asked!) */
9826 /* [HGM] bare: don't allow bare King to win */
9827 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9828 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9829 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9830 && result != GameIsDrawn)
9831 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9832 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9833 int p = (signed char)boards[forwardMostMove][i][j] - color;
9834 if(p >= 0 && p <= (int)WhiteKing) k++;
9836 if (appData.debugMode) {
9837 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9838 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9841 result = GameIsDrawn;
9842 snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
9843 resultDetails = buf;
9849 if(serverMoves != NULL && !loadFlag) { char c = '=';
9850 if(result==WhiteWins) c = '+';
9851 if(result==BlackWins) c = '-';
9852 if(resultDetails != NULL)
9853 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9855 if (resultDetails != NULL) {
9856 gameInfo.result = result;
9857 gameInfo.resultDetails = StrSave(resultDetails);
9859 /* display last move only if game was not loaded from file */
9860 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9861 DisplayMove(currentMove - 1);
9863 if (forwardMostMove != 0) {
9864 if (gameMode != PlayFromGameFile && gameMode != EditGame
9865 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9867 if (*appData.saveGameFile != NULLCHAR) {
9868 SaveGameToFile(appData.saveGameFile, TRUE);
9869 } else if (appData.autoSaveGames) {
9872 if (*appData.savePositionFile != NULLCHAR) {
9873 SavePositionToFile(appData.savePositionFile);
9878 /* Tell program how game ended in case it is learning */
9879 /* [HGM] Moved this to after saving the PGN, just in case */
9880 /* engine died and we got here through time loss. In that */
9881 /* case we will get a fatal error writing the pipe, which */
9882 /* would otherwise lose us the PGN. */
9883 /* [HGM] crash: not needed anymore, but doesn't hurt; */
9884 /* output during GameEnds should never be fatal anymore */
9885 if (gameMode == MachinePlaysWhite ||
9886 gameMode == MachinePlaysBlack ||
9887 gameMode == TwoMachinesPlay ||
9888 gameMode == IcsPlayingWhite ||
9889 gameMode == IcsPlayingBlack ||
9890 gameMode == BeginningOfGame) {
9892 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
9894 if (first.pr != NoProc) {
9895 SendToProgram(buf, &first);
9897 if (second.pr != NoProc &&
9898 gameMode == TwoMachinesPlay) {
9899 SendToProgram(buf, &second);
9904 if (appData.icsActive) {
9905 if (appData.quietPlay &&
9906 (gameMode == IcsPlayingWhite ||
9907 gameMode == IcsPlayingBlack)) {
9908 SendToICS(ics_prefix);
9909 SendToICS("set shout 1\n");
9911 nextGameMode = IcsIdle;
9912 ics_user_moved = FALSE;
9913 /* clean up premove. It's ugly when the game has ended and the
9914 * premove highlights are still on the board.
9918 ClearPremoveHighlights();
9919 DrawPosition(FALSE, boards[currentMove]);
9921 if (whosays == GE_ICS) {
9924 if (gameMode == IcsPlayingWhite)
9926 else if(gameMode == IcsPlayingBlack)
9930 if (gameMode == IcsPlayingBlack)
9932 else if(gameMode == IcsPlayingWhite)
9939 PlayIcsUnfinishedSound();
9942 } else if (gameMode == EditGame ||
9943 gameMode == PlayFromGameFile ||
9944 gameMode == AnalyzeMode ||
9945 gameMode == AnalyzeFile) {
9946 nextGameMode = gameMode;
9948 nextGameMode = EndOfGame;
9953 nextGameMode = gameMode;
9956 if (appData.noChessProgram) {
9957 gameMode = nextGameMode;
9959 endingGame = 0; /* [HGM] crash */
9964 /* Put first chess program into idle state */
9965 if (first.pr != NoProc &&
9966 (gameMode == MachinePlaysWhite ||
9967 gameMode == MachinePlaysBlack ||
9968 gameMode == TwoMachinesPlay ||
9969 gameMode == IcsPlayingWhite ||
9970 gameMode == IcsPlayingBlack ||
9971 gameMode == BeginningOfGame)) {
9972 SendToProgram("force\n", &first);
9973 if (first.usePing) {
9975 snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
9976 SendToProgram(buf, &first);
9979 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9980 /* Kill off first chess program */
9981 if (first.isr != NULL)
9982 RemoveInputSource(first.isr);
9985 if (first.pr != NoProc) {
9987 DoSleep( appData.delayBeforeQuit );
9988 SendToProgram("quit\n", &first);
9989 DoSleep( appData.delayAfterQuit );
9990 DestroyChildProcess(first.pr, first.useSigterm);
9995 /* Put second chess program into idle state */
9996 if (second.pr != NoProc &&
9997 gameMode == TwoMachinesPlay) {
9998 SendToProgram("force\n", &second);
9999 if (second.usePing) {
10001 snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10002 SendToProgram(buf, &second);
10005 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10006 /* Kill off second chess program */
10007 if (second.isr != NULL)
10008 RemoveInputSource(second.isr);
10011 if (second.pr != NoProc) {
10012 DoSleep( appData.delayBeforeQuit );
10013 SendToProgram("quit\n", &second);
10014 DoSleep( appData.delayAfterQuit );
10015 DestroyChildProcess(second.pr, second.useSigterm);
10017 second.pr = NoProc;
10020 if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10021 char resChar = '=';
10025 if (first.twoMachinesColor[0] == 'w') {
10028 second.matchWins++;
10033 if (first.twoMachinesColor[0] == 'b') {
10036 second.matchWins++;
10039 case GameUnfinished:
10045 if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10046 if(appData.tourneyFile[0] && !abortMatch){ // [HGM] we are in a tourney; update tourney file with game result
10047 ReserveGame(nextGame, resChar); // sets nextGame
10048 if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10049 } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10051 if (nextGame <= appData.matchGames && !abortMatch) {
10052 gameMode = nextGameMode;
10053 matchGame = nextGame; // this will be overruled in tourney mode!
10054 GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10055 ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10056 endingGame = 0; /* [HGM] crash */
10059 gameMode = nextGameMode;
10060 snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10061 first.tidy, second.tidy,
10062 first.matchWins, second.matchWins,
10063 appData.matchGames - (first.matchWins + second.matchWins));
10064 popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10065 if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10066 first.twoMachinesColor = "black\n";
10067 second.twoMachinesColor = "white\n";
10069 first.twoMachinesColor = "white\n";
10070 second.twoMachinesColor = "black\n";
10074 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10075 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10077 gameMode = nextGameMode;
10079 endingGame = 0; /* [HGM] crash */
10080 if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10081 if(matchMode == TRUE) { // match through command line: exit with or without popup
10083 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10085 } else DisplayFatalError(buf, 0, 0);
10086 } else { // match through menu; just stop, with or without popup
10087 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10089 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10090 } else DisplayNote(buf);
10092 if(ranking) free(ranking);
10096 /* Assumes program was just initialized (initString sent).
10097 Leaves program in force mode. */
10099 FeedMovesToProgram(cps, upto)
10100 ChessProgramState *cps;
10105 if (appData.debugMode)
10106 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10107 startedFromSetupPosition ? "position and " : "",
10108 backwardMostMove, upto, cps->which);
10109 if(currentlyInitializedVariant != gameInfo.variant) {
10111 // [HGM] variantswitch: make engine aware of new variant
10112 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10113 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10114 snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10115 SendToProgram(buf, cps);
10116 currentlyInitializedVariant = gameInfo.variant;
10118 SendToProgram("force\n", cps);
10119 if (startedFromSetupPosition) {
10120 SendBoard(cps, backwardMostMove);
10121 if (appData.debugMode) {
10122 fprintf(debugFP, "feedMoves\n");
10125 for (i = backwardMostMove; i < upto; i++) {
10126 SendMoveToProgram(i, cps);
10132 ResurrectChessProgram()
10134 /* The chess program may have exited.
10135 If so, restart it and feed it all the moves made so far. */
10136 static int doInit = 0;
10138 if (appData.noChessProgram) return 1;
10140 if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10141 if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10142 if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10143 doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10145 if (first.pr != NoProc) return 1;
10146 StartChessProgram(&first);
10148 InitChessProgram(&first, FALSE);
10149 FeedMovesToProgram(&first, currentMove);
10151 if (!first.sendTime) {
10152 /* can't tell gnuchess what its clock should read,
10153 so we bow to its notion. */
10155 timeRemaining[0][currentMove] = whiteTimeRemaining;
10156 timeRemaining[1][currentMove] = blackTimeRemaining;
10159 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10160 appData.icsEngineAnalyze) && first.analysisSupport) {
10161 SendToProgram("analyze\n", &first);
10162 first.analyzing = TRUE;
10168 * Button procedures
10171 Reset(redraw, init)
10176 if (appData.debugMode) {
10177 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10178 redraw, init, gameMode);
10180 CleanupTail(); // [HGM] vari: delete any stored variations
10181 pausing = pauseExamInvalid = FALSE;
10182 startedFromSetupPosition = blackPlaysFirst = FALSE;
10184 whiteFlag = blackFlag = FALSE;
10185 userOfferedDraw = FALSE;
10186 hintRequested = bookRequested = FALSE;
10187 first.maybeThinking = FALSE;
10188 second.maybeThinking = FALSE;
10189 first.bookSuspend = FALSE; // [HGM] book
10190 second.bookSuspend = FALSE;
10191 thinkOutput[0] = NULLCHAR;
10192 lastHint[0] = NULLCHAR;
10193 ClearGameInfo(&gameInfo);
10194 gameInfo.variant = StringToVariant(appData.variant);
10195 ics_user_moved = ics_clock_paused = FALSE;
10196 ics_getting_history = H_FALSE;
10198 white_holding[0] = black_holding[0] = NULLCHAR;
10199 ClearProgramStats();
10200 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10204 flipView = appData.flipView;
10205 ClearPremoveHighlights();
10206 gotPremove = FALSE;
10207 alarmSounded = FALSE;
10209 GameEnds(EndOfFile, NULL, GE_PLAYER);
10210 if(appData.serverMovesName != NULL) {
10211 /* [HGM] prepare to make moves file for broadcasting */
10212 clock_t t = clock();
10213 if(serverMoves != NULL) fclose(serverMoves);
10214 serverMoves = fopen(appData.serverMovesName, "r");
10215 if(serverMoves != NULL) {
10216 fclose(serverMoves);
10217 /* delay 15 sec before overwriting, so all clients can see end */
10218 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10220 serverMoves = fopen(appData.serverMovesName, "w");
10224 gameMode = BeginningOfGame;
10226 if(appData.icsActive) gameInfo.variant = VariantNormal;
10227 currentMove = forwardMostMove = backwardMostMove = 0;
10228 InitPosition(redraw);
10229 for (i = 0; i < MAX_MOVES; i++) {
10230 if (commentList[i] != NULL) {
10231 free(commentList[i]);
10232 commentList[i] = NULL;
10236 timeRemaining[0][0] = whiteTimeRemaining;
10237 timeRemaining[1][0] = blackTimeRemaining;
10239 if (first.pr == NULL) {
10240 StartChessProgram(&first);
10243 InitChessProgram(&first, startedFromSetupPosition);
10246 DisplayMessage("", "");
10247 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10248 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10255 if (!AutoPlayOneMove())
10257 if (matchMode || appData.timeDelay == 0)
10259 if (appData.timeDelay < 0)
10261 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10270 int fromX, fromY, toX, toY;
10272 if (appData.debugMode) {
10273 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10276 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10279 if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10280 pvInfoList[currentMove].depth = programStats.depth;
10281 pvInfoList[currentMove].score = programStats.score;
10282 pvInfoList[currentMove].time = 0;
10283 if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10286 if (currentMove >= forwardMostMove) {
10287 if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10288 gameMode = EditGame;
10291 /* [AS] Clear current move marker at the end of a game */
10292 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10297 toX = moveList[currentMove][2] - AAA;
10298 toY = moveList[currentMove][3] - ONE;
10300 if (moveList[currentMove][1] == '@') {
10301 if (appData.highlightLastMove) {
10302 SetHighlights(-1, -1, toX, toY);
10305 fromX = moveList[currentMove][0] - AAA;
10306 fromY = moveList[currentMove][1] - ONE;
10308 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10310 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10312 if (appData.highlightLastMove) {
10313 SetHighlights(fromX, fromY, toX, toY);
10316 DisplayMove(currentMove);
10317 SendMoveToProgram(currentMove++, &first);
10318 DisplayBothClocks();
10319 DrawPosition(FALSE, boards[currentMove]);
10320 // [HGM] PV info: always display, routine tests if empty
10321 DisplayComment(currentMove - 1, commentList[currentMove]);
10327 LoadGameOneMove(readAhead)
10328 ChessMove readAhead;
10330 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10331 char promoChar = NULLCHAR;
10332 ChessMove moveType;
10333 char move[MSG_SIZ];
10336 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10337 gameMode != AnalyzeMode && gameMode != Training) {
10342 yyboardindex = forwardMostMove;
10343 if (readAhead != EndOfFile) {
10344 moveType = readAhead;
10346 if (gameFileFP == NULL)
10348 moveType = (ChessMove) Myylex();
10352 switch (moveType) {
10354 if (appData.debugMode)
10355 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10358 /* append the comment but don't display it */
10359 AppendComment(currentMove, p, FALSE);
10362 case WhiteCapturesEnPassant:
10363 case BlackCapturesEnPassant:
10364 case WhitePromotion:
10365 case BlackPromotion:
10366 case WhiteNonPromotion:
10367 case BlackNonPromotion:
10369 case WhiteKingSideCastle:
10370 case WhiteQueenSideCastle:
10371 case BlackKingSideCastle:
10372 case BlackQueenSideCastle:
10373 case WhiteKingSideCastleWild:
10374 case WhiteQueenSideCastleWild:
10375 case BlackKingSideCastleWild:
10376 case BlackQueenSideCastleWild:
10378 case WhiteHSideCastleFR:
10379 case WhiteASideCastleFR:
10380 case BlackHSideCastleFR:
10381 case BlackASideCastleFR:
10383 if (appData.debugMode)
10384 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10385 fromX = currentMoveString[0] - AAA;
10386 fromY = currentMoveString[1] - ONE;
10387 toX = currentMoveString[2] - AAA;
10388 toY = currentMoveString[3] - ONE;
10389 promoChar = currentMoveString[4];
10394 if (appData.debugMode)
10395 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10396 fromX = moveType == WhiteDrop ?
10397 (int) CharToPiece(ToUpper(currentMoveString[0])) :
10398 (int) CharToPiece(ToLower(currentMoveString[0]));
10400 toX = currentMoveString[2] - AAA;
10401 toY = currentMoveString[3] - ONE;
10407 case GameUnfinished:
10408 if (appData.debugMode)
10409 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10410 p = strchr(yy_text, '{');
10411 if (p == NULL) p = strchr(yy_text, '(');
10414 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10416 q = strchr(p, *p == '{' ? '}' : ')');
10417 if (q != NULL) *q = NULLCHAR;
10420 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10421 GameEnds(moveType, p, GE_FILE);
10423 if (cmailMsgLoaded) {
10425 flipView = WhiteOnMove(currentMove);
10426 if (moveType == GameUnfinished) flipView = !flipView;
10427 if (appData.debugMode)
10428 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10433 if (appData.debugMode)
10434 fprintf(debugFP, "Parser hit end of file\n");
10435 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10441 if (WhiteOnMove(currentMove)) {
10442 GameEnds(BlackWins, "Black mates", GE_FILE);
10444 GameEnds(WhiteWins, "White mates", GE_FILE);
10448 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10454 case MoveNumberOne:
10455 if (lastLoadGameStart == GNUChessGame) {
10456 /* GNUChessGames have numbers, but they aren't move numbers */
10457 if (appData.debugMode)
10458 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10459 yy_text, (int) moveType);
10460 return LoadGameOneMove(EndOfFile); /* tail recursion */
10462 /* else fall thru */
10467 /* Reached start of next game in file */
10468 if (appData.debugMode)
10469 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10470 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10476 if (WhiteOnMove(currentMove)) {
10477 GameEnds(BlackWins, "Black mates", GE_FILE);
10479 GameEnds(WhiteWins, "White mates", GE_FILE);
10483 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10489 case PositionDiagram: /* should not happen; ignore */
10490 case ElapsedTime: /* ignore */
10491 case NAG: /* ignore */
10492 if (appData.debugMode)
10493 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10494 yy_text, (int) moveType);
10495 return LoadGameOneMove(EndOfFile); /* tail recursion */
10498 if (appData.testLegality) {
10499 if (appData.debugMode)
10500 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10501 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10502 (forwardMostMove / 2) + 1,
10503 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10504 DisplayError(move, 0);
10507 if (appData.debugMode)
10508 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10509 yy_text, currentMoveString);
10510 fromX = currentMoveString[0] - AAA;
10511 fromY = currentMoveString[1] - ONE;
10512 toX = currentMoveString[2] - AAA;
10513 toY = currentMoveString[3] - ONE;
10514 promoChar = currentMoveString[4];
10518 case AmbiguousMove:
10519 if (appData.debugMode)
10520 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10521 snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10522 (forwardMostMove / 2) + 1,
10523 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10524 DisplayError(move, 0);
10529 case ImpossibleMove:
10530 if (appData.debugMode)
10531 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10532 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10533 (forwardMostMove / 2) + 1,
10534 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10535 DisplayError(move, 0);
10541 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10542 DrawPosition(FALSE, boards[currentMove]);
10543 DisplayBothClocks();
10544 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10545 DisplayComment(currentMove - 1, commentList[currentMove]);
10547 (void) StopLoadGameTimer();
10549 cmailOldMove = forwardMostMove;
10552 /* currentMoveString is set as a side-effect of yylex */
10554 thinkOutput[0] = NULLCHAR;
10555 MakeMove(fromX, fromY, toX, toY, promoChar);
10556 currentMove = forwardMostMove;
10561 /* Load the nth game from the given file */
10563 LoadGameFromFile(filename, n, title, useList)
10567 /*Boolean*/ int useList;
10572 if (strcmp(filename, "-") == 0) {
10576 f = fopen(filename, "rb");
10578 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10579 DisplayError(buf, errno);
10583 if (fseek(f, 0, 0) == -1) {
10584 /* f is not seekable; probably a pipe */
10587 if (useList && n == 0) {
10588 int error = GameListBuild(f);
10590 DisplayError(_("Cannot build game list"), error);
10591 } else if (!ListEmpty(&gameList) &&
10592 ((ListGame *) gameList.tailPred)->number > 1) {
10593 GameListPopUp(f, title);
10600 return LoadGame(f, n, title, FALSE);
10605 MakeRegisteredMove()
10607 int fromX, fromY, toX, toY;
10609 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10610 switch (cmailMoveType[lastLoadGameNumber - 1]) {
10613 if (appData.debugMode)
10614 fprintf(debugFP, "Restoring %s for game %d\n",
10615 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10617 thinkOutput[0] = NULLCHAR;
10618 safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10619 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10620 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10621 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10622 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10623 promoChar = cmailMove[lastLoadGameNumber - 1][4];
10624 MakeMove(fromX, fromY, toX, toY, promoChar);
10625 ShowMove(fromX, fromY, toX, toY);
10627 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10634 if (WhiteOnMove(currentMove)) {
10635 GameEnds(BlackWins, "Black mates", GE_PLAYER);
10637 GameEnds(WhiteWins, "White mates", GE_PLAYER);
10642 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10649 if (WhiteOnMove(currentMove)) {
10650 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10652 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10657 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10668 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10670 CmailLoadGame(f, gameNumber, title, useList)
10678 if (gameNumber > nCmailGames) {
10679 DisplayError(_("No more games in this message"), 0);
10682 if (f == lastLoadGameFP) {
10683 int offset = gameNumber - lastLoadGameNumber;
10685 cmailMsg[0] = NULLCHAR;
10686 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10687 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10688 nCmailMovesRegistered--;
10690 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10691 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10692 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10695 if (! RegisterMove()) return FALSE;
10699 retVal = LoadGame(f, gameNumber, title, useList);
10701 /* Make move registered during previous look at this game, if any */
10702 MakeRegisteredMove();
10704 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10705 commentList[currentMove]
10706 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10707 DisplayComment(currentMove - 1, commentList[currentMove]);
10713 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10718 int gameNumber = lastLoadGameNumber + offset;
10719 if (lastLoadGameFP == NULL) {
10720 DisplayError(_("No game has been loaded yet"), 0);
10723 if (gameNumber <= 0) {
10724 DisplayError(_("Can't back up any further"), 0);
10727 if (cmailMsgLoaded) {
10728 return CmailLoadGame(lastLoadGameFP, gameNumber,
10729 lastLoadGameTitle, lastLoadGameUseList);
10731 return LoadGame(lastLoadGameFP, gameNumber,
10732 lastLoadGameTitle, lastLoadGameUseList);
10738 /* Load the nth game from open file f */
10740 LoadGame(f, gameNumber, title, useList)
10748 int gn = gameNumber;
10749 ListGame *lg = NULL;
10750 int numPGNTags = 0;
10752 GameMode oldGameMode;
10753 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10755 if (appData.debugMode)
10756 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10758 if (gameMode == Training )
10759 SetTrainingModeOff();
10761 oldGameMode = gameMode;
10762 if (gameMode != BeginningOfGame) {
10763 Reset(FALSE, TRUE);
10767 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10768 fclose(lastLoadGameFP);
10772 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10775 fseek(f, lg->offset, 0);
10776 GameListHighlight(gameNumber);
10780 DisplayError(_("Game number out of range"), 0);
10785 if (fseek(f, 0, 0) == -1) {
10786 if (f == lastLoadGameFP ?
10787 gameNumber == lastLoadGameNumber + 1 :
10791 DisplayError(_("Can't seek on game file"), 0);
10796 lastLoadGameFP = f;
10797 lastLoadGameNumber = gameNumber;
10798 safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10799 lastLoadGameUseList = useList;
10803 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10804 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10805 lg->gameInfo.black);
10807 } else if (*title != NULLCHAR) {
10808 if (gameNumber > 1) {
10809 snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10812 DisplayTitle(title);
10816 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10817 gameMode = PlayFromGameFile;
10821 currentMove = forwardMostMove = backwardMostMove = 0;
10822 CopyBoard(boards[0], initialPosition);
10826 * Skip the first gn-1 games in the file.
10827 * Also skip over anything that precedes an identifiable
10828 * start of game marker, to avoid being confused by
10829 * garbage at the start of the file. Currently
10830 * recognized start of game markers are the move number "1",
10831 * the pattern "gnuchess .* game", the pattern
10832 * "^[#;%] [^ ]* game file", and a PGN tag block.
10833 * A game that starts with one of the latter two patterns
10834 * will also have a move number 1, possibly
10835 * following a position diagram.
10836 * 5-4-02: Let's try being more lenient and allowing a game to
10837 * start with an unnumbered move. Does that break anything?
10839 cm = lastLoadGameStart = EndOfFile;
10841 yyboardindex = forwardMostMove;
10842 cm = (ChessMove) Myylex();
10845 if (cmailMsgLoaded) {
10846 nCmailGames = CMAIL_MAX_GAMES - gn;
10849 DisplayError(_("Game not found in file"), 0);
10856 lastLoadGameStart = cm;
10859 case MoveNumberOne:
10860 switch (lastLoadGameStart) {
10865 case MoveNumberOne:
10867 gn--; /* count this game */
10868 lastLoadGameStart = cm;
10877 switch (lastLoadGameStart) {
10880 case MoveNumberOne:
10882 gn--; /* count this game */
10883 lastLoadGameStart = cm;
10886 lastLoadGameStart = cm; /* game counted already */
10894 yyboardindex = forwardMostMove;
10895 cm = (ChessMove) Myylex();
10896 } while (cm == PGNTag || cm == Comment);
10903 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10904 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
10905 != CMAIL_OLD_RESULT) {
10907 cmailResult[ CMAIL_MAX_GAMES
10908 - gn - 1] = CMAIL_OLD_RESULT;
10914 /* Only a NormalMove can be at the start of a game
10915 * without a position diagram. */
10916 if (lastLoadGameStart == EndOfFile ) {
10918 lastLoadGameStart = MoveNumberOne;
10927 if (appData.debugMode)
10928 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10930 if (cm == XBoardGame) {
10931 /* Skip any header junk before position diagram and/or move 1 */
10933 yyboardindex = forwardMostMove;
10934 cm = (ChessMove) Myylex();
10936 if (cm == EndOfFile ||
10937 cm == GNUChessGame || cm == XBoardGame) {
10938 /* Empty game; pretend end-of-file and handle later */
10943 if (cm == MoveNumberOne || cm == PositionDiagram ||
10944 cm == PGNTag || cm == Comment)
10947 } else if (cm == GNUChessGame) {
10948 if (gameInfo.event != NULL) {
10949 free(gameInfo.event);
10951 gameInfo.event = StrSave(yy_text);
10954 startedFromSetupPosition = FALSE;
10955 while (cm == PGNTag) {
10956 if (appData.debugMode)
10957 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10958 err = ParsePGNTag(yy_text, &gameInfo);
10959 if (!err) numPGNTags++;
10961 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10962 if(gameInfo.variant != oldVariant) {
10963 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10964 ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10965 InitPosition(TRUE);
10966 oldVariant = gameInfo.variant;
10967 if (appData.debugMode)
10968 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10972 if (gameInfo.fen != NULL) {
10973 Board initial_position;
10974 startedFromSetupPosition = TRUE;
10975 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10977 DisplayError(_("Bad FEN position in file"), 0);
10980 CopyBoard(boards[0], initial_position);
10981 if (blackPlaysFirst) {
10982 currentMove = forwardMostMove = backwardMostMove = 1;
10983 CopyBoard(boards[1], initial_position);
10984 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10985 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10986 timeRemaining[0][1] = whiteTimeRemaining;
10987 timeRemaining[1][1] = blackTimeRemaining;
10988 if (commentList[0] != NULL) {
10989 commentList[1] = commentList[0];
10990 commentList[0] = NULL;
10993 currentMove = forwardMostMove = backwardMostMove = 0;
10995 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10997 initialRulePlies = FENrulePlies;
10998 for( i=0; i< nrCastlingRights; i++ )
10999 initialRights[i] = initial_position[CASTLING][i];
11001 yyboardindex = forwardMostMove;
11002 free(gameInfo.fen);
11003 gameInfo.fen = NULL;
11006 yyboardindex = forwardMostMove;
11007 cm = (ChessMove) Myylex();
11009 /* Handle comments interspersed among the tags */
11010 while (cm == Comment) {
11012 if (appData.debugMode)
11013 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11015 AppendComment(currentMove, p, FALSE);
11016 yyboardindex = forwardMostMove;
11017 cm = (ChessMove) Myylex();
11021 /* don't rely on existence of Event tag since if game was
11022 * pasted from clipboard the Event tag may not exist
11024 if (numPGNTags > 0){
11026 if (gameInfo.variant == VariantNormal) {
11027 VariantClass v = StringToVariant(gameInfo.event);
11028 // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11029 if(v < VariantShogi) gameInfo.variant = v;
11032 if( appData.autoDisplayTags ) {
11033 tags = PGNTags(&gameInfo);
11034 TagsPopUp(tags, CmailMsg());
11039 /* Make something up, but don't display it now */
11044 if (cm == PositionDiagram) {
11047 Board initial_position;
11049 if (appData.debugMode)
11050 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11052 if (!startedFromSetupPosition) {
11054 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11055 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11066 initial_position[i][j++] = CharToPiece(*p);
11069 while (*p == ' ' || *p == '\t' ||
11070 *p == '\n' || *p == '\r') p++;
11072 if (strncmp(p, "black", strlen("black"))==0)
11073 blackPlaysFirst = TRUE;
11075 blackPlaysFirst = FALSE;
11076 startedFromSetupPosition = TRUE;
11078 CopyBoard(boards[0], initial_position);
11079 if (blackPlaysFirst) {
11080 currentMove = forwardMostMove = backwardMostMove = 1;
11081 CopyBoard(boards[1], initial_position);
11082 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11083 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11084 timeRemaining[0][1] = whiteTimeRemaining;
11085 timeRemaining[1][1] = blackTimeRemaining;
11086 if (commentList[0] != NULL) {
11087 commentList[1] = commentList[0];
11088 commentList[0] = NULL;
11091 currentMove = forwardMostMove = backwardMostMove = 0;
11094 yyboardindex = forwardMostMove;
11095 cm = (ChessMove) Myylex();
11098 if (first.pr == NoProc) {
11099 StartChessProgram(&first);
11101 InitChessProgram(&first, FALSE);
11102 SendToProgram("force\n", &first);
11103 if (startedFromSetupPosition) {
11104 SendBoard(&first, forwardMostMove);
11105 if (appData.debugMode) {
11106 fprintf(debugFP, "Load Game\n");
11108 DisplayBothClocks();
11111 /* [HGM] server: flag to write setup moves in broadcast file as one */
11112 loadFlag = appData.suppressLoadMoves;
11114 while (cm == Comment) {
11116 if (appData.debugMode)
11117 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11119 AppendComment(currentMove, p, FALSE);
11120 yyboardindex = forwardMostMove;
11121 cm = (ChessMove) Myylex();
11124 if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11125 cm == WhiteWins || cm == BlackWins ||
11126 cm == GameIsDrawn || cm == GameUnfinished) {
11127 DisplayMessage("", _("No moves in game"));
11128 if (cmailMsgLoaded) {
11129 if (appData.debugMode)
11130 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11134 DrawPosition(FALSE, boards[currentMove]);
11135 DisplayBothClocks();
11136 gameMode = EditGame;
11143 // [HGM] PV info: routine tests if comment empty
11144 if (!matchMode && (pausing || appData.timeDelay != 0)) {
11145 DisplayComment(currentMove - 1, commentList[currentMove]);
11147 if (!matchMode && appData.timeDelay != 0)
11148 DrawPosition(FALSE, boards[currentMove]);
11150 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11151 programStats.ok_to_send = 1;
11154 /* if the first token after the PGN tags is a move
11155 * and not move number 1, retrieve it from the parser
11157 if (cm != MoveNumberOne)
11158 LoadGameOneMove(cm);
11160 /* load the remaining moves from the file */
11161 while (LoadGameOneMove(EndOfFile)) {
11162 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11163 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11166 /* rewind to the start of the game */
11167 currentMove = backwardMostMove;
11169 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11171 if (oldGameMode == AnalyzeFile ||
11172 oldGameMode == AnalyzeMode) {
11173 AnalyzeFileEvent();
11176 if (matchMode || appData.timeDelay == 0) {
11178 gameMode = EditGame;
11180 } else if (appData.timeDelay > 0) {
11181 AutoPlayGameLoop();
11184 if (appData.debugMode)
11185 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11187 loadFlag = 0; /* [HGM] true game starts */
11191 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11193 ReloadPosition(offset)
11196 int positionNumber = lastLoadPositionNumber + offset;
11197 if (lastLoadPositionFP == NULL) {
11198 DisplayError(_("No position has been loaded yet"), 0);
11201 if (positionNumber <= 0) {
11202 DisplayError(_("Can't back up any further"), 0);
11205 return LoadPosition(lastLoadPositionFP, positionNumber,
11206 lastLoadPositionTitle);
11209 /* Load the nth position from the given file */
11211 LoadPositionFromFile(filename, n, title)
11219 if (strcmp(filename, "-") == 0) {
11220 return LoadPosition(stdin, n, "stdin");
11222 f = fopen(filename, "rb");
11224 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11225 DisplayError(buf, errno);
11228 return LoadPosition(f, n, title);
11233 /* Load the nth position from the given open file, and close it */
11235 LoadPosition(f, positionNumber, title)
11237 int positionNumber;
11240 char *p, line[MSG_SIZ];
11241 Board initial_position;
11242 int i, j, fenMode, pn;
11244 if (gameMode == Training )
11245 SetTrainingModeOff();
11247 if (gameMode != BeginningOfGame) {
11248 Reset(FALSE, TRUE);
11250 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
11251 fclose(lastLoadPositionFP);
11253 if (positionNumber == 0) positionNumber = 1;
11254 lastLoadPositionFP = f;
11255 lastLoadPositionNumber = positionNumber;
11256 safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
11257 if (first.pr == NoProc) {
11258 StartChessProgram(&first);
11259 InitChessProgram(&first, FALSE);
11261 pn = positionNumber;
11262 if (positionNumber < 0) {
11263 /* Negative position number means to seek to that byte offset */
11264 if (fseek(f, -positionNumber, 0) == -1) {
11265 DisplayError(_("Can't seek on position file"), 0);
11270 if (fseek(f, 0, 0) == -1) {
11271 if (f == lastLoadPositionFP ?
11272 positionNumber == lastLoadPositionNumber + 1 :
11273 positionNumber == 1) {
11276 DisplayError(_("Can't seek on position file"), 0);
11281 /* See if this file is FEN or old-style xboard */
11282 if (fgets(line, MSG_SIZ, f) == NULL) {
11283 DisplayError(_("Position not found in file"), 0);
11286 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
11287 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
11290 if (fenMode || line[0] == '#') pn--;
11292 /* skip positions before number pn */
11293 if (fgets(line, MSG_SIZ, f) == NULL) {
11295 DisplayError(_("Position not found in file"), 0);
11298 if (fenMode || line[0] == '#') pn--;
11303 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
11304 DisplayError(_("Bad FEN position in file"), 0);
11308 (void) fgets(line, MSG_SIZ, f);
11309 (void) fgets(line, MSG_SIZ, f);
11311 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11312 (void) fgets(line, MSG_SIZ, f);
11313 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
11316 initial_position[i][j++] = CharToPiece(*p);
11320 blackPlaysFirst = FALSE;
11322 (void) fgets(line, MSG_SIZ, f);
11323 if (strncmp(line, "black", strlen("black"))==0)
11324 blackPlaysFirst = TRUE;
11327 startedFromSetupPosition = TRUE;
11329 SendToProgram("force\n", &first);
11330 CopyBoard(boards[0], initial_position);
11331 if (blackPlaysFirst) {
11332 currentMove = forwardMostMove = backwardMostMove = 1;
11333 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11334 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11335 CopyBoard(boards[1], initial_position);
11336 DisplayMessage("", _("Black to play"));
11338 currentMove = forwardMostMove = backwardMostMove = 0;
11339 DisplayMessage("", _("White to play"));
11341 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
11342 SendBoard(&first, forwardMostMove);
11343 if (appData.debugMode) {
11345 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
11346 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
11347 fprintf(debugFP, "Load Position\n");
11350 if (positionNumber > 1) {
11351 snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
11352 DisplayTitle(line);
11354 DisplayTitle(title);
11356 gameMode = EditGame;
11359 timeRemaining[0][1] = whiteTimeRemaining;
11360 timeRemaining[1][1] = blackTimeRemaining;
11361 DrawPosition(FALSE, boards[currentMove]);
11368 CopyPlayerNameIntoFileName(dest, src)
11371 while (*src != NULLCHAR && *src != ',') {
11376 *(*dest)++ = *src++;
11381 char *DefaultFileName(ext)
11384 static char def[MSG_SIZ];
11387 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
11389 CopyPlayerNameIntoFileName(&p, gameInfo.white);
11391 CopyPlayerNameIntoFileName(&p, gameInfo.black);
11393 safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
11400 /* Save the current game to the given file */
11402 SaveGameToFile(filename, append)
11410 if (strcmp(filename, "-") == 0) {
11411 return SaveGame(stdout, 0, NULL);
11413 f = fopen(filename, append ? "a" : "w");
11415 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11416 DisplayError(buf, errno);
11419 safeStrCpy(buf, lastMsg, MSG_SIZ);
11420 DisplayMessage(_("Waiting for access to save file"), "");
11421 flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
11422 DisplayMessage(_("Saving game"), "");
11423 if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError("Bad Seek", errno); // better safe than sorry...
11424 result = SaveGame(f, 0, NULL);
11425 DisplayMessage(buf, "");
11435 static char buf[MSG_SIZ];
11438 p = strchr(str, ' ');
11439 if (p == NULL) return str;
11440 strncpy(buf, str, p - str);
11441 buf[p - str] = NULLCHAR;
11445 #define PGN_MAX_LINE 75
11447 #define PGN_SIDE_WHITE 0
11448 #define PGN_SIDE_BLACK 1
11451 static int FindFirstMoveOutOfBook( int side )
11455 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
11456 int index = backwardMostMove;
11457 int has_book_hit = 0;
11459 if( (index % 2) != side ) {
11463 while( index < forwardMostMove ) {
11464 /* Check to see if engine is in book */
11465 int depth = pvInfoList[index].depth;
11466 int score = pvInfoList[index].score;
11472 else if( score == 0 && depth == 63 ) {
11473 in_book = 1; /* Zappa */
11475 else if( score == 2 && depth == 99 ) {
11476 in_book = 1; /* Abrok */
11479 has_book_hit += in_book;
11495 void GetOutOfBookInfo( char * buf )
11499 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11501 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
11502 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
11506 if( oob[0] >= 0 || oob[1] >= 0 ) {
11507 for( i=0; i<2; i++ ) {
11511 if( i > 0 && oob[0] >= 0 ) {
11512 strcat( buf, " " );
11515 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
11516 sprintf( buf+strlen(buf), "%s%.2f",
11517 pvInfoList[idx].score >= 0 ? "+" : "",
11518 pvInfoList[idx].score / 100.0 );
11524 /* Save game in PGN style and close the file */
11529 int i, offset, linelen, newblock;
11533 int movelen, numlen, blank;
11534 char move_buffer[100]; /* [AS] Buffer for move+PV info */
11536 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11538 tm = time((time_t *) NULL);
11540 PrintPGNTags(f, &gameInfo);
11542 if (backwardMostMove > 0 || startedFromSetupPosition) {
11543 char *fen = PositionToFEN(backwardMostMove, NULL);
11544 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
11545 fprintf(f, "\n{--------------\n");
11546 PrintPosition(f, backwardMostMove);
11547 fprintf(f, "--------------}\n");
11551 /* [AS] Out of book annotation */
11552 if( appData.saveOutOfBookInfo ) {
11555 GetOutOfBookInfo( buf );
11557 if( buf[0] != '\0' ) {
11558 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
11565 i = backwardMostMove;
11569 while (i < forwardMostMove) {
11570 /* Print comments preceding this move */
11571 if (commentList[i] != NULL) {
11572 if (linelen > 0) fprintf(f, "\n");
11573 fprintf(f, "%s", commentList[i]);
11578 /* Format move number */
11580 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
11583 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
11585 numtext[0] = NULLCHAR;
11587 numlen = strlen(numtext);
11590 /* Print move number */
11591 blank = linelen > 0 && numlen > 0;
11592 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
11601 fprintf(f, "%s", numtext);
11605 safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
11606 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
11609 blank = linelen > 0 && movelen > 0;
11610 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11619 fprintf(f, "%s", move_buffer);
11620 linelen += movelen;
11622 /* [AS] Add PV info if present */
11623 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
11624 /* [HGM] add time */
11625 char buf[MSG_SIZ]; int seconds;
11627 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
11633 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
11636 seconds = (seconds + 4)/10; // round to full seconds
11638 snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
11640 snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
11643 snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
11644 pvInfoList[i].score >= 0 ? "+" : "",
11645 pvInfoList[i].score / 100.0,
11646 pvInfoList[i].depth,
11649 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11651 /* Print score/depth */
11652 blank = linelen > 0 && movelen > 0;
11653 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11662 fprintf(f, "%s", move_buffer);
11663 linelen += movelen;
11669 /* Start a new line */
11670 if (linelen > 0) fprintf(f, "\n");
11672 /* Print comments after last move */
11673 if (commentList[i] != NULL) {
11674 fprintf(f, "%s\n", commentList[i]);
11678 if (gameInfo.resultDetails != NULL &&
11679 gameInfo.resultDetails[0] != NULLCHAR) {
11680 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11681 PGNResult(gameInfo.result));
11683 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11687 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11691 /* Save game in old style and close the file */
11693 SaveGameOldStyle(f)
11699 tm = time((time_t *) NULL);
11701 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11704 if (backwardMostMove > 0 || startedFromSetupPosition) {
11705 fprintf(f, "\n[--------------\n");
11706 PrintPosition(f, backwardMostMove);
11707 fprintf(f, "--------------]\n");
11712 i = backwardMostMove;
11713 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11715 while (i < forwardMostMove) {
11716 if (commentList[i] != NULL) {
11717 fprintf(f, "[%s]\n", commentList[i]);
11720 if ((i % 2) == 1) {
11721 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
11724 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
11726 if (commentList[i] != NULL) {
11730 if (i >= forwardMostMove) {
11734 fprintf(f, "%s\n", parseList[i]);
11739 if (commentList[i] != NULL) {
11740 fprintf(f, "[%s]\n", commentList[i]);
11743 /* This isn't really the old style, but it's close enough */
11744 if (gameInfo.resultDetails != NULL &&
11745 gameInfo.resultDetails[0] != NULLCHAR) {
11746 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11747 gameInfo.resultDetails);
11749 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11756 /* Save the current game to open file f and close the file */
11758 SaveGame(f, dummy, dummy2)
11763 if (gameMode == EditPosition) EditPositionDone(TRUE);
11764 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11765 if (appData.oldSaveStyle)
11766 return SaveGameOldStyle(f);
11768 return SaveGamePGN(f);
11771 /* Save the current position to the given file */
11773 SavePositionToFile(filename)
11779 if (strcmp(filename, "-") == 0) {
11780 return SavePosition(stdout, 0, NULL);
11782 f = fopen(filename, "a");
11784 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11785 DisplayError(buf, errno);
11788 safeStrCpy(buf, lastMsg, MSG_SIZ);
11789 DisplayMessage(_("Waiting for access to save file"), "");
11790 flock(fileno(f), LOCK_EX); // [HGM] lock
11791 DisplayMessage(_("Saving position"), "");
11792 lseek(fileno(f), 0, SEEK_END); // better safe than sorry...
11793 SavePosition(f, 0, NULL);
11794 DisplayMessage(buf, "");
11800 /* Save the current position to the given open file and close the file */
11802 SavePosition(f, dummy, dummy2)
11810 if (gameMode == EditPosition) EditPositionDone(TRUE);
11811 if (appData.oldSaveStyle) {
11812 tm = time((time_t *) NULL);
11814 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11816 fprintf(f, "[--------------\n");
11817 PrintPosition(f, currentMove);
11818 fprintf(f, "--------------]\n");
11820 fen = PositionToFEN(currentMove, NULL);
11821 fprintf(f, "%s\n", fen);
11829 ReloadCmailMsgEvent(unregister)
11833 static char *inFilename = NULL;
11834 static char *outFilename;
11836 struct stat inbuf, outbuf;
11839 /* Any registered moves are unregistered if unregister is set, */
11840 /* i.e. invoked by the signal handler */
11842 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11843 cmailMoveRegistered[i] = FALSE;
11844 if (cmailCommentList[i] != NULL) {
11845 free(cmailCommentList[i]);
11846 cmailCommentList[i] = NULL;
11849 nCmailMovesRegistered = 0;
11852 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11853 cmailResult[i] = CMAIL_NOT_RESULT;
11857 if (inFilename == NULL) {
11858 /* Because the filenames are static they only get malloced once */
11859 /* and they never get freed */
11860 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11861 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11863 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11864 sprintf(outFilename, "%s.out", appData.cmailGameName);
11867 status = stat(outFilename, &outbuf);
11869 cmailMailedMove = FALSE;
11871 status = stat(inFilename, &inbuf);
11872 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11875 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11876 counts the games, notes how each one terminated, etc.
11878 It would be nice to remove this kludge and instead gather all
11879 the information while building the game list. (And to keep it
11880 in the game list nodes instead of having a bunch of fixed-size
11881 parallel arrays.) Note this will require getting each game's
11882 termination from the PGN tags, as the game list builder does
11883 not process the game moves. --mann
11885 cmailMsgLoaded = TRUE;
11886 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11888 /* Load first game in the file or popup game menu */
11889 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11891 #endif /* !WIN32 */
11899 char string[MSG_SIZ];
11901 if ( cmailMailedMove
11902 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11903 return TRUE; /* Allow free viewing */
11906 /* Unregister move to ensure that we don't leave RegisterMove */
11907 /* with the move registered when the conditions for registering no */
11909 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11910 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11911 nCmailMovesRegistered --;
11913 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11915 free(cmailCommentList[lastLoadGameNumber - 1]);
11916 cmailCommentList[lastLoadGameNumber - 1] = NULL;
11920 if (cmailOldMove == -1) {
11921 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11925 if (currentMove > cmailOldMove + 1) {
11926 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11930 if (currentMove < cmailOldMove) {
11931 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11935 if (forwardMostMove > currentMove) {
11936 /* Silently truncate extra moves */
11940 if ( (currentMove == cmailOldMove + 1)
11941 || ( (currentMove == cmailOldMove)
11942 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11943 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11944 if (gameInfo.result != GameUnfinished) {
11945 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11948 if (commentList[currentMove] != NULL) {
11949 cmailCommentList[lastLoadGameNumber - 1]
11950 = StrSave(commentList[currentMove]);
11952 safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
11954 if (appData.debugMode)
11955 fprintf(debugFP, "Saving %s for game %d\n",
11956 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11958 snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11960 f = fopen(string, "w");
11961 if (appData.oldSaveStyle) {
11962 SaveGameOldStyle(f); /* also closes the file */
11964 snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
11965 f = fopen(string, "w");
11966 SavePosition(f, 0, NULL); /* also closes the file */
11968 fprintf(f, "{--------------\n");
11969 PrintPosition(f, currentMove);
11970 fprintf(f, "--------------}\n\n");
11972 SaveGame(f, 0, NULL); /* also closes the file*/
11975 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11976 nCmailMovesRegistered ++;
11977 } else if (nCmailGames == 1) {
11978 DisplayError(_("You have not made a move yet"), 0);
11989 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11990 FILE *commandOutput;
11991 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11992 int nBytes = 0; /* Suppress warnings on uninitialized variables */
11998 if (! cmailMsgLoaded) {
11999 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12003 if (nCmailGames == nCmailResults) {
12004 DisplayError(_("No unfinished games"), 0);
12008 #if CMAIL_PROHIBIT_REMAIL
12009 if (cmailMailedMove) {
12010 snprintf(msg, MSG_SIZ, _("You have already mailed a move.\nWait until a move arrives from your opponent.\nTo resend the same move, type\n\"cmail -remail -game %s\"\non the command line."), appData.cmailGameName);
12011 DisplayError(msg, 0);
12016 if (! (cmailMailedMove || RegisterMove())) return;
12018 if ( cmailMailedMove
12019 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12020 snprintf(string, MSG_SIZ, partCommandString,
12021 appData.debugMode ? " -v" : "", appData.cmailGameName);
12022 commandOutput = popen(string, "r");
12024 if (commandOutput == NULL) {
12025 DisplayError(_("Failed to invoke cmail"), 0);
12027 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12028 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12030 if (nBuffers > 1) {
12031 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12032 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12033 nBytes = MSG_SIZ - 1;
12035 (void) memcpy(msg, buffer, nBytes);
12037 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12039 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12040 cmailMailedMove = TRUE; /* Prevent >1 moves */
12043 for (i = 0; i < nCmailGames; i ++) {
12044 if (cmailResult[i] == CMAIL_NOT_RESULT) {
12049 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12051 snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12053 appData.cmailGameName,
12055 LoadGameFromFile(buffer, 1, buffer, FALSE);
12056 cmailMsgLoaded = FALSE;
12060 DisplayInformation(msg);
12061 pclose(commandOutput);
12064 if ((*cmailMsg) != '\0') {
12065 DisplayInformation(cmailMsg);
12070 #endif /* !WIN32 */
12079 int prependComma = 0;
12081 char string[MSG_SIZ]; /* Space for game-list */
12084 if (!cmailMsgLoaded) return "";
12086 if (cmailMailedMove) {
12087 snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12089 /* Create a list of games left */
12090 snprintf(string, MSG_SIZ, "[");
12091 for (i = 0; i < nCmailGames; i ++) {
12092 if (! ( cmailMoveRegistered[i]
12093 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12094 if (prependComma) {
12095 snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12097 snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12101 strcat(string, number);
12104 strcat(string, "]");
12106 if (nCmailMovesRegistered + nCmailResults == 0) {
12107 switch (nCmailGames) {
12109 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12113 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12117 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12122 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12124 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12129 if (nCmailResults == nCmailGames) {
12130 snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12132 snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12137 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12149 if (gameMode == Training)
12150 SetTrainingModeOff();
12153 cmailMsgLoaded = FALSE;
12154 if (appData.icsActive) {
12155 SendToICS(ics_prefix);
12156 SendToICS("refresh\n");
12166 /* Give up on clean exit */
12170 /* Keep trying for clean exit */
12174 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12176 if (telnetISR != NULL) {
12177 RemoveInputSource(telnetISR);
12179 if (icsPR != NoProc) {
12180 DestroyChildProcess(icsPR, TRUE);
12183 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12184 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12186 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12187 /* make sure this other one finishes before killing it! */
12188 if(endingGame) { int count = 0;
12189 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12190 while(endingGame && count++ < 10) DoSleep(1);
12191 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12194 /* Kill off chess programs */
12195 if (first.pr != NoProc) {
12198 DoSleep( appData.delayBeforeQuit );
12199 SendToProgram("quit\n", &first);
12200 DoSleep( appData.delayAfterQuit );
12201 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12203 if (second.pr != NoProc) {
12204 DoSleep( appData.delayBeforeQuit );
12205 SendToProgram("quit\n", &second);
12206 DoSleep( appData.delayAfterQuit );
12207 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12209 if (first.isr != NULL) {
12210 RemoveInputSource(first.isr);
12212 if (second.isr != NULL) {
12213 RemoveInputSource(second.isr);
12216 ShutDownFrontEnd();
12223 if (appData.debugMode)
12224 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
12228 if (gameMode == MachinePlaysWhite ||
12229 gameMode == MachinePlaysBlack) {
12232 DisplayBothClocks();
12234 if (gameMode == PlayFromGameFile) {
12235 if (appData.timeDelay >= 0)
12236 AutoPlayGameLoop();
12237 } else if (gameMode == IcsExamining && pauseExamInvalid) {
12238 Reset(FALSE, TRUE);
12239 SendToICS(ics_prefix);
12240 SendToICS("refresh\n");
12241 } else if (currentMove < forwardMostMove) {
12242 ForwardInner(forwardMostMove);
12244 pauseExamInvalid = FALSE;
12246 switch (gameMode) {
12250 pauseExamForwardMostMove = forwardMostMove;
12251 pauseExamInvalid = FALSE;
12254 case IcsPlayingWhite:
12255 case IcsPlayingBlack:
12259 case PlayFromGameFile:
12260 (void) StopLoadGameTimer();
12264 case BeginningOfGame:
12265 if (appData.icsActive) return;
12266 /* else fall through */
12267 case MachinePlaysWhite:
12268 case MachinePlaysBlack:
12269 case TwoMachinesPlay:
12270 if (forwardMostMove == 0)
12271 return; /* don't pause if no one has moved */
12272 if ((gameMode == MachinePlaysWhite &&
12273 !WhiteOnMove(forwardMostMove)) ||
12274 (gameMode == MachinePlaysBlack &&
12275 WhiteOnMove(forwardMostMove))) {
12288 char title[MSG_SIZ];
12290 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
12291 safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
12293 snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
12294 WhiteOnMove(currentMove - 1) ? " " : ".. ",
12295 parseList[currentMove - 1]);
12298 EditCommentPopUp(currentMove, title, commentList[currentMove]);
12305 char *tags = PGNTags(&gameInfo);
12306 EditTagsPopUp(tags, NULL);
12313 if (appData.noChessProgram || gameMode == AnalyzeMode)
12316 if (gameMode != AnalyzeFile) {
12317 if (!appData.icsEngineAnalyze) {
12319 if (gameMode != EditGame) return;
12321 ResurrectChessProgram();
12322 SendToProgram("analyze\n", &first);
12323 first.analyzing = TRUE;
12324 /*first.maybeThinking = TRUE;*/
12325 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12326 EngineOutputPopUp();
12328 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
12333 StartAnalysisClock();
12334 GetTimeMark(&lastNodeCountTime);
12341 if (appData.noChessProgram || gameMode == AnalyzeFile)
12344 if (gameMode != AnalyzeMode) {
12346 if (gameMode != EditGame) return;
12347 ResurrectChessProgram();
12348 SendToProgram("analyze\n", &first);
12349 first.analyzing = TRUE;
12350 /*first.maybeThinking = TRUE;*/
12351 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12352 EngineOutputPopUp();
12354 gameMode = AnalyzeFile;
12359 StartAnalysisClock();
12360 GetTimeMark(&lastNodeCountTime);
12365 MachineWhiteEvent()
12368 char *bookHit = NULL;
12370 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
12374 if (gameMode == PlayFromGameFile ||
12375 gameMode == TwoMachinesPlay ||
12376 gameMode == Training ||
12377 gameMode == AnalyzeMode ||
12378 gameMode == EndOfGame)
12381 if (gameMode == EditPosition)
12382 EditPositionDone(TRUE);
12384 if (!WhiteOnMove(currentMove)) {
12385 DisplayError(_("It is not White's turn"), 0);
12389 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12392 if (gameMode == EditGame || gameMode == AnalyzeMode ||
12393 gameMode == AnalyzeFile)
12396 ResurrectChessProgram(); /* in case it isn't running */
12397 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
12398 gameMode = MachinePlaysWhite;
12401 gameMode = MachinePlaysWhite;
12405 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12407 if (first.sendName) {
12408 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
12409 SendToProgram(buf, &first);
12411 if (first.sendTime) {
12412 if (first.useColors) {
12413 SendToProgram("black\n", &first); /*gnu kludge*/
12415 SendTimeRemaining(&first, TRUE);
12417 if (first.useColors) {
12418 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
12420 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12421 SetMachineThinkingEnables();
12422 first.maybeThinking = TRUE;
12426 if (appData.autoFlipView && !flipView) {
12427 flipView = !flipView;
12428 DrawPosition(FALSE, NULL);
12429 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
12432 if(bookHit) { // [HGM] book: simulate book reply
12433 static char bookMove[MSG_SIZ]; // a bit generous?
12435 programStats.nodes = programStats.depth = programStats.time =
12436 programStats.score = programStats.got_only_move = 0;
12437 sprintf(programStats.movelist, "%s (xbook)", bookHit);
12439 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12440 strcat(bookMove, bookHit);
12441 HandleMachineMove(bookMove, &first);
12446 MachineBlackEvent()
12449 char *bookHit = NULL;
12451 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
12455 if (gameMode == PlayFromGameFile ||
12456 gameMode == TwoMachinesPlay ||
12457 gameMode == Training ||
12458 gameMode == AnalyzeMode ||
12459 gameMode == EndOfGame)
12462 if (gameMode == EditPosition)
12463 EditPositionDone(TRUE);
12465 if (WhiteOnMove(currentMove)) {
12466 DisplayError(_("It is not Black's turn"), 0);
12470 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12473 if (gameMode == EditGame || gameMode == AnalyzeMode ||
12474 gameMode == AnalyzeFile)
12477 ResurrectChessProgram(); /* in case it isn't running */
12478 gameMode = MachinePlaysBlack;
12482 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12484 if (first.sendName) {
12485 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
12486 SendToProgram(buf, &first);
12488 if (first.sendTime) {
12489 if (first.useColors) {
12490 SendToProgram("white\n", &first); /*gnu kludge*/
12492 SendTimeRemaining(&first, FALSE);
12494 if (first.useColors) {
12495 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
12497 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12498 SetMachineThinkingEnables();
12499 first.maybeThinking = TRUE;
12502 if (appData.autoFlipView && flipView) {
12503 flipView = !flipView;
12504 DrawPosition(FALSE, NULL);
12505 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
12507 if(bookHit) { // [HGM] book: simulate book reply
12508 static char bookMove[MSG_SIZ]; // a bit generous?
12510 programStats.nodes = programStats.depth = programStats.time =
12511 programStats.score = programStats.got_only_move = 0;
12512 sprintf(programStats.movelist, "%s (xbook)", bookHit);
12514 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12515 strcat(bookMove, bookHit);
12516 HandleMachineMove(bookMove, &first);
12522 DisplayTwoMachinesTitle()
12525 if (appData.matchGames > 0) {
12526 if (first.twoMachinesColor[0] == 'w') {
12527 snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12528 gameInfo.white, gameInfo.black,
12529 first.matchWins, second.matchWins,
12530 matchGame - 1 - (first.matchWins + second.matchWins));
12532 snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12533 gameInfo.white, gameInfo.black,
12534 second.matchWins, first.matchWins,
12535 matchGame - 1 - (first.matchWins + second.matchWins));
12538 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12544 SettingsMenuIfReady()
12546 if (second.lastPing != second.lastPong) {
12547 DisplayMessage("", _("Waiting for second chess program"));
12548 ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
12552 DisplayMessage("", "");
12553 SettingsPopUp(&second);
12557 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
12560 if (cps->pr == NULL) {
12561 StartChessProgram(cps);
12562 if (cps->protocolVersion == 1) {
12565 /* kludge: allow timeout for initial "feature" command */
12567 snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
12568 DisplayMessage("", buf);
12569 ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
12577 TwoMachinesEvent P((void))
12581 ChessProgramState *onmove;
12582 char *bookHit = NULL;
12583 static int stalling = 0;
12587 if (appData.noChessProgram) return;
12589 switch (gameMode) {
12590 case TwoMachinesPlay:
12592 case MachinePlaysWhite:
12593 case MachinePlaysBlack:
12594 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12595 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12599 case BeginningOfGame:
12600 case PlayFromGameFile:
12603 if (gameMode != EditGame) return;
12606 EditPositionDone(TRUE);
12617 // forwardMostMove = currentMove;
12618 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
12620 if(!ResurrectChessProgram()) return; /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
12622 if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
12623 if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
12624 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12628 InitChessProgram(&second, FALSE); // unbalances ping of second engine
12629 SendToProgram("force\n", &second);
12631 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12634 GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
12635 if(appData.matchPause>10000 || appData.matchPause<10)
12636 appData.matchPause = 10000; /* [HGM] make pause adjustable */
12637 wait = SubtractTimeMarks(&now, &pauseStart);
12638 if(wait < appData.matchPause) {
12639 ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
12643 DisplayMessage("", "");
12644 if (startedFromSetupPosition) {
12645 SendBoard(&second, backwardMostMove);
12646 if (appData.debugMode) {
12647 fprintf(debugFP, "Two Machines\n");
12650 for (i = backwardMostMove; i < forwardMostMove; i++) {
12651 SendMoveToProgram(i, &second);
12654 gameMode = TwoMachinesPlay;
12658 DisplayTwoMachinesTitle();
12660 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
12665 if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
12666 SendToProgram(first.computerString, &first);
12667 if (first.sendName) {
12668 snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
12669 SendToProgram(buf, &first);
12671 SendToProgram(second.computerString, &second);
12672 if (second.sendName) {
12673 snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12674 SendToProgram(buf, &second);
12678 if (!first.sendTime || !second.sendTime) {
12679 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12680 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12682 if (onmove->sendTime) {
12683 if (onmove->useColors) {
12684 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12686 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12688 if (onmove->useColors) {
12689 SendToProgram(onmove->twoMachinesColor, onmove);
12691 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12692 // SendToProgram("go\n", onmove);
12693 onmove->maybeThinking = TRUE;
12694 SetMachineThinkingEnables();
12698 if(bookHit) { // [HGM] book: simulate book reply
12699 static char bookMove[MSG_SIZ]; // a bit generous?
12701 programStats.nodes = programStats.depth = programStats.time =
12702 programStats.score = programStats.got_only_move = 0;
12703 sprintf(programStats.movelist, "%s (xbook)", bookHit);
12705 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12706 strcat(bookMove, bookHit);
12707 savedMessage = bookMove; // args for deferred call
12708 savedState = onmove;
12709 ScheduleDelayedEvent(DeferredBookMove, 1);
12716 if (gameMode == Training) {
12717 SetTrainingModeOff();
12718 gameMode = PlayFromGameFile;
12719 DisplayMessage("", _("Training mode off"));
12721 gameMode = Training;
12722 animateTraining = appData.animate;
12724 /* make sure we are not already at the end of the game */
12725 if (currentMove < forwardMostMove) {
12726 SetTrainingModeOn();
12727 DisplayMessage("", _("Training mode on"));
12729 gameMode = PlayFromGameFile;
12730 DisplayError(_("Already at end of game"), 0);
12739 if (!appData.icsActive) return;
12740 switch (gameMode) {
12741 case IcsPlayingWhite:
12742 case IcsPlayingBlack:
12745 case BeginningOfGame:
12753 EditPositionDone(TRUE);
12766 gameMode = IcsIdle;
12777 switch (gameMode) {
12779 SetTrainingModeOff();
12781 case MachinePlaysWhite:
12782 case MachinePlaysBlack:
12783 case BeginningOfGame:
12784 SendToProgram("force\n", &first);
12785 SetUserThinkingEnables();
12787 case PlayFromGameFile:
12788 (void) StopLoadGameTimer();
12789 if (gameFileFP != NULL) {
12794 EditPositionDone(TRUE);
12799 SendToProgram("force\n", &first);
12801 case TwoMachinesPlay:
12802 GameEnds(EndOfFile, NULL, GE_PLAYER);
12803 ResurrectChessProgram();
12804 SetUserThinkingEnables();
12807 ResurrectChessProgram();
12809 case IcsPlayingBlack:
12810 case IcsPlayingWhite:
12811 DisplayError(_("Warning: You are still playing a game"), 0);
12814 DisplayError(_("Warning: You are still observing a game"), 0);
12817 DisplayError(_("Warning: You are still examining a game"), 0);
12828 first.offeredDraw = second.offeredDraw = 0;
12830 if (gameMode == PlayFromGameFile) {
12831 whiteTimeRemaining = timeRemaining[0][currentMove];
12832 blackTimeRemaining = timeRemaining[1][currentMove];
12836 if (gameMode == MachinePlaysWhite ||
12837 gameMode == MachinePlaysBlack ||
12838 gameMode == TwoMachinesPlay ||
12839 gameMode == EndOfGame) {
12840 i = forwardMostMove;
12841 while (i > currentMove) {
12842 SendToProgram("undo\n", &first);
12845 whiteTimeRemaining = timeRemaining[0][currentMove];
12846 blackTimeRemaining = timeRemaining[1][currentMove];
12847 DisplayBothClocks();
12848 if (whiteFlag || blackFlag) {
12849 whiteFlag = blackFlag = 0;
12854 gameMode = EditGame;
12861 EditPositionEvent()
12863 if (gameMode == EditPosition) {
12869 if (gameMode != EditGame) return;
12871 gameMode = EditPosition;
12874 if (currentMove > 0)
12875 CopyBoard(boards[0], boards[currentMove]);
12877 blackPlaysFirst = !WhiteOnMove(currentMove);
12879 currentMove = forwardMostMove = backwardMostMove = 0;
12880 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12887 /* [DM] icsEngineAnalyze - possible call from other functions */
12888 if (appData.icsEngineAnalyze) {
12889 appData.icsEngineAnalyze = FALSE;
12891 DisplayMessage("",_("Close ICS engine analyze..."));
12893 if (first.analysisSupport && first.analyzing) {
12894 SendToProgram("exit\n", &first);
12895 first.analyzing = FALSE;
12897 thinkOutput[0] = NULLCHAR;
12901 EditPositionDone(Boolean fakeRights)
12903 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12905 startedFromSetupPosition = TRUE;
12906 InitChessProgram(&first, FALSE);
12907 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12908 boards[0][EP_STATUS] = EP_NONE;
12909 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12910 if(boards[0][0][BOARD_WIDTH>>1] == king) {
12911 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12912 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12913 } else boards[0][CASTLING][2] = NoRights;
12914 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12915 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12916 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12917 } else boards[0][CASTLING][5] = NoRights;
12919 SendToProgram("force\n", &first);
12920 if (blackPlaysFirst) {
12921 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12922 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12923 currentMove = forwardMostMove = backwardMostMove = 1;
12924 CopyBoard(boards[1], boards[0]);
12926 currentMove = forwardMostMove = backwardMostMove = 0;
12928 SendBoard(&first, forwardMostMove);
12929 if (appData.debugMode) {
12930 fprintf(debugFP, "EditPosDone\n");
12933 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12934 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12935 gameMode = EditGame;
12937 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12938 ClearHighlights(); /* [AS] */
12941 /* Pause for `ms' milliseconds */
12942 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12952 } while (SubtractTimeMarks(&m2, &m1) < ms);
12955 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12957 SendMultiLineToICS(buf)
12960 char temp[MSG_SIZ+1], *p;
12967 strncpy(temp, buf, len);
12972 if (*p == '\n' || *p == '\r')
12977 strcat(temp, "\n");
12979 SendToPlayer(temp, strlen(temp));
12983 SetWhiteToPlayEvent()
12985 if (gameMode == EditPosition) {
12986 blackPlaysFirst = FALSE;
12987 DisplayBothClocks(); /* works because currentMove is 0 */
12988 } else if (gameMode == IcsExamining) {
12989 SendToICS(ics_prefix);
12990 SendToICS("tomove white\n");
12995 SetBlackToPlayEvent()
12997 if (gameMode == EditPosition) {
12998 blackPlaysFirst = TRUE;
12999 currentMove = 1; /* kludge */
13000 DisplayBothClocks();
13002 } else if (gameMode == IcsExamining) {
13003 SendToICS(ics_prefix);
13004 SendToICS("tomove black\n");
13009 EditPositionMenuEvent(selection, x, y)
13010 ChessSquare selection;
13014 ChessSquare piece = boards[0][y][x];
13016 if (gameMode != EditPosition && gameMode != IcsExamining) return;
13018 switch (selection) {
13020 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13021 SendToICS(ics_prefix);
13022 SendToICS("bsetup clear\n");
13023 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13024 SendToICS(ics_prefix);
13025 SendToICS("clearboard\n");
13027 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13028 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13029 for (y = 0; y < BOARD_HEIGHT; y++) {
13030 if (gameMode == IcsExamining) {
13031 if (boards[currentMove][y][x] != EmptySquare) {
13032 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13037 boards[0][y][x] = p;
13042 if (gameMode == EditPosition) {
13043 DrawPosition(FALSE, boards[0]);
13048 SetWhiteToPlayEvent();
13052 SetBlackToPlayEvent();
13056 if (gameMode == IcsExamining) {
13057 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13058 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13061 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13062 if(x == BOARD_LEFT-2) {
13063 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13064 boards[0][y][1] = 0;
13066 if(x == BOARD_RGHT+1) {
13067 if(y >= gameInfo.holdingsSize) break;
13068 boards[0][y][BOARD_WIDTH-2] = 0;
13071 boards[0][y][x] = EmptySquare;
13072 DrawPosition(FALSE, boards[0]);
13077 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13078 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
13079 selection = (ChessSquare) (PROMOTED piece);
13080 } else if(piece == EmptySquare) selection = WhiteSilver;
13081 else selection = (ChessSquare)((int)piece - 1);
13085 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13086 piece > (int)BlackMan && piece <= (int)BlackKing ) {
13087 selection = (ChessSquare) (DEMOTED piece);
13088 } else if(piece == EmptySquare) selection = BlackSilver;
13089 else selection = (ChessSquare)((int)piece + 1);
13094 if(gameInfo.variant == VariantShatranj ||
13095 gameInfo.variant == VariantXiangqi ||
13096 gameInfo.variant == VariantCourier ||
13097 gameInfo.variant == VariantMakruk )
13098 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13103 if(gameInfo.variant == VariantXiangqi)
13104 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13105 if(gameInfo.variant == VariantKnightmate)
13106 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13109 if (gameMode == IcsExamining) {
13110 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13111 snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13112 PieceToChar(selection), AAA + x, ONE + y);
13115 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13117 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13118 n = PieceToNumber(selection - BlackPawn);
13119 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13120 boards[0][BOARD_HEIGHT-1-n][0] = selection;
13121 boards[0][BOARD_HEIGHT-1-n][1]++;
13123 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13124 n = PieceToNumber(selection);
13125 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13126 boards[0][n][BOARD_WIDTH-1] = selection;
13127 boards[0][n][BOARD_WIDTH-2]++;
13130 boards[0][y][x] = selection;
13131 DrawPosition(TRUE, boards[0]);
13139 DropMenuEvent(selection, x, y)
13140 ChessSquare selection;
13143 ChessMove moveType;
13145 switch (gameMode) {
13146 case IcsPlayingWhite:
13147 case MachinePlaysBlack:
13148 if (!WhiteOnMove(currentMove)) {
13149 DisplayMoveError(_("It is Black's turn"));
13152 moveType = WhiteDrop;
13154 case IcsPlayingBlack:
13155 case MachinePlaysWhite:
13156 if (WhiteOnMove(currentMove)) {
13157 DisplayMoveError(_("It is White's turn"));
13160 moveType = BlackDrop;
13163 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13169 if (moveType == BlackDrop && selection < BlackPawn) {
13170 selection = (ChessSquare) ((int) selection
13171 + (int) BlackPawn - (int) WhitePawn);
13173 if (boards[currentMove][y][x] != EmptySquare) {
13174 DisplayMoveError(_("That square is occupied"));
13178 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13184 /* Accept a pending offer of any kind from opponent */
13186 if (appData.icsActive) {
13187 SendToICS(ics_prefix);
13188 SendToICS("accept\n");
13189 } else if (cmailMsgLoaded) {
13190 if (currentMove == cmailOldMove &&
13191 commentList[cmailOldMove] != NULL &&
13192 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13193 "Black offers a draw" : "White offers a draw")) {
13195 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13196 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13198 DisplayError(_("There is no pending offer on this move"), 0);
13199 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13202 /* Not used for offers from chess program */
13209 /* Decline a pending offer of any kind from opponent */
13211 if (appData.icsActive) {
13212 SendToICS(ics_prefix);
13213 SendToICS("decline\n");
13214 } else if (cmailMsgLoaded) {
13215 if (currentMove == cmailOldMove &&
13216 commentList[cmailOldMove] != NULL &&
13217 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13218 "Black offers a draw" : "White offers a draw")) {
13220 AppendComment(cmailOldMove, "Draw declined", TRUE);
13221 DisplayComment(cmailOldMove - 1, "Draw declined");
13224 DisplayError(_("There is no pending offer on this move"), 0);
13227 /* Not used for offers from chess program */
13234 /* Issue ICS rematch command */
13235 if (appData.icsActive) {
13236 SendToICS(ics_prefix);
13237 SendToICS("rematch\n");
13244 /* Call your opponent's flag (claim a win on time) */
13245 if (appData.icsActive) {
13246 SendToICS(ics_prefix);
13247 SendToICS("flag\n");
13249 switch (gameMode) {
13252 case MachinePlaysWhite:
13255 GameEnds(GameIsDrawn, "Both players ran out of time",
13258 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
13260 DisplayError(_("Your opponent is not out of time"), 0);
13263 case MachinePlaysBlack:
13266 GameEnds(GameIsDrawn, "Both players ran out of time",
13269 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
13271 DisplayError(_("Your opponent is not out of time"), 0);
13279 ClockClick(int which)
13280 { // [HGM] code moved to back-end from winboard.c
13281 if(which) { // black clock
13282 if (gameMode == EditPosition || gameMode == IcsExamining) {
13283 if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13284 SetBlackToPlayEvent();
13285 } else if (gameMode == EditGame || shiftKey) {
13286 AdjustClock(which, -1);
13287 } else if (gameMode == IcsPlayingWhite ||
13288 gameMode == MachinePlaysBlack) {
13291 } else { // white clock
13292 if (gameMode == EditPosition || gameMode == IcsExamining) {
13293 if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13294 SetWhiteToPlayEvent();
13295 } else if (gameMode == EditGame || shiftKey) {
13296 AdjustClock(which, -1);
13297 } else if (gameMode == IcsPlayingBlack ||
13298 gameMode == MachinePlaysWhite) {
13307 /* Offer draw or accept pending draw offer from opponent */
13309 if (appData.icsActive) {
13310 /* Note: tournament rules require draw offers to be
13311 made after you make your move but before you punch
13312 your clock. Currently ICS doesn't let you do that;
13313 instead, you immediately punch your clock after making
13314 a move, but you can offer a draw at any time. */
13316 SendToICS(ics_prefix);
13317 SendToICS("draw\n");
13318 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
13319 } else if (cmailMsgLoaded) {
13320 if (currentMove == cmailOldMove &&
13321 commentList[cmailOldMove] != NULL &&
13322 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13323 "Black offers a draw" : "White offers a draw")) {
13324 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13325 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13326 } else if (currentMove == cmailOldMove + 1) {
13327 char *offer = WhiteOnMove(cmailOldMove) ?
13328 "White offers a draw" : "Black offers a draw";
13329 AppendComment(currentMove, offer, TRUE);
13330 DisplayComment(currentMove - 1, offer);
13331 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
13333 DisplayError(_("You must make your move before offering a draw"), 0);
13334 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13336 } else if (first.offeredDraw) {
13337 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
13339 if (first.sendDrawOffers) {
13340 SendToProgram("draw\n", &first);
13341 userOfferedDraw = TRUE;
13349 /* Offer Adjourn or accept pending Adjourn offer from opponent */
13351 if (appData.icsActive) {
13352 SendToICS(ics_prefix);
13353 SendToICS("adjourn\n");
13355 /* Currently GNU Chess doesn't offer or accept Adjourns */
13363 /* Offer Abort or accept pending Abort offer from opponent */
13365 if (appData.icsActive) {
13366 SendToICS(ics_prefix);
13367 SendToICS("abort\n");
13369 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
13376 /* Resign. You can do this even if it's not your turn. */
13378 if (appData.icsActive) {
13379 SendToICS(ics_prefix);
13380 SendToICS("resign\n");
13382 switch (gameMode) {
13383 case MachinePlaysWhite:
13384 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13386 case MachinePlaysBlack:
13387 GameEnds(BlackWins, "White resigns", GE_PLAYER);
13390 if (cmailMsgLoaded) {
13392 if (WhiteOnMove(cmailOldMove)) {
13393 GameEnds(BlackWins, "White resigns", GE_PLAYER);
13395 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13397 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
13408 StopObservingEvent()
13410 /* Stop observing current games */
13411 SendToICS(ics_prefix);
13412 SendToICS("unobserve\n");
13416 StopExaminingEvent()
13418 /* Stop observing current game */
13419 SendToICS(ics_prefix);
13420 SendToICS("unexamine\n");
13424 ForwardInner(target)
13429 if (appData.debugMode)
13430 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
13431 target, currentMove, forwardMostMove);
13433 if (gameMode == EditPosition)
13436 if (gameMode == PlayFromGameFile && !pausing)
13439 if (gameMode == IcsExamining && pausing)
13440 limit = pauseExamForwardMostMove;
13442 limit = forwardMostMove;
13444 if (target > limit) target = limit;
13446 if (target > 0 && moveList[target - 1][0]) {
13447 int fromX, fromY, toX, toY;
13448 toX = moveList[target - 1][2] - AAA;
13449 toY = moveList[target - 1][3] - ONE;
13450 if (moveList[target - 1][1] == '@') {
13451 if (appData.highlightLastMove) {
13452 SetHighlights(-1, -1, toX, toY);
13455 fromX = moveList[target - 1][0] - AAA;
13456 fromY = moveList[target - 1][1] - ONE;
13457 if (target == currentMove + 1) {
13458 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
13460 if (appData.highlightLastMove) {
13461 SetHighlights(fromX, fromY, toX, toY);
13465 if (gameMode == EditGame || gameMode == AnalyzeMode ||
13466 gameMode == Training || gameMode == PlayFromGameFile ||
13467 gameMode == AnalyzeFile) {
13468 while (currentMove < target) {
13469 SendMoveToProgram(currentMove++, &first);
13472 currentMove = target;
13475 if (gameMode == EditGame || gameMode == EndOfGame) {
13476 whiteTimeRemaining = timeRemaining[0][currentMove];
13477 blackTimeRemaining = timeRemaining[1][currentMove];
13479 DisplayBothClocks();
13480 DisplayMove(currentMove - 1);
13481 DrawPosition(FALSE, boards[currentMove]);
13482 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13483 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
13484 DisplayComment(currentMove - 1, commentList[currentMove]);
13492 if (gameMode == IcsExamining && !pausing) {
13493 SendToICS(ics_prefix);
13494 SendToICS("forward\n");
13496 ForwardInner(currentMove + 1);
13503 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13504 /* to optimze, we temporarily turn off analysis mode while we feed
13505 * the remaining moves to the engine. Otherwise we get analysis output
13508 if (first.analysisSupport) {
13509 SendToProgram("exit\nforce\n", &first);
13510 first.analyzing = FALSE;
13514 if (gameMode == IcsExamining && !pausing) {
13515 SendToICS(ics_prefix);
13516 SendToICS("forward 999999\n");
13518 ForwardInner(forwardMostMove);
13521 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13522 /* we have fed all the moves, so reactivate analysis mode */
13523 SendToProgram("analyze\n", &first);
13524 first.analyzing = TRUE;
13525 /*first.maybeThinking = TRUE;*/
13526 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13531 BackwardInner(target)
13534 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
13536 if (appData.debugMode)
13537 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
13538 target, currentMove, forwardMostMove);
13540 if (gameMode == EditPosition) return;
13541 if (currentMove <= backwardMostMove) {
13543 DrawPosition(full_redraw, boards[currentMove]);
13546 if (gameMode == PlayFromGameFile && !pausing)
13549 if (moveList[target][0]) {
13550 int fromX, fromY, toX, toY;
13551 toX = moveList[target][2] - AAA;
13552 toY = moveList[target][3] - ONE;
13553 if (moveList[target][1] == '@') {
13554 if (appData.highlightLastMove) {
13555 SetHighlights(-1, -1, toX, toY);
13558 fromX = moveList[target][0] - AAA;
13559 fromY = moveList[target][1] - ONE;
13560 if (target == currentMove - 1) {
13561 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
13563 if (appData.highlightLastMove) {
13564 SetHighlights(fromX, fromY, toX, toY);
13568 if (gameMode == EditGame || gameMode==AnalyzeMode ||
13569 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
13570 while (currentMove > target) {
13571 SendToProgram("undo\n", &first);
13575 currentMove = target;
13578 if (gameMode == EditGame || gameMode == EndOfGame) {
13579 whiteTimeRemaining = timeRemaining[0][currentMove];
13580 blackTimeRemaining = timeRemaining[1][currentMove];
13582 DisplayBothClocks();
13583 DisplayMove(currentMove - 1);
13584 DrawPosition(full_redraw, boards[currentMove]);
13585 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13586 // [HGM] PV info: routine tests if comment empty
13587 DisplayComment(currentMove - 1, commentList[currentMove]);
13593 if (gameMode == IcsExamining && !pausing) {
13594 SendToICS(ics_prefix);
13595 SendToICS("backward\n");
13597 BackwardInner(currentMove - 1);
13604 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13605 /* to optimize, we temporarily turn off analysis mode while we undo
13606 * all the moves. Otherwise we get analysis output after each undo.
13608 if (first.analysisSupport) {
13609 SendToProgram("exit\nforce\n", &first);
13610 first.analyzing = FALSE;
13614 if (gameMode == IcsExamining && !pausing) {
13615 SendToICS(ics_prefix);
13616 SendToICS("backward 999999\n");
13618 BackwardInner(backwardMostMove);
13621 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13622 /* we have fed all the moves, so reactivate analysis mode */
13623 SendToProgram("analyze\n", &first);
13624 first.analyzing = TRUE;
13625 /*first.maybeThinking = TRUE;*/
13626 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13633 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
13634 if (to >= forwardMostMove) to = forwardMostMove;
13635 if (to <= backwardMostMove) to = backwardMostMove;
13636 if (to < currentMove) {
13644 RevertEvent(Boolean annotate)
13646 if(PopTail(annotate)) { // [HGM] vari: restore old game tail
13649 if (gameMode != IcsExamining) {
13650 DisplayError(_("You are not examining a game"), 0);
13654 DisplayError(_("You can't revert while pausing"), 0);
13657 SendToICS(ics_prefix);
13658 SendToICS("revert\n");
13664 switch (gameMode) {
13665 case MachinePlaysWhite:
13666 case MachinePlaysBlack:
13667 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13668 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13671 if (forwardMostMove < 2) return;
13672 currentMove = forwardMostMove = forwardMostMove - 2;
13673 whiteTimeRemaining = timeRemaining[0][currentMove];
13674 blackTimeRemaining = timeRemaining[1][currentMove];
13675 DisplayBothClocks();
13676 DisplayMove(currentMove - 1);
13677 ClearHighlights();/*!! could figure this out*/
13678 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
13679 SendToProgram("remove\n", &first);
13680 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
13683 case BeginningOfGame:
13687 case IcsPlayingWhite:
13688 case IcsPlayingBlack:
13689 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
13690 SendToICS(ics_prefix);
13691 SendToICS("takeback 2\n");
13693 SendToICS(ics_prefix);
13694 SendToICS("takeback 1\n");
13703 ChessProgramState *cps;
13705 switch (gameMode) {
13706 case MachinePlaysWhite:
13707 if (!WhiteOnMove(forwardMostMove)) {
13708 DisplayError(_("It is your turn"), 0);
13713 case MachinePlaysBlack:
13714 if (WhiteOnMove(forwardMostMove)) {
13715 DisplayError(_("It is your turn"), 0);
13720 case TwoMachinesPlay:
13721 if (WhiteOnMove(forwardMostMove) ==
13722 (first.twoMachinesColor[0] == 'w')) {
13728 case BeginningOfGame:
13732 SendToProgram("?\n", cps);
13736 TruncateGameEvent()
13739 if (gameMode != EditGame) return;
13746 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13747 if (forwardMostMove > currentMove) {
13748 if (gameInfo.resultDetails != NULL) {
13749 free(gameInfo.resultDetails);
13750 gameInfo.resultDetails = NULL;
13751 gameInfo.result = GameUnfinished;
13753 forwardMostMove = currentMove;
13754 HistorySet(parseList, backwardMostMove, forwardMostMove,
13762 if (appData.noChessProgram) return;
13763 switch (gameMode) {
13764 case MachinePlaysWhite:
13765 if (WhiteOnMove(forwardMostMove)) {
13766 DisplayError(_("Wait until your turn"), 0);
13770 case BeginningOfGame:
13771 case MachinePlaysBlack:
13772 if (!WhiteOnMove(forwardMostMove)) {
13773 DisplayError(_("Wait until your turn"), 0);
13778 DisplayError(_("No hint available"), 0);
13781 SendToProgram("hint\n", &first);
13782 hintRequested = TRUE;
13788 if (appData.noChessProgram) return;
13789 switch (gameMode) {
13790 case MachinePlaysWhite:
13791 if (WhiteOnMove(forwardMostMove)) {
13792 DisplayError(_("Wait until your turn"), 0);
13796 case BeginningOfGame:
13797 case MachinePlaysBlack:
13798 if (!WhiteOnMove(forwardMostMove)) {
13799 DisplayError(_("Wait until your turn"), 0);
13804 EditPositionDone(TRUE);
13806 case TwoMachinesPlay:
13811 SendToProgram("bk\n", &first);
13812 bookOutput[0] = NULLCHAR;
13813 bookRequested = TRUE;
13819 char *tags = PGNTags(&gameInfo);
13820 TagsPopUp(tags, CmailMsg());
13824 /* end button procedures */
13827 PrintPosition(fp, move)
13833 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13834 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13835 char c = PieceToChar(boards[move][i][j]);
13836 fputc(c == 'x' ? '.' : c, fp);
13837 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13840 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13841 fprintf(fp, "white to play\n");
13843 fprintf(fp, "black to play\n");
13850 if (gameInfo.white != NULL) {
13851 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13857 /* Find last component of program's own name, using some heuristics */
13859 TidyProgramName(prog, host, buf)
13860 char *prog, *host, buf[MSG_SIZ];
13863 int local = (strcmp(host, "localhost") == 0);
13864 while (!local && (p = strchr(prog, ';')) != NULL) {
13866 while (*p == ' ') p++;
13869 if (*prog == '"' || *prog == '\'') {
13870 q = strchr(prog + 1, *prog);
13872 q = strchr(prog, ' ');
13874 if (q == NULL) q = prog + strlen(prog);
13876 while (p >= prog && *p != '/' && *p != '\\') p--;
13878 if(p == prog && *p == '"') p++;
13879 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13880 memcpy(buf, p, q - p);
13881 buf[q - p] = NULLCHAR;
13889 TimeControlTagValue()
13892 if (!appData.clockMode) {
13893 safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
13894 } else if (movesPerSession > 0) {
13895 snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
13896 } else if (timeIncrement == 0) {
13897 snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
13899 snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13901 return StrSave(buf);
13907 /* This routine is used only for certain modes */
13908 VariantClass v = gameInfo.variant;
13909 ChessMove r = GameUnfinished;
13912 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13913 r = gameInfo.result;
13914 p = gameInfo.resultDetails;
13915 gameInfo.resultDetails = NULL;
13917 ClearGameInfo(&gameInfo);
13918 gameInfo.variant = v;
13920 switch (gameMode) {
13921 case MachinePlaysWhite:
13922 gameInfo.event = StrSave( appData.pgnEventHeader );
13923 gameInfo.site = StrSave(HostName());
13924 gameInfo.date = PGNDate();
13925 gameInfo.round = StrSave("-");
13926 gameInfo.white = StrSave(first.tidy);
13927 gameInfo.black = StrSave(UserName());
13928 gameInfo.timeControl = TimeControlTagValue();
13931 case MachinePlaysBlack:
13932 gameInfo.event = StrSave( appData.pgnEventHeader );
13933 gameInfo.site = StrSave(HostName());
13934 gameInfo.date = PGNDate();
13935 gameInfo.round = StrSave("-");
13936 gameInfo.white = StrSave(UserName());
13937 gameInfo.black = StrSave(first.tidy);
13938 gameInfo.timeControl = TimeControlTagValue();
13941 case TwoMachinesPlay:
13942 gameInfo.event = StrSave( appData.pgnEventHeader );
13943 gameInfo.site = StrSave(HostName());
13944 gameInfo.date = PGNDate();
13947 snprintf(buf, MSG_SIZ, "%d", roundNr);
13948 gameInfo.round = StrSave(buf);
13950 gameInfo.round = StrSave("-");
13952 if (first.twoMachinesColor[0] == 'w') {
13953 gameInfo.white = StrSave(first.tidy);
13954 gameInfo.black = StrSave(second.tidy);
13956 gameInfo.white = StrSave(second.tidy);
13957 gameInfo.black = StrSave(first.tidy);
13959 gameInfo.timeControl = TimeControlTagValue();
13963 gameInfo.event = StrSave("Edited game");
13964 gameInfo.site = StrSave(HostName());
13965 gameInfo.date = PGNDate();
13966 gameInfo.round = StrSave("-");
13967 gameInfo.white = StrSave("-");
13968 gameInfo.black = StrSave("-");
13969 gameInfo.result = r;
13970 gameInfo.resultDetails = p;
13974 gameInfo.event = StrSave("Edited position");
13975 gameInfo.site = StrSave(HostName());
13976 gameInfo.date = PGNDate();
13977 gameInfo.round = StrSave("-");
13978 gameInfo.white = StrSave("-");
13979 gameInfo.black = StrSave("-");
13982 case IcsPlayingWhite:
13983 case IcsPlayingBlack:
13988 case PlayFromGameFile:
13989 gameInfo.event = StrSave("Game from non-PGN file");
13990 gameInfo.site = StrSave(HostName());
13991 gameInfo.date = PGNDate();
13992 gameInfo.round = StrSave("-");
13993 gameInfo.white = StrSave("?");
13994 gameInfo.black = StrSave("?");
14003 ReplaceComment(index, text)
14011 if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
14012 pvInfoList[index-1].depth == len &&
14013 fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14014 (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14015 while (*text == '\n') text++;
14016 len = strlen(text);
14017 while (len > 0 && text[len - 1] == '\n') len--;
14019 if (commentList[index] != NULL)
14020 free(commentList[index]);
14023 commentList[index] = NULL;
14026 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14027 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14028 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14029 commentList[index] = (char *) malloc(len + 2);
14030 strncpy(commentList[index], text, len);
14031 commentList[index][len] = '\n';
14032 commentList[index][len + 1] = NULLCHAR;
14034 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14036 commentList[index] = (char *) malloc(len + 7);
14037 safeStrCpy(commentList[index], "{\n", 3);
14038 safeStrCpy(commentList[index]+2, text, len+1);
14039 commentList[index][len+2] = NULLCHAR;
14040 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14041 strcat(commentList[index], "\n}\n");
14055 if (ch == '\r') continue;
14057 } while (ch != '\0');
14061 AppendComment(index, text, addBraces)
14064 Boolean addBraces; // [HGM] braces: tells if we should add {}
14069 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14070 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14073 while (*text == '\n') text++;
14074 len = strlen(text);
14075 while (len > 0 && text[len - 1] == '\n') len--;
14077 if (len == 0) return;
14079 if (commentList[index] != NULL) {
14080 old = commentList[index];
14081 oldlen = strlen(old);
14082 while(commentList[index][oldlen-1] == '\n')
14083 commentList[index][--oldlen] = NULLCHAR;
14084 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14085 safeStrCpy(commentList[index], old, oldlen + len + 6);
14087 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14088 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14089 if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14090 while (*text == '\n') { text++; len--; }
14091 commentList[index][--oldlen] = NULLCHAR;
14093 if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14094 else strcat(commentList[index], "\n");
14095 strcat(commentList[index], text);
14096 if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
14097 else strcat(commentList[index], "\n");
14099 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14101 safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14102 else commentList[index][0] = NULLCHAR;
14103 strcat(commentList[index], text);
14104 strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14105 if(addBraces == TRUE) strcat(commentList[index], "}\n");
14109 static char * FindStr( char * text, char * sub_text )
14111 char * result = strstr( text, sub_text );
14113 if( result != NULL ) {
14114 result += strlen( sub_text );
14120 /* [AS] Try to extract PV info from PGN comment */
14121 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14122 char *GetInfoFromComment( int index, char * text )
14124 char * sep = text, *p;
14126 if( text != NULL && index > 0 ) {
14129 int time = -1, sec = 0, deci;
14130 char * s_eval = FindStr( text, "[%eval " );
14131 char * s_emt = FindStr( text, "[%emt " );
14133 if( s_eval != NULL || s_emt != NULL ) {
14137 if( s_eval != NULL ) {
14138 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14142 if( delim != ']' ) {
14147 if( s_emt != NULL ) {
14152 /* We expect something like: [+|-]nnn.nn/dd */
14155 if(*text != '{') return text; // [HGM] braces: must be normal comment
14157 sep = strchr( text, '/' );
14158 if( sep == NULL || sep < (text+4) ) {
14163 if(p[1] == '(') { // comment starts with PV
14164 p = strchr(p, ')'); // locate end of PV
14165 if(p == NULL || sep < p+5) return text;
14166 // at this point we have something like "{(.*) +0.23/6 ..."
14167 p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14168 *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14169 // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14171 time = -1; sec = -1; deci = -1;
14172 if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14173 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14174 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14175 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
14179 if( score_lo < 0 || score_lo >= 100 ) {
14183 if(sec >= 0) time = 600*time + 10*sec; else
14184 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14186 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14188 /* [HGM] PV time: now locate end of PV info */
14189 while( *++sep >= '0' && *sep <= '9'); // strip depth
14191 while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14193 while( *++sep >= '0' && *sep <= '9'); // strip seconds
14195 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14196 while(*sep == ' ') sep++;
14207 pvInfoList[index-1].depth = depth;
14208 pvInfoList[index-1].score = score;
14209 pvInfoList[index-1].time = 10*time; // centi-sec
14210 if(*sep == '}') *sep = 0; else *--sep = '{';
14211 if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
14217 SendToProgram(message, cps)
14219 ChessProgramState *cps;
14221 int count, outCount, error;
14224 if (cps->pr == NULL) return;
14227 if (appData.debugMode) {
14230 fprintf(debugFP, "%ld >%-6s: %s",
14231 SubtractTimeMarks(&now, &programStartTime),
14232 cps->which, message);
14235 count = strlen(message);
14236 outCount = OutputToProcess(cps->pr, message, count, &error);
14237 if (outCount < count && !exiting
14238 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
14239 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14240 snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
14241 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14242 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14243 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14244 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14245 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14247 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14248 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14249 gameInfo.result = res;
14251 gameInfo.resultDetails = StrSave(buf);
14253 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14254 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14259 ReceiveFromProgram(isr, closure, message, count, error)
14260 InputSourceRef isr;
14268 ChessProgramState *cps = (ChessProgramState *)closure;
14270 if (isr != cps->isr) return; /* Killed intentionally */
14273 RemoveInputSource(cps->isr);
14274 snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
14275 _(cps->which), cps->program);
14276 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14277 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14278 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14279 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14280 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14282 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14283 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14284 gameInfo.result = res;
14286 gameInfo.resultDetails = StrSave(buf);
14288 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14289 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
14291 snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
14292 _(cps->which), cps->program);
14293 RemoveInputSource(cps->isr);
14295 /* [AS] Program is misbehaving badly... kill it */
14296 if( count == -2 ) {
14297 DestroyChildProcess( cps->pr, 9 );
14301 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14306 if ((end_str = strchr(message, '\r')) != NULL)
14307 *end_str = NULLCHAR;
14308 if ((end_str = strchr(message, '\n')) != NULL)
14309 *end_str = NULLCHAR;
14311 if (appData.debugMode) {
14312 TimeMark now; int print = 1;
14313 char *quote = ""; char c; int i;
14315 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
14316 char start = message[0];
14317 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
14318 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
14319 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
14320 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
14321 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
14322 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
14323 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
14324 sscanf(message, "setboard %c", &c)!=1 && sscanf(message, "setup %c", &c)!=1 &&
14325 sscanf(message, "hint: %c", &c)!=1 &&
14326 sscanf(message, "pong %c", &c)!=1 && start != '#') {
14327 quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
14328 print = (appData.engineComments >= 2);
14330 message[0] = start; // restore original message
14334 fprintf(debugFP, "%ld <%-6s: %s%s\n",
14335 SubtractTimeMarks(&now, &programStartTime), cps->which,
14341 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
14342 if (appData.icsEngineAnalyze) {
14343 if (strstr(message, "whisper") != NULL ||
14344 strstr(message, "kibitz") != NULL ||
14345 strstr(message, "tellics") != NULL) return;
14348 HandleMachineMove(message, cps);
14353 SendTimeControl(cps, mps, tc, inc, sd, st)
14354 ChessProgramState *cps;
14355 int mps, inc, sd, st;
14361 if( timeControl_2 > 0 ) {
14362 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
14363 tc = timeControl_2;
14366 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
14367 inc /= cps->timeOdds;
14368 st /= cps->timeOdds;
14370 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
14373 /* Set exact time per move, normally using st command */
14374 if (cps->stKludge) {
14375 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
14377 if (seconds == 0) {
14378 snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
14380 snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
14383 snprintf(buf, MSG_SIZ, "st %d\n", st);
14386 /* Set conventional or incremental time control, using level command */
14387 if (seconds == 0) {
14388 /* Note old gnuchess bug -- minutes:seconds used to not work.
14389 Fixed in later versions, but still avoid :seconds
14390 when seconds is 0. */
14391 snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
14393 snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
14394 seconds, inc/1000.);
14397 SendToProgram(buf, cps);
14399 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
14400 /* Orthogonally, limit search to given depth */
14402 if (cps->sdKludge) {
14403 snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
14405 snprintf(buf, MSG_SIZ, "sd %d\n", sd);
14407 SendToProgram(buf, cps);
14410 if(cps->nps >= 0) { /* [HGM] nps */
14411 if(cps->supportsNPS == FALSE)
14412 cps->nps = -1; // don't use if engine explicitly says not supported!
14414 snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
14415 SendToProgram(buf, cps);
14420 ChessProgramState *WhitePlayer()
14421 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
14423 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
14424 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
14430 SendTimeRemaining(cps, machineWhite)
14431 ChessProgramState *cps;
14432 int /*boolean*/ machineWhite;
14434 char message[MSG_SIZ];
14437 /* Note: this routine must be called when the clocks are stopped
14438 or when they have *just* been set or switched; otherwise
14439 it will be off by the time since the current tick started.
14441 if (machineWhite) {
14442 time = whiteTimeRemaining / 10;
14443 otime = blackTimeRemaining / 10;
14445 time = blackTimeRemaining / 10;
14446 otime = whiteTimeRemaining / 10;
14448 /* [HGM] translate opponent's time by time-odds factor */
14449 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
14450 if (appData.debugMode) {
14451 fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
14454 if (time <= 0) time = 1;
14455 if (otime <= 0) otime = 1;
14457 snprintf(message, MSG_SIZ, "time %ld\n", time);
14458 SendToProgram(message, cps);
14460 snprintf(message, MSG_SIZ, "otim %ld\n", otime);
14461 SendToProgram(message, cps);
14465 BoolFeature(p, name, loc, cps)
14469 ChessProgramState *cps;
14472 int len = strlen(name);
14475 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14477 sscanf(*p, "%d", &val);
14479 while (**p && **p != ' ')
14481 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14482 SendToProgram(buf, cps);
14489 IntFeature(p, name, loc, cps)
14493 ChessProgramState *cps;
14496 int len = strlen(name);
14497 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14499 sscanf(*p, "%d", loc);
14500 while (**p && **p != ' ') (*p)++;
14501 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14502 SendToProgram(buf, cps);
14509 StringFeature(p, name, loc, cps)
14513 ChessProgramState *cps;
14516 int len = strlen(name);
14517 if (strncmp((*p), name, len) == 0
14518 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
14520 sscanf(*p, "%[^\"]", loc);
14521 while (**p && **p != '\"') (*p)++;
14522 if (**p == '\"') (*p)++;
14523 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14524 SendToProgram(buf, cps);
14531 ParseOption(Option *opt, ChessProgramState *cps)
14532 // [HGM] options: process the string that defines an engine option, and determine
14533 // name, type, default value, and allowed value range
14535 char *p, *q, buf[MSG_SIZ];
14536 int n, min = (-1)<<31, max = 1<<31, def;
14538 if(p = strstr(opt->name, " -spin ")) {
14539 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14540 if(max < min) max = min; // enforce consistency
14541 if(def < min) def = min;
14542 if(def > max) def = max;
14547 } else if((p = strstr(opt->name, " -slider "))) {
14548 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
14549 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14550 if(max < min) max = min; // enforce consistency
14551 if(def < min) def = min;
14552 if(def > max) def = max;
14556 opt->type = Spin; // Slider;
14557 } else if((p = strstr(opt->name, " -string "))) {
14558 opt->textValue = p+9;
14559 opt->type = TextBox;
14560 } else if((p = strstr(opt->name, " -file "))) {
14561 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14562 opt->textValue = p+7;
14563 opt->type = FileName; // FileName;
14564 } else if((p = strstr(opt->name, " -path "))) {
14565 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14566 opt->textValue = p+7;
14567 opt->type = PathName; // PathName;
14568 } else if(p = strstr(opt->name, " -check ")) {
14569 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
14570 opt->value = (def != 0);
14571 opt->type = CheckBox;
14572 } else if(p = strstr(opt->name, " -combo ")) {
14573 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
14574 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
14575 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
14576 opt->value = n = 0;
14577 while(q = StrStr(q, " /// ")) {
14578 n++; *q = 0; // count choices, and null-terminate each of them
14580 if(*q == '*') { // remember default, which is marked with * prefix
14584 cps->comboList[cps->comboCnt++] = q;
14586 cps->comboList[cps->comboCnt++] = NULL;
14588 opt->type = ComboBox;
14589 } else if(p = strstr(opt->name, " -button")) {
14590 opt->type = Button;
14591 } else if(p = strstr(opt->name, " -save")) {
14592 opt->type = SaveButton;
14593 } else return FALSE;
14594 *p = 0; // terminate option name
14595 // now look if the command-line options define a setting for this engine option.
14596 if(cps->optionSettings && cps->optionSettings[0])
14597 p = strstr(cps->optionSettings, opt->name); else p = NULL;
14598 if(p && (p == cps->optionSettings || p[-1] == ',')) {
14599 snprintf(buf, MSG_SIZ, "option %s", p);
14600 if(p = strstr(buf, ",")) *p = 0;
14601 if(q = strchr(buf, '=')) switch(opt->type) {
14603 for(n=0; n<opt->max; n++)
14604 if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
14607 safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
14611 opt->value = atoi(q+1);
14616 SendToProgram(buf, cps);
14622 FeatureDone(cps, val)
14623 ChessProgramState* cps;
14626 DelayedEventCallback cb = GetDelayedEvent();
14627 if ((cb == InitBackEnd3 && cps == &first) ||
14628 (cb == SettingsMenuIfReady && cps == &second) ||
14629 (cb == LoadEngine) ||
14630 (cb == TwoMachinesEventIfReady)) {
14631 CancelDelayedEvent();
14632 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
14634 cps->initDone = val;
14637 /* Parse feature command from engine */
14639 ParseFeatures(args, cps)
14641 ChessProgramState *cps;
14649 while (*p == ' ') p++;
14650 if (*p == NULLCHAR) return;
14652 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
14653 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
14654 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
14655 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
14656 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
14657 if (BoolFeature(&p, "reuse", &val, cps)) {
14658 /* Engine can disable reuse, but can't enable it if user said no */
14659 if (!val) cps->reuse = FALSE;
14662 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
14663 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
14664 if (gameMode == TwoMachinesPlay) {
14665 DisplayTwoMachinesTitle();
14671 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
14672 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
14673 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
14674 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
14675 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
14676 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
14677 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
14678 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
14679 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
14680 if (IntFeature(&p, "done", &val, cps)) {
14681 FeatureDone(cps, val);
14684 /* Added by Tord: */
14685 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
14686 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
14687 /* End of additions by Tord */
14689 /* [HGM] added features: */
14690 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
14691 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
14692 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
14693 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
14694 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14695 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
14696 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
14697 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
14698 snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
14699 SendToProgram(buf, cps);
14702 if(cps->nrOptions >= MAX_OPTIONS) {
14704 snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
14705 DisplayError(buf, 0);
14709 /* End of additions by HGM */
14711 /* unknown feature: complain and skip */
14713 while (*q && *q != '=') q++;
14714 snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
14715 SendToProgram(buf, cps);
14721 while (*p && *p != '\"') p++;
14722 if (*p == '\"') p++;
14724 while (*p && *p != ' ') p++;
14732 PeriodicUpdatesEvent(newState)
14735 if (newState == appData.periodicUpdates)
14738 appData.periodicUpdates=newState;
14740 /* Display type changes, so update it now */
14741 // DisplayAnalysis();
14743 /* Get the ball rolling again... */
14745 AnalysisPeriodicEvent(1);
14746 StartAnalysisClock();
14751 PonderNextMoveEvent(newState)
14754 if (newState == appData.ponderNextMove) return;
14755 if (gameMode == EditPosition) EditPositionDone(TRUE);
14757 SendToProgram("hard\n", &first);
14758 if (gameMode == TwoMachinesPlay) {
14759 SendToProgram("hard\n", &second);
14762 SendToProgram("easy\n", &first);
14763 thinkOutput[0] = NULLCHAR;
14764 if (gameMode == TwoMachinesPlay) {
14765 SendToProgram("easy\n", &second);
14768 appData.ponderNextMove = newState;
14772 NewSettingEvent(option, feature, command, value)
14774 int option, value, *feature;
14778 if (gameMode == EditPosition) EditPositionDone(TRUE);
14779 snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
14780 if(feature == NULL || *feature) SendToProgram(buf, &first);
14781 if (gameMode == TwoMachinesPlay) {
14782 if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14787 ShowThinkingEvent()
14788 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14790 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
14791 int newState = appData.showThinking
14792 // [HGM] thinking: other features now need thinking output as well
14793 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
14795 if (oldState == newState) return;
14796 oldState = newState;
14797 if (gameMode == EditPosition) EditPositionDone(TRUE);
14799 SendToProgram("post\n", &first);
14800 if (gameMode == TwoMachinesPlay) {
14801 SendToProgram("post\n", &second);
14804 SendToProgram("nopost\n", &first);
14805 thinkOutput[0] = NULLCHAR;
14806 if (gameMode == TwoMachinesPlay) {
14807 SendToProgram("nopost\n", &second);
14810 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
14814 AskQuestionEvent(title, question, replyPrefix, which)
14815 char *title; char *question; char *replyPrefix; char *which;
14817 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
14818 if (pr == NoProc) return;
14819 AskQuestion(title, question, replyPrefix, pr);
14823 TypeInEvent(char firstChar)
14825 if ((gameMode == BeginningOfGame && !appData.icsActive) ||
\r
14826 gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
\r
14827 gameMode == AnalyzeMode || gameMode == EditGame ||
\r
14828 gameMode == EditPosition || gameMode == IcsExamining ||
\r
14829 gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
\r
14830 isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
\r
14831 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
\r
14832 gameMode == IcsObserving || gameMode == TwoMachinesPlay ) ||
\r
14833 gameMode == Training) PopUpMoveDialog(firstChar);
14837 TypeInDoneEvent(char *move)
14840 int n, fromX, fromY, toX, toY;
14842 ChessMove moveType;
\r
14845 if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
\r
14846 EditPositionPasteFEN(move);
\r
14849 // [HGM] movenum: allow move number to be typed in any mode
\r
14850 if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
\r
14851 ToNrEvent(2*n-1);
\r
14855 if (gameMode != EditGame && currentMove != forwardMostMove &&
\r
14856 gameMode != Training) {
\r
14857 DisplayMoveError(_("Displayed move is not current"));
\r
14859 int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
\r
14860 &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
\r
14861 if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
\r
14862 if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
\r
14863 &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
\r
14864 UserMoveEvent(fromX, fromY, toX, toY, promoChar);
\r
14866 DisplayMoveError(_("Could not parse move"));
\r
14872 DisplayMove(moveNumber)
14875 char message[MSG_SIZ];
14877 char cpThinkOutput[MSG_SIZ];
14879 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
14881 if (moveNumber == forwardMostMove - 1 ||
14882 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14884 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
14886 if (strchr(cpThinkOutput, '\n')) {
14887 *strchr(cpThinkOutput, '\n') = NULLCHAR;
14890 *cpThinkOutput = NULLCHAR;
14893 /* [AS] Hide thinking from human user */
14894 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
14895 *cpThinkOutput = NULLCHAR;
14896 if( thinkOutput[0] != NULLCHAR ) {
14899 for( i=0; i<=hiddenThinkOutputState; i++ ) {
14900 cpThinkOutput[i] = '.';
14902 cpThinkOutput[i] = NULLCHAR;
14903 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
14907 if (moveNumber == forwardMostMove - 1 &&
14908 gameInfo.resultDetails != NULL) {
14909 if (gameInfo.resultDetails[0] == NULLCHAR) {
14910 snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
14912 snprintf(res, MSG_SIZ, " {%s} %s",
14913 T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
14919 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14920 DisplayMessage(res, cpThinkOutput);
14922 snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
14923 WhiteOnMove(moveNumber) ? " " : ".. ",
14924 parseList[moveNumber], res);
14925 DisplayMessage(message, cpThinkOutput);
14930 DisplayComment(moveNumber, text)
14934 char title[MSG_SIZ];
14935 char buf[8000]; // comment can be long!
14938 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14939 safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
14941 snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
14942 WhiteOnMove(moveNumber) ? " " : ".. ",
14943 parseList[moveNumber]);
14945 // [HGM] PV info: display PV info together with (or as) comment
14946 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14947 if(text == NULL) text = "";
14948 score = pvInfoList[moveNumber].score;
14949 snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14950 depth, (pvInfoList[moveNumber].time+50)/100, text);
14953 if (text != NULL && (appData.autoDisplayComment || commentUp))
14954 CommentPopUp(title, text);
14957 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14958 * might be busy thinking or pondering. It can be omitted if your
14959 * gnuchess is configured to stop thinking immediately on any user
14960 * input. However, that gnuchess feature depends on the FIONREAD
14961 * ioctl, which does not work properly on some flavors of Unix.
14965 ChessProgramState *cps;
14968 if (!cps->useSigint) return;
14969 if (appData.noChessProgram || (cps->pr == NoProc)) return;
14970 switch (gameMode) {
14971 case MachinePlaysWhite:
14972 case MachinePlaysBlack:
14973 case TwoMachinesPlay:
14974 case IcsPlayingWhite:
14975 case IcsPlayingBlack:
14978 /* Skip if we know it isn't thinking */
14979 if (!cps->maybeThinking) return;
14980 if (appData.debugMode)
14981 fprintf(debugFP, "Interrupting %s\n", cps->which);
14982 InterruptChildProcess(cps->pr);
14983 cps->maybeThinking = FALSE;
14988 #endif /*ATTENTION*/
14994 if (whiteTimeRemaining <= 0) {
14997 if (appData.icsActive) {
14998 if (appData.autoCallFlag &&
14999 gameMode == IcsPlayingBlack && !blackFlag) {
15000 SendToICS(ics_prefix);
15001 SendToICS("flag\n");
15005 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15007 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15008 if (appData.autoCallFlag) {
15009 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15016 if (blackTimeRemaining <= 0) {
15019 if (appData.icsActive) {
15020 if (appData.autoCallFlag &&
15021 gameMode == IcsPlayingWhite && !whiteFlag) {
15022 SendToICS(ics_prefix);
15023 SendToICS("flag\n");
15027 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15029 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15030 if (appData.autoCallFlag) {
15031 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15044 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15045 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15048 * add time to clocks when time control is achieved ([HGM] now also used for increment)
15050 if ( !WhiteOnMove(forwardMostMove) ) {
15051 /* White made time control */
15052 lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15053 whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15054 /* [HGM] time odds: correct new time quota for time odds! */
15055 / WhitePlayer()->timeOdds;
15056 lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15058 lastBlack -= blackTimeRemaining;
15059 /* Black made time control */
15060 blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15061 / WhitePlayer()->other->timeOdds;
15062 lastWhite = whiteTimeRemaining;
15067 DisplayBothClocks()
15069 int wom = gameMode == EditPosition ?
15070 !blackPlaysFirst : WhiteOnMove(currentMove);
15071 DisplayWhiteClock(whiteTimeRemaining, wom);
15072 DisplayBlackClock(blackTimeRemaining, !wom);
15076 /* Timekeeping seems to be a portability nightmare. I think everyone
15077 has ftime(), but I'm really not sure, so I'm including some ifdefs
15078 to use other calls if you don't. Clocks will be less accurate if
15079 you have neither ftime nor gettimeofday.
15082 /* VS 2008 requires the #include outside of the function */
15083 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15084 #include <sys/timeb.h>
15087 /* Get the current time as a TimeMark */
15092 #if HAVE_GETTIMEOFDAY
15094 struct timeval timeVal;
15095 struct timezone timeZone;
15097 gettimeofday(&timeVal, &timeZone);
15098 tm->sec = (long) timeVal.tv_sec;
15099 tm->ms = (int) (timeVal.tv_usec / 1000L);
15101 #else /*!HAVE_GETTIMEOFDAY*/
15104 // include <sys/timeb.h> / moved to just above start of function
15105 struct timeb timeB;
15108 tm->sec = (long) timeB.time;
15109 tm->ms = (int) timeB.millitm;
15111 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15112 tm->sec = (long) time(NULL);
15118 /* Return the difference in milliseconds between two
15119 time marks. We assume the difference will fit in a long!
15122 SubtractTimeMarks(tm2, tm1)
15123 TimeMark *tm2, *tm1;
15125 return 1000L*(tm2->sec - tm1->sec) +
15126 (long) (tm2->ms - tm1->ms);
15131 * Code to manage the game clocks.
15133 * In tournament play, black starts the clock and then white makes a move.
15134 * We give the human user a slight advantage if he is playing white---the
15135 * clocks don't run until he makes his first move, so it takes zero time.
15136 * Also, we don't account for network lag, so we could get out of sync
15137 * with GNU Chess's clock -- but then, referees are always right.
15140 static TimeMark tickStartTM;
15141 static long intendedTickLength;
15144 NextTickLength(timeRemaining)
15145 long timeRemaining;
15147 long nominalTickLength, nextTickLength;
15149 if (timeRemaining > 0L && timeRemaining <= 10000L)
15150 nominalTickLength = 100L;
15152 nominalTickLength = 1000L;
15153 nextTickLength = timeRemaining % nominalTickLength;
15154 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15156 return nextTickLength;
15159 /* Adjust clock one minute up or down */
15161 AdjustClock(Boolean which, int dir)
15163 if(which) blackTimeRemaining += 60000*dir;
15164 else whiteTimeRemaining += 60000*dir;
15165 DisplayBothClocks();
15168 /* Stop clocks and reset to a fresh time control */
15172 (void) StopClockTimer();
15173 if (appData.icsActive) {
15174 whiteTimeRemaining = blackTimeRemaining = 0;
15175 } else if (searchTime) {
15176 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15177 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15178 } else { /* [HGM] correct new time quote for time odds */
15179 whiteTC = blackTC = fullTimeControlString;
15180 whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15181 blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15183 if (whiteFlag || blackFlag) {
15185 whiteFlag = blackFlag = FALSE;
15187 lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15188 DisplayBothClocks();
15191 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15193 /* Decrement running clock by amount of time that has passed */
15197 long timeRemaining;
15198 long lastTickLength, fudge;
15201 if (!appData.clockMode) return;
15202 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15206 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15208 /* Fudge if we woke up a little too soon */
15209 fudge = intendedTickLength - lastTickLength;
15210 if (fudge < 0 || fudge > FUDGE) fudge = 0;
15212 if (WhiteOnMove(forwardMostMove)) {
15213 if(whiteNPS >= 0) lastTickLength = 0;
15214 timeRemaining = whiteTimeRemaining -= lastTickLength;
15215 if(timeRemaining < 0 && !appData.icsActive) {
15216 GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15217 if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15218 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15219 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15222 DisplayWhiteClock(whiteTimeRemaining - fudge,
15223 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15225 if(blackNPS >= 0) lastTickLength = 0;
15226 timeRemaining = blackTimeRemaining -= lastTickLength;
15227 if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15228 GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15230 blackStartMove = forwardMostMove;
15231 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
15234 DisplayBlackClock(blackTimeRemaining - fudge,
15235 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15237 if (CheckFlags()) return;
15240 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
15241 StartClockTimer(intendedTickLength);
15243 /* if the time remaining has fallen below the alarm threshold, sound the
15244 * alarm. if the alarm has sounded and (due to a takeback or time control
15245 * with increment) the time remaining has increased to a level above the
15246 * threshold, reset the alarm so it can sound again.
15249 if (appData.icsActive && appData.icsAlarm) {
15251 /* make sure we are dealing with the user's clock */
15252 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
15253 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
15256 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
15257 alarmSounded = FALSE;
15258 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
15260 alarmSounded = TRUE;
15266 /* A player has just moved, so stop the previously running
15267 clock and (if in clock mode) start the other one.
15268 We redisplay both clocks in case we're in ICS mode, because
15269 ICS gives us an update to both clocks after every move.
15270 Note that this routine is called *after* forwardMostMove
15271 is updated, so the last fractional tick must be subtracted
15272 from the color that is *not* on move now.
15275 SwitchClocks(int newMoveNr)
15277 long lastTickLength;
15279 int flagged = FALSE;
15283 if (StopClockTimer() && appData.clockMode) {
15284 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15285 if (!WhiteOnMove(forwardMostMove)) {
15286 if(blackNPS >= 0) lastTickLength = 0;
15287 blackTimeRemaining -= lastTickLength;
15288 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15289 // if(pvInfoList[forwardMostMove].time == -1)
15290 pvInfoList[forwardMostMove].time = // use GUI time
15291 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
15293 if(whiteNPS >= 0) lastTickLength = 0;
15294 whiteTimeRemaining -= lastTickLength;
15295 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15296 // if(pvInfoList[forwardMostMove].time == -1)
15297 pvInfoList[forwardMostMove].time =
15298 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
15300 flagged = CheckFlags();
15302 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
15303 CheckTimeControl();
15305 if (flagged || !appData.clockMode) return;
15307 switch (gameMode) {
15308 case MachinePlaysBlack:
15309 case MachinePlaysWhite:
15310 case BeginningOfGame:
15311 if (pausing) return;
15315 case PlayFromGameFile:
15323 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
15324 if(WhiteOnMove(forwardMostMove))
15325 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15326 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15330 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15331 whiteTimeRemaining : blackTimeRemaining);
15332 StartClockTimer(intendedTickLength);
15336 /* Stop both clocks */
15340 long lastTickLength;
15343 if (!StopClockTimer()) return;
15344 if (!appData.clockMode) return;
15348 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15349 if (WhiteOnMove(forwardMostMove)) {
15350 if(whiteNPS >= 0) lastTickLength = 0;
15351 whiteTimeRemaining -= lastTickLength;
15352 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
15354 if(blackNPS >= 0) lastTickLength = 0;
15355 blackTimeRemaining -= lastTickLength;
15356 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
15361 /* Start clock of player on move. Time may have been reset, so
15362 if clock is already running, stop and restart it. */
15366 (void) StopClockTimer(); /* in case it was running already */
15367 DisplayBothClocks();
15368 if (CheckFlags()) return;
15370 if (!appData.clockMode) return;
15371 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
15373 GetTimeMark(&tickStartTM);
15374 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15375 whiteTimeRemaining : blackTimeRemaining);
15377 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
15378 whiteNPS = blackNPS = -1;
15379 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
15380 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
15381 whiteNPS = first.nps;
15382 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
15383 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
15384 blackNPS = first.nps;
15385 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
15386 whiteNPS = second.nps;
15387 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
15388 blackNPS = second.nps;
15389 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
15391 StartClockTimer(intendedTickLength);
15398 long second, minute, hour, day;
15400 static char buf[32];
15402 if (ms > 0 && ms <= 9900) {
15403 /* convert milliseconds to tenths, rounding up */
15404 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
15406 snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
15410 /* convert milliseconds to seconds, rounding up */
15411 /* use floating point to avoid strangeness of integer division
15412 with negative dividends on many machines */
15413 second = (long) floor(((double) (ms + 999L)) / 1000.0);
15420 day = second / (60 * 60 * 24);
15421 second = second % (60 * 60 * 24);
15422 hour = second / (60 * 60);
15423 second = second % (60 * 60);
15424 minute = second / 60;
15425 second = second % 60;
15428 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
15429 sign, day, hour, minute, second);
15431 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
15433 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
15440 * This is necessary because some C libraries aren't ANSI C compliant yet.
15443 StrStr(string, match)
15444 char *string, *match;
15448 length = strlen(match);
15450 for (i = strlen(string) - length; i >= 0; i--, string++)
15451 if (!strncmp(match, string, length))
15458 StrCaseStr(string, match)
15459 char *string, *match;
15463 length = strlen(match);
15465 for (i = strlen(string) - length; i >= 0; i--, string++) {
15466 for (j = 0; j < length; j++) {
15467 if (ToLower(match[j]) != ToLower(string[j]))
15470 if (j == length) return string;
15484 c1 = ToLower(*s1++);
15485 c2 = ToLower(*s2++);
15486 if (c1 > c2) return 1;
15487 if (c1 < c2) return -1;
15488 if (c1 == NULLCHAR) return 0;
15497 return isupper(c) ? tolower(c) : c;
15505 return islower(c) ? toupper(c) : c;
15507 #endif /* !_amigados */
15515 if ((ret = (char *) malloc(strlen(s) + 1)))
15517 safeStrCpy(ret, s, strlen(s)+1);
15523 StrSavePtr(s, savePtr)
15524 char *s, **savePtr;
15529 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
15530 safeStrCpy(*savePtr, s, strlen(s)+1);
15542 clock = time((time_t *)NULL);
15543 tm = localtime(&clock);
15544 snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
15545 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
15546 return StrSave(buf);
15551 PositionToFEN(move, overrideCastling)
15553 char *overrideCastling;
15555 int i, j, fromX, fromY, toX, toY;
15562 whiteToPlay = (gameMode == EditPosition) ?
15563 !blackPlaysFirst : (move % 2 == 0);
15566 /* Piece placement data */
15567 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15569 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15570 if (boards[move][i][j] == EmptySquare) {
15572 } else { ChessSquare piece = boards[move][i][j];
15573 if (emptycount > 0) {
15574 if(emptycount<10) /* [HGM] can be >= 10 */
15575 *p++ = '0' + emptycount;
15576 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15579 if(PieceToChar(piece) == '+') {
15580 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
15582 piece = (ChessSquare)(DEMOTED piece);
15584 *p++ = PieceToChar(piece);
15586 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
15587 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
15592 if (emptycount > 0) {
15593 if(emptycount<10) /* [HGM] can be >= 10 */
15594 *p++ = '0' + emptycount;
15595 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15602 /* [HGM] print Crazyhouse or Shogi holdings */
15603 if( gameInfo.holdingsWidth ) {
15604 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
15606 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
15607 piece = boards[move][i][BOARD_WIDTH-1];
15608 if( piece != EmptySquare )
15609 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
15610 *p++ = PieceToChar(piece);
15612 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
15613 piece = boards[move][BOARD_HEIGHT-i-1][0];
15614 if( piece != EmptySquare )
15615 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
15616 *p++ = PieceToChar(piece);
15619 if( q == p ) *p++ = '-';
15625 *p++ = whiteToPlay ? 'w' : 'b';
15628 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
15629 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
15631 if(nrCastlingRights) {
15633 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
15634 /* [HGM] write directly from rights */
15635 if(boards[move][CASTLING][2] != NoRights &&
15636 boards[move][CASTLING][0] != NoRights )
15637 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
15638 if(boards[move][CASTLING][2] != NoRights &&
15639 boards[move][CASTLING][1] != NoRights )
15640 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
15641 if(boards[move][CASTLING][5] != NoRights &&
15642 boards[move][CASTLING][3] != NoRights )
15643 *p++ = boards[move][CASTLING][3] + AAA;
15644 if(boards[move][CASTLING][5] != NoRights &&
15645 boards[move][CASTLING][4] != NoRights )
15646 *p++ = boards[move][CASTLING][4] + AAA;
15649 /* [HGM] write true castling rights */
15650 if( nrCastlingRights == 6 ) {
15651 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
15652 boards[move][CASTLING][2] != NoRights ) *p++ = 'K';
15653 if(boards[move][CASTLING][1] == BOARD_LEFT &&
15654 boards[move][CASTLING][2] != NoRights ) *p++ = 'Q';
15655 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
15656 boards[move][CASTLING][5] != NoRights ) *p++ = 'k';
15657 if(boards[move][CASTLING][4] == BOARD_LEFT &&
15658 boards[move][CASTLING][5] != NoRights ) *p++ = 'q';
15661 if (q == p) *p++ = '-'; /* No castling rights */
15665 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
15666 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15667 /* En passant target square */
15668 if (move > backwardMostMove) {
15669 fromX = moveList[move - 1][0] - AAA;
15670 fromY = moveList[move - 1][1] - ONE;
15671 toX = moveList[move - 1][2] - AAA;
15672 toY = moveList[move - 1][3] - ONE;
15673 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
15674 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
15675 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
15677 /* 2-square pawn move just happened */
15679 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15683 } else if(move == backwardMostMove) {
15684 // [HGM] perhaps we should always do it like this, and forget the above?
15685 if((signed char)boards[move][EP_STATUS] >= 0) {
15686 *p++ = boards[move][EP_STATUS] + AAA;
15687 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15698 /* [HGM] find reversible plies */
15699 { int i = 0, j=move;
15701 if (appData.debugMode) { int k;
15702 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
15703 for(k=backwardMostMove; k<=forwardMostMove; k++)
15704 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
15708 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
15709 if( j == backwardMostMove ) i += initialRulePlies;
15710 sprintf(p, "%d ", i);
15711 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
15713 /* Fullmove number */
15714 sprintf(p, "%d", (move / 2) + 1);
15716 return StrSave(buf);
15720 ParseFEN(board, blackPlaysFirst, fen)
15722 int *blackPlaysFirst;
15732 /* [HGM] by default clear Crazyhouse holdings, if present */
15733 if(gameInfo.holdingsWidth) {
15734 for(i=0; i<BOARD_HEIGHT; i++) {
15735 board[i][0] = EmptySquare; /* black holdings */
15736 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
15737 board[i][1] = (ChessSquare) 0; /* black counts */
15738 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
15742 /* Piece placement data */
15743 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15746 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
15747 if (*p == '/') p++;
15748 emptycount = gameInfo.boardWidth - j;
15749 while (emptycount--)
15750 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15752 #if(BOARD_FILES >= 10)
15753 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
15754 p++; emptycount=10;
15755 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15756 while (emptycount--)
15757 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15759 } else if (isdigit(*p)) {
15760 emptycount = *p++ - '0';
15761 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
15762 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15763 while (emptycount--)
15764 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15765 } else if (*p == '+' || isalpha(*p)) {
15766 if (j >= gameInfo.boardWidth) return FALSE;
15768 piece = CharToPiece(*++p);
15769 if(piece == EmptySquare) return FALSE; /* unknown piece */
15770 piece = (ChessSquare) (PROMOTED piece ); p++;
15771 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
15772 } else piece = CharToPiece(*p++);
15774 if(piece==EmptySquare) return FALSE; /* unknown piece */
15775 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
15776 piece = (ChessSquare) (PROMOTED piece);
15777 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
15780 board[i][(j++)+gameInfo.holdingsWidth] = piece;
15786 while (*p == '/' || *p == ' ') p++;
15788 /* [HGM] look for Crazyhouse holdings here */
15789 while(*p==' ') p++;
15790 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
15792 if(*p == '-' ) p++; /* empty holdings */ else {
15793 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
15794 /* if we would allow FEN reading to set board size, we would */
15795 /* have to add holdings and shift the board read so far here */
15796 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
15798 if((int) piece >= (int) BlackPawn ) {
15799 i = (int)piece - (int)BlackPawn;
15800 i = PieceToNumber((ChessSquare)i);
15801 if( i >= gameInfo.holdingsSize ) return FALSE;
15802 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
15803 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
15805 i = (int)piece - (int)WhitePawn;
15806 i = PieceToNumber((ChessSquare)i);
15807 if( i >= gameInfo.holdingsSize ) return FALSE;
15808 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
15809 board[i][BOARD_WIDTH-2]++; /* black holdings */
15816 while(*p == ' ') p++;
15820 if(appData.colorNickNames) {
15821 if( c == appData.colorNickNames[0] ) c = 'w'; else
15822 if( c == appData.colorNickNames[1] ) c = 'b';
15826 *blackPlaysFirst = FALSE;
15829 *blackPlaysFirst = TRUE;
15835 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
15836 /* return the extra info in global variiables */
15838 /* set defaults in case FEN is incomplete */
15839 board[EP_STATUS] = EP_UNKNOWN;
15840 for(i=0; i<nrCastlingRights; i++ ) {
15841 board[CASTLING][i] =
15842 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
15843 } /* assume possible unless obviously impossible */
15844 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
15845 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
15846 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
15847 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
15848 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
15849 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
15850 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
15851 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
15854 while(*p==' ') p++;
15855 if(nrCastlingRights) {
15856 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
15857 /* castling indicator present, so default becomes no castlings */
15858 for(i=0; i<nrCastlingRights; i++ ) {
15859 board[CASTLING][i] = NoRights;
15862 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
15863 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
15864 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
15865 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
15866 char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
15868 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
15869 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
15870 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
15872 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
15873 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
15874 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
15875 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
15876 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
15877 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
15880 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
15881 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
15882 board[CASTLING][2] = whiteKingFile;
15885 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
15886 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
15887 board[CASTLING][2] = whiteKingFile;
15890 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
15891 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
15892 board[CASTLING][5] = blackKingFile;
15895 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
15896 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
15897 board[CASTLING][5] = blackKingFile;
15900 default: /* FRC castlings */
15901 if(c >= 'a') { /* black rights */
15902 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15903 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
15904 if(i == BOARD_RGHT) break;
15905 board[CASTLING][5] = i;
15907 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
15908 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
15910 board[CASTLING][3] = c;
15912 board[CASTLING][4] = c;
15913 } else { /* white rights */
15914 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15915 if(board[0][i] == WhiteKing) break;
15916 if(i == BOARD_RGHT) break;
15917 board[CASTLING][2] = i;
15918 c -= AAA - 'a' + 'A';
15919 if(board[0][c] >= WhiteKing) break;
15921 board[CASTLING][0] = c;
15923 board[CASTLING][1] = c;
15927 for(i=0; i<nrCastlingRights; i++)
15928 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
15929 if (appData.debugMode) {
15930 fprintf(debugFP, "FEN castling rights:");
15931 for(i=0; i<nrCastlingRights; i++)
15932 fprintf(debugFP, " %d", board[CASTLING][i]);
15933 fprintf(debugFP, "\n");
15936 while(*p==' ') p++;
15939 /* read e.p. field in games that know e.p. capture */
15940 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
15941 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15943 p++; board[EP_STATUS] = EP_NONE;
15945 char c = *p++ - AAA;
15947 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
15948 if(*p >= '0' && *p <='9') p++;
15949 board[EP_STATUS] = c;
15954 if(sscanf(p, "%d", &i) == 1) {
15955 FENrulePlies = i; /* 50-move ply counter */
15956 /* (The move number is still ignored) */
15963 EditPositionPasteFEN(char *fen)
15966 Board initial_position;
15968 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15969 DisplayError(_("Bad FEN position in clipboard"), 0);
15972 int savedBlackPlaysFirst = blackPlaysFirst;
15973 EditPositionEvent();
15974 blackPlaysFirst = savedBlackPlaysFirst;
15975 CopyBoard(boards[0], initial_position);
15976 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15977 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15978 DisplayBothClocks();
15979 DrawPosition(FALSE, boards[currentMove]);
15984 static char cseq[12] = "\\ ";
15986 Boolean set_cont_sequence(char *new_seq)
15991 // handle bad attempts to set the sequence
15993 return 0; // acceptable error - no debug
15995 len = strlen(new_seq);
15996 ret = (len > 0) && (len < sizeof(cseq));
15998 safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
15999 else if (appData.debugMode)
16000 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16005 reformat a source message so words don't cross the width boundary. internal
16006 newlines are not removed. returns the wrapped size (no null character unless
16007 included in source message). If dest is NULL, only calculate the size required
16008 for the dest buffer. lp argument indicats line position upon entry, and it's
16009 passed back upon exit.
16011 int wrap(char *dest, char *src, int count, int width, int *lp)
16013 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16015 cseq_len = strlen(cseq);
16016 old_line = line = *lp;
16017 ansi = len = clen = 0;
16019 for (i=0; i < count; i++)
16021 if (src[i] == '\033')
16024 // if we hit the width, back up
16025 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16027 // store i & len in case the word is too long
16028 old_i = i, old_len = len;
16030 // find the end of the last word
16031 while (i && src[i] != ' ' && src[i] != '\n')
16037 // word too long? restore i & len before splitting it
16038 if ((old_i-i+clen) >= width)
16045 if (i && src[i-1] == ' ')
16048 if (src[i] != ' ' && src[i] != '\n')
16055 // now append the newline and continuation sequence
16060 strncpy(dest+len, cseq, cseq_len);
16068 dest[len] = src[i];
16072 if (src[i] == '\n')
16077 if (dest && appData.debugMode)
16079 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16080 count, width, line, len, *lp);
16081 show_bytes(debugFP, src, count);
16082 fprintf(debugFP, "\ndest: ");
16083 show_bytes(debugFP, dest, len);
16084 fprintf(debugFP, "\n");
16086 *lp = dest ? line : old_line;
16091 // [HGM] vari: routines for shelving variations
16094 PushInner(int firstMove, int lastMove)
16096 int i, j, nrMoves = lastMove - firstMove;
16098 // push current tail of game on stack
16099 savedResult[storedGames] = gameInfo.result;
16100 savedDetails[storedGames] = gameInfo.resultDetails;
16101 gameInfo.resultDetails = NULL;
16102 savedFirst[storedGames] = firstMove;
16103 savedLast [storedGames] = lastMove;
16104 savedFramePtr[storedGames] = framePtr;
16105 framePtr -= nrMoves; // reserve space for the boards
16106 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16107 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16108 for(j=0; j<MOVE_LEN; j++)
16109 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16110 for(j=0; j<2*MOVE_LEN; j++)
16111 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16112 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16113 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16114 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16115 pvInfoList[firstMove+i-1].depth = 0;
16116 commentList[framePtr+i] = commentList[firstMove+i];
16117 commentList[firstMove+i] = NULL;
16121 forwardMostMove = firstMove; // truncate game so we can start variation
16125 PushTail(int firstMove, int lastMove)
16127 if(appData.icsActive) { // only in local mode
16128 forwardMostMove = currentMove; // mimic old ICS behavior
16131 if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16133 PushInner(firstMove, lastMove);
16134 if(storedGames == 1) GreyRevert(FALSE);
16138 PopInner(Boolean annotate)
16141 char buf[8000], moveBuf[20];
16144 ToNrEvent(savedFirst[storedGames]); // sets currentMove
16145 nrMoves = savedLast[storedGames] - currentMove;
16148 if(!WhiteOnMove(currentMove))
16149 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16150 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16151 for(i=currentMove; i<forwardMostMove; i++) {
16153 snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16154 else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16155 strcat(buf, moveBuf);
16156 if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16157 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16161 for(i=1; i<=nrMoves; i++) { // copy last variation back
16162 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16163 for(j=0; j<MOVE_LEN; j++)
16164 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16165 for(j=0; j<2*MOVE_LEN; j++)
16166 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16167 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16168 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16169 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16170 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16171 commentList[currentMove+i] = commentList[framePtr+i];
16172 commentList[framePtr+i] = NULL;
16174 if(annotate) AppendComment(currentMove+1, buf, FALSE);
16175 framePtr = savedFramePtr[storedGames];
16176 gameInfo.result = savedResult[storedGames];
16177 if(gameInfo.resultDetails != NULL) {
16178 free(gameInfo.resultDetails);
16180 gameInfo.resultDetails = savedDetails[storedGames];
16181 forwardMostMove = currentMove + nrMoves;
16185 PopTail(Boolean annotate)
16187 if(appData.icsActive) return FALSE; // only in local mode
16188 if(!storedGames) return FALSE; // sanity
16189 CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16191 PopInner(annotate);
16193 if(storedGames == 0) GreyRevert(TRUE);
16199 { // remove all shelved variations
16201 for(i=0; i<storedGames; i++) {
16202 if(savedDetails[i])
16203 free(savedDetails[i]);
16204 savedDetails[i] = NULL;
16206 for(i=framePtr; i<MAX_MOVES; i++) {
16207 if(commentList[i]) free(commentList[i]);
16208 commentList[i] = NULL;
16210 framePtr = MAX_MOVES-1;
16215 LoadVariation(int index, char *text)
16216 { // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16217 char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16218 int level = 0, move;
16220 if(gameMode != EditGame && gameMode != AnalyzeMode) return;
16221 // first find outermost bracketing variation
16222 while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16223 if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16224 if(*p == '{') wait = '}'; else
16225 if(*p == '[') wait = ']'; else
16226 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16227 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16229 if(*p == wait) wait = NULLCHAR; // closing ]} found
16232 if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16233 if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16234 end[1] = NULLCHAR; // clip off comment beyond variation
16235 ToNrEvent(currentMove-1);
16236 PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
16237 // kludge: use ParsePV() to append variation to game
16238 move = currentMove;
16239 ParsePV(start, TRUE);
16240 forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
16241 ClearPremoveHighlights();
16243 ToNrEvent(currentMove+1);