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);
67 #define DoSleep( n ) if( (n) >= 0) sleep(n)
78 #include <sys/types.h>
87 #else /* not STDC_HEADERS */
90 # else /* not HAVE_STRING_H */
92 # endif /* not HAVE_STRING_H */
93 #endif /* not STDC_HEADERS */
96 # include <sys/fcntl.h>
97 #else /* not HAVE_SYS_FCNTL_H */
100 # endif /* HAVE_FCNTL_H */
101 #endif /* not HAVE_SYS_FCNTL_H */
103 #if TIME_WITH_SYS_TIME
104 # include <sys/time.h>
108 # include <sys/time.h>
114 #if defined(_amigados) && !defined(__GNUC__)
119 extern int gettimeofday(struct timeval *, struct timezone *);
127 #include "frontend.h"
134 #include "backendz.h"
138 # define _(s) gettext (s)
139 # define N_(s) gettext_noop (s)
140 # define T_(s) gettext(s)
153 /* A point in time */
155 long sec; /* Assuming this is >= 32 bits */
156 int ms; /* Assuming this is >= 16 bits */
159 int establish P((void));
160 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
161 char *buf, int count, int error));
162 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
163 char *buf, int count, int error));
164 void ics_printf P((char *format, ...));
165 void SendToICS P((char *s));
166 void SendToICSDelayed P((char *s, long msdelay));
167 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
168 void HandleMachineMove P((char *message, ChessProgramState *cps));
169 int AutoPlayOneMove P((void));
170 int LoadGameOneMove P((ChessMove readAhead));
171 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
172 int LoadPositionFromFile P((char *filename, int n, char *title));
173 int SavePositionToFile P((char *filename));
174 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
176 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
177 void ShowMove P((int fromX, int fromY, int toX, int toY));
178 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
179 /*char*/int promoChar));
180 void BackwardInner P((int target));
181 void ForwardInner P((int target));
182 int Adjudicate P((ChessProgramState *cps));
183 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
184 void EditPositionDone P((Boolean fakeRights));
185 void PrintOpponents P((FILE *fp));
186 void PrintPosition P((FILE *fp, int move));
187 void StartChessProgram P((ChessProgramState *cps));
188 void SendToProgram P((char *message, ChessProgramState *cps));
189 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
190 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
191 char *buf, int count, int error));
192 void SendTimeControl P((ChessProgramState *cps,
193 int mps, long tc, int inc, int sd, int st));
194 char *TimeControlTagValue P((void));
195 void Attention P((ChessProgramState *cps));
196 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
197 int ResurrectChessProgram P((void));
198 void DisplayComment P((int moveNumber, char *text));
199 void DisplayMove P((int moveNumber));
201 void ParseGameHistory P((char *game));
202 void ParseBoard12 P((char *string));
203 void KeepAlive P((void));
204 void StartClocks P((void));
205 void SwitchClocks P((int nr));
206 void StopClocks P((void));
207 void ResetClocks P((void));
208 char *PGNDate P((void));
209 void SetGameInfo P((void));
210 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
211 int RegisterMove P((void));
212 void MakeRegisteredMove P((void));
213 void TruncateGame P((void));
214 int looking_at P((char *, int *, char *));
215 void CopyPlayerNameIntoFileName P((char **, char *));
216 char *SavePart P((char *));
217 int SaveGameOldStyle P((FILE *));
218 int SaveGamePGN P((FILE *));
219 void GetTimeMark P((TimeMark *));
220 long SubtractTimeMarks P((TimeMark *, TimeMark *));
221 int CheckFlags P((void));
222 long NextTickLength P((long));
223 void CheckTimeControl P((void));
224 void show_bytes P((FILE *, char *, int));
225 int string_to_rating P((char *str));
226 void ParseFeatures P((char* args, ChessProgramState *cps));
227 void InitBackEnd3 P((void));
228 void FeatureDone P((ChessProgramState* cps, int val));
229 void InitChessProgram P((ChessProgramState *cps, int setup));
230 void OutputKibitz(int window, char *text);
231 int PerpetualChase(int first, int last);
232 int EngineOutputIsUp();
233 void InitDrawingSizes(int x, int y);
234 void NextMatchGame P((void));
235 int NextTourneyGame P((int nr, int *swap));
236 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
237 FILE *WriteTourneyFile P((char *results));
238 void DisplayTwoMachinesTitle P(());
241 extern void ConsoleCreate();
244 ChessProgramState *WhitePlayer();
245 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
246 int VerifyDisplayMode P(());
248 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
249 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
250 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
251 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
252 void ics_update_width P((int new_width));
253 extern char installDir[MSG_SIZ];
254 VariantClass startVariant; /* [HGM] nicks: initial variant */
257 extern int tinyLayout, smallLayout;
258 ChessProgramStats programStats;
259 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
261 static int exiting = 0; /* [HGM] moved to top */
262 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
263 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
264 Board partnerBoard; /* [HGM] bughouse: for peeking at partner game */
265 int partnerHighlight[2];
266 Boolean partnerBoardValid = 0;
267 char partnerStatus[MSG_SIZ];
269 Boolean originalFlip;
270 Boolean twoBoards = 0;
271 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
272 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
273 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
274 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
275 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
276 int opponentKibitzes;
277 int lastSavedGame; /* [HGM] save: ID of game */
278 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
279 extern int chatCount;
281 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
282 char lastMsg[MSG_SIZ];
283 ChessSquare pieceSweep = EmptySquare;
284 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
285 int promoDefaultAltered;
287 /* States for ics_getting_history */
289 #define H_REQUESTED 1
290 #define H_GOT_REQ_HEADER 2
291 #define H_GOT_UNREQ_HEADER 3
292 #define H_GETTING_MOVES 4
293 #define H_GOT_UNWANTED_HEADER 5
295 /* whosays values for GameEnds */
304 /* Maximum number of games in a cmail message */
305 #define CMAIL_MAX_GAMES 20
307 /* Different types of move when calling RegisterMove */
309 #define CMAIL_RESIGN 1
311 #define CMAIL_ACCEPT 3
313 /* Different types of result to remember for each game */
314 #define CMAIL_NOT_RESULT 0
315 #define CMAIL_OLD_RESULT 1
316 #define CMAIL_NEW_RESULT 2
318 /* Telnet protocol constants */
329 safeStrCpy( char *dst, const char *src, size_t count )
332 assert( dst != NULL );
333 assert( src != NULL );
336 for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
337 if( i == count && dst[count-1] != NULLCHAR)
339 dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
340 if(appData.debugMode)
341 fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
347 /* Some compiler can't cast u64 to double
348 * This function do the job for us:
350 * We use the highest bit for cast, this only
351 * works if the highest bit is not
352 * in use (This should not happen)
354 * We used this for all compiler
357 u64ToDouble(u64 value)
360 u64 tmp = value & u64Const(0x7fffffffffffffff);
361 r = (double)(s64)tmp;
362 if (value & u64Const(0x8000000000000000))
363 r += 9.2233720368547758080e18; /* 2^63 */
367 /* Fake up flags for now, as we aren't keeping track of castling
368 availability yet. [HGM] Change of logic: the flag now only
369 indicates the type of castlings allowed by the rule of the game.
370 The actual rights themselves are maintained in the array
371 castlingRights, as part of the game history, and are not probed
377 int flags = F_ALL_CASTLE_OK;
378 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
379 switch (gameInfo.variant) {
381 flags &= ~F_ALL_CASTLE_OK;
382 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
383 flags |= F_IGNORE_CHECK;
385 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
388 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
390 case VariantKriegspiel:
391 flags |= F_KRIEGSPIEL_CAPTURE;
393 case VariantCapaRandom:
394 case VariantFischeRandom:
395 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
396 case VariantNoCastle:
397 case VariantShatranj:
401 flags &= ~F_ALL_CASTLE_OK;
409 FILE *gameFileFP, *debugFP;
412 [AS] Note: sometimes, the sscanf() function is used to parse the input
413 into a fixed-size buffer. Because of this, we must be prepared to
414 receive strings as long as the size of the input buffer, which is currently
415 set to 4K for Windows and 8K for the rest.
416 So, we must either allocate sufficiently large buffers here, or
417 reduce the size of the input buffer in the input reading part.
420 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
421 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
422 char thinkOutput1[MSG_SIZ*10];
424 ChessProgramState first, second, pairing;
426 /* premove variables */
429 int premoveFromX = 0;
430 int premoveFromY = 0;
431 int premovePromoChar = 0;
433 Boolean alarmSounded;
434 /* end premove variables */
436 char *ics_prefix = "$";
437 int ics_type = ICS_GENERIC;
439 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
440 int pauseExamForwardMostMove = 0;
441 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
442 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
443 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
444 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
445 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
446 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
447 int whiteFlag = FALSE, blackFlag = FALSE;
448 int userOfferedDraw = FALSE;
449 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
450 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
451 int cmailMoveType[CMAIL_MAX_GAMES];
452 long ics_clock_paused = 0;
453 ProcRef icsPR = NoProc, cmailPR = NoProc;
454 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
455 GameMode gameMode = BeginningOfGame;
456 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
457 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
458 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
459 int hiddenThinkOutputState = 0; /* [AS] */
460 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
461 int adjudicateLossPlies = 6;
462 char white_holding[64], black_holding[64];
463 TimeMark lastNodeCountTime;
464 long lastNodeCount=0;
465 int shiftKey; // [HGM] set by mouse handler
467 int have_sent_ICS_logon = 0;
469 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
470 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
471 long timeControl_2; /* [AS] Allow separate time controls */
472 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
473 long timeRemaining[2][MAX_MOVES];
474 int matchGame = 0, nextGame = 0, roundNr = 0;
475 Boolean waitingForGame = FALSE;
476 TimeMark programStartTime, pauseStart;
477 char ics_handle[MSG_SIZ];
478 int have_set_title = 0;
480 /* animateTraining preserves the state of appData.animate
481 * when Training mode is activated. This allows the
482 * response to be animated when appData.animate == TRUE and
483 * appData.animateDragging == TRUE.
485 Boolean animateTraining;
491 Board boards[MAX_MOVES];
492 /* [HGM] Following 7 needed for accurate legality tests: */
493 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
494 signed char initialRights[BOARD_FILES];
495 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
496 int initialRulePlies, FENrulePlies;
497 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
499 Boolean shuffleOpenings;
500 int mute; // mute all sounds
502 // [HGM] vari: next 12 to save and restore variations
503 #define MAX_VARIATIONS 10
504 int framePtr = MAX_MOVES-1; // points to free stack entry
506 int savedFirst[MAX_VARIATIONS];
507 int savedLast[MAX_VARIATIONS];
508 int savedFramePtr[MAX_VARIATIONS];
509 char *savedDetails[MAX_VARIATIONS];
510 ChessMove savedResult[MAX_VARIATIONS];
512 void PushTail P((int firstMove, int lastMove));
513 Boolean PopTail P((Boolean annotate));
514 void PushInner P((int firstMove, int lastMove));
515 void PopInner P((Boolean annotate));
516 void CleanupTail P((void));
518 ChessSquare FIDEArray[2][BOARD_FILES] = {
519 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
520 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
521 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
522 BlackKing, BlackBishop, BlackKnight, BlackRook }
525 ChessSquare twoKingsArray[2][BOARD_FILES] = {
526 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
527 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
528 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
529 BlackKing, BlackKing, BlackKnight, BlackRook }
532 ChessSquare KnightmateArray[2][BOARD_FILES] = {
533 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
534 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
535 { BlackRook, BlackMan, BlackBishop, BlackQueen,
536 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
539 ChessSquare SpartanArray[2][BOARD_FILES] = {
540 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
541 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
542 { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
543 BlackDragon, BlackKing, BlackAngel, BlackAlfil }
546 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
547 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
548 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
549 { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
550 BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
553 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
554 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
555 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
556 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
557 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
560 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
561 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
562 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
563 { BlackRook, BlackKnight, BlackMan, BlackFerz,
564 BlackKing, BlackMan, BlackKnight, BlackRook }
568 #if (BOARD_FILES>=10)
569 ChessSquare ShogiArray[2][BOARD_FILES] = {
570 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
571 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
572 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
573 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
576 ChessSquare XiangqiArray[2][BOARD_FILES] = {
577 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
578 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
579 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
580 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
583 ChessSquare CapablancaArray[2][BOARD_FILES] = {
584 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
585 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
586 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
587 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
590 ChessSquare GreatArray[2][BOARD_FILES] = {
591 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
592 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
593 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
594 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
597 ChessSquare JanusArray[2][BOARD_FILES] = {
598 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
599 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
600 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
601 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
604 ChessSquare GrandArray[2][BOARD_FILES] = {
605 { EmptySquare, WhiteKnight, WhiteBishop, WhiteQueen, WhiteKing,
606 WhiteMarshall, WhiteAngel, WhiteBishop, WhiteKnight, EmptySquare },
607 { EmptySquare, BlackKnight, BlackBishop, BlackQueen, BlackKing,
608 BlackMarshall, BlackAngel, BlackBishop, BlackKnight, EmptySquare }
612 ChessSquare GothicArray[2][BOARD_FILES] = {
613 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
614 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
615 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
616 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
619 #define GothicArray CapablancaArray
623 ChessSquare FalconArray[2][BOARD_FILES] = {
624 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
625 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
626 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
627 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
630 #define FalconArray CapablancaArray
633 #else // !(BOARD_FILES>=10)
634 #define XiangqiPosition FIDEArray
635 #define CapablancaArray FIDEArray
636 #define GothicArray FIDEArray
637 #define GreatArray FIDEArray
638 #endif // !(BOARD_FILES>=10)
640 #if (BOARD_FILES>=12)
641 ChessSquare CourierArray[2][BOARD_FILES] = {
642 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
643 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
644 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
645 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
647 #else // !(BOARD_FILES>=12)
648 #define CourierArray CapablancaArray
649 #endif // !(BOARD_FILES>=12)
652 Board initialPosition;
655 /* Convert str to a rating. Checks for special cases of "----",
657 "++++", etc. Also strips ()'s */
659 string_to_rating(str)
662 while(*str && !isdigit(*str)) ++str;
664 return 0; /* One of the special "no rating" cases */
672 /* Init programStats */
673 programStats.movelist[0] = 0;
674 programStats.depth = 0;
675 programStats.nr_moves = 0;
676 programStats.moves_left = 0;
677 programStats.nodes = 0;
678 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
679 programStats.score = 0;
680 programStats.got_only_move = 0;
681 programStats.got_fail = 0;
682 programStats.line_is_book = 0;
687 { // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
688 if (appData.firstPlaysBlack) {
689 first.twoMachinesColor = "black\n";
690 second.twoMachinesColor = "white\n";
692 first.twoMachinesColor = "white\n";
693 second.twoMachinesColor = "black\n";
696 first.other = &second;
697 second.other = &first;
700 if(appData.timeOddsMode) {
701 norm = appData.timeOdds[0];
702 if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
704 first.timeOdds = appData.timeOdds[0]/norm;
705 second.timeOdds = appData.timeOdds[1]/norm;
708 if(programVersion) free(programVersion);
709 if (appData.noChessProgram) {
710 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
711 sprintf(programVersion, "%s", PACKAGE_STRING);
713 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
714 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
715 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
720 UnloadEngine(ChessProgramState *cps)
722 /* Kill off first chess program */
723 if (cps->isr != NULL)
724 RemoveInputSource(cps->isr);
727 if (cps->pr != NoProc) {
729 DoSleep( appData.delayBeforeQuit );
730 SendToProgram("quit\n", cps);
731 DoSleep( appData.delayAfterQuit );
732 DestroyChildProcess(cps->pr, cps->useSigterm);
735 if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
739 ClearOptions(ChessProgramState *cps)
742 cps->nrOptions = cps->comboCnt = 0;
743 for(i=0; i<MAX_OPTIONS; i++) {
744 cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
745 cps->option[i].textValue = 0;
749 char *engineNames[] = {
755 InitEngine(ChessProgramState *cps, int n)
756 { // [HGM] all engine initialiation put in a function that does one engine
760 cps->which = engineNames[n];
761 cps->maybeThinking = FALSE;
765 cps->sendDrawOffers = 1;
767 cps->program = appData.chessProgram[n];
768 cps->host = appData.host[n];
769 cps->dir = appData.directory[n];
770 cps->initString = appData.engInitString[n];
771 cps->computerString = appData.computerString[n];
772 cps->useSigint = TRUE;
773 cps->useSigterm = TRUE;
774 cps->reuse = appData.reuse[n];
775 cps->nps = appData.NPS[n]; // [HGM] nps: copy nodes per second
776 cps->useSetboard = FALSE;
778 cps->usePing = FALSE;
781 cps->usePlayother = FALSE;
782 cps->useColors = TRUE;
783 cps->useUsermove = FALSE;
784 cps->sendICS = FALSE;
785 cps->sendName = appData.icsActive;
786 cps->sdKludge = FALSE;
787 cps->stKludge = FALSE;
788 TidyProgramName(cps->program, cps->host, cps->tidy);
790 safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
791 cps->analysisSupport = 2; /* detect */
792 cps->analyzing = FALSE;
793 cps->initDone = FALSE;
795 /* New features added by Tord: */
796 cps->useFEN960 = FALSE;
797 cps->useOOCastle = TRUE;
798 /* End of new features added by Tord. */
799 cps->fenOverride = appData.fenOverride[n];
801 /* [HGM] time odds: set factor for each machine */
802 cps->timeOdds = appData.timeOdds[n];
804 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
805 cps->accumulateTC = appData.accumulateTC[n];
806 cps->maxNrOfSessions = 1;
811 cps->supportsNPS = UNKNOWN;
812 cps->memSize = FALSE;
813 cps->maxCores = FALSE;
814 cps->egtFormats[0] = NULLCHAR;
817 cps->optionSettings = appData.engOptions[n];
819 cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
820 cps->isUCI = appData.isUCI[n]; /* [AS] */
821 cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
823 if (appData.protocolVersion[n] > PROTOVER
824 || appData.protocolVersion[n] < 1)
829 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
830 appData.protocolVersion[n]);
831 if( (len > MSG_SIZ) && appData.debugMode )
832 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
834 DisplayFatalError(buf, 0, 2);
838 cps->protocolVersion = appData.protocolVersion[n];
841 InitEngineUCI( installDir, cps ); // [HGM] moved here from winboard.c, to make available in xboard
844 ChessProgramState *savCps;
850 if(WaitForEngine(savCps, LoadEngine)) return;
851 CommonEngineInit(); // recalculate time odds
852 if(gameInfo.variant != StringToVariant(appData.variant)) {
853 // we changed variant when loading the engine; this forces us to reset
854 Reset(TRUE, savCps != &first);
855 EditGameEvent(); // for consistency with other path, as Reset changes mode
857 InitChessProgram(savCps, FALSE);
858 SendToProgram("force\n", savCps);
859 DisplayMessage("", "");
860 if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
861 for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
867 ReplaceEngine(ChessProgramState *cps, int n)
871 appData.noChessProgram = FALSE;
872 appData.clockMode = TRUE;
875 if(n) return; // only startup first engine immediately; second can wait
876 savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
880 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
881 extern Boolean isUCI, hasBook, storeVariant, v1, addToList, useNick;
883 static char resetOptions[] =
884 "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 "
885 "-firstOptions \"\" -firstNPS -1 -fn \"\"";
888 Load(ChessProgramState *cps, int i)
890 char *p, *q, buf[MSG_SIZ], command[MSG_SIZ], buf2[MSG_SIZ];
891 if(engineLine[0]) { // an engine was selected from the combo box
892 snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
893 SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
894 ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL;
895 ParseArgsFromString(buf);
897 ReplaceEngine(cps, i);
901 while(q = strchr(p, SLASH)) p = q+1;
902 if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
903 if(engineDir[0] != NULLCHAR)
904 appData.directory[i] = engineDir;
905 else if(p != engineName) { // derive directory from engine path, when not given
907 appData.directory[i] = strdup(engineName);
909 } else appData.directory[i] = ".";
910 if(strchr(p, ' ') && !strchr(p, '"')) snprintf(buf2, MSG_SIZ, "\"%s\"", p), p = buf2; // quote if it contains spaces
912 snprintf(command, MSG_SIZ, "%s %s", p, params);
915 appData.chessProgram[i] = strdup(p);
916 appData.isUCI[i] = isUCI;
917 appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
918 appData.hasOwnBookUCI[i] = hasBook;
919 if(!nickName[0]) useNick = FALSE;
920 if(useNick) ASSIGN(appData.pgnName[i], nickName);
924 q = firstChessProgramNames;
925 if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
926 quote = strchr(p, '"') ? '\'' : '"'; // use single quotes around engine command if it contains double quotes
927 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "%c%s%c -fd \"%s\"%s%s%s%s%s%s%s%s\n",
928 quote, p, quote, appData.directory[i],
929 useNick ? " -fn \"" : "",
930 useNick ? nickName : "",
932 v1 ? " -firstProtocolVersion 1" : "",
933 hasBook ? "" : " -fNoOwnBookUCI",
934 isUCI ? (isUCI == TRUE ? " -fUCI" : gameInfo.variant == VariantShogi ? " -fUSI" : " -fUCCI") : "",
935 storeVariant ? " -variant " : "",
936 storeVariant ? VariantName(gameInfo.variant) : "");
937 firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
938 snprintf(firstChessProgramNames, len, "%s%s", q, buf);
941 ReplaceEngine(cps, i);
947 int matched, min, sec;
949 * Parse timeControl resource
951 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
952 appData.movesPerSession)) {
954 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
955 DisplayFatalError(buf, 0, 2);
959 * Parse searchTime resource
961 if (*appData.searchTime != NULLCHAR) {
962 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
964 searchTime = min * 60;
965 } else if (matched == 2) {
966 searchTime = min * 60 + sec;
969 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
970 DisplayFatalError(buf, 0, 2);
979 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
980 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
982 GetTimeMark(&programStartTime);
983 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
984 pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
987 programStats.ok_to_send = 1;
988 programStats.seen_stat = 0;
991 * Initialize game list
997 * Internet chess server status
999 if (appData.icsActive) {
1000 appData.matchMode = FALSE;
1001 appData.matchGames = 0;
1003 appData.noChessProgram = !appData.zippyPlay;
1005 appData.zippyPlay = FALSE;
1006 appData.zippyTalk = FALSE;
1007 appData.noChessProgram = TRUE;
1009 if (*appData.icsHelper != NULLCHAR) {
1010 appData.useTelnet = TRUE;
1011 appData.telnetProgram = appData.icsHelper;
1014 appData.zippyTalk = appData.zippyPlay = FALSE;
1017 /* [AS] Initialize pv info list [HGM] and game state */
1021 for( i=0; i<=framePtr; i++ ) {
1022 pvInfoList[i].depth = -1;
1023 boards[i][EP_STATUS] = EP_NONE;
1024 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
1030 /* [AS] Adjudication threshold */
1031 adjudicateLossThreshold = appData.adjudicateLossThreshold;
1033 InitEngine(&first, 0);
1034 InitEngine(&second, 1);
1037 pairing.which = "pairing"; // pairing engine
1038 pairing.pr = NoProc;
1040 pairing.program = appData.pairingEngine;
1041 pairing.host = "localhost";
1044 if (appData.icsActive) {
1045 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
1046 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1047 appData.clockMode = FALSE;
1048 first.sendTime = second.sendTime = 0;
1052 /* Override some settings from environment variables, for backward
1053 compatibility. Unfortunately it's not feasible to have the env
1054 vars just set defaults, at least in xboard. Ugh.
1056 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1061 if (!appData.icsActive) {
1065 /* Check for variants that are supported only in ICS mode,
1066 or not at all. Some that are accepted here nevertheless
1067 have bugs; see comments below.
1069 VariantClass variant = StringToVariant(appData.variant);
1071 case VariantBughouse: /* need four players and two boards */
1072 case VariantKriegspiel: /* need to hide pieces and move details */
1073 /* case VariantFischeRandom: (Fabien: moved below) */
1074 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1075 if( (len > MSG_SIZ) && appData.debugMode )
1076 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1078 DisplayFatalError(buf, 0, 2);
1081 case VariantUnknown:
1082 case VariantLoadable:
1092 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1093 if( (len > MSG_SIZ) && appData.debugMode )
1094 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1096 DisplayFatalError(buf, 0, 2);
1099 case VariantXiangqi: /* [HGM] repetition rules not implemented */
1100 case VariantFairy: /* [HGM] TestLegality definitely off! */
1101 case VariantGothic: /* [HGM] should work */
1102 case VariantCapablanca: /* [HGM] should work */
1103 case VariantCourier: /* [HGM] initial forced moves not implemented */
1104 case VariantShogi: /* [HGM] could still mate with pawn drop */
1105 case VariantKnightmate: /* [HGM] should work */
1106 case VariantCylinder: /* [HGM] untested */
1107 case VariantFalcon: /* [HGM] untested */
1108 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1109 offboard interposition not understood */
1110 case VariantNormal: /* definitely works! */
1111 case VariantWildCastle: /* pieces not automatically shuffled */
1112 case VariantNoCastle: /* pieces not automatically shuffled */
1113 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1114 case VariantLosers: /* should work except for win condition,
1115 and doesn't know captures are mandatory */
1116 case VariantSuicide: /* should work except for win condition,
1117 and doesn't know captures are mandatory */
1118 case VariantGiveaway: /* should work except for win condition,
1119 and doesn't know captures are mandatory */
1120 case VariantTwoKings: /* should work */
1121 case VariantAtomic: /* should work except for win condition */
1122 case Variant3Check: /* should work except for win condition */
1123 case VariantShatranj: /* should work except for all win conditions */
1124 case VariantMakruk: /* should work except for draw countdown */
1125 case VariantBerolina: /* might work if TestLegality is off */
1126 case VariantCapaRandom: /* should work */
1127 case VariantJanus: /* should work */
1128 case VariantSuper: /* experimental */
1129 case VariantGreat: /* experimental, requires legality testing to be off */
1130 case VariantSChess: /* S-Chess, should work */
1131 case VariantGrand: /* should work */
1132 case VariantSpartan: /* should work */
1139 int NextIntegerFromString( char ** str, long * value )
1144 while( *s == ' ' || *s == '\t' ) {
1150 if( *s >= '0' && *s <= '9' ) {
1151 while( *s >= '0' && *s <= '9' ) {
1152 *value = *value * 10 + (*s - '0');
1164 int NextTimeControlFromString( char ** str, long * value )
1167 int result = NextIntegerFromString( str, &temp );
1170 *value = temp * 60; /* Minutes */
1171 if( **str == ':' ) {
1173 result = NextIntegerFromString( str, &temp );
1174 *value += temp; /* Seconds */
1181 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1182 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1183 int result = -1, type = 0; long temp, temp2;
1185 if(**str != ':') return -1; // old params remain in force!
1187 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1188 if( NextIntegerFromString( str, &temp ) ) return -1;
1189 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1192 /* time only: incremental or sudden-death time control */
1193 if(**str == '+') { /* increment follows; read it */
1195 if(**str == '!') type = *(*str)++; // Bronstein TC
1196 if(result = NextIntegerFromString( str, &temp2)) return -1;
1197 *inc = temp2 * 1000;
1198 if(**str == '.') { // read fraction of increment
1199 char *start = ++(*str);
1200 if(result = NextIntegerFromString( str, &temp2)) return -1;
1202 while(start++ < *str) temp2 /= 10;
1206 *moves = 0; *tc = temp * 1000; *incType = type;
1210 (*str)++; /* classical time control */
1211 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1222 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1223 { /* [HGM] get time to add from the multi-session time-control string */
1224 int incType, moves=1; /* kludge to force reading of first session */
1225 long time, increment;
1228 if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1229 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1231 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1232 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1233 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1234 if(movenr == -1) return time; /* last move before new session */
1235 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1236 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1237 if(!moves) return increment; /* current session is incremental */
1238 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1239 } while(movenr >= -1); /* try again for next session */
1241 return 0; // no new time quota on this move
1245 ParseTimeControl(tc, ti, mps)
1252 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1255 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1256 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1257 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1261 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1263 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1266 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1268 snprintf(buf, MSG_SIZ, ":%s", mytc);
1270 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1272 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1277 /* Parse second time control */
1280 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1288 timeControl_2 = tc2 * 1000;
1298 timeControl = tc1 * 1000;
1301 timeIncrement = ti * 1000; /* convert to ms */
1302 movesPerSession = 0;
1305 movesPerSession = mps;
1313 if (appData.debugMode) {
1314 fprintf(debugFP, "%s\n", programVersion);
1317 set_cont_sequence(appData.wrapContSeq);
1318 if (appData.matchGames > 0) {
1319 appData.matchMode = TRUE;
1320 } else if (appData.matchMode) {
1321 appData.matchGames = 1;
1323 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1324 appData.matchGames = appData.sameColorGames;
1325 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1326 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1327 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1330 if (appData.noChessProgram || first.protocolVersion == 1) {
1333 /* kludge: allow timeout for initial "feature" commands */
1335 DisplayMessage("", _("Starting chess program"));
1336 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1341 CalculateIndex(int index, int gameNr)
1342 { // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1344 if(index > 0) return index; // fixed nmber
1345 if(index == 0) return 1;
1346 res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1347 if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1352 LoadGameOrPosition(int gameNr)
1353 { // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1354 if (*appData.loadGameFile != NULLCHAR) {
1355 if (!LoadGameFromFile(appData.loadGameFile,
1356 CalculateIndex(appData.loadGameIndex, gameNr),
1357 appData.loadGameFile, FALSE)) {
1358 DisplayFatalError(_("Bad game file"), 0, 1);
1361 } else if (*appData.loadPositionFile != NULLCHAR) {
1362 if (!LoadPositionFromFile(appData.loadPositionFile,
1363 CalculateIndex(appData.loadPositionIndex, gameNr),
1364 appData.loadPositionFile)) {
1365 DisplayFatalError(_("Bad position file"), 0, 1);
1373 ReserveGame(int gameNr, char resChar)
1375 FILE *tf = fopen(appData.tourneyFile, "r+");
1376 char *p, *q, c, buf[MSG_SIZ];
1377 if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1378 safeStrCpy(buf, lastMsg, MSG_SIZ);
1379 DisplayMessage(_("Pick new game"), "");
1380 flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1381 ParseArgsFromFile(tf);
1382 p = q = appData.results;
1383 if(appData.debugMode) {
1384 char *r = appData.participants;
1385 fprintf(debugFP, "results = '%s'\n", p);
1386 while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1387 fprintf(debugFP, "\n");
1389 while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1391 q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1392 safeStrCpy(q, p, strlen(p) + 2);
1393 if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1394 if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1395 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch) { // reserve next game if tourney not yet done
1396 if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1399 fseek(tf, -(strlen(p)+4), SEEK_END);
1401 if(c != '"') // depending on DOS or Unix line endings we can be one off
1402 fseek(tf, -(strlen(p)+2), SEEK_END);
1403 else fseek(tf, -(strlen(p)+3), SEEK_END);
1404 fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1405 DisplayMessage(buf, "");
1406 free(p); appData.results = q;
1407 if(nextGame <= appData.matchGames && resChar != ' ' && !abortMatch &&
1408 (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1409 UnloadEngine(&first); // next game belongs to other pairing;
1410 UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1415 MatchEvent(int mode)
1416 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1418 if(matchMode) { // already in match mode: switch it off
1420 if(!appData.tourneyFile[0]) appData.matchGames = matchGame; // kludge to let match terminate after next game.
1423 // if(gameMode != BeginningOfGame) {
1424 // DisplayError(_("You can only start a match from the initial position."), 0);
1428 if(mode == 2) appData.matchGames = appData.defaultMatchGames;
1429 /* Set up machine vs. machine match */
1431 NextTourneyGame(-1, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1432 if(appData.tourneyFile[0]) {
1434 if(nextGame > appData.matchGames) {
1436 if(strchr(appData.results, '*') == NULL) {
1438 appData.tourneyCycles++;
1439 if(f = WriteTourneyFile(appData.results)) { // make a tourney file with increased number of cycles
1441 NextTourneyGame(-1, &dummy);
1443 if(nextGame <= appData.matchGames) {
1444 DisplayNote(_("You restarted an already completed tourney\nOne more cycle will now be added to it\nGames commence in 10 sec"));
1446 ScheduleDelayedEvent(NextMatchGame, 10000);
1451 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1452 DisplayError(buf, 0);
1453 appData.tourneyFile[0] = 0;
1457 if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically
1458 DisplayFatalError(_("Can't have a match with no chess programs"),
1463 matchGame = roundNr = 1;
1464 first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
1469 InitBackEnd3 P((void))
1471 GameMode initialMode;
1475 InitChessProgram(&first, startedFromSetupPosition);
1477 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1478 free(programVersion);
1479 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1480 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1483 if (appData.icsActive) {
1485 /* [DM] Make a console window if needed [HGM] merged ifs */
1491 if (*appData.icsCommPort != NULLCHAR)
1492 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1493 appData.icsCommPort);
1495 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1496 appData.icsHost, appData.icsPort);
1498 if( (len > MSG_SIZ) && appData.debugMode )
1499 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1501 DisplayFatalError(buf, err, 1);
1506 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1508 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1509 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1510 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1511 } else if (appData.noChessProgram) {
1517 if (*appData.cmailGameName != NULLCHAR) {
1519 OpenLoopback(&cmailPR);
1521 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1525 DisplayMessage("", "");
1526 if (StrCaseCmp(appData.initialMode, "") == 0) {
1527 initialMode = BeginningOfGame;
1528 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1529 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1530 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1531 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1534 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1535 initialMode = TwoMachinesPlay;
1536 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1537 initialMode = AnalyzeFile;
1538 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1539 initialMode = AnalyzeMode;
1540 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1541 initialMode = MachinePlaysWhite;
1542 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1543 initialMode = MachinePlaysBlack;
1544 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1545 initialMode = EditGame;
1546 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1547 initialMode = EditPosition;
1548 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1549 initialMode = Training;
1551 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1552 if( (len > MSG_SIZ) && appData.debugMode )
1553 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1555 DisplayFatalError(buf, 0, 2);
1559 if (appData.matchMode) {
1560 if(appData.tourneyFile[0]) { // start tourney from command line
1562 if(f = fopen(appData.tourneyFile, "r")) {
1563 ParseArgsFromFile(f); // make sure tourney parmeters re known
1565 appData.clockMode = TRUE;
1567 } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1570 } else if (*appData.cmailGameName != NULLCHAR) {
1571 /* Set up cmail mode */
1572 ReloadCmailMsgEvent(TRUE);
1574 /* Set up other modes */
1575 if (initialMode == AnalyzeFile) {
1576 if (*appData.loadGameFile == NULLCHAR) {
1577 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1581 if (*appData.loadGameFile != NULLCHAR) {
1582 (void) LoadGameFromFile(appData.loadGameFile,
1583 appData.loadGameIndex,
1584 appData.loadGameFile, TRUE);
1585 } else if (*appData.loadPositionFile != NULLCHAR) {
1586 (void) LoadPositionFromFile(appData.loadPositionFile,
1587 appData.loadPositionIndex,
1588 appData.loadPositionFile);
1589 /* [HGM] try to make self-starting even after FEN load */
1590 /* to allow automatic setup of fairy variants with wtm */
1591 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1592 gameMode = BeginningOfGame;
1593 setboardSpoiledMachineBlack = 1;
1595 /* [HGM] loadPos: make that every new game uses the setup */
1596 /* from file as long as we do not switch variant */
1597 if(!blackPlaysFirst) {
1598 startedFromPositionFile = TRUE;
1599 CopyBoard(filePosition, boards[0]);
1602 if (initialMode == AnalyzeMode) {
1603 if (appData.noChessProgram) {
1604 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1607 if (appData.icsActive) {
1608 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1612 } else if (initialMode == AnalyzeFile) {
1613 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1614 ShowThinkingEvent();
1616 AnalysisPeriodicEvent(1);
1617 } else if (initialMode == MachinePlaysWhite) {
1618 if (appData.noChessProgram) {
1619 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1623 if (appData.icsActive) {
1624 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1628 MachineWhiteEvent();
1629 } else if (initialMode == MachinePlaysBlack) {
1630 if (appData.noChessProgram) {
1631 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1635 if (appData.icsActive) {
1636 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1640 MachineBlackEvent();
1641 } else if (initialMode == TwoMachinesPlay) {
1642 if (appData.noChessProgram) {
1643 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1647 if (appData.icsActive) {
1648 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1653 } else if (initialMode == EditGame) {
1655 } else if (initialMode == EditPosition) {
1656 EditPositionEvent();
1657 } else if (initialMode == Training) {
1658 if (*appData.loadGameFile == NULLCHAR) {
1659 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1668 * Establish will establish a contact to a remote host.port.
1669 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1670 * used to talk to the host.
1671 * Returns 0 if okay, error code if not.
1678 if (*appData.icsCommPort != NULLCHAR) {
1679 /* Talk to the host through a serial comm port */
1680 return OpenCommPort(appData.icsCommPort, &icsPR);
1682 } else if (*appData.gateway != NULLCHAR) {
1683 if (*appData.remoteShell == NULLCHAR) {
1684 /* Use the rcmd protocol to run telnet program on a gateway host */
1685 snprintf(buf, sizeof(buf), "%s %s %s",
1686 appData.telnetProgram, appData.icsHost, appData.icsPort);
1687 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1690 /* Use the rsh program to run telnet program on a gateway host */
1691 if (*appData.remoteUser == NULLCHAR) {
1692 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1693 appData.gateway, appData.telnetProgram,
1694 appData.icsHost, appData.icsPort);
1696 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1697 appData.remoteShell, appData.gateway,
1698 appData.remoteUser, appData.telnetProgram,
1699 appData.icsHost, appData.icsPort);
1701 return StartChildProcess(buf, "", &icsPR);
1704 } else if (appData.useTelnet) {
1705 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1708 /* TCP socket interface differs somewhat between
1709 Unix and NT; handle details in the front end.
1711 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1715 void EscapeExpand(char *p, char *q)
1716 { // [HGM] initstring: routine to shape up string arguments
1717 while(*p++ = *q++) if(p[-1] == '\\')
1719 case 'n': p[-1] = '\n'; break;
1720 case 'r': p[-1] = '\r'; break;
1721 case 't': p[-1] = '\t'; break;
1722 case '\\': p[-1] = '\\'; break;
1723 case 0: *p = 0; return;
1724 default: p[-1] = q[-1]; break;
1729 show_bytes(fp, buf, count)
1735 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1736 fprintf(fp, "\\%03o", *buf & 0xff);
1745 /* Returns an errno value */
1747 OutputMaybeTelnet(pr, message, count, outError)
1753 char buf[8192], *p, *q, *buflim;
1754 int left, newcount, outcount;
1756 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1757 *appData.gateway != NULLCHAR) {
1758 if (appData.debugMode) {
1759 fprintf(debugFP, ">ICS: ");
1760 show_bytes(debugFP, message, count);
1761 fprintf(debugFP, "\n");
1763 return OutputToProcess(pr, message, count, outError);
1766 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1773 if (appData.debugMode) {
1774 fprintf(debugFP, ">ICS: ");
1775 show_bytes(debugFP, buf, newcount);
1776 fprintf(debugFP, "\n");
1778 outcount = OutputToProcess(pr, buf, newcount, outError);
1779 if (outcount < newcount) return -1; /* to be sure */
1786 } else if (((unsigned char) *p) == TN_IAC) {
1787 *q++ = (char) TN_IAC;
1794 if (appData.debugMode) {
1795 fprintf(debugFP, ">ICS: ");
1796 show_bytes(debugFP, buf, newcount);
1797 fprintf(debugFP, "\n");
1799 outcount = OutputToProcess(pr, buf, newcount, outError);
1800 if (outcount < newcount) return -1; /* to be sure */
1805 read_from_player(isr, closure, message, count, error)
1812 int outError, outCount;
1813 static int gotEof = 0;
1815 /* Pass data read from player on to ICS */
1818 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1819 if (outCount < count) {
1820 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1822 } else if (count < 0) {
1823 RemoveInputSource(isr);
1824 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1825 } else if (gotEof++ > 0) {
1826 RemoveInputSource(isr);
1827 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1833 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1834 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1835 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1836 SendToICS("date\n");
1837 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1840 /* added routine for printf style output to ics */
1841 void ics_printf(char *format, ...)
1843 char buffer[MSG_SIZ];
1846 va_start(args, format);
1847 vsnprintf(buffer, sizeof(buffer), format, args);
1848 buffer[sizeof(buffer)-1] = '\0';
1857 int count, outCount, outError;
1859 if (icsPR == NULL) return;
1862 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1863 if (outCount < count) {
1864 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1868 /* This is used for sending logon scripts to the ICS. Sending
1869 without a delay causes problems when using timestamp on ICC
1870 (at least on my machine). */
1872 SendToICSDelayed(s,msdelay)
1876 int count, outCount, outError;
1878 if (icsPR == NULL) return;
1881 if (appData.debugMode) {
1882 fprintf(debugFP, ">ICS: ");
1883 show_bytes(debugFP, s, count);
1884 fprintf(debugFP, "\n");
1886 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1888 if (outCount < count) {
1889 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1894 /* Remove all highlighting escape sequences in s
1895 Also deletes any suffix starting with '('
1898 StripHighlightAndTitle(s)
1901 static char retbuf[MSG_SIZ];
1904 while (*s != NULLCHAR) {
1905 while (*s == '\033') {
1906 while (*s != NULLCHAR && !isalpha(*s)) s++;
1907 if (*s != NULLCHAR) s++;
1909 while (*s != NULLCHAR && *s != '\033') {
1910 if (*s == '(' || *s == '[') {
1921 /* Remove all highlighting escape sequences in s */
1926 static char retbuf[MSG_SIZ];
1929 while (*s != NULLCHAR) {
1930 while (*s == '\033') {
1931 while (*s != NULLCHAR && !isalpha(*s)) s++;
1932 if (*s != NULLCHAR) s++;
1934 while (*s != NULLCHAR && *s != '\033') {
1942 char *variantNames[] = VARIANT_NAMES;
1947 return variantNames[v];
1951 /* Identify a variant from the strings the chess servers use or the
1952 PGN Variant tag names we use. */
1959 VariantClass v = VariantNormal;
1960 int i, found = FALSE;
1966 /* [HGM] skip over optional board-size prefixes */
1967 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1968 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1969 while( *e++ != '_');
1972 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1976 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1977 if (StrCaseStr(e, variantNames[i])) {
1978 v = (VariantClass) i;
1985 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1986 || StrCaseStr(e, "wild/fr")
1987 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1988 v = VariantFischeRandom;
1989 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1990 (i = 1, p = StrCaseStr(e, "w"))) {
1992 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1999 case 0: /* FICS only, actually */
2001 /* Castling legal even if K starts on d-file */
2002 v = VariantWildCastle;
2007 /* Castling illegal even if K & R happen to start in
2008 normal positions. */
2009 v = VariantNoCastle;
2022 /* Castling legal iff K & R start in normal positions */
2028 /* Special wilds for position setup; unclear what to do here */
2029 v = VariantLoadable;
2032 /* Bizarre ICC game */
2033 v = VariantTwoKings;
2036 v = VariantKriegspiel;
2042 v = VariantFischeRandom;
2045 v = VariantCrazyhouse;
2048 v = VariantBughouse;
2054 /* Not quite the same as FICS suicide! */
2055 v = VariantGiveaway;
2061 v = VariantShatranj;
2064 /* Temporary names for future ICC types. The name *will* change in
2065 the next xboard/WinBoard release after ICC defines it. */
2103 v = VariantCapablanca;
2106 v = VariantKnightmate;
2112 v = VariantCylinder;
2118 v = VariantCapaRandom;
2121 v = VariantBerolina;
2133 /* Found "wild" or "w" in the string but no number;
2134 must assume it's normal chess. */
2138 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2139 if( (len > MSG_SIZ) && appData.debugMode )
2140 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2142 DisplayError(buf, 0);
2148 if (appData.debugMode) {
2149 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2150 e, wnum, VariantName(v));
2155 static int leftover_start = 0, leftover_len = 0;
2156 char star_match[STAR_MATCH_N][MSG_SIZ];
2158 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2159 advance *index beyond it, and set leftover_start to the new value of
2160 *index; else return FALSE. If pattern contains the character '*', it
2161 matches any sequence of characters not containing '\r', '\n', or the
2162 character following the '*' (if any), and the matched sequence(s) are
2163 copied into star_match.
2166 looking_at(buf, index, pattern)
2171 char *bufp = &buf[*index], *patternp = pattern;
2173 char *matchp = star_match[0];
2176 if (*patternp == NULLCHAR) {
2177 *index = leftover_start = bufp - buf;
2181 if (*bufp == NULLCHAR) return FALSE;
2182 if (*patternp == '*') {
2183 if (*bufp == *(patternp + 1)) {
2185 matchp = star_match[++star_count];
2189 } else if (*bufp == '\n' || *bufp == '\r') {
2191 if (*patternp == NULLCHAR)
2196 *matchp++ = *bufp++;
2200 if (*patternp != *bufp) return FALSE;
2207 SendToPlayer(data, length)
2211 int error, outCount;
2212 outCount = OutputToProcess(NoProc, data, length, &error);
2213 if (outCount < length) {
2214 DisplayFatalError(_("Error writing to display"), error, 1);
2219 PackHolding(packed, holding)
2231 switch (runlength) {
2242 sprintf(q, "%d", runlength);
2254 /* Telnet protocol requests from the front end */
2256 TelnetRequest(ddww, option)
2257 unsigned char ddww, option;
2259 unsigned char msg[3];
2260 int outCount, outError;
2262 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2264 if (appData.debugMode) {
2265 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2281 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2290 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2293 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2298 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2300 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2307 if (!appData.icsActive) return;
2308 TelnetRequest(TN_DO, TN_ECHO);
2314 if (!appData.icsActive) return;
2315 TelnetRequest(TN_DONT, TN_ECHO);
2319 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2321 /* put the holdings sent to us by the server on the board holdings area */
2322 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2326 if(gameInfo.holdingsWidth < 2) return;
2327 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2328 return; // prevent overwriting by pre-board holdings
2330 if( (int)lowestPiece >= BlackPawn ) {
2333 holdingsStartRow = BOARD_HEIGHT-1;
2336 holdingsColumn = BOARD_WIDTH-1;
2337 countsColumn = BOARD_WIDTH-2;
2338 holdingsStartRow = 0;
2342 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2343 board[i][holdingsColumn] = EmptySquare;
2344 board[i][countsColumn] = (ChessSquare) 0;
2346 while( (p=*holdings++) != NULLCHAR ) {
2347 piece = CharToPiece( ToUpper(p) );
2348 if(piece == EmptySquare) continue;
2349 /*j = (int) piece - (int) WhitePawn;*/
2350 j = PieceToNumber(piece);
2351 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2352 if(j < 0) continue; /* should not happen */
2353 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2354 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2355 board[holdingsStartRow+j*direction][countsColumn]++;
2361 VariantSwitch(Board board, VariantClass newVariant)
2363 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2364 static Board oldBoard;
2366 startedFromPositionFile = FALSE;
2367 if(gameInfo.variant == newVariant) return;
2369 /* [HGM] This routine is called each time an assignment is made to
2370 * gameInfo.variant during a game, to make sure the board sizes
2371 * are set to match the new variant. If that means adding or deleting
2372 * holdings, we shift the playing board accordingly
2373 * This kludge is needed because in ICS observe mode, we get boards
2374 * of an ongoing game without knowing the variant, and learn about the
2375 * latter only later. This can be because of the move list we requested,
2376 * in which case the game history is refilled from the beginning anyway,
2377 * but also when receiving holdings of a crazyhouse game. In the latter
2378 * case we want to add those holdings to the already received position.
2382 if (appData.debugMode) {
2383 fprintf(debugFP, "Switch board from %s to %s\n",
2384 VariantName(gameInfo.variant), VariantName(newVariant));
2385 setbuf(debugFP, NULL);
2387 shuffleOpenings = 0; /* [HGM] shuffle */
2388 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2392 newWidth = 9; newHeight = 9;
2393 gameInfo.holdingsSize = 7;
2394 case VariantBughouse:
2395 case VariantCrazyhouse:
2396 newHoldingsWidth = 2; break;
2400 newHoldingsWidth = 2;
2401 gameInfo.holdingsSize = 8;
2404 case VariantCapablanca:
2405 case VariantCapaRandom:
2408 newHoldingsWidth = gameInfo.holdingsSize = 0;
2411 if(newWidth != gameInfo.boardWidth ||
2412 newHeight != gameInfo.boardHeight ||
2413 newHoldingsWidth != gameInfo.holdingsWidth ) {
2415 /* shift position to new playing area, if needed */
2416 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2417 for(i=0; i<BOARD_HEIGHT; i++)
2418 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2419 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2421 for(i=0; i<newHeight; i++) {
2422 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2423 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2425 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2426 for(i=0; i<BOARD_HEIGHT; i++)
2427 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2428 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2431 gameInfo.boardWidth = newWidth;
2432 gameInfo.boardHeight = newHeight;
2433 gameInfo.holdingsWidth = newHoldingsWidth;
2434 gameInfo.variant = newVariant;
2435 InitDrawingSizes(-2, 0);
2436 } else gameInfo.variant = newVariant;
2437 CopyBoard(oldBoard, board); // remember correctly formatted board
2438 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2439 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2442 static int loggedOn = FALSE;
2444 /*-- Game start info cache: --*/
2446 char gs_kind[MSG_SIZ];
2447 static char player1Name[128] = "";
2448 static char player2Name[128] = "";
2449 static char cont_seq[] = "\n\\ ";
2450 static int player1Rating = -1;
2451 static int player2Rating = -1;
2452 /*----------------------------*/
2454 ColorClass curColor = ColorNormal;
2455 int suppressKibitz = 0;
2458 Boolean soughtPending = FALSE;
2459 Boolean seekGraphUp;
2460 #define MAX_SEEK_ADS 200
2462 char *seekAdList[MAX_SEEK_ADS];
2463 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2464 float tcList[MAX_SEEK_ADS];
2465 char colorList[MAX_SEEK_ADS];
2466 int nrOfSeekAds = 0;
2467 int minRating = 1010, maxRating = 2800;
2468 int hMargin = 10, vMargin = 20, h, w;
2469 extern int squareSize, lineGap;
2474 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2475 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2476 if(r < minRating+100 && r >=0 ) r = minRating+100;
2477 if(r > maxRating) r = maxRating;
2478 if(tc < 1.) tc = 1.;
2479 if(tc > 95.) tc = 95.;
2480 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2481 y = ((double)r - minRating)/(maxRating - minRating)
2482 * (h-vMargin-squareSize/8-1) + vMargin;
2483 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2484 if(strstr(seekAdList[i], " u ")) color = 1;
2485 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2486 !strstr(seekAdList[i], "bullet") &&
2487 !strstr(seekAdList[i], "blitz") &&
2488 !strstr(seekAdList[i], "standard") ) color = 2;
2489 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2490 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2494 AddAd(char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2496 char buf[MSG_SIZ], *ext = "";
2497 VariantClass v = StringToVariant(type);
2498 if(strstr(type, "wild")) {
2499 ext = type + 4; // append wild number
2500 if(v == VariantFischeRandom) type = "chess960"; else
2501 if(v == VariantLoadable) type = "setup"; else
2502 type = VariantName(v);
2504 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2505 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2506 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2507 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2508 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2509 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2510 seekNrList[nrOfSeekAds] = nr;
2511 zList[nrOfSeekAds] = 0;
2512 seekAdList[nrOfSeekAds++] = StrSave(buf);
2513 if(plot) PlotSeekAd(nrOfSeekAds-1);
2520 int x = xList[i], y = yList[i], d=squareSize/4, k;
2521 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2522 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2523 // now replot every dot that overlapped
2524 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2525 int xx = xList[k], yy = yList[k];
2526 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2527 DrawSeekDot(xx, yy, colorList[k]);
2532 RemoveSeekAd(int nr)
2535 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2537 if(seekAdList[i]) free(seekAdList[i]);
2538 seekAdList[i] = seekAdList[--nrOfSeekAds];
2539 seekNrList[i] = seekNrList[nrOfSeekAds];
2540 ratingList[i] = ratingList[nrOfSeekAds];
2541 colorList[i] = colorList[nrOfSeekAds];
2542 tcList[i] = tcList[nrOfSeekAds];
2543 xList[i] = xList[nrOfSeekAds];
2544 yList[i] = yList[nrOfSeekAds];
2545 zList[i] = zList[nrOfSeekAds];
2546 seekAdList[nrOfSeekAds] = NULL;
2552 MatchSoughtLine(char *line)
2554 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2555 int nr, base, inc, u=0; char dummy;
2557 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2558 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2560 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2561 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2562 // match: compact and save the line
2563 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2573 if(!seekGraphUp) return FALSE;
2574 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2575 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2577 DrawSeekBackground(0, 0, w, h);
2578 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2579 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2580 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2581 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2583 DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2586 snprintf(buf, MSG_SIZ, "%d", i);
2587 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2590 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2591 for(i=1; i<100; i+=(i<10?1:5)) {
2592 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2593 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2594 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2596 snprintf(buf, MSG_SIZ, "%d", i);
2597 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2600 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2604 int SeekGraphClick(ClickType click, int x, int y, int moving)
2606 static int lastDown = 0, displayed = 0, lastSecond;
2607 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2608 if(click == Release || moving) return FALSE;
2610 soughtPending = TRUE;
2611 SendToICS(ics_prefix);
2612 SendToICS("sought\n"); // should this be "sought all"?
2613 } else { // issue challenge based on clicked ad
2614 int dist = 10000; int i, closest = 0, second = 0;
2615 for(i=0; i<nrOfSeekAds; i++) {
2616 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2617 if(d < dist) { dist = d; closest = i; }
2618 second += (d - zList[i] < 120); // count in-range ads
2619 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2623 second = (second > 1);
2624 if(displayed != closest || second != lastSecond) {
2625 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2626 lastSecond = second; displayed = closest;
2628 if(click == Press) {
2629 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2632 } // on press 'hit', only show info
2633 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2634 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2635 SendToICS(ics_prefix);
2637 return TRUE; // let incoming board of started game pop down the graph
2638 } else if(click == Release) { // release 'miss' is ignored
2639 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2640 if(moving == 2) { // right up-click
2641 nrOfSeekAds = 0; // refresh graph
2642 soughtPending = TRUE;
2643 SendToICS(ics_prefix);
2644 SendToICS("sought\n"); // should this be "sought all"?
2647 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2648 // press miss or release hit 'pop down' seek graph
2649 seekGraphUp = FALSE;
2650 DrawPosition(TRUE, NULL);
2656 read_from_ics(isr, closure, data, count, error)
2663 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2664 #define STARTED_NONE 0
2665 #define STARTED_MOVES 1
2666 #define STARTED_BOARD 2
2667 #define STARTED_OBSERVE 3
2668 #define STARTED_HOLDINGS 4
2669 #define STARTED_CHATTER 5
2670 #define STARTED_COMMENT 6
2671 #define STARTED_MOVES_NOHIDE 7
2673 static int started = STARTED_NONE;
2674 static char parse[20000];
2675 static int parse_pos = 0;
2676 static char buf[BUF_SIZE + 1];
2677 static int firstTime = TRUE, intfSet = FALSE;
2678 static ColorClass prevColor = ColorNormal;
2679 static int savingComment = FALSE;
2680 static int cmatch = 0; // continuation sequence match
2687 int backup; /* [DM] For zippy color lines */
2689 char talker[MSG_SIZ]; // [HGM] chat
2692 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2694 if (appData.debugMode) {
2696 fprintf(debugFP, "<ICS: ");
2697 show_bytes(debugFP, data, count);
2698 fprintf(debugFP, "\n");
2702 if (appData.debugMode) { int f = forwardMostMove;
2703 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2704 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2705 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2708 /* If last read ended with a partial line that we couldn't parse,
2709 prepend it to the new read and try again. */
2710 if (leftover_len > 0) {
2711 for (i=0; i<leftover_len; i++)
2712 buf[i] = buf[leftover_start + i];
2715 /* copy new characters into the buffer */
2716 bp = buf + leftover_len;
2717 buf_len=leftover_len;
2718 for (i=0; i<count; i++)
2721 if (data[i] == '\r')
2724 // join lines split by ICS?
2725 if (!appData.noJoin)
2728 Joining just consists of finding matches against the
2729 continuation sequence, and discarding that sequence
2730 if found instead of copying it. So, until a match
2731 fails, there's nothing to do since it might be the
2732 complete sequence, and thus, something we don't want
2735 if (data[i] == cont_seq[cmatch])
2738 if (cmatch == strlen(cont_seq))
2740 cmatch = 0; // complete match. just reset the counter
2743 it's possible for the ICS to not include the space
2744 at the end of the last word, making our [correct]
2745 join operation fuse two separate words. the server
2746 does this when the space occurs at the width setting.
2748 if (!buf_len || buf[buf_len-1] != ' ')
2759 match failed, so we have to copy what matched before
2760 falling through and copying this character. In reality,
2761 this will only ever be just the newline character, but
2762 it doesn't hurt to be precise.
2764 strncpy(bp, cont_seq, cmatch);
2776 buf[buf_len] = NULLCHAR;
2777 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2782 while (i < buf_len) {
2783 /* Deal with part of the TELNET option negotiation
2784 protocol. We refuse to do anything beyond the
2785 defaults, except that we allow the WILL ECHO option,
2786 which ICS uses to turn off password echoing when we are
2787 directly connected to it. We reject this option
2788 if localLineEditing mode is on (always on in xboard)
2789 and we are talking to port 23, which might be a real
2790 telnet server that will try to keep WILL ECHO on permanently.
2792 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2793 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2794 unsigned char option;
2796 switch ((unsigned char) buf[++i]) {
2798 if (appData.debugMode)
2799 fprintf(debugFP, "\n<WILL ");
2800 switch (option = (unsigned char) buf[++i]) {
2802 if (appData.debugMode)
2803 fprintf(debugFP, "ECHO ");
2804 /* Reply only if this is a change, according
2805 to the protocol rules. */
2806 if (remoteEchoOption) break;
2807 if (appData.localLineEditing &&
2808 atoi(appData.icsPort) == TN_PORT) {
2809 TelnetRequest(TN_DONT, TN_ECHO);
2812 TelnetRequest(TN_DO, TN_ECHO);
2813 remoteEchoOption = TRUE;
2817 if (appData.debugMode)
2818 fprintf(debugFP, "%d ", option);
2819 /* Whatever this is, we don't want it. */
2820 TelnetRequest(TN_DONT, option);
2825 if (appData.debugMode)
2826 fprintf(debugFP, "\n<WONT ");
2827 switch (option = (unsigned char) buf[++i]) {
2829 if (appData.debugMode)
2830 fprintf(debugFP, "ECHO ");
2831 /* Reply only if this is a change, according
2832 to the protocol rules. */
2833 if (!remoteEchoOption) break;
2835 TelnetRequest(TN_DONT, TN_ECHO);
2836 remoteEchoOption = FALSE;
2839 if (appData.debugMode)
2840 fprintf(debugFP, "%d ", (unsigned char) option);
2841 /* Whatever this is, it must already be turned
2842 off, because we never agree to turn on
2843 anything non-default, so according to the
2844 protocol rules, we don't reply. */
2849 if (appData.debugMode)
2850 fprintf(debugFP, "\n<DO ");
2851 switch (option = (unsigned char) buf[++i]) {
2853 /* Whatever this is, we refuse to do it. */
2854 if (appData.debugMode)
2855 fprintf(debugFP, "%d ", option);
2856 TelnetRequest(TN_WONT, option);
2861 if (appData.debugMode)
2862 fprintf(debugFP, "\n<DONT ");
2863 switch (option = (unsigned char) buf[++i]) {
2865 if (appData.debugMode)
2866 fprintf(debugFP, "%d ", option);
2867 /* Whatever this is, we are already not doing
2868 it, because we never agree to do anything
2869 non-default, so according to the protocol
2870 rules, we don't reply. */
2875 if (appData.debugMode)
2876 fprintf(debugFP, "\n<IAC ");
2877 /* Doubled IAC; pass it through */
2881 if (appData.debugMode)
2882 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2883 /* Drop all other telnet commands on the floor */
2886 if (oldi > next_out)
2887 SendToPlayer(&buf[next_out], oldi - next_out);
2893 /* OK, this at least will *usually* work */
2894 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2898 if (loggedOn && !intfSet) {
2899 if (ics_type == ICS_ICC) {
2900 snprintf(str, MSG_SIZ,
2901 "/set-quietly interface %s\n/set-quietly style 12\n",
2903 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2904 strcat(str, "/set-2 51 1\n/set seek 1\n");
2905 } else if (ics_type == ICS_CHESSNET) {
2906 snprintf(str, MSG_SIZ, "/style 12\n");
2908 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2909 strcat(str, programVersion);
2910 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2911 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2912 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2914 strcat(str, "$iset nohighlight 1\n");
2916 strcat(str, "$iset lock 1\n$style 12\n");
2919 NotifyFrontendLogin();
2923 if (started == STARTED_COMMENT) {
2924 /* Accumulate characters in comment */
2925 parse[parse_pos++] = buf[i];
2926 if (buf[i] == '\n') {
2927 parse[parse_pos] = NULLCHAR;
2928 if(chattingPartner>=0) {
2930 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2931 OutputChatMessage(chattingPartner, mess);
2932 chattingPartner = -1;
2933 next_out = i+1; // [HGM] suppress printing in ICS window
2935 if(!suppressKibitz) // [HGM] kibitz
2936 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2937 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2938 int nrDigit = 0, nrAlph = 0, j;
2939 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2940 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2941 parse[parse_pos] = NULLCHAR;
2942 // try to be smart: if it does not look like search info, it should go to
2943 // ICS interaction window after all, not to engine-output window.
2944 for(j=0; j<parse_pos; j++) { // count letters and digits
2945 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2946 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2947 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2949 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2950 int depth=0; float score;
2951 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2952 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2953 pvInfoList[forwardMostMove-1].depth = depth;
2954 pvInfoList[forwardMostMove-1].score = 100*score;
2956 OutputKibitz(suppressKibitz, parse);
2959 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2960 SendToPlayer(tmp, strlen(tmp));
2962 next_out = i+1; // [HGM] suppress printing in ICS window
2964 started = STARTED_NONE;
2966 /* Don't match patterns against characters in comment */
2971 if (started == STARTED_CHATTER) {
2972 if (buf[i] != '\n') {
2973 /* Don't match patterns against characters in chatter */
2977 started = STARTED_NONE;
2978 if(suppressKibitz) next_out = i+1;
2981 /* Kludge to deal with rcmd protocol */
2982 if (firstTime && looking_at(buf, &i, "\001*")) {
2983 DisplayFatalError(&buf[1], 0, 1);
2989 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2992 if (appData.debugMode)
2993 fprintf(debugFP, "ics_type %d\n", ics_type);
2996 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2997 ics_type = ICS_FICS;
2999 if (appData.debugMode)
3000 fprintf(debugFP, "ics_type %d\n", ics_type);
3003 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
3004 ics_type = ICS_CHESSNET;
3006 if (appData.debugMode)
3007 fprintf(debugFP, "ics_type %d\n", ics_type);
3012 (looking_at(buf, &i, "\"*\" is *a registered name") ||
3013 looking_at(buf, &i, "Logging you in as \"*\"") ||
3014 looking_at(buf, &i, "will be \"*\""))) {
3015 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
3019 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
3021 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
3022 DisplayIcsInteractionTitle(buf);
3023 have_set_title = TRUE;
3026 /* skip finger notes */
3027 if (started == STARTED_NONE &&
3028 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
3029 (buf[i] == '1' && buf[i+1] == '0')) &&
3030 buf[i+2] == ':' && buf[i+3] == ' ') {
3031 started = STARTED_CHATTER;
3037 // [HGM] seekgraph: recognize sought lines and end-of-sought message
3038 if(appData.seekGraph) {
3039 if(soughtPending && MatchSoughtLine(buf+i)) {
3040 i = strstr(buf+i, "rated") - buf;
3041 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3042 next_out = leftover_start = i;
3043 started = STARTED_CHATTER;
3044 suppressKibitz = TRUE;
3047 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
3048 && looking_at(buf, &i, "* ads displayed")) {
3049 soughtPending = FALSE;
3054 if(appData.autoRefresh) {
3055 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
3056 int s = (ics_type == ICS_ICC); // ICC format differs
3058 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3059 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3060 looking_at(buf, &i, "*% "); // eat prompt
3061 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3062 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3063 next_out = i; // suppress
3066 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3067 char *p = star_match[0];
3069 if(seekGraphUp) RemoveSeekAd(atoi(p));
3070 while(*p && *p++ != ' '); // next
3072 looking_at(buf, &i, "*% "); // eat prompt
3073 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3080 /* skip formula vars */
3081 if (started == STARTED_NONE &&
3082 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3083 started = STARTED_CHATTER;
3088 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3089 if (appData.autoKibitz && started == STARTED_NONE &&
3090 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3091 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3092 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3093 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3094 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3095 suppressKibitz = TRUE;
3096 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3098 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3099 && (gameMode == IcsPlayingWhite)) ||
3100 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3101 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3102 started = STARTED_CHATTER; // own kibitz we simply discard
3104 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3105 parse_pos = 0; parse[0] = NULLCHAR;
3106 savingComment = TRUE;
3107 suppressKibitz = gameMode != IcsObserving ? 2 :
3108 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3112 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3113 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3114 && atoi(star_match[0])) {
3115 // suppress the acknowledgements of our own autoKibitz
3117 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3118 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3119 SendToPlayer(star_match[0], strlen(star_match[0]));
3120 if(looking_at(buf, &i, "*% ")) // eat prompt
3121 suppressKibitz = FALSE;
3125 } // [HGM] kibitz: end of patch
3127 // [HGM] chat: intercept tells by users for which we have an open chat window
3129 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3130 looking_at(buf, &i, "* whispers:") ||
3131 looking_at(buf, &i, "* kibitzes:") ||
3132 looking_at(buf, &i, "* shouts:") ||
3133 looking_at(buf, &i, "* c-shouts:") ||
3134 looking_at(buf, &i, "--> * ") ||
3135 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3136 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3137 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3138 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3140 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3141 chattingPartner = -1;
3143 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3144 for(p=0; p<MAX_CHAT; p++) {
3145 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3146 talker[0] = '['; strcat(talker, "] ");
3147 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3148 chattingPartner = p; break;
3151 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3152 for(p=0; p<MAX_CHAT; p++) {
3153 if(!strcmp("kibitzes", chatPartner[p])) {
3154 talker[0] = '['; strcat(talker, "] ");
3155 chattingPartner = p; break;
3158 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3159 for(p=0; p<MAX_CHAT; p++) {
3160 if(!strcmp("whispers", chatPartner[p])) {
3161 talker[0] = '['; strcat(talker, "] ");
3162 chattingPartner = p; break;
3165 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3166 if(buf[i-8] == '-' && buf[i-3] == 't')
3167 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3168 if(!strcmp("c-shouts", chatPartner[p])) {
3169 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3170 chattingPartner = p; break;
3173 if(chattingPartner < 0)
3174 for(p=0; p<MAX_CHAT; p++) {
3175 if(!strcmp("shouts", chatPartner[p])) {
3176 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3177 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3178 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3179 chattingPartner = p; break;
3183 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3184 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3185 talker[0] = 0; Colorize(ColorTell, FALSE);
3186 chattingPartner = p; break;
3188 if(chattingPartner<0) i = oldi; else {
3189 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3190 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3191 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3192 started = STARTED_COMMENT;
3193 parse_pos = 0; parse[0] = NULLCHAR;
3194 savingComment = 3 + chattingPartner; // counts as TRUE
3195 suppressKibitz = TRUE;
3198 } // [HGM] chat: end of patch
3201 if (appData.zippyTalk || appData.zippyPlay) {
3202 /* [DM] Backup address for color zippy lines */
3204 if (loggedOn == TRUE)
3205 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3206 (appData.zippyPlay && ZippyMatch(buf, &backup)));
3208 } // [DM] 'else { ' deleted
3210 /* Regular tells and says */
3211 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3212 looking_at(buf, &i, "* (your partner) tells you: ") ||
3213 looking_at(buf, &i, "* says: ") ||
3214 /* Don't color "message" or "messages" output */
3215 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3216 looking_at(buf, &i, "*. * at *:*: ") ||
3217 looking_at(buf, &i, "--* (*:*): ") ||
3218 /* Message notifications (same color as tells) */
3219 looking_at(buf, &i, "* has left a message ") ||
3220 looking_at(buf, &i, "* just sent you a message:\n") ||
3221 /* Whispers and kibitzes */
3222 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3223 looking_at(buf, &i, "* kibitzes: ") ||
3225 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3227 if (tkind == 1 && strchr(star_match[0], ':')) {
3228 /* Avoid "tells you:" spoofs in channels */
3231 if (star_match[0][0] == NULLCHAR ||
3232 strchr(star_match[0], ' ') ||
3233 (tkind == 3 && strchr(star_match[1], ' '))) {
3234 /* Reject bogus matches */
3237 if (appData.colorize) {
3238 if (oldi > next_out) {
3239 SendToPlayer(&buf[next_out], oldi - next_out);
3244 Colorize(ColorTell, FALSE);
3245 curColor = ColorTell;
3248 Colorize(ColorKibitz, FALSE);
3249 curColor = ColorKibitz;
3252 p = strrchr(star_match[1], '(');
3259 Colorize(ColorChannel1, FALSE);
3260 curColor = ColorChannel1;
3262 Colorize(ColorChannel, FALSE);
3263 curColor = ColorChannel;
3267 curColor = ColorNormal;
3271 if (started == STARTED_NONE && appData.autoComment &&
3272 (gameMode == IcsObserving ||
3273 gameMode == IcsPlayingWhite ||
3274 gameMode == IcsPlayingBlack)) {
3275 parse_pos = i - oldi;
3276 memcpy(parse, &buf[oldi], parse_pos);
3277 parse[parse_pos] = NULLCHAR;
3278 started = STARTED_COMMENT;
3279 savingComment = TRUE;
3281 started = STARTED_CHATTER;
3282 savingComment = FALSE;
3289 if (looking_at(buf, &i, "* s-shouts: ") ||
3290 looking_at(buf, &i, "* c-shouts: ")) {
3291 if (appData.colorize) {
3292 if (oldi > next_out) {
3293 SendToPlayer(&buf[next_out], oldi - next_out);
3296 Colorize(ColorSShout, FALSE);
3297 curColor = ColorSShout;
3300 started = STARTED_CHATTER;
3304 if (looking_at(buf, &i, "--->")) {
3309 if (looking_at(buf, &i, "* shouts: ") ||
3310 looking_at(buf, &i, "--> ")) {
3311 if (appData.colorize) {
3312 if (oldi > next_out) {
3313 SendToPlayer(&buf[next_out], oldi - next_out);
3316 Colorize(ColorShout, FALSE);
3317 curColor = ColorShout;
3320 started = STARTED_CHATTER;
3324 if (looking_at( buf, &i, "Challenge:")) {
3325 if (appData.colorize) {
3326 if (oldi > next_out) {
3327 SendToPlayer(&buf[next_out], oldi - next_out);
3330 Colorize(ColorChallenge, FALSE);
3331 curColor = ColorChallenge;
3337 if (looking_at(buf, &i, "* offers you") ||
3338 looking_at(buf, &i, "* offers to be") ||
3339 looking_at(buf, &i, "* would like to") ||
3340 looking_at(buf, &i, "* requests to") ||
3341 looking_at(buf, &i, "Your opponent offers") ||
3342 looking_at(buf, &i, "Your opponent requests")) {
3344 if (appData.colorize) {
3345 if (oldi > next_out) {
3346 SendToPlayer(&buf[next_out], oldi - next_out);
3349 Colorize(ColorRequest, FALSE);
3350 curColor = ColorRequest;
3355 if (looking_at(buf, &i, "* (*) seeking")) {
3356 if (appData.colorize) {
3357 if (oldi > next_out) {
3358 SendToPlayer(&buf[next_out], oldi - next_out);
3361 Colorize(ColorSeek, FALSE);
3362 curColor = ColorSeek;
3367 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3369 if (looking_at(buf, &i, "\\ ")) {
3370 if (prevColor != ColorNormal) {
3371 if (oldi > next_out) {
3372 SendToPlayer(&buf[next_out], oldi - next_out);
3375 Colorize(prevColor, TRUE);
3376 curColor = prevColor;
3378 if (savingComment) {
3379 parse_pos = i - oldi;
3380 memcpy(parse, &buf[oldi], parse_pos);
3381 parse[parse_pos] = NULLCHAR;
3382 started = STARTED_COMMENT;
3383 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3384 chattingPartner = savingComment - 3; // kludge to remember the box
3386 started = STARTED_CHATTER;
3391 if (looking_at(buf, &i, "Black Strength :") ||
3392 looking_at(buf, &i, "<<< style 10 board >>>") ||
3393 looking_at(buf, &i, "<10>") ||
3394 looking_at(buf, &i, "#@#")) {
3395 /* Wrong board style */
3397 SendToICS(ics_prefix);
3398 SendToICS("set style 12\n");
3399 SendToICS(ics_prefix);
3400 SendToICS("refresh\n");
3404 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3406 have_sent_ICS_logon = 1;
3410 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3411 (looking_at(buf, &i, "\n<12> ") ||
3412 looking_at(buf, &i, "<12> "))) {
3414 if (oldi > next_out) {
3415 SendToPlayer(&buf[next_out], oldi - next_out);
3418 started = STARTED_BOARD;
3423 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3424 looking_at(buf, &i, "<b1> ")) {
3425 if (oldi > next_out) {
3426 SendToPlayer(&buf[next_out], oldi - next_out);
3429 started = STARTED_HOLDINGS;
3434 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3436 /* Header for a move list -- first line */
3438 switch (ics_getting_history) {
3442 case BeginningOfGame:
3443 /* User typed "moves" or "oldmoves" while we
3444 were idle. Pretend we asked for these
3445 moves and soak them up so user can step
3446 through them and/or save them.
3449 gameMode = IcsObserving;
3452 ics_getting_history = H_GOT_UNREQ_HEADER;
3454 case EditGame: /*?*/
3455 case EditPosition: /*?*/
3456 /* Should above feature work in these modes too? */
3457 /* For now it doesn't */
3458 ics_getting_history = H_GOT_UNWANTED_HEADER;
3461 ics_getting_history = H_GOT_UNWANTED_HEADER;
3466 /* Is this the right one? */
3467 if (gameInfo.white && gameInfo.black &&
3468 strcmp(gameInfo.white, star_match[0]) == 0 &&
3469 strcmp(gameInfo.black, star_match[2]) == 0) {
3471 ics_getting_history = H_GOT_REQ_HEADER;
3474 case H_GOT_REQ_HEADER:
3475 case H_GOT_UNREQ_HEADER:
3476 case H_GOT_UNWANTED_HEADER:
3477 case H_GETTING_MOVES:
3478 /* Should not happen */
3479 DisplayError(_("Error gathering move list: two headers"), 0);
3480 ics_getting_history = H_FALSE;
3484 /* Save player ratings into gameInfo if needed */
3485 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3486 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3487 (gameInfo.whiteRating == -1 ||
3488 gameInfo.blackRating == -1)) {
3490 gameInfo.whiteRating = string_to_rating(star_match[1]);
3491 gameInfo.blackRating = string_to_rating(star_match[3]);
3492 if (appData.debugMode)
3493 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3494 gameInfo.whiteRating, gameInfo.blackRating);
3499 if (looking_at(buf, &i,
3500 "* * match, initial time: * minute*, increment: * second")) {
3501 /* Header for a move list -- second line */
3502 /* Initial board will follow if this is a wild game */
3503 if (gameInfo.event != NULL) free(gameInfo.event);
3504 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3505 gameInfo.event = StrSave(str);
3506 /* [HGM] we switched variant. Translate boards if needed. */
3507 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3511 if (looking_at(buf, &i, "Move ")) {
3512 /* Beginning of a move list */
3513 switch (ics_getting_history) {
3515 /* Normally should not happen */
3516 /* Maybe user hit reset while we were parsing */
3519 /* Happens if we are ignoring a move list that is not
3520 * the one we just requested. Common if the user
3521 * tries to observe two games without turning off
3524 case H_GETTING_MOVES:
3525 /* Should not happen */
3526 DisplayError(_("Error gathering move list: nested"), 0);
3527 ics_getting_history = H_FALSE;
3529 case H_GOT_REQ_HEADER:
3530 ics_getting_history = H_GETTING_MOVES;
3531 started = STARTED_MOVES;
3533 if (oldi > next_out) {
3534 SendToPlayer(&buf[next_out], oldi - next_out);
3537 case H_GOT_UNREQ_HEADER:
3538 ics_getting_history = H_GETTING_MOVES;
3539 started = STARTED_MOVES_NOHIDE;
3542 case H_GOT_UNWANTED_HEADER:
3543 ics_getting_history = H_FALSE;
3549 if (looking_at(buf, &i, "% ") ||
3550 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3551 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3552 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3553 soughtPending = FALSE;
3557 if(suppressKibitz) next_out = i;
3558 savingComment = FALSE;
3562 case STARTED_MOVES_NOHIDE:
3563 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3564 parse[parse_pos + i - oldi] = NULLCHAR;
3565 ParseGameHistory(parse);
3567 if (appData.zippyPlay && first.initDone) {
3568 FeedMovesToProgram(&first, forwardMostMove);
3569 if (gameMode == IcsPlayingWhite) {
3570 if (WhiteOnMove(forwardMostMove)) {
3571 if (first.sendTime) {
3572 if (first.useColors) {
3573 SendToProgram("black\n", &first);
3575 SendTimeRemaining(&first, TRUE);
3577 if (first.useColors) {
3578 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3580 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3581 first.maybeThinking = TRUE;
3583 if (first.usePlayother) {
3584 if (first.sendTime) {
3585 SendTimeRemaining(&first, TRUE);
3587 SendToProgram("playother\n", &first);
3593 } else if (gameMode == IcsPlayingBlack) {
3594 if (!WhiteOnMove(forwardMostMove)) {
3595 if (first.sendTime) {
3596 if (first.useColors) {
3597 SendToProgram("white\n", &first);
3599 SendTimeRemaining(&first, FALSE);
3601 if (first.useColors) {
3602 SendToProgram("black\n", &first);
3604 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3605 first.maybeThinking = TRUE;
3607 if (first.usePlayother) {
3608 if (first.sendTime) {
3609 SendTimeRemaining(&first, FALSE);
3611 SendToProgram("playother\n", &first);
3620 if (gameMode == IcsObserving && ics_gamenum == -1) {
3621 /* Moves came from oldmoves or moves command
3622 while we weren't doing anything else.
3624 currentMove = forwardMostMove;
3625 ClearHighlights();/*!!could figure this out*/
3626 flipView = appData.flipView;
3627 DrawPosition(TRUE, boards[currentMove]);
3628 DisplayBothClocks();
3629 snprintf(str, MSG_SIZ, "%s vs. %s",
3630 gameInfo.white, gameInfo.black);
3634 /* Moves were history of an active game */
3635 if (gameInfo.resultDetails != NULL) {
3636 free(gameInfo.resultDetails);
3637 gameInfo.resultDetails = NULL;
3640 HistorySet(parseList, backwardMostMove,
3641 forwardMostMove, currentMove-1);
3642 DisplayMove(currentMove - 1);
3643 if (started == STARTED_MOVES) next_out = i;
3644 started = STARTED_NONE;
3645 ics_getting_history = H_FALSE;
3648 case STARTED_OBSERVE:
3649 started = STARTED_NONE;
3650 SendToICS(ics_prefix);
3651 SendToICS("refresh\n");
3657 if(bookHit) { // [HGM] book: simulate book reply
3658 static char bookMove[MSG_SIZ]; // a bit generous?
3660 programStats.nodes = programStats.depth = programStats.time =
3661 programStats.score = programStats.got_only_move = 0;
3662 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3664 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3665 strcat(bookMove, bookHit);
3666 HandleMachineMove(bookMove, &first);
3671 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3672 started == STARTED_HOLDINGS ||
3673 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3674 /* Accumulate characters in move list or board */
3675 parse[parse_pos++] = buf[i];
3678 /* Start of game messages. Mostly we detect start of game
3679 when the first board image arrives. On some versions
3680 of the ICS, though, we need to do a "refresh" after starting
3681 to observe in order to get the current board right away. */
3682 if (looking_at(buf, &i, "Adding game * to observation list")) {
3683 started = STARTED_OBSERVE;
3687 /* Handle auto-observe */
3688 if (appData.autoObserve &&
3689 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3690 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3692 /* Choose the player that was highlighted, if any. */
3693 if (star_match[0][0] == '\033' ||
3694 star_match[1][0] != '\033') {
3695 player = star_match[0];
3697 player = star_match[2];
3699 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3700 ics_prefix, StripHighlightAndTitle(player));
3703 /* Save ratings from notify string */
3704 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3705 player1Rating = string_to_rating(star_match[1]);
3706 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3707 player2Rating = string_to_rating(star_match[3]);
3709 if (appData.debugMode)
3711 "Ratings from 'Game notification:' %s %d, %s %d\n",
3712 player1Name, player1Rating,
3713 player2Name, player2Rating);
3718 /* Deal with automatic examine mode after a game,
3719 and with IcsObserving -> IcsExamining transition */
3720 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3721 looking_at(buf, &i, "has made you an examiner of game *")) {
3723 int gamenum = atoi(star_match[0]);
3724 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3725 gamenum == ics_gamenum) {
3726 /* We were already playing or observing this game;
3727 no need to refetch history */
3728 gameMode = IcsExamining;
3730 pauseExamForwardMostMove = forwardMostMove;
3731 } else if (currentMove < forwardMostMove) {
3732 ForwardInner(forwardMostMove);
3735 /* I don't think this case really can happen */
3736 SendToICS(ics_prefix);
3737 SendToICS("refresh\n");
3742 /* Error messages */
3743 // if (ics_user_moved) {
3744 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3745 if (looking_at(buf, &i, "Illegal move") ||
3746 looking_at(buf, &i, "Not a legal move") ||
3747 looking_at(buf, &i, "Your king is in check") ||
3748 looking_at(buf, &i, "It isn't your turn") ||
3749 looking_at(buf, &i, "It is not your move")) {
3751 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3752 currentMove = forwardMostMove-1;
3753 DisplayMove(currentMove - 1); /* before DMError */
3754 DrawPosition(FALSE, boards[currentMove]);
3755 SwitchClocks(forwardMostMove-1); // [HGM] race
3756 DisplayBothClocks();
3758 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3764 if (looking_at(buf, &i, "still have time") ||
3765 looking_at(buf, &i, "not out of time") ||
3766 looking_at(buf, &i, "either player is out of time") ||
3767 looking_at(buf, &i, "has timeseal; checking")) {
3768 /* We must have called his flag a little too soon */
3769 whiteFlag = blackFlag = FALSE;
3773 if (looking_at(buf, &i, "added * seconds to") ||
3774 looking_at(buf, &i, "seconds were added to")) {
3775 /* Update the clocks */
3776 SendToICS(ics_prefix);
3777 SendToICS("refresh\n");
3781 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3782 ics_clock_paused = TRUE;
3787 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3788 ics_clock_paused = FALSE;
3793 /* Grab player ratings from the Creating: message.
3794 Note we have to check for the special case when
3795 the ICS inserts things like [white] or [black]. */
3796 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3797 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3799 0 player 1 name (not necessarily white)
3801 2 empty, white, or black (IGNORED)
3802 3 player 2 name (not necessarily black)
3805 The names/ratings are sorted out when the game
3806 actually starts (below).
3808 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3809 player1Rating = string_to_rating(star_match[1]);
3810 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3811 player2Rating = string_to_rating(star_match[4]);
3813 if (appData.debugMode)
3815 "Ratings from 'Creating:' %s %d, %s %d\n",
3816 player1Name, player1Rating,
3817 player2Name, player2Rating);
3822 /* Improved generic start/end-of-game messages */
3823 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3824 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3825 /* If tkind == 0: */
3826 /* star_match[0] is the game number */
3827 /* [1] is the white player's name */
3828 /* [2] is the black player's name */
3829 /* For end-of-game: */
3830 /* [3] is the reason for the game end */
3831 /* [4] is a PGN end game-token, preceded by " " */
3832 /* For start-of-game: */
3833 /* [3] begins with "Creating" or "Continuing" */
3834 /* [4] is " *" or empty (don't care). */
3835 int gamenum = atoi(star_match[0]);
3836 char *whitename, *blackname, *why, *endtoken;
3837 ChessMove endtype = EndOfFile;
3840 whitename = star_match[1];
3841 blackname = star_match[2];
3842 why = star_match[3];
3843 endtoken = star_match[4];
3845 whitename = star_match[1];
3846 blackname = star_match[3];
3847 why = star_match[5];
3848 endtoken = star_match[6];
3851 /* Game start messages */
3852 if (strncmp(why, "Creating ", 9) == 0 ||
3853 strncmp(why, "Continuing ", 11) == 0) {
3854 gs_gamenum = gamenum;
3855 safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3856 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3858 if (appData.zippyPlay) {
3859 ZippyGameStart(whitename, blackname);
3862 partnerBoardValid = FALSE; // [HGM] bughouse
3866 /* Game end messages */
3867 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3868 ics_gamenum != gamenum) {
3871 while (endtoken[0] == ' ') endtoken++;
3872 switch (endtoken[0]) {
3875 endtype = GameUnfinished;
3878 endtype = BlackWins;
3881 if (endtoken[1] == '/')
3882 endtype = GameIsDrawn;
3884 endtype = WhiteWins;
3887 GameEnds(endtype, why, GE_ICS);
3889 if (appData.zippyPlay && first.initDone) {
3890 ZippyGameEnd(endtype, why);
3891 if (first.pr == NULL) {
3892 /* Start the next process early so that we'll
3893 be ready for the next challenge */
3894 StartChessProgram(&first);
3896 /* Send "new" early, in case this command takes
3897 a long time to finish, so that we'll be ready
3898 for the next challenge. */
3899 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3903 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3907 if (looking_at(buf, &i, "Removing game * from observation") ||
3908 looking_at(buf, &i, "no longer observing game *") ||
3909 looking_at(buf, &i, "Game * (*) has no examiners")) {
3910 if (gameMode == IcsObserving &&
3911 atoi(star_match[0]) == ics_gamenum)
3913 /* icsEngineAnalyze */
3914 if (appData.icsEngineAnalyze) {
3921 ics_user_moved = FALSE;
3926 if (looking_at(buf, &i, "no longer examining game *")) {
3927 if (gameMode == IcsExamining &&
3928 atoi(star_match[0]) == ics_gamenum)
3932 ics_user_moved = FALSE;
3937 /* Advance leftover_start past any newlines we find,
3938 so only partial lines can get reparsed */
3939 if (looking_at(buf, &i, "\n")) {
3940 prevColor = curColor;
3941 if (curColor != ColorNormal) {
3942 if (oldi > next_out) {
3943 SendToPlayer(&buf[next_out], oldi - next_out);
3946 Colorize(ColorNormal, FALSE);
3947 curColor = ColorNormal;
3949 if (started == STARTED_BOARD) {
3950 started = STARTED_NONE;
3951 parse[parse_pos] = NULLCHAR;
3952 ParseBoard12(parse);
3955 /* Send premove here */
3956 if (appData.premove) {
3958 if (currentMove == 0 &&
3959 gameMode == IcsPlayingWhite &&
3960 appData.premoveWhite) {
3961 snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3962 if (appData.debugMode)
3963 fprintf(debugFP, "Sending premove:\n");
3965 } else if (currentMove == 1 &&
3966 gameMode == IcsPlayingBlack &&
3967 appData.premoveBlack) {
3968 snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3969 if (appData.debugMode)
3970 fprintf(debugFP, "Sending premove:\n");
3972 } else if (gotPremove) {
3974 ClearPremoveHighlights();
3975 if (appData.debugMode)
3976 fprintf(debugFP, "Sending premove:\n");
3977 UserMoveEvent(premoveFromX, premoveFromY,
3978 premoveToX, premoveToY,
3983 /* Usually suppress following prompt */
3984 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3985 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3986 if (looking_at(buf, &i, "*% ")) {
3987 savingComment = FALSE;
3992 } else if (started == STARTED_HOLDINGS) {
3994 char new_piece[MSG_SIZ];
3995 started = STARTED_NONE;
3996 parse[parse_pos] = NULLCHAR;
3997 if (appData.debugMode)
3998 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3999 parse, currentMove);
4000 if (sscanf(parse, " game %d", &gamenum) == 1) {
4001 if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
4002 if (gameInfo.variant == VariantNormal) {
4003 /* [HGM] We seem to switch variant during a game!
4004 * Presumably no holdings were displayed, so we have
4005 * to move the position two files to the right to
4006 * create room for them!
4008 VariantClass newVariant;
4009 switch(gameInfo.boardWidth) { // base guess on board width
4010 case 9: newVariant = VariantShogi; break;
4011 case 10: newVariant = VariantGreat; break;
4012 default: newVariant = VariantCrazyhouse; break;
4014 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4015 /* Get a move list just to see the header, which
4016 will tell us whether this is really bug or zh */
4017 if (ics_getting_history == H_FALSE) {
4018 ics_getting_history = H_REQUESTED;
4019 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4023 new_piece[0] = NULLCHAR;
4024 sscanf(parse, "game %d white [%s black [%s <- %s",
4025 &gamenum, white_holding, black_holding,
4027 white_holding[strlen(white_holding)-1] = NULLCHAR;
4028 black_holding[strlen(black_holding)-1] = NULLCHAR;
4029 /* [HGM] copy holdings to board holdings area */
4030 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
4031 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
4032 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
4034 if (appData.zippyPlay && first.initDone) {
4035 ZippyHoldings(white_holding, black_holding,
4039 if (tinyLayout || smallLayout) {
4040 char wh[16], bh[16];
4041 PackHolding(wh, white_holding);
4042 PackHolding(bh, black_holding);
4043 snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
4044 gameInfo.white, gameInfo.black);
4046 snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
4047 gameInfo.white, white_holding,
4048 gameInfo.black, black_holding);
4050 if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
4051 DrawPosition(FALSE, boards[currentMove]);
4053 } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
4054 sscanf(parse, "game %d white [%s black [%s <- %s",
4055 &gamenum, white_holding, black_holding,
4057 white_holding[strlen(white_holding)-1] = NULLCHAR;
4058 black_holding[strlen(black_holding)-1] = NULLCHAR;
4059 /* [HGM] copy holdings to partner-board holdings area */
4060 CopyHoldings(partnerBoard, white_holding, WhitePawn);
4061 CopyHoldings(partnerBoard, black_holding, BlackPawn);
4062 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4063 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4064 if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4067 /* Suppress following prompt */
4068 if (looking_at(buf, &i, "*% ")) {
4069 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4070 savingComment = FALSE;
4078 i++; /* skip unparsed character and loop back */
4081 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4082 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4083 // SendToPlayer(&buf[next_out], i - next_out);
4084 started != STARTED_HOLDINGS && leftover_start > next_out) {
4085 SendToPlayer(&buf[next_out], leftover_start - next_out);
4089 leftover_len = buf_len - leftover_start;
4090 /* if buffer ends with something we couldn't parse,
4091 reparse it after appending the next read */
4093 } else if (count == 0) {
4094 RemoveInputSource(isr);
4095 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4097 DisplayFatalError(_("Error reading from ICS"), error, 1);
4102 /* Board style 12 looks like this:
4104 <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
4106 * The "<12> " is stripped before it gets to this routine. The two
4107 * trailing 0's (flip state and clock ticking) are later addition, and
4108 * some chess servers may not have them, or may have only the first.
4109 * Additional trailing fields may be added in the future.
4112 #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"
4114 #define RELATION_OBSERVING_PLAYED 0
4115 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
4116 #define RELATION_PLAYING_MYMOVE 1
4117 #define RELATION_PLAYING_NOTMYMOVE -1
4118 #define RELATION_EXAMINING 2
4119 #define RELATION_ISOLATED_BOARD -3
4120 #define RELATION_STARTING_POSITION -4 /* FICS only */
4123 ParseBoard12(string)
4126 GameMode newGameMode;
4127 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4128 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4129 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4130 char to_play, board_chars[200];
4131 char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4132 char black[32], white[32];
4134 int prevMove = currentMove;
4137 int fromX, fromY, toX, toY;
4139 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4140 char *bookHit = NULL; // [HGM] book
4141 Boolean weird = FALSE, reqFlag = FALSE;
4143 fromX = fromY = toX = toY = -1;
4147 if (appData.debugMode)
4148 fprintf(debugFP, _("Parsing board: %s\n"), string);
4150 move_str[0] = NULLCHAR;
4151 elapsed_time[0] = NULLCHAR;
4152 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4154 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4155 if(string[i] == ' ') { ranks++; files = 0; }
4157 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4160 for(j = 0; j <i; j++) board_chars[j] = string[j];
4161 board_chars[i] = '\0';
4164 n = sscanf(string, PATTERN, &to_play, &double_push,
4165 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4166 &gamenum, white, black, &relation, &basetime, &increment,
4167 &white_stren, &black_stren, &white_time, &black_time,
4168 &moveNum, str, elapsed_time, move_str, &ics_flip,
4172 snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4173 DisplayError(str, 0);
4177 /* Convert the move number to internal form */
4178 moveNum = (moveNum - 1) * 2;
4179 if (to_play == 'B') moveNum++;
4180 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4181 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4187 case RELATION_OBSERVING_PLAYED:
4188 case RELATION_OBSERVING_STATIC:
4189 if (gamenum == -1) {
4190 /* Old ICC buglet */
4191 relation = RELATION_OBSERVING_STATIC;
4193 newGameMode = IcsObserving;
4195 case RELATION_PLAYING_MYMOVE:
4196 case RELATION_PLAYING_NOTMYMOVE:
4198 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4199 IcsPlayingWhite : IcsPlayingBlack;
4201 case RELATION_EXAMINING:
4202 newGameMode = IcsExamining;
4204 case RELATION_ISOLATED_BOARD:
4206 /* Just display this board. If user was doing something else,
4207 we will forget about it until the next board comes. */
4208 newGameMode = IcsIdle;
4210 case RELATION_STARTING_POSITION:
4211 newGameMode = gameMode;
4215 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4216 && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4217 // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4219 for (k = 0; k < ranks; k++) {
4220 for (j = 0; j < files; j++)
4221 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4222 if(gameInfo.holdingsWidth > 1) {
4223 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4224 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4227 CopyBoard(partnerBoard, board);
4228 if(toSqr = strchr(str, '/')) { // extract highlights from long move
4229 partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4230 partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4231 } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4232 if(toSqr = strchr(str, '-')) {
4233 partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4234 partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4235 } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4236 if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4237 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4238 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4239 if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4240 snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4241 (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4242 DisplayMessage(partnerStatus, "");
4243 partnerBoardValid = TRUE;
4247 /* Modify behavior for initial board display on move listing
4250 switch (ics_getting_history) {
4254 case H_GOT_REQ_HEADER:
4255 case H_GOT_UNREQ_HEADER:
4256 /* This is the initial position of the current game */
4257 gamenum = ics_gamenum;
4258 moveNum = 0; /* old ICS bug workaround */
4259 if (to_play == 'B') {
4260 startedFromSetupPosition = TRUE;
4261 blackPlaysFirst = TRUE;
4263 if (forwardMostMove == 0) forwardMostMove = 1;
4264 if (backwardMostMove == 0) backwardMostMove = 1;
4265 if (currentMove == 0) currentMove = 1;
4267 newGameMode = gameMode;
4268 relation = RELATION_STARTING_POSITION; /* ICC needs this */
4270 case H_GOT_UNWANTED_HEADER:
4271 /* This is an initial board that we don't want */
4273 case H_GETTING_MOVES:
4274 /* Should not happen */
4275 DisplayError(_("Error gathering move list: extra board"), 0);
4276 ics_getting_history = H_FALSE;
4280 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4281 weird && (int)gameInfo.variant < (int)VariantShogi) {
4282 /* [HGM] We seem to have switched variant unexpectedly
4283 * Try to guess new variant from board size
4285 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4286 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4287 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4288 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4289 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
4290 if(!weird) newVariant = VariantNormal;
4291 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4292 /* Get a move list just to see the header, which
4293 will tell us whether this is really bug or zh */
4294 if (ics_getting_history == H_FALSE) {
4295 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4296 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4301 /* Take action if this is the first board of a new game, or of a
4302 different game than is currently being displayed. */
4303 if (gamenum != ics_gamenum || newGameMode != gameMode ||
4304 relation == RELATION_ISOLATED_BOARD) {
4306 /* Forget the old game and get the history (if any) of the new one */
4307 if (gameMode != BeginningOfGame) {
4311 if (appData.autoRaiseBoard) BoardToTop();
4313 if (gamenum == -1) {
4314 newGameMode = IcsIdle;
4315 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4316 appData.getMoveList && !reqFlag) {
4317 /* Need to get game history */
4318 ics_getting_history = H_REQUESTED;
4319 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4323 /* Initially flip the board to have black on the bottom if playing
4324 black or if the ICS flip flag is set, but let the user change
4325 it with the Flip View button. */
4326 flipView = appData.autoFlipView ?
4327 (newGameMode == IcsPlayingBlack) || ics_flip :
4330 /* Done with values from previous mode; copy in new ones */
4331 gameMode = newGameMode;
4333 ics_gamenum = gamenum;
4334 if (gamenum == gs_gamenum) {
4335 int klen = strlen(gs_kind);
4336 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4337 snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4338 gameInfo.event = StrSave(str);
4340 gameInfo.event = StrSave("ICS game");
4342 gameInfo.site = StrSave(appData.icsHost);
4343 gameInfo.date = PGNDate();
4344 gameInfo.round = StrSave("-");
4345 gameInfo.white = StrSave(white);
4346 gameInfo.black = StrSave(black);
4347 timeControl = basetime * 60 * 1000;
4349 timeIncrement = increment * 1000;
4350 movesPerSession = 0;
4351 gameInfo.timeControl = TimeControlTagValue();
4352 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4353 if (appData.debugMode) {
4354 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4355 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4356 setbuf(debugFP, NULL);
4359 gameInfo.outOfBook = NULL;
4361 /* Do we have the ratings? */
4362 if (strcmp(player1Name, white) == 0 &&
4363 strcmp(player2Name, black) == 0) {
4364 if (appData.debugMode)
4365 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4366 player1Rating, player2Rating);
4367 gameInfo.whiteRating = player1Rating;
4368 gameInfo.blackRating = player2Rating;
4369 } else if (strcmp(player2Name, white) == 0 &&
4370 strcmp(player1Name, black) == 0) {
4371 if (appData.debugMode)
4372 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4373 player2Rating, player1Rating);
4374 gameInfo.whiteRating = player2Rating;
4375 gameInfo.blackRating = player1Rating;
4377 player1Name[0] = player2Name[0] = NULLCHAR;
4379 /* Silence shouts if requested */
4380 if (appData.quietPlay &&
4381 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4382 SendToICS(ics_prefix);
4383 SendToICS("set shout 0\n");
4387 /* Deal with midgame name changes */
4389 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4390 if (gameInfo.white) free(gameInfo.white);
4391 gameInfo.white = StrSave(white);
4393 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4394 if (gameInfo.black) free(gameInfo.black);
4395 gameInfo.black = StrSave(black);
4399 /* Throw away game result if anything actually changes in examine mode */
4400 if (gameMode == IcsExamining && !newGame) {
4401 gameInfo.result = GameUnfinished;
4402 if (gameInfo.resultDetails != NULL) {
4403 free(gameInfo.resultDetails);
4404 gameInfo.resultDetails = NULL;
4408 /* In pausing && IcsExamining mode, we ignore boards coming
4409 in if they are in a different variation than we are. */
4410 if (pauseExamInvalid) return;
4411 if (pausing && gameMode == IcsExamining) {
4412 if (moveNum <= pauseExamForwardMostMove) {
4413 pauseExamInvalid = TRUE;
4414 forwardMostMove = pauseExamForwardMostMove;
4419 if (appData.debugMode) {
4420 fprintf(debugFP, "load %dx%d board\n", files, ranks);
4422 /* Parse the board */
4423 for (k = 0; k < ranks; k++) {
4424 for (j = 0; j < files; j++)
4425 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4426 if(gameInfo.holdingsWidth > 1) {
4427 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4428 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4431 CopyBoard(boards[moveNum], board);
4432 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4434 startedFromSetupPosition =
4435 !CompareBoards(board, initialPosition);
4436 if(startedFromSetupPosition)
4437 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4440 /* [HGM] Set castling rights. Take the outermost Rooks,
4441 to make it also work for FRC opening positions. Note that board12
4442 is really defective for later FRC positions, as it has no way to
4443 indicate which Rook can castle if they are on the same side of King.
4444 For the initial position we grant rights to the outermost Rooks,
4445 and remember thos rights, and we then copy them on positions
4446 later in an FRC game. This means WB might not recognize castlings with
4447 Rooks that have moved back to their original position as illegal,
4448 but in ICS mode that is not its job anyway.
4450 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4451 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4453 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4454 if(board[0][i] == WhiteRook) j = i;
4455 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4456 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4457 if(board[0][i] == WhiteRook) j = i;
4458 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4459 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4460 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4461 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4462 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4463 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4464 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4466 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4467 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4468 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4469 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4470 if(board[BOARD_HEIGHT-1][k] == bKing)
4471 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4472 if(gameInfo.variant == VariantTwoKings) {
4473 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4474 if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4475 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4478 r = boards[moveNum][CASTLING][0] = initialRights[0];
4479 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4480 r = boards[moveNum][CASTLING][1] = initialRights[1];
4481 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4482 r = boards[moveNum][CASTLING][3] = initialRights[3];
4483 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4484 r = boards[moveNum][CASTLING][4] = initialRights[4];
4485 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4486 /* wildcastle kludge: always assume King has rights */
4487 r = boards[moveNum][CASTLING][2] = initialRights[2];
4488 r = boards[moveNum][CASTLING][5] = initialRights[5];
4490 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4491 boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4494 if (ics_getting_history == H_GOT_REQ_HEADER ||
4495 ics_getting_history == H_GOT_UNREQ_HEADER) {
4496 /* This was an initial position from a move list, not
4497 the current position */
4501 /* Update currentMove and known move number limits */
4502 newMove = newGame || moveNum > forwardMostMove;
4505 forwardMostMove = backwardMostMove = currentMove = moveNum;
4506 if (gameMode == IcsExamining && moveNum == 0) {
4507 /* Workaround for ICS limitation: we are not told the wild
4508 type when starting to examine a game. But if we ask for
4509 the move list, the move list header will tell us */
4510 ics_getting_history = H_REQUESTED;
4511 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4514 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4515 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4517 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4518 /* [HGM] applied this also to an engine that is silently watching */
4519 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4520 (gameMode == IcsObserving || gameMode == IcsExamining) &&
4521 gameInfo.variant == currentlyInitializedVariant) {
4522 takeback = forwardMostMove - moveNum;
4523 for (i = 0; i < takeback; i++) {
4524 if (appData.debugMode) fprintf(debugFP, "take back move\n");
4525 SendToProgram("undo\n", &first);
4530 forwardMostMove = moveNum;
4531 if (!pausing || currentMove > forwardMostMove)
4532 currentMove = forwardMostMove;
4534 /* New part of history that is not contiguous with old part */
4535 if (pausing && gameMode == IcsExamining) {
4536 pauseExamInvalid = TRUE;
4537 forwardMostMove = pauseExamForwardMostMove;
4540 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4542 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4543 // [HGM] when we will receive the move list we now request, it will be
4544 // fed to the engine from the first move on. So if the engine is not
4545 // in the initial position now, bring it there.
4546 InitChessProgram(&first, 0);
4549 ics_getting_history = H_REQUESTED;
4550 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4553 forwardMostMove = backwardMostMove = currentMove = moveNum;
4556 /* Update the clocks */
4557 if (strchr(elapsed_time, '.')) {
4559 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4560 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4562 /* Time is in seconds */
4563 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4564 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4569 if (appData.zippyPlay && newGame &&
4570 gameMode != IcsObserving && gameMode != IcsIdle &&
4571 gameMode != IcsExamining)
4572 ZippyFirstBoard(moveNum, basetime, increment);
4575 /* Put the move on the move list, first converting
4576 to canonical algebraic form. */
4578 if (appData.debugMode) {
4579 if (appData.debugMode) { int f = forwardMostMove;
4580 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4581 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4582 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4584 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4585 fprintf(debugFP, "moveNum = %d\n", moveNum);
4586 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4587 setbuf(debugFP, NULL);
4589 if (moveNum <= backwardMostMove) {
4590 /* We don't know what the board looked like before
4592 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4593 strcat(parseList[moveNum - 1], " ");
4594 strcat(parseList[moveNum - 1], elapsed_time);
4595 moveList[moveNum - 1][0] = NULLCHAR;
4596 } else if (strcmp(move_str, "none") == 0) {
4597 // [HGM] long SAN: swapped order; test for 'none' before parsing move
4598 /* Again, we don't know what the board looked like;
4599 this is really the start of the game. */
4600 parseList[moveNum - 1][0] = NULLCHAR;
4601 moveList[moveNum - 1][0] = NULLCHAR;
4602 backwardMostMove = moveNum;
4603 startedFromSetupPosition = TRUE;
4604 fromX = fromY = toX = toY = -1;
4606 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4607 // So we parse the long-algebraic move string in stead of the SAN move
4608 int valid; char buf[MSG_SIZ], *prom;
4610 if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4611 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4612 // str looks something like "Q/a1-a2"; kill the slash
4614 snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4615 else safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4616 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4617 strcat(buf, prom); // long move lacks promo specification!
4618 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4619 if(appData.debugMode)
4620 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4621 safeStrCpy(move_str, buf, MSG_SIZ);
4623 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4624 &fromX, &fromY, &toX, &toY, &promoChar)
4625 || ParseOneMove(buf, moveNum - 1, &moveType,
4626 &fromX, &fromY, &toX, &toY, &promoChar);
4627 // end of long SAN patch
4629 (void) CoordsToAlgebraic(boards[moveNum - 1],
4630 PosFlags(moveNum - 1),
4631 fromY, fromX, toY, toX, promoChar,
4632 parseList[moveNum-1]);
4633 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4639 if(gameInfo.variant != VariantShogi)
4640 strcat(parseList[moveNum - 1], "+");
4643 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4644 strcat(parseList[moveNum - 1], "#");
4647 strcat(parseList[moveNum - 1], " ");
4648 strcat(parseList[moveNum - 1], elapsed_time);
4649 /* currentMoveString is set as a side-effect of ParseOneMove */
4650 if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4651 safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4652 strcat(moveList[moveNum - 1], "\n");
4654 if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper && gameInfo.variant != VariantGreat
4655 && gameInfo.variant != VariantGrand) // inherit info that ICS does not give from previous board
4656 for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4657 ChessSquare old, new = boards[moveNum][k][j];
4658 if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4659 old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4660 if(old == new) continue;
4661 if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4662 else if(new == WhiteWazir || new == BlackWazir) {
4663 if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4664 boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4665 else boards[moveNum][k][j] = old; // preserve type of Gold
4666 } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4667 boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4670 /* Move from ICS was illegal!? Punt. */
4671 if (appData.debugMode) {
4672 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4673 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4675 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4676 strcat(parseList[moveNum - 1], " ");
4677 strcat(parseList[moveNum - 1], elapsed_time);
4678 moveList[moveNum - 1][0] = NULLCHAR;
4679 fromX = fromY = toX = toY = -1;
4682 if (appData.debugMode) {
4683 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4684 setbuf(debugFP, NULL);
4688 /* Send move to chess program (BEFORE animating it). */
4689 if (appData.zippyPlay && !newGame && newMove &&
4690 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4692 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4693 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4694 if (moveList[moveNum - 1][0] == NULLCHAR) {
4695 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4697 DisplayError(str, 0);
4699 if (first.sendTime) {
4700 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4702 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4703 if (firstMove && !bookHit) {
4705 if (first.useColors) {
4706 SendToProgram(gameMode == IcsPlayingWhite ?
4708 "black\ngo\n", &first);
4710 SendToProgram("go\n", &first);
4712 first.maybeThinking = TRUE;
4715 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4716 if (moveList[moveNum - 1][0] == NULLCHAR) {
4717 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4718 DisplayError(str, 0);
4720 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4721 SendMoveToProgram(moveNum - 1, &first);
4728 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4729 /* If move comes from a remote source, animate it. If it
4730 isn't remote, it will have already been animated. */
4731 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4732 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4734 if (!pausing && appData.highlightLastMove) {
4735 SetHighlights(fromX, fromY, toX, toY);
4739 /* Start the clocks */
4740 whiteFlag = blackFlag = FALSE;
4741 appData.clockMode = !(basetime == 0 && increment == 0);
4743 ics_clock_paused = TRUE;
4745 } else if (ticking == 1) {
4746 ics_clock_paused = FALSE;
4748 if (gameMode == IcsIdle ||
4749 relation == RELATION_OBSERVING_STATIC ||
4750 relation == RELATION_EXAMINING ||
4752 DisplayBothClocks();
4756 /* Display opponents and material strengths */
4757 if (gameInfo.variant != VariantBughouse &&
4758 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4759 if (tinyLayout || smallLayout) {
4760 if(gameInfo.variant == VariantNormal)
4761 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4762 gameInfo.white, white_stren, gameInfo.black, black_stren,
4763 basetime, increment);
4765 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4766 gameInfo.white, white_stren, gameInfo.black, black_stren,
4767 basetime, increment, (int) gameInfo.variant);
4769 if(gameInfo.variant == VariantNormal)
4770 snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4771 gameInfo.white, white_stren, gameInfo.black, black_stren,
4772 basetime, increment);
4774 snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4775 gameInfo.white, white_stren, gameInfo.black, black_stren,
4776 basetime, increment, VariantName(gameInfo.variant));
4779 if (appData.debugMode) {
4780 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4785 /* Display the board */
4786 if (!pausing && !appData.noGUI) {
4788 if (appData.premove)
4790 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4791 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4792 ClearPremoveHighlights();
4794 j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4795 if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4796 DrawPosition(j, boards[currentMove]);
4798 DisplayMove(moveNum - 1);
4799 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4800 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4801 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4802 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4806 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4808 if(bookHit) { // [HGM] book: simulate book reply
4809 static char bookMove[MSG_SIZ]; // a bit generous?
4811 programStats.nodes = programStats.depth = programStats.time =
4812 programStats.score = programStats.got_only_move = 0;
4813 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4815 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4816 strcat(bookMove, bookHit);
4817 HandleMachineMove(bookMove, &first);
4826 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4827 ics_getting_history = H_REQUESTED;
4828 snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4834 AnalysisPeriodicEvent(force)
4837 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4838 && !force) || !appData.periodicUpdates)
4841 /* Send . command to Crafty to collect stats */
4842 SendToProgram(".\n", &first);
4844 /* Don't send another until we get a response (this makes
4845 us stop sending to old Crafty's which don't understand
4846 the "." command (sending illegal cmds resets node count & time,
4847 which looks bad)) */
4848 programStats.ok_to_send = 0;
4851 void ics_update_width(new_width)
4854 ics_printf("set width %d\n", new_width);
4858 SendMoveToProgram(moveNum, cps)
4860 ChessProgramState *cps;
4864 if (cps->useUsermove) {
4865 SendToProgram("usermove ", cps);
4869 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4870 int len = space - parseList[moveNum];
4871 memcpy(buf, parseList[moveNum], len);
4873 buf[len] = NULLCHAR;
4875 snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4877 SendToProgram(buf, cps);
4879 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4880 AlphaRank(moveList[moveNum], 4);
4881 SendToProgram(moveList[moveNum], cps);
4882 AlphaRank(moveList[moveNum], 4); // and back
4884 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4885 * the engine. It would be nice to have a better way to identify castle
4887 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4888 && cps->useOOCastle) {
4889 int fromX = moveList[moveNum][0] - AAA;
4890 int fromY = moveList[moveNum][1] - ONE;
4891 int toX = moveList[moveNum][2] - AAA;
4892 int toY = moveList[moveNum][3] - ONE;
4893 if((boards[moveNum][fromY][fromX] == WhiteKing
4894 && boards[moveNum][toY][toX] == WhiteRook)
4895 || (boards[moveNum][fromY][fromX] == BlackKing
4896 && boards[moveNum][toY][toX] == BlackRook)) {
4897 if(toX > fromX) SendToProgram("O-O\n", cps);
4898 else SendToProgram("O-O-O\n", cps);
4900 else SendToProgram(moveList[moveNum], cps);
4902 if(BOARD_HEIGHT > 10) { // [HGM] big: convert ranks to double-digit where needed
4903 if(moveList[moveNum][1] == '@' && (BOARD_HEIGHT < 16 || moveList[moveNum][0] <= 'Z')) { // drop move
4904 snprintf(buf, MSG_SIZ, "%c@%c%d%s", moveList[moveNum][0],
4905 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4907 snprintf(buf, MSG_SIZ, "%c%d%c%d%s", moveList[moveNum][0], moveList[moveNum][1] - '0',
4908 moveList[moveNum][2], moveList[moveNum][3] - '0', moveList[moveNum]+4);
4909 SendToProgram(buf, cps);
4911 else SendToProgram(moveList[moveNum], cps);
4912 /* End of additions by Tord */
4915 /* [HGM] setting up the opening has brought engine in force mode! */
4916 /* Send 'go' if we are in a mode where machine should play. */
4917 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4918 (gameMode == TwoMachinesPlay ||
4920 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4922 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4923 SendToProgram("go\n", cps);
4924 if (appData.debugMode) {
4925 fprintf(debugFP, "(extra)\n");
4928 setboardSpoiledMachineBlack = 0;
4932 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4934 int fromX, fromY, toX, toY;
4937 char user_move[MSG_SIZ];
4941 snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4942 (int)moveType, fromX, fromY, toX, toY);
4943 DisplayError(user_move + strlen("say "), 0);
4945 case WhiteKingSideCastle:
4946 case BlackKingSideCastle:
4947 case WhiteQueenSideCastleWild:
4948 case BlackQueenSideCastleWild:
4950 case WhiteHSideCastleFR:
4951 case BlackHSideCastleFR:
4953 snprintf(user_move, MSG_SIZ, "o-o\n");
4955 case WhiteQueenSideCastle:
4956 case BlackQueenSideCastle:
4957 case WhiteKingSideCastleWild:
4958 case BlackKingSideCastleWild:
4960 case WhiteASideCastleFR:
4961 case BlackASideCastleFR:
4963 snprintf(user_move, MSG_SIZ, "o-o-o\n");
4965 case WhiteNonPromotion:
4966 case BlackNonPromotion:
4967 sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4969 case WhitePromotion:
4970 case BlackPromotion:
4971 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4972 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4973 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4974 PieceToChar(WhiteFerz));
4975 else if(gameInfo.variant == VariantGreat)
4976 snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4977 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4978 PieceToChar(WhiteMan));
4980 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4981 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4987 snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4988 ToUpper(PieceToChar((ChessSquare) fromX)),
4989 AAA + toX, ONE + toY);
4991 case IllegalMove: /* could be a variant we don't quite understand */
4992 if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4994 case WhiteCapturesEnPassant:
4995 case BlackCapturesEnPassant:
4996 snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4997 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
5000 SendToICS(user_move);
5001 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
5002 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
5007 { // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
5008 int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
5009 static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
5010 if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
5011 DisplayError("You cannot do this while you are playing or observing", 0);
5014 if(gameMode != IcsExamining) { // is this ever not the case?
5015 char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
5017 if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
5018 snprintf(command,MSG_SIZ, "match %s", ics_handle);
5019 } else { // on FICS we must first go to general examine mode
5020 safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
5022 if(gameInfo.variant != VariantNormal) {
5023 // try figure out wild number, as xboard names are not always valid on ICS
5024 for(i=1; i<=36; i++) {
5025 snprintf(buf, MSG_SIZ, "wild/%d", i);
5026 if(StringToVariant(buf) == gameInfo.variant) break;
5028 if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
5029 else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
5030 else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
5031 } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
5032 SendToICS(ics_prefix);
5034 if(startedFromSetupPosition || backwardMostMove != 0) {
5035 fen = PositionToFEN(backwardMostMove, NULL);
5036 if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
5037 snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
5039 } else { // FICS: everything has to set by separate bsetup commands
5040 p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
5041 snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
5043 if(!WhiteOnMove(backwardMostMove)) {
5044 SendToICS("bsetup tomove black\n");
5046 i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
5047 snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
5049 i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
5050 snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
5052 i = boards[backwardMostMove][EP_STATUS];
5053 if(i >= 0) { // set e.p.
5054 snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
5060 if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
5061 SendToICS("bsetup done\n"); // switch to normal examining.
5063 for(i = backwardMostMove; i<last; i++) {
5065 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5068 SendToICS(ics_prefix);
5069 SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5073 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
5078 if (rf == DROP_RANK) {
5079 sprintf(move, "%c@%c%c\n",
5080 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5082 if (promoChar == 'x' || promoChar == NULLCHAR) {
5083 sprintf(move, "%c%c%c%c\n",
5084 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5086 sprintf(move, "%c%c%c%c%c\n",
5087 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5093 ProcessICSInitScript(f)
5098 while (fgets(buf, MSG_SIZ, f)) {
5099 SendToICSDelayed(buf,(long)appData.msLoginDelay);
5106 static int lastX, lastY, selectFlag, dragging;
5111 ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5112 if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5113 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5114 if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5115 if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5116 if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5119 if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5120 else if((int)promoSweep == -1) promoSweep = WhiteKing;
5121 else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5122 else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5124 } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5125 appData.testLegality && (promoSweep == king ||
5126 gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5127 ChangeDragPiece(promoSweep);
5130 int PromoScroll(int x, int y)
5134 if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5135 if(abs(x - lastX) < 15 && abs(y - lastY) < 15) return FALSE;
5136 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5137 if(!step) return FALSE;
5138 lastX = x; lastY = y;
5139 if((promoSweep < BlackPawn) == flipView) step = -step;
5140 if(step > 0) selectFlag = 1;
5141 if(!selectFlag) Sweep(step);
5148 ChessSquare piece = boards[currentMove][toY][toX];
5151 if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5152 if((int)pieceSweep == -1) pieceSweep = BlackKing;
5153 if(!step) step = -1;
5154 } while(PieceToChar(pieceSweep) == '.');
5155 boards[currentMove][toY][toX] = pieceSweep;
5156 DrawPosition(FALSE, boards[currentMove]);
5157 boards[currentMove][toY][toX] = piece;
5159 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5161 AlphaRank(char *move, int n)
5163 // char *p = move, c; int x, y;
5165 if (appData.debugMode) {
5166 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5170 move[2]>='0' && move[2]<='9' &&
5171 move[3]>='a' && move[3]<='x' ) {
5173 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5174 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5176 if(move[0]>='0' && move[0]<='9' &&
5177 move[1]>='a' && move[1]<='x' &&
5178 move[2]>='0' && move[2]<='9' &&
5179 move[3]>='a' && move[3]<='x' ) {
5180 /* input move, Shogi -> normal */
5181 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
5182 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5183 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5184 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5187 move[3]>='0' && move[3]<='9' &&
5188 move[2]>='a' && move[2]<='x' ) {
5190 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5191 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5194 move[0]>='a' && move[0]<='x' &&
5195 move[3]>='0' && move[3]<='9' &&
5196 move[2]>='a' && move[2]<='x' ) {
5197 /* output move, normal -> Shogi */
5198 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5199 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5200 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5201 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5202 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5204 if (appData.debugMode) {
5205 fprintf(debugFP, " out = '%s'\n", move);
5209 char yy_textstr[8000];
5211 /* Parser for moves from gnuchess, ICS, or user typein box */
5213 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
5216 ChessMove *moveType;
5217 int *fromX, *fromY, *toX, *toY;
5220 *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5222 switch (*moveType) {
5223 case WhitePromotion:
5224 case BlackPromotion:
5225 case WhiteNonPromotion:
5226 case BlackNonPromotion:
5228 case WhiteCapturesEnPassant:
5229 case BlackCapturesEnPassant:
5230 case WhiteKingSideCastle:
5231 case WhiteQueenSideCastle:
5232 case BlackKingSideCastle:
5233 case BlackQueenSideCastle:
5234 case WhiteKingSideCastleWild:
5235 case WhiteQueenSideCastleWild:
5236 case BlackKingSideCastleWild:
5237 case BlackQueenSideCastleWild:
5238 /* Code added by Tord: */
5239 case WhiteHSideCastleFR:
5240 case WhiteASideCastleFR:
5241 case BlackHSideCastleFR:
5242 case BlackASideCastleFR:
5243 /* End of code added by Tord */
5244 case IllegalMove: /* bug or odd chess variant */
5245 *fromX = currentMoveString[0] - AAA;
5246 *fromY = currentMoveString[1] - ONE;
5247 *toX = currentMoveString[2] - AAA;
5248 *toY = currentMoveString[3] - ONE;
5249 *promoChar = currentMoveString[4];
5250 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5251 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5252 if (appData.debugMode) {
5253 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5255 *fromX = *fromY = *toX = *toY = 0;
5258 if (appData.testLegality) {
5259 return (*moveType != IllegalMove);
5261 return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5262 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5267 *fromX = *moveType == WhiteDrop ?
5268 (int) CharToPiece(ToUpper(currentMoveString[0])) :
5269 (int) CharToPiece(ToLower(currentMoveString[0]));
5271 *toX = currentMoveString[2] - AAA;
5272 *toY = currentMoveString[3] - ONE;
5273 *promoChar = NULLCHAR;
5277 case ImpossibleMove:
5287 if (appData.debugMode) {
5288 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5291 *fromX = *fromY = *toX = *toY = 0;
5292 *promoChar = NULLCHAR;
5297 Boolean pushed = FALSE;
5298 char *lastParseAttempt;
5301 ParsePV(char *pv, Boolean storeComments, Boolean atEnd)
5302 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5303 int fromX, fromY, toX, toY; char promoChar;
5308 if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5309 PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5312 endPV = forwardMostMove;
5314 while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5315 if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5316 lastParseAttempt = pv;
5317 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5318 if(appData.debugMode){
5319 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);
5321 if(!valid && nr == 0 &&
5322 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5323 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5324 // Hande case where played move is different from leading PV move
5325 CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5326 CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5327 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5328 if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5329 endPV += 2; // if position different, keep this
5330 moveList[endPV-1][0] = fromX + AAA;
5331 moveList[endPV-1][1] = fromY + ONE;
5332 moveList[endPV-1][2] = toX + AAA;
5333 moveList[endPV-1][3] = toY + ONE;
5334 parseList[endPV-1][0] = NULLCHAR;
5335 safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5338 pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5339 if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5340 if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5341 if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5342 valid++; // allow comments in PV
5346 if(endPV+1 > framePtr) break; // no space, truncate
5349 CopyBoard(boards[endPV], boards[endPV-1]);
5350 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5351 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar, moveList[endPV - 1]);
5352 strncat(moveList[endPV-1], "\n", MOVE_LEN);
5353 CoordsToAlgebraic(boards[endPV - 1],
5354 PosFlags(endPV - 1),
5355 fromY, fromX, toY, toX, promoChar,
5356 parseList[endPV - 1]);
5358 if(atEnd == 2) return; // used hidden, for PV conversion
5359 currentMove = (atEnd || endPV == forwardMostMove) ? endPV : forwardMostMove + 1;
5360 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5361 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5362 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5363 DrawPosition(TRUE, boards[currentMove]);
5367 MultiPV(ChessProgramState *cps)
5368 { // check if engine supports MultiPV, and if so, return the number of the option that sets it
5370 for(i=0; i<cps->nrOptions; i++)
5371 if(!strcmp(cps->option[i].name, "MultiPV") && cps->option[i].type == Spin)
5377 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5379 int startPV, multi, lineStart, origIndex = index;
5380 char *p, buf2[MSG_SIZ];
5382 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5383 lastX = x; lastY = y;
5384 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5385 lineStart = startPV = index;
5386 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5387 if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5389 do{ while(buf[index] && buf[index] != '\n') index++;
5390 } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5392 if(lineStart == 0 && gameMode == AnalyzeMode && (multi = MultiPV(&first)) >= 0) {
5393 int n = first.option[multi].value;
5394 if(origIndex > 17 && origIndex < 24) { if(n>1) n--; } else if(origIndex > index - 6) n++;
5395 snprintf(buf2, MSG_SIZ, "option MultiPV=%d\n", n);
5396 if(first.option[multi].value != n) SendToProgram(buf2, &first);
5397 first.option[multi].value = n;
5401 ParsePV(buf+startPV, FALSE, gameMode != AnalyzeMode);
5402 *start = startPV; *end = index-1;
5409 static char buf[10*MSG_SIZ];
5410 int i, k=0, savedEnd=endPV;
5412 if(forwardMostMove < endPV) PushInner(forwardMostMove, endPV);
5413 ParsePV(pv, FALSE, 2); // this appends PV to game, suppressing any display of it
5414 for(i = forwardMostMove; i<endPV; i++){
5415 if(i&1) snprintf(buf+k, 10*MSG_SIZ-k, "%s ", parseList[i]);
5416 else snprintf(buf+k, 10*MSG_SIZ-k, "%d. %s ", i/2 + 1, parseList[i]);
5419 snprintf(buf+k, 10*MSG_SIZ-k, "%s", lastParseAttempt); // if we ran into stuff that could not be parsed, print it verbatim
5420 if(forwardMostMove < savedEnd) PopInner(0);
5426 LoadPV(int x, int y)
5427 { // called on right mouse click to load PV
5428 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5429 lastX = x; lastY = y;
5430 ParsePV(lastPV[which], FALSE, TRUE); // load the PV of the thinking engine in the boards array.
5437 int oldFMM = forwardMostMove; // N.B.: this was currentMove before PV was loaded!
5438 if(endPV < 0) return;
5440 if(gameMode == AnalyzeMode && currentMove > forwardMostMove) {
5441 Boolean saveAnimate = appData.animate;
5443 if(shiftKey && storedGames < MAX_VARIATIONS-2) { // wants to start variation, and there is space
5444 if(storedGames == 1) GreyRevert(FALSE); // we already pushed the tail, so just make it official
5445 } else storedGames--; // abandon shelved tail of original game
5448 forwardMostMove = currentMove;
5449 currentMove = oldFMM;
5450 appData.animate = FALSE;
5451 ToNrEvent(forwardMostMove);
5452 appData.animate = saveAnimate;
5454 currentMove = forwardMostMove;
5455 if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game continuation
5456 ClearPremoveHighlights();
5457 DrawPosition(TRUE, boards[currentMove]);
5461 MovePV(int x, int y, int h)
5462 { // step through PV based on mouse coordinates (called on mouse move)
5463 int margin = h>>3, step = 0;
5465 // we must somehow check if right button is still down (might be released off board!)
5466 if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5467 if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return;
5468 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5470 lastX = x; lastY = y;
5472 if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5473 if(endPV < 0) return;
5474 if(y < margin) step = 1; else
5475 if(y > h - margin) step = -1;
5476 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5477 currentMove += step;
5478 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5479 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5480 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5481 DrawPosition(FALSE, boards[currentMove]);
5485 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5486 // All positions will have equal probability, but the current method will not provide a unique
5487 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5493 int piecesLeft[(int)BlackPawn];
5494 int seed, nrOfShuffles;
5496 void GetPositionNumber()
5497 { // sets global variable seed
5500 seed = appData.defaultFrcPosition;
5501 if(seed < 0) { // randomize based on time for negative FRC position numbers
5502 for(i=0; i<50; i++) seed += random();
5503 seed = random() ^ random() >> 8 ^ random() << 8;
5504 if(seed<0) seed = -seed;
5508 int put(Board board, int pieceType, int rank, int n, int shade)
5509 // put the piece on the (n-1)-th empty squares of the given shade
5513 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5514 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5515 board[rank][i] = (ChessSquare) pieceType;
5516 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5518 piecesLeft[pieceType]--;
5526 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5527 // calculate where the next piece goes, (any empty square), and put it there
5531 i = seed % squaresLeft[shade];
5532 nrOfShuffles *= squaresLeft[shade];
5533 seed /= squaresLeft[shade];
5534 put(board, pieceType, rank, i, shade);
5537 void AddTwoPieces(Board board, int pieceType, int rank)
5538 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5540 int i, n=squaresLeft[ANY], j=n-1, k;
5542 k = n*(n-1)/2; // nr of possibilities, not counting permutations
5543 i = seed % k; // pick one
5546 while(i >= j) i -= j--;
5547 j = n - 1 - j; i += j;
5548 put(board, pieceType, rank, j, ANY);
5549 put(board, pieceType, rank, i, ANY);
5552 void SetUpShuffle(Board board, int number)
5556 GetPositionNumber(); nrOfShuffles = 1;
5558 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5559 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
5560 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5562 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5564 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5565 p = (int) board[0][i];
5566 if(p < (int) BlackPawn) piecesLeft[p] ++;
5567 board[0][i] = EmptySquare;
5570 if(PosFlags(0) & F_ALL_CASTLE_OK) {
5571 // shuffles restricted to allow normal castling put KRR first
5572 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5573 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5574 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5575 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5576 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5577 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5578 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5579 put(board, WhiteRook, 0, 0, ANY);
5580 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5583 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5584 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5585 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5586 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5587 while(piecesLeft[p] >= 2) {
5588 AddOnePiece(board, p, 0, LITE);
5589 AddOnePiece(board, p, 0, DARK);
5591 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5594 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5595 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5596 // but we leave King and Rooks for last, to possibly obey FRC restriction
5597 if(p == (int)WhiteRook) continue;
5598 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5599 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
5602 // now everything is placed, except perhaps King (Unicorn) and Rooks
5604 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5605 // Last King gets castling rights
5606 while(piecesLeft[(int)WhiteUnicorn]) {
5607 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5608 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5611 while(piecesLeft[(int)WhiteKing]) {
5612 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5613 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5618 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
5619 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5622 // Only Rooks can be left; simply place them all
5623 while(piecesLeft[(int)WhiteRook]) {
5624 i = put(board, WhiteRook, 0, 0, ANY);
5625 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5628 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
5630 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
5633 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5634 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5637 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5640 int SetCharTable( char *table, const char * map )
5641 /* [HGM] moved here from winboard.c because of its general usefulness */
5642 /* Basically a safe strcpy that uses the last character as King */
5644 int result = FALSE; int NrPieces;
5646 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5647 && NrPieces >= 12 && !(NrPieces&1)) {
5648 int i; /* [HGM] Accept even length from 12 to 34 */
5650 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5651 for( i=0; i<NrPieces/2-1; i++ ) {
5653 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5655 table[(int) WhiteKing] = map[NrPieces/2-1];
5656 table[(int) BlackKing] = map[NrPieces-1];
5664 void Prelude(Board board)
5665 { // [HGM] superchess: random selection of exo-pieces
5666 int i, j, k; ChessSquare p;
5667 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5669 GetPositionNumber(); // use FRC position number
5671 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5672 SetCharTable(pieceToChar, appData.pieceToCharTable);
5673 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5674 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5677 j = seed%4; seed /= 4;
5678 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5679 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5680 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5681 j = seed%3 + (seed%3 >= j); seed /= 3;
5682 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5683 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5684 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5685 j = seed%3; seed /= 3;
5686 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5687 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5688 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5689 j = seed%2 + (seed%2 >= j); seed /= 2;
5690 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5691 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5692 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5693 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
5694 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
5695 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5696 put(board, exoPieces[0], 0, 0, ANY);
5697 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5701 InitPosition(redraw)
5704 ChessSquare (* pieces)[BOARD_FILES];
5705 int i, j, pawnRow, overrule,
5706 oldx = gameInfo.boardWidth,
5707 oldy = gameInfo.boardHeight,
5708 oldh = gameInfo.holdingsWidth;
5711 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5713 /* [AS] Initialize pv info list [HGM] and game status */
5715 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5716 pvInfoList[i].depth = 0;
5717 boards[i][EP_STATUS] = EP_NONE;
5718 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5721 initialRulePlies = 0; /* 50-move counter start */
5723 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5724 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5728 /* [HGM] logic here is completely changed. In stead of full positions */
5729 /* the initialized data only consist of the two backranks. The switch */
5730 /* selects which one we will use, which is than copied to the Board */
5731 /* initialPosition, which for the rest is initialized by Pawns and */
5732 /* empty squares. This initial position is then copied to boards[0], */
5733 /* possibly after shuffling, so that it remains available. */
5735 gameInfo.holdingsWidth = 0; /* default board sizes */
5736 gameInfo.boardWidth = 8;
5737 gameInfo.boardHeight = 8;
5738 gameInfo.holdingsSize = 0;
5739 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5740 for(i=0; i<BOARD_FILES-2; i++)
5741 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5742 initialPosition[EP_STATUS] = EP_NONE;
5743 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5744 if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5745 SetCharTable(pieceNickName, appData.pieceNickNames);
5746 else SetCharTable(pieceNickName, "............");
5749 switch (gameInfo.variant) {
5750 case VariantFischeRandom:
5751 shuffleOpenings = TRUE;
5754 case VariantShatranj:
5755 pieces = ShatranjArray;
5756 nrCastlingRights = 0;
5757 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5760 pieces = makrukArray;
5761 nrCastlingRights = 0;
5762 startedFromSetupPosition = TRUE;
5763 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5765 case VariantTwoKings:
5766 pieces = twoKingsArray;
5769 pieces = GrandArray;
5770 nrCastlingRights = 0;
5771 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5772 gameInfo.boardWidth = 10;
5773 gameInfo.boardHeight = 10;
5774 gameInfo.holdingsSize = 7;
5776 case VariantCapaRandom:
5777 shuffleOpenings = TRUE;
5778 case VariantCapablanca:
5779 pieces = CapablancaArray;
5780 gameInfo.boardWidth = 10;
5781 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5784 pieces = GothicArray;
5785 gameInfo.boardWidth = 10;
5786 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5789 SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5790 gameInfo.holdingsSize = 7;
5793 pieces = JanusArray;
5794 gameInfo.boardWidth = 10;
5795 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5796 nrCastlingRights = 6;
5797 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5798 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5799 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5800 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5801 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5802 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5805 pieces = FalconArray;
5806 gameInfo.boardWidth = 10;
5807 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5809 case VariantXiangqi:
5810 pieces = XiangqiArray;
5811 gameInfo.boardWidth = 9;
5812 gameInfo.boardHeight = 10;
5813 nrCastlingRights = 0;
5814 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5817 pieces = ShogiArray;
5818 gameInfo.boardWidth = 9;
5819 gameInfo.boardHeight = 9;
5820 gameInfo.holdingsSize = 7;
5821 nrCastlingRights = 0;
5822 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5824 case VariantCourier:
5825 pieces = CourierArray;
5826 gameInfo.boardWidth = 12;
5827 nrCastlingRights = 0;
5828 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5830 case VariantKnightmate:
5831 pieces = KnightmateArray;
5832 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5834 case VariantSpartan:
5835 pieces = SpartanArray;
5836 SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5839 pieces = fairyArray;
5840 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5843 pieces = GreatArray;
5844 gameInfo.boardWidth = 10;
5845 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5846 gameInfo.holdingsSize = 8;
5850 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5851 gameInfo.holdingsSize = 8;
5852 startedFromSetupPosition = TRUE;
5854 case VariantCrazyhouse:
5855 case VariantBughouse:
5857 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5858 gameInfo.holdingsSize = 5;
5860 case VariantWildCastle:
5862 /* !!?shuffle with kings guaranteed to be on d or e file */
5863 shuffleOpenings = 1;
5865 case VariantNoCastle:
5867 nrCastlingRights = 0;
5868 /* !!?unconstrained back-rank shuffle */
5869 shuffleOpenings = 1;
5874 if(appData.NrFiles >= 0) {
5875 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5876 gameInfo.boardWidth = appData.NrFiles;
5878 if(appData.NrRanks >= 0) {
5879 gameInfo.boardHeight = appData.NrRanks;
5881 if(appData.holdingsSize >= 0) {
5882 i = appData.holdingsSize;
5883 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5884 gameInfo.holdingsSize = i;
5886 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5887 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5888 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5890 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5891 if(pawnRow < 1) pawnRow = 1;
5892 if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) pawnRow = 2;
5894 /* User pieceToChar list overrules defaults */
5895 if(appData.pieceToCharTable != NULL)
5896 SetCharTable(pieceToChar, appData.pieceToCharTable);
5898 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5900 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5901 s = (ChessSquare) 0; /* account holding counts in guard band */
5902 for( i=0; i<BOARD_HEIGHT; i++ )
5903 initialPosition[i][j] = s;
5905 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5906 initialPosition[gameInfo.variant == VariantGrand][j] = pieces[0][j-gameInfo.holdingsWidth];
5907 initialPosition[pawnRow][j] = WhitePawn;
5908 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5909 if(gameInfo.variant == VariantXiangqi) {
5911 initialPosition[pawnRow][j] =
5912 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5913 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5914 initialPosition[2][j] = WhiteCannon;
5915 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5919 if(gameInfo.variant == VariantGrand) {
5920 if(j==BOARD_LEFT || j>=BOARD_RGHT-1) {
5921 initialPosition[0][j] = WhiteRook;
5922 initialPosition[BOARD_HEIGHT-1][j] = BlackRook;
5925 initialPosition[BOARD_HEIGHT-1-(gameInfo.variant == VariantGrand)][j] = pieces[1][j-gameInfo.holdingsWidth];
5927 if( (gameInfo.variant == VariantShogi) && !overrule ) {
5930 initialPosition[1][j] = WhiteBishop;
5931 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5933 initialPosition[1][j] = WhiteRook;
5934 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5937 if( nrCastlingRights == -1) {
5938 /* [HGM] Build normal castling rights (must be done after board sizing!) */
5939 /* This sets default castling rights from none to normal corners */
5940 /* Variants with other castling rights must set them themselves above */
5941 nrCastlingRights = 6;
5943 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5944 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5945 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5946 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5947 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5948 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5951 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5952 if(gameInfo.variant == VariantGreat) { // promotion commoners
5953 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5954 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5955 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5956 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5958 if( gameInfo.variant == VariantSChess ) {
5959 initialPosition[1][0] = BlackMarshall;
5960 initialPosition[2][0] = BlackAngel;
5961 initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5962 initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5963 initialPosition[1][1] = initialPosition[2][1] =
5964 initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5966 if (appData.debugMode) {
5967 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5969 if(shuffleOpenings) {
5970 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5971 startedFromSetupPosition = TRUE;
5973 if(startedFromPositionFile) {
5974 /* [HGM] loadPos: use PositionFile for every new game */
5975 CopyBoard(initialPosition, filePosition);
5976 for(i=0; i<nrCastlingRights; i++)
5977 initialRights[i] = filePosition[CASTLING][i];
5978 startedFromSetupPosition = TRUE;
5981 CopyBoard(boards[0], initialPosition);
5983 if(oldx != gameInfo.boardWidth ||
5984 oldy != gameInfo.boardHeight ||
5985 oldv != gameInfo.variant ||
5986 oldh != gameInfo.holdingsWidth
5988 InitDrawingSizes(-2 ,0);
5990 oldv = gameInfo.variant;
5992 DrawPosition(TRUE, boards[currentMove]);
5996 SendBoard(cps, moveNum)
5997 ChessProgramState *cps;
6000 char message[MSG_SIZ];
6002 if (cps->useSetboard) {
6003 char* fen = PositionToFEN(moveNum, cps->fenOverride);
6004 snprintf(message, MSG_SIZ,"setboard %s\n", fen);
6005 SendToProgram(message, cps);
6011 /* Kludge to set black to move, avoiding the troublesome and now
6012 * deprecated "black" command.
6014 if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
6015 SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
6017 SendToProgram("edit\n", cps);
6018 SendToProgram("#\n", cps);
6019 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6020 bp = &boards[moveNum][i][BOARD_LEFT];
6021 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
6022 if ((int) *bp < (int) BlackPawn) {
6023 snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
6025 if(message[0] == '+' || message[0] == '~') {
6026 snprintf(message, MSG_SIZ,"%c%c%c+\n",
6027 PieceToChar((ChessSquare)(DEMOTED *bp)),
6030 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6031 message[1] = BOARD_RGHT - 1 - j + '1';
6032 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6034 SendToProgram(message, cps);
6039 SendToProgram("c\n", cps);
6040 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
6041 bp = &boards[moveNum][i][BOARD_LEFT];
6042 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
6043 if (((int) *bp != (int) EmptySquare)
6044 && ((int) *bp >= (int) BlackPawn)) {
6045 snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
6047 if(message[0] == '+' || message[0] == '~') {
6048 snprintf(message, MSG_SIZ,"%c%c%c+\n",
6049 PieceToChar((ChessSquare)(DEMOTED *bp)),
6052 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
6053 message[1] = BOARD_RGHT - 1 - j + '1';
6054 message[2] = BOARD_HEIGHT - 1 - i + 'a';
6056 SendToProgram(message, cps);
6061 SendToProgram(".\n", cps);
6063 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
6067 DefaultPromoChoice(int white)
6070 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
6071 result = WhiteFerz; // no choice
6072 else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
6073 result= WhiteKing; // in Suicide Q is the last thing we want
6074 else if(gameInfo.variant == VariantSpartan)
6075 result = white ? WhiteQueen : WhiteAngel;
6076 else result = WhiteQueen;
6077 if(!white) result = WHITE_TO_BLACK result;
6081 static int autoQueen; // [HGM] oneclick
6084 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice, int sweepSelect)
6086 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
6087 /* [HGM] add Shogi promotions */
6088 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
6093 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
6094 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
6096 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
6097 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
6100 piece = boards[currentMove][fromY][fromX];
6101 if(gameInfo.variant == VariantShogi) {
6102 promotionZoneSize = BOARD_HEIGHT/3;
6103 highestPromotingPiece = (int)WhiteFerz;
6104 } else if(gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand) {
6105 promotionZoneSize = 3;
6108 // Treat Lance as Pawn when it is not representing Amazon
6109 if(gameInfo.variant != VariantSuper) {
6110 if(piece == WhiteLance) piece = WhitePawn; else
6111 if(piece == BlackLance) piece = BlackPawn;
6114 // next weed out all moves that do not touch the promotion zone at all
6115 if((int)piece >= BlackPawn) {
6116 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
6118 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
6120 if( toY < BOARD_HEIGHT - promotionZoneSize &&
6121 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
6124 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
6126 // weed out mandatory Shogi promotions
6127 if(gameInfo.variant == VariantShogi) {
6128 if(piece >= BlackPawn) {
6129 if(toY == 0 && piece == BlackPawn ||
6130 toY == 0 && piece == BlackQueen ||
6131 toY <= 1 && piece == BlackKnight) {
6136 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6137 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6138 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6145 // weed out obviously illegal Pawn moves
6146 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
6147 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6148 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6149 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6150 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6151 // note we are not allowed to test for valid (non-)capture, due to premove
6154 // we either have a choice what to promote to, or (in Shogi) whether to promote
6155 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6156 *promoChoice = PieceToChar(BlackFerz); // no choice
6159 // no sense asking what we must promote to if it is going to explode...
6160 if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6161 *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6164 // give caller the default choice even if we will not make it
6165 *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6166 if(gameInfo.variant == VariantShogi) *promoChoice = (defaultPromoChoice == piece ? '=' : '+');
6167 if( sweepSelect && gameInfo.variant != VariantGreat
6168 && gameInfo.variant != VariantGrand
6169 && gameInfo.variant != VariantSuper) return FALSE;
6170 if(autoQueen) return FALSE; // predetermined
6172 // suppress promotion popup on illegal moves that are not premoves
6173 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6174 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
6175 if(appData.testLegality && !premove) {
6176 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6177 fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6178 if(moveType != WhitePromotion && moveType != BlackPromotion)
6186 InPalace(row, column)
6188 { /* [HGM] for Xiangqi */
6189 if( (row < 3 || row > BOARD_HEIGHT-4) &&
6190 column < (BOARD_WIDTH + 4)/2 &&
6191 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6196 PieceForSquare (x, y)
6200 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6203 return boards[currentMove][y][x];
6207 OKToStartUserMove(x, y)
6210 ChessSquare from_piece;
6213 if (matchMode) return FALSE;
6214 if (gameMode == EditPosition) return TRUE;
6216 if (x >= 0 && y >= 0)
6217 from_piece = boards[currentMove][y][x];
6219 from_piece = EmptySquare;
6221 if (from_piece == EmptySquare) return FALSE;
6223 white_piece = (int)from_piece >= (int)WhitePawn &&
6224 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6227 case PlayFromGameFile:
6229 case TwoMachinesPlay:
6237 case MachinePlaysWhite:
6238 case IcsPlayingBlack:
6239 if (appData.zippyPlay) return FALSE;
6241 DisplayMoveError(_("You are playing Black"));
6246 case MachinePlaysBlack:
6247 case IcsPlayingWhite:
6248 if (appData.zippyPlay) return FALSE;
6250 DisplayMoveError(_("You are playing White"));
6256 if (!white_piece && WhiteOnMove(currentMove)) {
6257 DisplayMoveError(_("It is White's turn"));
6260 if (white_piece && !WhiteOnMove(currentMove)) {
6261 DisplayMoveError(_("It is Black's turn"));
6264 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6265 /* Editing correspondence game history */
6266 /* Could disallow this or prompt for confirmation */
6271 case BeginningOfGame:
6272 if (appData.icsActive) return FALSE;
6273 if (!appData.noChessProgram) {
6275 DisplayMoveError(_("You are playing White"));
6282 if (!white_piece && WhiteOnMove(currentMove)) {
6283 DisplayMoveError(_("It is White's turn"));
6286 if (white_piece && !WhiteOnMove(currentMove)) {
6287 DisplayMoveError(_("It is Black's turn"));
6296 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6297 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6298 && gameMode != AnalyzeFile && gameMode != Training) {
6299 DisplayMoveError(_("Displayed position is not current"));
6306 OnlyMove(int *x, int *y, Boolean captures) {
6307 DisambiguateClosure cl;
6308 if (appData.zippyPlay || !appData.testLegality) return FALSE;
6310 case MachinePlaysBlack:
6311 case IcsPlayingWhite:
6312 case BeginningOfGame:
6313 if(!WhiteOnMove(currentMove)) return FALSE;
6315 case MachinePlaysWhite:
6316 case IcsPlayingBlack:
6317 if(WhiteOnMove(currentMove)) return FALSE;
6324 cl.pieceIn = EmptySquare;
6329 cl.promoCharIn = NULLCHAR;
6330 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6331 if( cl.kind == NormalMove ||
6332 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6333 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6334 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6341 if(cl.kind != ImpossibleMove) return FALSE;
6342 cl.pieceIn = EmptySquare;
6347 cl.promoCharIn = NULLCHAR;
6348 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6349 if( cl.kind == NormalMove ||
6350 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6351 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6352 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6357 autoQueen = TRUE; // act as if autoQueen on when we click to-square
6363 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6364 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6365 int lastLoadGameUseList = FALSE;
6366 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6367 ChessMove lastLoadGameStart = EndOfFile;
6370 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6371 int fromX, fromY, toX, toY;
6375 ChessSquare pdown, pup;
6377 /* Check if the user is playing in turn. This is complicated because we
6378 let the user "pick up" a piece before it is his turn. So the piece he
6379 tried to pick up may have been captured by the time he puts it down!
6380 Therefore we use the color the user is supposed to be playing in this
6381 test, not the color of the piece that is currently on the starting
6382 square---except in EditGame mode, where the user is playing both
6383 sides; fortunately there the capture race can't happen. (It can
6384 now happen in IcsExamining mode, but that's just too bad. The user
6385 will get a somewhat confusing message in that case.)
6389 case PlayFromGameFile:
6391 case TwoMachinesPlay:
6395 /* We switched into a game mode where moves are not accepted,
6396 perhaps while the mouse button was down. */
6399 case MachinePlaysWhite:
6400 /* User is moving for Black */
6401 if (WhiteOnMove(currentMove)) {
6402 DisplayMoveError(_("It is White's turn"));
6407 case MachinePlaysBlack:
6408 /* User is moving for White */
6409 if (!WhiteOnMove(currentMove)) {
6410 DisplayMoveError(_("It is Black's turn"));
6417 case BeginningOfGame:
6420 if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6421 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6422 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6423 /* User is moving for Black */
6424 if (WhiteOnMove(currentMove)) {
6425 DisplayMoveError(_("It is White's turn"));
6429 /* User is moving for White */
6430 if (!WhiteOnMove(currentMove)) {
6431 DisplayMoveError(_("It is Black's turn"));
6437 case IcsPlayingBlack:
6438 /* User is moving for Black */
6439 if (WhiteOnMove(currentMove)) {
6440 if (!appData.premove) {
6441 DisplayMoveError(_("It is White's turn"));
6442 } else if (toX >= 0 && toY >= 0) {
6445 premoveFromX = fromX;
6446 premoveFromY = fromY;
6447 premovePromoChar = promoChar;
6449 if (appData.debugMode)
6450 fprintf(debugFP, "Got premove: fromX %d,"
6451 "fromY %d, toX %d, toY %d\n",
6452 fromX, fromY, toX, toY);
6458 case IcsPlayingWhite:
6459 /* User is moving for White */
6460 if (!WhiteOnMove(currentMove)) {
6461 if (!appData.premove) {
6462 DisplayMoveError(_("It is Black's turn"));
6463 } else if (toX >= 0 && toY >= 0) {
6466 premoveFromX = fromX;
6467 premoveFromY = fromY;
6468 premovePromoChar = promoChar;
6470 if (appData.debugMode)
6471 fprintf(debugFP, "Got premove: fromX %d,"
6472 "fromY %d, toX %d, toY %d\n",
6473 fromX, fromY, toX, toY);
6483 /* EditPosition, empty square, or different color piece;
6484 click-click move is possible */
6485 if (toX == -2 || toY == -2) {
6486 boards[0][fromY][fromX] = EmptySquare;
6487 DrawPosition(FALSE, boards[currentMove]);
6489 } else if (toX >= 0 && toY >= 0) {
6490 boards[0][toY][toX] = boards[0][fromY][fromX];
6491 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6492 if(boards[0][fromY][0] != EmptySquare) {
6493 if(boards[0][fromY][1]) boards[0][fromY][1]--;
6494 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
6497 if(fromX == BOARD_RGHT+1) {
6498 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6499 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6500 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6503 boards[0][fromY][fromX] = EmptySquare;
6504 DrawPosition(FALSE, boards[currentMove]);
6510 if(toX < 0 || toY < 0) return;
6511 pdown = boards[currentMove][fromY][fromX];
6512 pup = boards[currentMove][toY][toX];
6514 /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6515 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6516 if( pup != EmptySquare ) return;
6517 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6518 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6519 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6520 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6521 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6522 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6523 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6527 /* [HGM] always test for legality, to get promotion info */
6528 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6529 fromY, fromX, toY, toX, promoChar);
6530 /* [HGM] but possibly ignore an IllegalMove result */
6531 if (appData.testLegality) {
6532 if (moveType == IllegalMove || moveType == ImpossibleMove) {
6533 DisplayMoveError(_("Illegal move"));
6538 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6541 /* Common tail of UserMoveEvent and DropMenuEvent */
6543 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6545 int fromX, fromY, toX, toY;
6546 /*char*/int promoChar;
6550 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) && promoChar != NULLCHAR) {
6551 // [HGM] superchess: suppress promotions to non-available piece (but P always allowed)
6552 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6553 if(WhiteOnMove(currentMove)) {
6554 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6556 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6560 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6561 move type in caller when we know the move is a legal promotion */
6562 if(moveType == NormalMove && promoChar)
6563 moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6565 /* [HGM] <popupFix> The following if has been moved here from
6566 UserMoveEvent(). Because it seemed to belong here (why not allow
6567 piece drops in training games?), and because it can only be
6568 performed after it is known to what we promote. */
6569 if (gameMode == Training) {
6570 /* compare the move played on the board to the next move in the
6571 * game. If they match, display the move and the opponent's response.
6572 * If they don't match, display an error message.
6576 CopyBoard(testBoard, boards[currentMove]);
6577 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6579 if (CompareBoards(testBoard, boards[currentMove+1])) {
6580 ForwardInner(currentMove+1);
6582 /* Autoplay the opponent's response.
6583 * if appData.animate was TRUE when Training mode was entered,
6584 * the response will be animated.
6586 saveAnimate = appData.animate;
6587 appData.animate = animateTraining;
6588 ForwardInner(currentMove+1);
6589 appData.animate = saveAnimate;
6591 /* check for the end of the game */
6592 if (currentMove >= forwardMostMove) {
6593 gameMode = PlayFromGameFile;
6595 SetTrainingModeOff();
6596 DisplayInformation(_("End of game"));
6599 DisplayError(_("Incorrect move"), 0);
6604 /* Ok, now we know that the move is good, so we can kill
6605 the previous line in Analysis Mode */
6606 if ((gameMode == AnalyzeMode || gameMode == EditGame)
6607 && currentMove < forwardMostMove) {
6608 if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6609 else forwardMostMove = currentMove;
6612 /* If we need the chess program but it's dead, restart it */
6613 ResurrectChessProgram();
6615 /* A user move restarts a paused game*/
6619 thinkOutput[0] = NULLCHAR;
6621 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6623 if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6624 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6628 if (gameMode == BeginningOfGame) {
6629 if (appData.noChessProgram) {
6630 gameMode = EditGame;
6634 gameMode = MachinePlaysBlack;
6637 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6639 if (first.sendName) {
6640 snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6641 SendToProgram(buf, &first);
6648 /* Relay move to ICS or chess engine */
6649 if (appData.icsActive) {
6650 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6651 gameMode == IcsExamining) {
6652 if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6653 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6655 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6657 // also send plain move, in case ICS does not understand atomic claims
6658 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6662 if (first.sendTime && (gameMode == BeginningOfGame ||
6663 gameMode == MachinePlaysWhite ||
6664 gameMode == MachinePlaysBlack)) {
6665 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6667 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6668 // [HGM] book: if program might be playing, let it use book
6669 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6670 first.maybeThinking = TRUE;
6671 } else SendMoveToProgram(forwardMostMove-1, &first);
6672 if (currentMove == cmailOldMove + 1) {
6673 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6677 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6681 if(appData.testLegality)
6682 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6688 if (WhiteOnMove(currentMove)) {
6689 GameEnds(BlackWins, "Black mates", GE_PLAYER);
6691 GameEnds(WhiteWins, "White mates", GE_PLAYER);
6695 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6700 case MachinePlaysBlack:
6701 case MachinePlaysWhite:
6702 /* disable certain menu options while machine is thinking */
6703 SetMachineThinkingEnables();
6710 userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6711 promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6713 if(bookHit) { // [HGM] book: simulate book reply
6714 static char bookMove[MSG_SIZ]; // a bit generous?
6716 programStats.nodes = programStats.depth = programStats.time =
6717 programStats.score = programStats.got_only_move = 0;
6718 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6720 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6721 strcat(bookMove, bookHit);
6722 HandleMachineMove(bookMove, &first);
6728 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6735 typedef char Markers[BOARD_RANKS][BOARD_FILES];
6736 Markers *m = (Markers *) closure;
6737 if(rf == fromY && ff == fromX)
6738 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6739 || kind == WhiteCapturesEnPassant
6740 || kind == BlackCapturesEnPassant);
6741 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6745 MarkTargetSquares(int clear)
6748 if(!appData.markers || !appData.highlightDragging || appData.icsActive && gameInfo.variant < VariantShogi ||
6749 !appData.testLegality || gameMode == EditPosition) return;
6751 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6754 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker, EmptySquare);
6755 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6756 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6758 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6761 DrawPosition(TRUE, NULL);
6765 Explode(Board board, int fromX, int fromY, int toX, int toY)
6767 if(gameInfo.variant == VariantAtomic &&
6768 (board[toY][toX] != EmptySquare || // capture?
6769 toX != fromX && (board[fromY][fromX] == WhitePawn || // e.p. ?
6770 board[fromY][fromX] == BlackPawn )
6772 AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6778 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6780 int CanPromote(ChessSquare piece, int y)
6782 if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6783 // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6784 if(gameInfo.variant == VariantShogi || gameInfo.variant == VariantXiangqi ||
6785 gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat ||
6786 gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6787 gameInfo.variant == VariantMakruk) return FALSE;
6788 return (piece == BlackPawn && y == 1 ||
6789 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6790 piece == BlackLance && y == 1 ||
6791 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6794 void LeftClick(ClickType clickType, int xPix, int yPix)
6797 Boolean saveAnimate;
6798 static int second = 0, promotionChoice = 0, clearFlag = 0;
6799 char promoChoice = NULLCHAR;
6802 if(appData.seekGraph && appData.icsActive && loggedOn &&
6803 (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6804 SeekGraphClick(clickType, xPix, yPix, 0);
6808 if (clickType == Press) ErrorPopDown();
6810 x = EventToSquare(xPix, BOARD_WIDTH);
6811 y = EventToSquare(yPix, BOARD_HEIGHT);
6812 if (!flipView && y >= 0) {
6813 y = BOARD_HEIGHT - 1 - y;
6815 if (flipView && x >= 0) {
6816 x = BOARD_WIDTH - 1 - x;
6819 if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6820 defaultPromoChoice = promoSweep;
6821 promoSweep = EmptySquare; // terminate sweep
6822 promoDefaultAltered = TRUE;
6823 if(!selectFlag && (x != toX || y != toY)) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6826 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6827 if(clickType == Release) return; // ignore upclick of click-click destination
6828 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6829 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6830 if(gameInfo.holdingsWidth &&
6831 (WhiteOnMove(currentMove)
6832 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y >= 0
6833 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT) ) {
6834 // click in right holdings, for determining promotion piece
6835 ChessSquare p = boards[currentMove][y][x];
6836 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6837 if(p == WhitePawn || p == BlackPawn) p = EmptySquare; // [HGM] Pawns could be valid as deferral
6838 if(p != EmptySquare || gameInfo.variant == VariantGrand && toY != 0 && toY != BOARD_HEIGHT-1) { // [HGM] grand: empty square means defer
6839 FinishMove(NormalMove, fromX, fromY, toX, toY, p==EmptySquare ? NULLCHAR : ToLower(PieceToChar(p)));
6844 DrawPosition(FALSE, boards[currentMove]);
6848 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6849 if(clickType == Press
6850 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6851 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6852 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6855 if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6856 fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6858 if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6859 int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6860 gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6861 defaultPromoChoice = DefaultPromoChoice(side);
6864 autoQueen = appData.alwaysPromoteToQueen;
6868 gatingPiece = EmptySquare;
6869 if (clickType != Press) {
6870 if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6871 DragPieceEnd(xPix, yPix); dragging = 0;
6872 DrawPosition(FALSE, NULL);
6876 fromX = x; fromY = y; toX = toY = -1;
6877 if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6878 // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6879 appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6881 if (OKToStartUserMove(fromX, fromY)) {
6883 MarkTargetSquares(0);
6884 DragPieceBegin(xPix, yPix, FALSE); dragging = 1;
6885 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6886 promoSweep = defaultPromoChoice;
6887 selectFlag = 0; lastX = xPix; lastY = yPix;
6888 Sweep(0); // Pawn that is going to promote: preview promotion piece
6889 DisplayMessage("", _("Pull pawn backwards to under-promote"));
6891 if (appData.highlightDragging) {
6892 SetHighlights(fromX, fromY, -1, -1);
6894 } else fromX = fromY = -1;
6900 if (clickType == Press && gameMode != EditPosition) {
6905 // ignore off-board to clicks
6906 if(y < 0 || x < 0) return;
6908 /* Check if clicking again on the same color piece */
6909 fromP = boards[currentMove][fromY][fromX];
6910 toP = boards[currentMove][y][x];
6911 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6912 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6913 WhitePawn <= toP && toP <= WhiteKing &&
6914 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6915 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6916 (BlackPawn <= fromP && fromP <= BlackKing &&
6917 BlackPawn <= toP && toP <= BlackKing &&
6918 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6919 !(fromP == BlackKing && toP == BlackRook && frc))) {
6920 /* Clicked again on same color piece -- changed his mind */
6921 second = (x == fromX && y == fromY);
6922 promoDefaultAltered = FALSE;
6923 MarkTargetSquares(1);
6924 if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6925 if (appData.highlightDragging) {
6926 SetHighlights(x, y, -1, -1);
6930 if (OKToStartUserMove(x, y)) {
6931 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6932 (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6933 y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6934 gatingPiece = boards[currentMove][fromY][fromX];
6935 else gatingPiece = EmptySquare;
6937 fromY = y; dragging = 1;
6938 MarkTargetSquares(0);
6939 DragPieceBegin(xPix, yPix, FALSE);
6940 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6941 promoSweep = defaultPromoChoice;
6942 selectFlag = 0; lastX = xPix; lastY = yPix;
6943 Sweep(0); // Pawn that is going to promote: preview promotion piece
6947 if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6950 // ignore clicks on holdings
6951 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6954 if (clickType == Release && x == fromX && y == fromY) {
6955 DragPieceEnd(xPix, yPix); dragging = 0;
6957 // a deferred attempt to click-click move an empty square on top of a piece
6958 boards[currentMove][y][x] = EmptySquare;
6960 DrawPosition(FALSE, boards[currentMove]);
6961 fromX = fromY = -1; clearFlag = 0;
6964 if (appData.animateDragging) {
6965 /* Undo animation damage if any */
6966 DrawPosition(FALSE, NULL);
6969 /* Second up/down in same square; just abort move */
6972 gatingPiece = EmptySquare;
6975 ClearPremoveHighlights();
6977 /* First upclick in same square; start click-click mode */
6978 SetHighlights(x, y, -1, -1);
6985 /* we now have a different from- and (possibly off-board) to-square */
6986 /* Completed move */
6989 saveAnimate = appData.animate;
6990 MarkTargetSquares(1);
6991 if (clickType == Press) {
6992 if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
6993 // must be Edit Position mode with empty-square selected
6994 fromX = x; fromY = y; DragPieceBegin(xPix, yPix, FALSE); dragging = 1; // consider this a new attempt to drag
6995 if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
6998 if(appData.sweepSelect && HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, FALSE)) {
6999 ChessSquare piece = boards[currentMove][fromY][fromX];
7000 DragPieceBegin(xPix, yPix, TRUE); dragging = 1;
7001 promoSweep = defaultPromoChoice;
7002 if(PieceToChar(PROMOTED piece) == '+') promoSweep = PROMOTED piece;
7003 selectFlag = 0; lastX = xPix; lastY = yPix;
7004 Sweep(0); // Pawn that is going to promote: preview promotion piece
7005 DisplayMessage("", _("Pull pawn backwards to under-promote"));
7006 DrawPosition(FALSE, boards[currentMove]);
7009 /* Finish clickclick move */
7010 if (appData.animate || appData.highlightLastMove) {
7011 SetHighlights(fromX, fromY, toX, toY);
7016 /* Finish drag move */
7017 if (appData.highlightLastMove) {
7018 SetHighlights(fromX, fromY, toX, toY);
7022 DragPieceEnd(xPix, yPix); dragging = 0;
7023 /* Don't animate move and drag both */
7024 appData.animate = FALSE;
7027 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
7028 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
7029 ChessSquare piece = boards[currentMove][fromY][fromX];
7030 if(gameMode == EditPosition && piece != EmptySquare &&
7031 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
7034 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
7035 n = PieceToNumber(piece - (int)BlackPawn);
7036 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
7037 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
7038 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
7040 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
7041 n = PieceToNumber(piece);
7042 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
7043 boards[currentMove][n][BOARD_WIDTH-1] = piece;
7044 boards[currentMove][n][BOARD_WIDTH-2]++;
7046 boards[currentMove][fromY][fromX] = EmptySquare;
7050 DrawPosition(TRUE, boards[currentMove]);
7054 // off-board moves should not be highlighted
7055 if(x < 0 || y < 0) ClearHighlights();
7057 if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
7059 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice, appData.sweepSelect)) {
7060 SetHighlights(fromX, fromY, toX, toY);
7061 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
7062 // [HGM] super: promotion to captured piece selected from holdings
7063 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
7064 promotionChoice = TRUE;
7065 // kludge follows to temporarily execute move on display, without promoting yet
7066 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
7067 boards[currentMove][toY][toX] = p;
7068 DrawPosition(FALSE, boards[currentMove]);
7069 boards[currentMove][fromY][fromX] = p; // take back, but display stays
7070 boards[currentMove][toY][toX] = q;
7071 DisplayMessage("Click in holdings to choose piece", "");
7076 int oldMove = currentMove;
7077 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
7078 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
7079 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
7080 if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
7081 Explode(boards[currentMove-1], fromX, fromY, toX, toY))
7082 DrawPosition(TRUE, boards[currentMove]);
7085 appData.animate = saveAnimate;
7086 if (appData.animate || appData.animateDragging) {
7087 /* Undo animation damage if needed */
7088 DrawPosition(FALSE, NULL);
7092 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
7093 { // front-end-free part taken out of PieceMenuPopup
7094 int whichMenu; int xSqr, ySqr;
7096 if(seekGraphUp) { // [HGM] seekgraph
7097 if(action == Press) SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
7098 if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
7102 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
7103 && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
7104 if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
7105 if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
7106 if(action == Press) {
7107 originalFlip = flipView;
7108 flipView = !flipView; // temporarily flip board to see game from partners perspective
7109 DrawPosition(TRUE, partnerBoard);
7110 DisplayMessage(partnerStatus, "");
7112 } else if(action == Release) {
7113 flipView = originalFlip;
7114 DrawPosition(TRUE, boards[currentMove]);
7120 xSqr = EventToSquare(x, BOARD_WIDTH);
7121 ySqr = EventToSquare(y, BOARD_HEIGHT);
7122 if (action == Release) {
7123 if(pieceSweep != EmptySquare) {
7124 EditPositionMenuEvent(pieceSweep, toX, toY);
7125 pieceSweep = EmptySquare;
7126 } else UnLoadPV(); // [HGM] pv
7128 if (action != Press) return -2; // return code to be ignored
7131 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
7133 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
7134 if (xSqr < 0 || ySqr < 0) return -1;
7135 if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
7136 pieceSweep = shiftKey ? BlackPawn : WhitePawn; // [HGM] sweep: prepare selecting piece by mouse sweep
7137 toX = xSqr; toY = ySqr; lastX = x, lastY = y;
7138 if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
7142 if(!appData.icsEngineAnalyze) return -1;
7143 case IcsPlayingWhite:
7144 case IcsPlayingBlack:
7145 if(!appData.zippyPlay) goto noZip;
7148 case MachinePlaysWhite:
7149 case MachinePlaysBlack:
7150 case TwoMachinesPlay: // [HGM] pv: use for showing PV
7151 if (!appData.dropMenu) {
7153 return 2; // flag front-end to grab mouse events
7155 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7156 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7159 if (xSqr < 0 || ySqr < 0) return -1;
7160 if (!appData.dropMenu || appData.testLegality &&
7161 gameInfo.variant != VariantBughouse &&
7162 gameInfo.variant != VariantCrazyhouse) return -1;
7163 whichMenu = 1; // drop menu
7169 if (((*fromX = xSqr) < 0) ||
7170 ((*fromY = ySqr) < 0)) {
7171 *fromX = *fromY = -1;
7175 *fromX = BOARD_WIDTH - 1 - *fromX;
7177 *fromY = BOARD_HEIGHT - 1 - *fromY;
7182 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
7184 // char * hint = lastHint;
7185 FrontEndProgramStats stats;
7187 stats.which = cps == &first ? 0 : 1;
7188 stats.depth = cpstats->depth;
7189 stats.nodes = cpstats->nodes;
7190 stats.score = cpstats->score;
7191 stats.time = cpstats->time;
7192 stats.pv = cpstats->movelist;
7193 stats.hint = lastHint;
7194 stats.an_move_index = 0;
7195 stats.an_move_count = 0;
7197 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7198 stats.hint = cpstats->move_name;
7199 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7200 stats.an_move_count = cpstats->nr_moves;
7203 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
7205 SetProgramStats( &stats );
7209 ClearEngineOutputPane(int which)
7211 static FrontEndProgramStats dummyStats;
7212 dummyStats.which = which;
7213 dummyStats.pv = "#";
7214 SetProgramStats( &dummyStats );
7217 #define MAXPLAYERS 500
7220 TourneyStandings(int display)
7222 int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7223 int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7224 char result, *p, *names[MAXPLAYERS];
7226 if(appData.tourneyType < 0 && !strchr(appData.results, '*'))
7227 return strdup(_("Swiss tourney finished")); // standings of Swiss yet TODO
7228 names[0] = p = strdup(appData.participants);
7229 while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7231 for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7233 while(result = appData.results[nr]) {
7234 color = Pairing(nr, nPlayers, &w, &b, &dummy);
7235 if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7236 wScore = bScore = 0;
7238 case '+': wScore = 2; break;
7239 case '-': bScore = 2; break;
7240 case '=': wScore = bScore = 1; break;
7242 case '*': return strdup("busy"); // tourney not finished
7250 if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7251 for(w=0; w<nPlayers; w++) {
7253 for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7254 ranking[w] = b; points[w] = bScore; score[b] = -2;
7256 p = malloc(nPlayers*34+1);
7257 for(w=0; w<nPlayers && w<display; w++)
7258 sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7264 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7265 { // count all piece types
7267 *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7268 for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7269 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7272 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7273 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7274 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7275 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7276 p == BlackBishop || p == BlackFerz || p == BlackAlfil )
7277 *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7282 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
7284 int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7285 int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7287 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7288 if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7289 if(myPawns == 2 && nMine == 3) // KPP
7290 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7291 if(myPawns == 1 && nMine == 2) // KP
7292 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7293 if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7294 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7295 if(myPawns) return FALSE;
7296 if(pCnt[WhiteRook+side])
7297 return pCnt[BlackRook-side] ||
7298 pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7299 pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7300 pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7301 if(pCnt[WhiteCannon+side]) {
7302 if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7303 return majorDefense || pCnt[BlackAlfil-side] >= 2;
7305 if(pCnt[WhiteKnight+side])
7306 return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7311 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7313 VariantClass v = gameInfo.variant;
7315 if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7316 if(v == VariantShatranj) return TRUE; // always winnable through baring
7317 if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7318 if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7320 if(v == VariantXiangqi) {
7321 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7323 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7324 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7325 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7326 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7327 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7328 if(stale) // we have at least one last-rank P plus perhaps C
7329 return majors // KPKX
7330 || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7332 return pCnt[WhiteFerz+side] // KCAK
7333 || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7334 || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7335 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7337 } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7338 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7340 if(nMine == 1) return FALSE; // bare King
7341 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
7342 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7343 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7344 // by now we have King + 1 piece (or multiple Bishops on the same color)
7345 if(pCnt[WhiteKnight+side])
7346 return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7347 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7348 || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7350 return (pCnt[BlackKnight-side]); // KBKN, KFKN
7351 if(pCnt[WhiteAlfil+side])
7352 return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7353 if(pCnt[WhiteWazir+side])
7354 return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7361 Adjudicate(ChessProgramState *cps)
7362 { // [HGM] some adjudications useful with buggy engines
7363 // [HGM] adjudicate: made into separate routine, which now can be called after every move
7364 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7365 // Actually ending the game is now based on the additional internal condition canAdjudicate.
7366 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7367 int k, count = 0; static int bare = 1;
7368 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7369 Boolean canAdjudicate = !appData.icsActive;
7371 // most tests only when we understand the game, i.e. legality-checking on
7372 if( appData.testLegality )
7373 { /* [HGM] Some more adjudications for obstinate engines */
7374 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7375 static int moveCount = 6;
7377 char *reason = NULL;
7379 /* Count what is on board. */
7380 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7382 /* Some material-based adjudications that have to be made before stalemate test */
7383 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7384 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7385 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7386 if(canAdjudicate && appData.checkMates) {
7388 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7389 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7390 "Xboard adjudication: King destroyed", GE_XBOARD );
7395 /* Bare King in Shatranj (loses) or Losers (wins) */
7396 if( nrW == 1 || nrB == 1) {
7397 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7398 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
7399 if(canAdjudicate && appData.checkMates) {
7401 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7402 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7403 "Xboard adjudication: Bare king", GE_XBOARD );
7407 if( gameInfo.variant == VariantShatranj && --bare < 0)
7409 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7410 if(canAdjudicate && appData.checkMates) {
7411 /* but only adjudicate if adjudication enabled */
7413 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7414 GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7415 "Xboard adjudication: Bare king", GE_XBOARD );
7422 // don't wait for engine to announce game end if we can judge ourselves
7423 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7425 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7426 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
7427 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7428 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7431 reason = "Xboard adjudication: 3rd check";
7432 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7442 reason = "Xboard adjudication: Stalemate";
7443 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7444 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
7445 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7446 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
7447 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7448 boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7449 ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7450 EP_CHECKMATE : EP_WINS);
7451 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7452 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7456 reason = "Xboard adjudication: Checkmate";
7457 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7461 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7463 result = GameIsDrawn; break;
7465 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7467 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7471 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7473 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7474 GameEnds( result, reason, GE_XBOARD );
7478 /* Next absolutely insufficient mating material. */
7479 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7480 !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7481 { /* includes KBK, KNK, KK of KBKB with like Bishops */
7483 /* always flag draws, for judging claims */
7484 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7486 if(canAdjudicate && appData.materialDraws) {
7487 /* but only adjudicate them if adjudication enabled */
7488 if(engineOpponent) {
7489 SendToProgram("force\n", engineOpponent); // suppress reply
7490 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7492 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7497 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7498 if(gameInfo.variant == VariantXiangqi ?
7499 SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7501 ( nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7502 || nr[WhiteQueen] && nr[BlackQueen]==1 /* KQKQ */
7503 || nr[WhiteKnight]==2 || nr[BlackKnight]==2 /* KNNK */
7504 || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7506 if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7507 { /* if the first 3 moves do not show a tactical win, declare draw */
7508 if(engineOpponent) {
7509 SendToProgram("force\n", engineOpponent); // suppress reply
7510 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7512 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7515 } else moveCount = 6;
7517 if (appData.debugMode) { int i;
7518 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7519 forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7520 appData.drawRepeats);
7521 for( i=forwardMostMove; i>=backwardMostMove; i-- )
7522 fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7526 // Repetition draws and 50-move rule can be applied independently of legality testing
7528 /* Check for rep-draws */
7530 for(k = forwardMostMove-2;
7531 k>=backwardMostMove && k>=forwardMostMove-100 &&
7532 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7533 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7536 if(CompareBoards(boards[k], boards[forwardMostMove])) {
7537 /* compare castling rights */
7538 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7539 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7540 rights++; /* King lost rights, while rook still had them */
7541 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7542 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7543 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7544 rights++; /* but at least one rook lost them */
7546 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7547 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7549 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7550 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7551 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7554 if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7555 && appData.drawRepeats > 1) {
7556 /* adjudicate after user-specified nr of repeats */
7557 int result = GameIsDrawn;
7558 char *details = "XBoard adjudication: repetition draw";
7559 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7560 // [HGM] xiangqi: check for forbidden perpetuals
7561 int m, ourPerpetual = 1, hisPerpetual = 1;
7562 for(m=forwardMostMove; m>k; m-=2) {
7563 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7564 ourPerpetual = 0; // the current mover did not always check
7565 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7566 hisPerpetual = 0; // the opponent did not always check
7568 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7569 ourPerpetual, hisPerpetual);
7570 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7571 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7572 details = "Xboard adjudication: perpetual checking";
7574 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7575 break; // (or we would have caught him before). Abort repetition-checking loop.
7577 // Now check for perpetual chases
7578 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7579 hisPerpetual = PerpetualChase(k, forwardMostMove);
7580 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7581 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7582 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7583 details = "Xboard adjudication: perpetual chasing";
7585 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
7586 break; // Abort repetition-checking loop.
7588 // if neither of us is checking or chasing all the time, or both are, it is draw
7590 if(engineOpponent) {
7591 SendToProgram("force\n", engineOpponent); // suppress reply
7592 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7594 GameEnds( result, details, GE_XBOARD );
7597 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7598 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7602 /* Now we test for 50-move draws. Determine ply count */
7603 count = forwardMostMove;
7604 /* look for last irreversble move */
7605 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7607 /* if we hit starting position, add initial plies */
7608 if( count == backwardMostMove )
7609 count -= initialRulePlies;
7610 count = forwardMostMove - count;
7611 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7612 // adjust reversible move counter for checks in Xiangqi
7613 int i = forwardMostMove - count, inCheck = 0, lastCheck;
7614 if(i < backwardMostMove) i = backwardMostMove;
7615 while(i <= forwardMostMove) {
7616 lastCheck = inCheck; // check evasion does not count
7617 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7618 if(inCheck || lastCheck) count--; // check does not count
7623 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7624 /* this is used to judge if draw claims are legal */
7625 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7626 if(engineOpponent) {
7627 SendToProgram("force\n", engineOpponent); // suppress reply
7628 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7630 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7634 /* if draw offer is pending, treat it as a draw claim
7635 * when draw condition present, to allow engines a way to
7636 * claim draws before making their move to avoid a race
7637 * condition occurring after their move
7639 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7641 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7642 p = "Draw claim: 50-move rule";
7643 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7644 p = "Draw claim: 3-fold repetition";
7645 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7646 p = "Draw claim: insufficient mating material";
7647 if( p != NULL && canAdjudicate) {
7648 if(engineOpponent) {
7649 SendToProgram("force\n", engineOpponent); // suppress reply
7650 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7652 GameEnds( GameIsDrawn, p, GE_XBOARD );
7657 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7658 if(engineOpponent) {
7659 SendToProgram("force\n", engineOpponent); // suppress reply
7660 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7662 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7668 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7669 { // [HGM] book: this routine intercepts moves to simulate book replies
7670 char *bookHit = NULL;
7672 //first determine if the incoming move brings opponent into his book
7673 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7674 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7675 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7676 if(bookHit != NULL && !cps->bookSuspend) {
7677 // make sure opponent is not going to reply after receiving move to book position
7678 SendToProgram("force\n", cps);
7679 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7681 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7682 // now arrange restart after book miss
7684 // after a book hit we never send 'go', and the code after the call to this routine
7685 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7686 char buf[MSG_SIZ], *move = bookHit;
7688 int fromX, fromY, toX, toY;
7692 if (ParseOneMove(bookHit, forwardMostMove, &moveType,
7693 &fromX, &fromY, &toX, &toY, &promoChar)) {
7694 (void) CoordsToAlgebraic(boards[forwardMostMove],
7695 PosFlags(forwardMostMove),
7696 fromY, fromX, toY, toX, promoChar, move);
7698 if(appData.debugMode) fprintf(debugFP, "Book move could not be parsed\n");
7702 snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), move); // force book move into program supposed to play it
7703 SendToProgram(buf, cps);
7704 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7705 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7706 SendToProgram("go\n", cps);
7707 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7708 } else { // 'go' might be sent based on 'firstMove' after this routine returns
7709 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7710 SendToProgram("go\n", cps);
7711 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7713 return bookHit; // notify caller of hit, so it can take action to send move to opponent
7717 ChessProgramState *savedState;
7718 void DeferredBookMove(void)
7720 if(savedState->lastPing != savedState->lastPong)
7721 ScheduleDelayedEvent(DeferredBookMove, 10);
7723 HandleMachineMove(savedMessage, savedState);
7726 static int savedWhitePlayer, savedBlackPlayer, pairingReceived;
7729 HandleMachineMove(message, cps)
7731 ChessProgramState *cps;
7733 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7734 char realname[MSG_SIZ];
7735 int fromX, fromY, toX, toY;
7742 if(cps == &pairing && sscanf(message, "%d-%d", &savedWhitePlayer, &savedBlackPlayer) == 2) {
7743 // [HGM] pairing: Mega-hack! Pairing engine also uses this routine (so it could give other WB commands).
7744 if(savedWhitePlayer == 0 || savedBlackPlayer == 0) {
7745 DisplayError(_("Invalid pairing from pairing engine"), 0);
7748 pairingReceived = 1;
7750 return; // Skim the pairing messages here.
7755 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7757 * Kludge to ignore BEL characters
7759 while (*message == '\007') message++;
7762 * [HGM] engine debug message: ignore lines starting with '#' character
7764 if(cps->debug && *message == '#') return;
7767 * Look for book output
7769 if (cps == &first && bookRequested) {
7770 if (message[0] == '\t' || message[0] == ' ') {
7771 /* Part of the book output is here; append it */
7772 strcat(bookOutput, message);
7773 strcat(bookOutput, " \n");
7775 } else if (bookOutput[0] != NULLCHAR) {
7776 /* All of book output has arrived; display it */
7777 char *p = bookOutput;
7778 while (*p != NULLCHAR) {
7779 if (*p == '\t') *p = ' ';
7782 DisplayInformation(bookOutput);
7783 bookRequested = FALSE;
7784 /* Fall through to parse the current output */
7789 * Look for machine move.
7791 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7792 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7794 /* This method is only useful on engines that support ping */
7795 if (cps->lastPing != cps->lastPong) {
7796 if (gameMode == BeginningOfGame) {
7797 /* Extra move from before last new; ignore */
7798 if (appData.debugMode) {
7799 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7802 if (appData.debugMode) {
7803 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7804 cps->which, gameMode);
7807 SendToProgram("undo\n", cps);
7813 case BeginningOfGame:
7814 /* Extra move from before last reset; ignore */
7815 if (appData.debugMode) {
7816 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7823 /* Extra move after we tried to stop. The mode test is
7824 not a reliable way of detecting this problem, but it's
7825 the best we can do on engines that don't support ping.
7827 if (appData.debugMode) {
7828 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7829 cps->which, gameMode);
7831 SendToProgram("undo\n", cps);
7834 case MachinePlaysWhite:
7835 case IcsPlayingWhite:
7836 machineWhite = TRUE;
7839 case MachinePlaysBlack:
7840 case IcsPlayingBlack:
7841 machineWhite = FALSE;
7844 case TwoMachinesPlay:
7845 machineWhite = (cps->twoMachinesColor[0] == 'w');
7848 if (WhiteOnMove(forwardMostMove) != machineWhite) {
7849 if (appData.debugMode) {
7851 "Ignoring move out of turn by %s, gameMode %d"
7852 ", forwardMost %d\n",
7853 cps->which, gameMode, forwardMostMove);
7858 if (appData.debugMode) { int f = forwardMostMove;
7859 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7860 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7861 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7863 if(cps->alphaRank) AlphaRank(machineMove, 4);
7864 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7865 &fromX, &fromY, &toX, &toY, &promoChar)) {
7866 /* Machine move could not be parsed; ignore it. */
7867 snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7868 machineMove, _(cps->which));
7869 DisplayError(buf1, 0);
7870 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7871 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7872 if (gameMode == TwoMachinesPlay) {
7873 GameEnds(machineWhite ? BlackWins : WhiteWins,
7879 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7880 /* So we have to redo legality test with true e.p. status here, */
7881 /* to make sure an illegal e.p. capture does not slip through, */
7882 /* to cause a forfeit on a justified illegal-move complaint */
7883 /* of the opponent. */
7884 if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7886 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7887 fromY, fromX, toY, toX, promoChar);
7888 if (appData.debugMode) {
7890 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7891 boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7892 fprintf(debugFP, "castling rights\n");
7894 if(moveType == IllegalMove) {
7895 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7896 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7897 GameEnds(machineWhite ? BlackWins : WhiteWins,
7900 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7901 /* [HGM] Kludge to handle engines that send FRC-style castling
7902 when they shouldn't (like TSCP-Gothic) */
7904 case WhiteASideCastleFR:
7905 case BlackASideCastleFR:
7907 currentMoveString[2]++;
7909 case WhiteHSideCastleFR:
7910 case BlackHSideCastleFR:
7912 currentMoveString[2]--;
7914 default: ; // nothing to do, but suppresses warning of pedantic compilers
7917 hintRequested = FALSE;
7918 lastHint[0] = NULLCHAR;
7919 bookRequested = FALSE;
7920 /* Program may be pondering now */
7921 cps->maybeThinking = TRUE;
7922 if (cps->sendTime == 2) cps->sendTime = 1;
7923 if (cps->offeredDraw) cps->offeredDraw--;
7925 /* [AS] Save move info*/
7926 pvInfoList[ forwardMostMove ].score = programStats.score;
7927 pvInfoList[ forwardMostMove ].depth = programStats.depth;
7928 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
7930 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7932 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7933 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7936 while( count < adjudicateLossPlies ) {
7937 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7940 score = -score; /* Flip score for winning side */
7943 if( score > adjudicateLossThreshold ) {
7950 if( count >= adjudicateLossPlies ) {
7951 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7953 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7954 "Xboard adjudication",
7961 if(Adjudicate(cps)) {
7962 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7963 return; // [HGM] adjudicate: for all automatic game ends
7967 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7969 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7970 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7972 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7974 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7976 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7977 char buf[3*MSG_SIZ];
7979 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7980 programStats.score / 100.,
7982 programStats.time / 100.,
7983 (unsigned int)programStats.nodes,
7984 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7985 programStats.movelist);
7987 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7992 /* [AS] Clear stats for next move */
7993 ClearProgramStats();
7994 thinkOutput[0] = NULLCHAR;
7995 hiddenThinkOutputState = 0;
7998 if (gameMode == TwoMachinesPlay) {
7999 /* [HGM] relaying draw offers moved to after reception of move */
8000 /* and interpreting offer as claim if it brings draw condition */
8001 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
8002 SendToProgram("draw\n", cps->other);
8004 if (cps->other->sendTime) {
8005 SendTimeRemaining(cps->other,
8006 cps->other->twoMachinesColor[0] == 'w');
8008 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
8009 if (firstMove && !bookHit) {
8011 if (cps->other->useColors) {
8012 SendToProgram(cps->other->twoMachinesColor, cps->other);
8014 SendToProgram("go\n", cps->other);
8016 cps->other->maybeThinking = TRUE;
8019 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
8021 if (!pausing && appData.ringBellAfterMoves) {
8026 * Reenable menu items that were disabled while
8027 * machine was thinking
8029 if (gameMode != TwoMachinesPlay)
8030 SetUserThinkingEnables();
8032 // [HGM] book: after book hit opponent has received move and is now in force mode
8033 // force the book reply into it, and then fake that it outputted this move by jumping
8034 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
8036 static char bookMove[MSG_SIZ]; // a bit generous?
8038 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
8039 strcat(bookMove, bookHit);
8042 programStats.nodes = programStats.depth = programStats.time =
8043 programStats.score = programStats.got_only_move = 0;
8044 sprintf(programStats.movelist, "%s (xbook)", bookHit);
8046 if(cps->lastPing != cps->lastPong) {
8047 savedMessage = message; // args for deferred call
8049 ScheduleDelayedEvent(DeferredBookMove, 10);
8058 /* Set special modes for chess engines. Later something general
8059 * could be added here; for now there is just one kludge feature,
8060 * needed because Crafty 15.10 and earlier don't ignore SIGINT
8061 * when "xboard" is given as an interactive command.
8063 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
8064 cps->useSigint = FALSE;
8065 cps->useSigterm = FALSE;
8067 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
8068 ParseFeatures(message+8, cps);
8069 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
8072 if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
8073 int dummy, s=6; char buf[MSG_SIZ];
8074 if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
8075 if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
8076 ParseFEN(boards[0], &dummy, message+s);
8077 DrawPosition(TRUE, boards[0]);
8078 startedFromSetupPosition = TRUE;
8081 /* [HGM] Allow engine to set up a position. Don't ask me why one would
8082 * want this, I was asked to put it in, and obliged.
8084 if (!strncmp(message, "setboard ", 9)) {
8085 Board initial_position;
8087 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
8089 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
8090 DisplayError(_("Bad FEN received from engine"), 0);
8094 CopyBoard(boards[0], initial_position);
8095 initialRulePlies = FENrulePlies;
8096 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
8097 else gameMode = MachinePlaysBlack;
8098 DrawPosition(FALSE, boards[currentMove]);
8104 * Look for communication commands
8106 if (!strncmp(message, "telluser ", 9)) {
8107 if(message[9] == '\\' && message[10] == '\\')
8108 EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
8110 DisplayNote(message + 9);
8113 if (!strncmp(message, "tellusererror ", 14)) {
8115 if(message[14] == '\\' && message[15] == '\\')
8116 EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
8118 DisplayError(message + 14, 0);
8121 if (!strncmp(message, "tellopponent ", 13)) {
8122 if (appData.icsActive) {
8124 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
8128 DisplayNote(message + 13);
8132 if (!strncmp(message, "tellothers ", 11)) {
8133 if (appData.icsActive) {
8135 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
8141 if (!strncmp(message, "tellall ", 8)) {
8142 if (appData.icsActive) {
8144 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
8148 DisplayNote(message + 8);
8152 if (strncmp(message, "warning", 7) == 0) {
8153 /* Undocumented feature, use tellusererror in new code */
8154 DisplayError(message, 0);
8157 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
8158 safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
8159 strcat(realname, " query");
8160 AskQuestion(realname, buf2, buf1, cps->pr);
8163 /* Commands from the engine directly to ICS. We don't allow these to be
8164 * sent until we are logged on. Crafty kibitzes have been known to
8165 * interfere with the login process.
8168 if (!strncmp(message, "tellics ", 8)) {
8169 SendToICS(message + 8);
8173 if (!strncmp(message, "tellicsnoalias ", 15)) {
8174 SendToICS(ics_prefix);
8175 SendToICS(message + 15);
8179 /* The following are for backward compatibility only */
8180 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
8181 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
8182 SendToICS(ics_prefix);
8188 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8192 * If the move is illegal, cancel it and redraw the board.
8193 * Also deal with other error cases. Matching is rather loose
8194 * here to accommodate engines written before the spec.
8196 if (strncmp(message + 1, "llegal move", 11) == 0 ||
8197 strncmp(message, "Error", 5) == 0) {
8198 if (StrStr(message, "name") ||
8199 StrStr(message, "rating") || StrStr(message, "?") ||
8200 StrStr(message, "result") || StrStr(message, "board") ||
8201 StrStr(message, "bk") || StrStr(message, "computer") ||
8202 StrStr(message, "variant") || StrStr(message, "hint") ||
8203 StrStr(message, "random") || StrStr(message, "depth") ||
8204 StrStr(message, "accepted")) {
8207 if (StrStr(message, "protover")) {
8208 /* Program is responding to input, so it's apparently done
8209 initializing, and this error message indicates it is
8210 protocol version 1. So we don't need to wait any longer
8211 for it to initialize and send feature commands. */
8212 FeatureDone(cps, 1);
8213 cps->protocolVersion = 1;
8216 cps->maybeThinking = FALSE;
8218 if (StrStr(message, "draw")) {
8219 /* Program doesn't have "draw" command */
8220 cps->sendDrawOffers = 0;
8223 if (cps->sendTime != 1 &&
8224 (StrStr(message, "time") || StrStr(message, "otim"))) {
8225 /* Program apparently doesn't have "time" or "otim" command */
8229 if (StrStr(message, "analyze")) {
8230 cps->analysisSupport = FALSE;
8231 cps->analyzing = FALSE;
8233 snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8234 DisplayError(buf2, 0);
8237 if (StrStr(message, "(no matching move)st")) {
8238 /* Special kludge for GNU Chess 4 only */
8239 cps->stKludge = TRUE;
8240 SendTimeControl(cps, movesPerSession, timeControl,
8241 timeIncrement, appData.searchDepth,
8245 if (StrStr(message, "(no matching move)sd")) {
8246 /* Special kludge for GNU Chess 4 only */
8247 cps->sdKludge = TRUE;
8248 SendTimeControl(cps, movesPerSession, timeControl,
8249 timeIncrement, appData.searchDepth,
8253 if (!StrStr(message, "llegal")) {
8256 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8257 gameMode == IcsIdle) return;
8258 if (forwardMostMove <= backwardMostMove) return;
8259 if (pausing) PauseEvent();
8260 if(appData.forceIllegal) {
8261 // [HGM] illegal: machine refused move; force position after move into it
8262 SendToProgram("force\n", cps);
8263 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8264 // we have a real problem now, as SendBoard will use the a2a3 kludge
8265 // when black is to move, while there might be nothing on a2 or black
8266 // might already have the move. So send the board as if white has the move.
8267 // But first we must change the stm of the engine, as it refused the last move
8268 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8269 if(WhiteOnMove(forwardMostMove)) {
8270 SendToProgram("a7a6\n", cps); // for the engine black still had the move
8271 SendBoard(cps, forwardMostMove); // kludgeless board
8273 SendToProgram("a2a3\n", cps); // for the engine white still had the move
8274 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8275 SendBoard(cps, forwardMostMove+1); // kludgeless board
8277 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8278 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8279 gameMode == TwoMachinesPlay)
8280 SendToProgram("go\n", cps);
8283 if (gameMode == PlayFromGameFile) {
8284 /* Stop reading this game file */
8285 gameMode = EditGame;
8288 /* [HGM] illegal-move claim should forfeit game when Xboard */
8289 /* only passes fully legal moves */
8290 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8291 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8292 "False illegal-move claim", GE_XBOARD );
8293 return; // do not take back move we tested as valid
8295 currentMove = forwardMostMove-1;
8296 DisplayMove(currentMove-1); /* before DisplayMoveError */
8297 SwitchClocks(forwardMostMove-1); // [HGM] race
8298 DisplayBothClocks();
8299 snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8300 parseList[currentMove], _(cps->which));
8301 DisplayMoveError(buf1);
8302 DrawPosition(FALSE, boards[currentMove]);
8305 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8306 /* Program has a broken "time" command that
8307 outputs a string not ending in newline.
8313 * If chess program startup fails, exit with an error message.
8314 * Attempts to recover here are futile.
8316 if ((StrStr(message, "unknown host") != NULL)
8317 || (StrStr(message, "No remote directory") != NULL)
8318 || (StrStr(message, "not found") != NULL)
8319 || (StrStr(message, "No such file") != NULL)
8320 || (StrStr(message, "can't alloc") != NULL)
8321 || (StrStr(message, "Permission denied") != NULL)) {
8323 cps->maybeThinking = FALSE;
8324 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8325 _(cps->which), cps->program, cps->host, message);
8326 RemoveInputSource(cps->isr);
8327 if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8328 if(cps == &first) appData.noChessProgram = TRUE;
8329 DisplayError(buf1, 0);
8335 * Look for hint output
8337 if (sscanf(message, "Hint: %s", buf1) == 1) {
8338 if (cps == &first && hintRequested) {
8339 hintRequested = FALSE;
8340 if (ParseOneMove(buf1, forwardMostMove, &moveType,
8341 &fromX, &fromY, &toX, &toY, &promoChar)) {
8342 (void) CoordsToAlgebraic(boards[forwardMostMove],
8343 PosFlags(forwardMostMove),
8344 fromY, fromX, toY, toX, promoChar, buf1);
8345 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8346 DisplayInformation(buf2);
8348 /* Hint move could not be parsed!? */
8349 snprintf(buf2, sizeof(buf2),
8350 _("Illegal hint move \"%s\"\nfrom %s chess program"),
8351 buf1, _(cps->which));
8352 DisplayError(buf2, 0);
8355 safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8361 * Ignore other messages if game is not in progress
8363 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8364 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8367 * look for win, lose, draw, or draw offer
8369 if (strncmp(message, "1-0", 3) == 0) {
8370 char *p, *q, *r = "";
8371 p = strchr(message, '{');
8379 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8381 } else if (strncmp(message, "0-1", 3) == 0) {
8382 char *p, *q, *r = "";
8383 p = strchr(message, '{');
8391 /* Kludge for Arasan 4.1 bug */
8392 if (strcmp(r, "Black resigns") == 0) {
8393 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8396 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8398 } else if (strncmp(message, "1/2", 3) == 0) {
8399 char *p, *q, *r = "";
8400 p = strchr(message, '{');
8409 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8412 } else if (strncmp(message, "White resign", 12) == 0) {
8413 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8415 } else if (strncmp(message, "Black resign", 12) == 0) {
8416 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8418 } else if (strncmp(message, "White matches", 13) == 0 ||
8419 strncmp(message, "Black matches", 13) == 0 ) {
8420 /* [HGM] ignore GNUShogi noises */
8422 } else if (strncmp(message, "White", 5) == 0 &&
8423 message[5] != '(' &&
8424 StrStr(message, "Black") == NULL) {
8425 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8427 } else if (strncmp(message, "Black", 5) == 0 &&
8428 message[5] != '(') {
8429 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8431 } else if (strcmp(message, "resign") == 0 ||
8432 strcmp(message, "computer resigns") == 0) {
8434 case MachinePlaysBlack:
8435 case IcsPlayingBlack:
8436 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8438 case MachinePlaysWhite:
8439 case IcsPlayingWhite:
8440 GameEnds(BlackWins, "White resigns", GE_ENGINE);
8442 case TwoMachinesPlay:
8443 if (cps->twoMachinesColor[0] == 'w')
8444 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8446 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8453 } else if (strncmp(message, "opponent mates", 14) == 0) {
8455 case MachinePlaysBlack:
8456 case IcsPlayingBlack:
8457 GameEnds(WhiteWins, "White mates", GE_ENGINE);
8459 case MachinePlaysWhite:
8460 case IcsPlayingWhite:
8461 GameEnds(BlackWins, "Black mates", GE_ENGINE);
8463 case TwoMachinesPlay:
8464 if (cps->twoMachinesColor[0] == 'w')
8465 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8467 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8474 } else if (strncmp(message, "computer mates", 14) == 0) {
8476 case MachinePlaysBlack:
8477 case IcsPlayingBlack:
8478 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8480 case MachinePlaysWhite:
8481 case IcsPlayingWhite:
8482 GameEnds(WhiteWins, "White mates", GE_ENGINE);
8484 case TwoMachinesPlay:
8485 if (cps->twoMachinesColor[0] == 'w')
8486 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8488 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8495 } else if (strncmp(message, "checkmate", 9) == 0) {
8496 if (WhiteOnMove(forwardMostMove)) {
8497 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8499 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8502 } else if (strstr(message, "Draw") != NULL ||
8503 strstr(message, "game is a draw") != NULL) {
8504 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8506 } else if (strstr(message, "offer") != NULL &&
8507 strstr(message, "draw") != NULL) {
8509 if (appData.zippyPlay && first.initDone) {
8510 /* Relay offer to ICS */
8511 SendToICS(ics_prefix);
8512 SendToICS("draw\n");
8515 cps->offeredDraw = 2; /* valid until this engine moves twice */
8516 if (gameMode == TwoMachinesPlay) {
8517 if (cps->other->offeredDraw) {
8518 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8519 /* [HGM] in two-machine mode we delay relaying draw offer */
8520 /* until after we also have move, to see if it is really claim */
8522 } else if (gameMode == MachinePlaysWhite ||
8523 gameMode == MachinePlaysBlack) {
8524 if (userOfferedDraw) {
8525 DisplayInformation(_("Machine accepts your draw offer"));
8526 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8528 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8535 * Look for thinking output
8537 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8538 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8540 int plylev, mvleft, mvtot, curscore, time;
8541 char mvname[MOVE_LEN];
8545 int prefixHint = FALSE;
8546 mvname[0] = NULLCHAR;
8549 case MachinePlaysBlack:
8550 case IcsPlayingBlack:
8551 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8553 case MachinePlaysWhite:
8554 case IcsPlayingWhite:
8555 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8560 case IcsObserving: /* [DM] icsEngineAnalyze */
8561 if (!appData.icsEngineAnalyze) ignore = TRUE;
8563 case TwoMachinesPlay:
8564 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8574 ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8576 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8577 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8579 if (plyext != ' ' && plyext != '\t') {
8583 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8584 if( cps->scoreIsAbsolute &&
8585 ( gameMode == MachinePlaysBlack ||
8586 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8587 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
8588 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8589 !WhiteOnMove(currentMove)
8592 curscore = -curscore;
8595 if(appData.pvSAN[cps==&second]) pv = PvToSAN(buf1);
8597 tempStats.depth = plylev;
8598 tempStats.nodes = nodes;
8599 tempStats.time = time;
8600 tempStats.score = curscore;
8601 tempStats.got_only_move = 0;
8603 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8606 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
8607 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8608 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8609 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8610 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8611 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8612 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8613 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8616 /* Buffer overflow protection */
8617 if (pv[0] != NULLCHAR) {
8618 if (strlen(pv) >= sizeof(tempStats.movelist)
8619 && appData.debugMode) {
8621 "PV is too long; using the first %u bytes.\n",
8622 (unsigned) sizeof(tempStats.movelist) - 1);
8625 safeStrCpy( tempStats.movelist, pv, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8627 sprintf(tempStats.movelist, " no PV\n");
8630 if (tempStats.seen_stat) {
8631 tempStats.ok_to_send = 1;
8634 if (strchr(tempStats.movelist, '(') != NULL) {
8635 tempStats.line_is_book = 1;
8636 tempStats.nr_moves = 0;
8637 tempStats.moves_left = 0;
8639 tempStats.line_is_book = 0;
8642 if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8643 programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8645 SendProgramStatsToFrontend( cps, &tempStats );
8648 [AS] Protect the thinkOutput buffer from overflow... this
8649 is only useful if buf1 hasn't overflowed first!
8651 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8653 (gameMode == TwoMachinesPlay ?
8654 ToUpper(cps->twoMachinesColor[0]) : ' '),
8655 ((double) curscore) / 100.0,
8656 prefixHint ? lastHint : "",
8657 prefixHint ? " " : "" );
8659 if( buf1[0] != NULLCHAR ) {
8660 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8662 if( strlen(pv) > max_len ) {
8663 if( appData.debugMode) {
8664 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8666 pv[max_len+1] = '\0';
8669 strcat( thinkOutput, pv);
8672 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8673 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8674 DisplayMove(currentMove - 1);
8678 } else if ((p=StrStr(message, "(only move)")) != NULL) {
8679 /* crafty (9.25+) says "(only move) <move>"
8680 * if there is only 1 legal move
8682 sscanf(p, "(only move) %s", buf1);
8683 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8684 sprintf(programStats.movelist, "%s (only move)", buf1);
8685 programStats.depth = 1;
8686 programStats.nr_moves = 1;
8687 programStats.moves_left = 1;
8688 programStats.nodes = 1;
8689 programStats.time = 1;
8690 programStats.got_only_move = 1;
8692 /* Not really, but we also use this member to
8693 mean "line isn't going to change" (Crafty
8694 isn't searching, so stats won't change) */
8695 programStats.line_is_book = 1;
8697 SendProgramStatsToFrontend( cps, &programStats );
8699 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8700 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8701 DisplayMove(currentMove - 1);
8704 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8705 &time, &nodes, &plylev, &mvleft,
8706 &mvtot, mvname) >= 5) {
8707 /* The stat01: line is from Crafty (9.29+) in response
8708 to the "." command */
8709 programStats.seen_stat = 1;
8710 cps->maybeThinking = TRUE;
8712 if (programStats.got_only_move || !appData.periodicUpdates)
8715 programStats.depth = plylev;
8716 programStats.time = time;
8717 programStats.nodes = nodes;
8718 programStats.moves_left = mvleft;
8719 programStats.nr_moves = mvtot;
8720 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8721 programStats.ok_to_send = 1;
8722 programStats.movelist[0] = '\0';
8724 SendProgramStatsToFrontend( cps, &programStats );
8728 } else if (strncmp(message,"++",2) == 0) {
8729 /* Crafty 9.29+ outputs this */
8730 programStats.got_fail = 2;
8733 } else if (strncmp(message,"--",2) == 0) {
8734 /* Crafty 9.29+ outputs this */
8735 programStats.got_fail = 1;
8738 } else if (thinkOutput[0] != NULLCHAR &&
8739 strncmp(message, " ", 4) == 0) {
8740 unsigned message_len;
8743 while (*p && *p == ' ') p++;
8745 message_len = strlen( p );
8747 /* [AS] Avoid buffer overflow */
8748 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8749 strcat(thinkOutput, " ");
8750 strcat(thinkOutput, p);
8753 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8754 strcat(programStats.movelist, " ");
8755 strcat(programStats.movelist, p);
8758 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8759 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8760 DisplayMove(currentMove - 1);
8768 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8769 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8771 ChessProgramStats cpstats;
8773 if (plyext != ' ' && plyext != '\t') {
8777 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8778 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8779 curscore = -curscore;
8782 cpstats.depth = plylev;
8783 cpstats.nodes = nodes;
8784 cpstats.time = time;
8785 cpstats.score = curscore;
8786 cpstats.got_only_move = 0;
8787 cpstats.movelist[0] = '\0';
8789 if (buf1[0] != NULLCHAR) {
8790 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8793 cpstats.ok_to_send = 0;
8794 cpstats.line_is_book = 0;
8795 cpstats.nr_moves = 0;
8796 cpstats.moves_left = 0;
8798 SendProgramStatsToFrontend( cps, &cpstats );
8805 /* Parse a game score from the character string "game", and
8806 record it as the history of the current game. The game
8807 score is NOT assumed to start from the standard position.
8808 The display is not updated in any way.
8811 ParseGameHistory(game)
8815 int fromX, fromY, toX, toY, boardIndex;
8820 if (appData.debugMode)
8821 fprintf(debugFP, "Parsing game history: %s\n", game);
8823 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8824 gameInfo.site = StrSave(appData.icsHost);
8825 gameInfo.date = PGNDate();
8826 gameInfo.round = StrSave("-");
8828 /* Parse out names of players */
8829 while (*game == ' ') game++;
8831 while (*game != ' ') *p++ = *game++;
8833 gameInfo.white = StrSave(buf);
8834 while (*game == ' ') game++;
8836 while (*game != ' ' && *game != '\n') *p++ = *game++;
8838 gameInfo.black = StrSave(buf);
8841 boardIndex = blackPlaysFirst ? 1 : 0;
8844 yyboardindex = boardIndex;
8845 moveType = (ChessMove) Myylex();
8847 case IllegalMove: /* maybe suicide chess, etc. */
8848 if (appData.debugMode) {
8849 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8850 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8851 setbuf(debugFP, NULL);
8853 case WhitePromotion:
8854 case BlackPromotion:
8855 case WhiteNonPromotion:
8856 case BlackNonPromotion:
8858 case WhiteCapturesEnPassant:
8859 case BlackCapturesEnPassant:
8860 case WhiteKingSideCastle:
8861 case WhiteQueenSideCastle:
8862 case BlackKingSideCastle:
8863 case BlackQueenSideCastle:
8864 case WhiteKingSideCastleWild:
8865 case WhiteQueenSideCastleWild:
8866 case BlackKingSideCastleWild:
8867 case BlackQueenSideCastleWild:
8869 case WhiteHSideCastleFR:
8870 case WhiteASideCastleFR:
8871 case BlackHSideCastleFR:
8872 case BlackASideCastleFR:
8874 fromX = currentMoveString[0] - AAA;
8875 fromY = currentMoveString[1] - ONE;
8876 toX = currentMoveString[2] - AAA;
8877 toY = currentMoveString[3] - ONE;
8878 promoChar = currentMoveString[4];
8882 fromX = moveType == WhiteDrop ?
8883 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8884 (int) CharToPiece(ToLower(currentMoveString[0]));
8886 toX = currentMoveString[2] - AAA;
8887 toY = currentMoveString[3] - ONE;
8888 promoChar = NULLCHAR;
8892 snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8893 if (appData.debugMode) {
8894 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8895 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8896 setbuf(debugFP, NULL);
8898 DisplayError(buf, 0);
8900 case ImpossibleMove:
8902 snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8903 if (appData.debugMode) {
8904 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8905 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8906 setbuf(debugFP, NULL);
8908 DisplayError(buf, 0);
8911 if (boardIndex < backwardMostMove) {
8912 /* Oops, gap. How did that happen? */
8913 DisplayError(_("Gap in move list"), 0);
8916 backwardMostMove = blackPlaysFirst ? 1 : 0;
8917 if (boardIndex > forwardMostMove) {
8918 forwardMostMove = boardIndex;
8922 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8923 strcat(parseList[boardIndex-1], " ");
8924 strcat(parseList[boardIndex-1], yy_text);
8936 case GameUnfinished:
8937 if (gameMode == IcsExamining) {
8938 if (boardIndex < backwardMostMove) {
8939 /* Oops, gap. How did that happen? */
8942 backwardMostMove = blackPlaysFirst ? 1 : 0;
8945 gameInfo.result = moveType;
8946 p = strchr(yy_text, '{');
8947 if (p == NULL) p = strchr(yy_text, '(');
8950 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8952 q = strchr(p, *p == '{' ? '}' : ')');
8953 if (q != NULL) *q = NULLCHAR;
8956 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8957 gameInfo.resultDetails = StrSave(p);
8960 if (boardIndex >= forwardMostMove &&
8961 !(gameMode == IcsObserving && ics_gamenum == -1)) {
8962 backwardMostMove = blackPlaysFirst ? 1 : 0;
8965 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8966 fromY, fromX, toY, toX, promoChar,
8967 parseList[boardIndex]);
8968 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8969 /* currentMoveString is set as a side-effect of yylex */
8970 safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8971 strcat(moveList[boardIndex], "\n");
8973 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8974 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8980 if(gameInfo.variant != VariantShogi)
8981 strcat(parseList[boardIndex - 1], "+");
8985 strcat(parseList[boardIndex - 1], "#");
8992 /* Apply a move to the given board */
8994 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8995 int fromX, fromY, toX, toY;
8999 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
9000 int promoRank = gameInfo.variant == VariantMakruk || gameInfo.variant == VariantGrand ? 3 : 1;
9002 /* [HGM] compute & store e.p. status and castling rights for new position */
9003 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
9005 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
9006 oldEP = (signed char)board[EP_STATUS];
9007 board[EP_STATUS] = EP_NONE;
9009 if( board[toY][toX] != EmptySquare )
9010 board[EP_STATUS] = EP_CAPTURE;
9012 if (fromY == DROP_RANK) {
9014 piece = board[toY][toX] = (ChessSquare) fromX;
9018 if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
9019 if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
9020 board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
9022 if( board[fromY][fromX] == WhitePawn ) {
9023 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9024 board[EP_STATUS] = EP_PAWN_MOVE;
9026 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
9027 gameInfo.variant != VariantBerolina || toX < fromX)
9028 board[EP_STATUS] = toX | berolina;
9029 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
9030 gameInfo.variant != VariantBerolina || toX > fromX)
9031 board[EP_STATUS] = toX;
9034 if( board[fromY][fromX] == BlackPawn ) {
9035 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
9036 board[EP_STATUS] = EP_PAWN_MOVE;
9037 if( toY-fromY== -2) {
9038 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
9039 gameInfo.variant != VariantBerolina || toX < fromX)
9040 board[EP_STATUS] = toX | berolina;
9041 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
9042 gameInfo.variant != VariantBerolina || toX > fromX)
9043 board[EP_STATUS] = toX;
9047 for(i=0; i<nrCastlingRights; i++) {
9048 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
9049 board[CASTLING][i] == toX && castlingRank[i] == toY
9050 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
9053 if (fromX == toX && fromY == toY) return;
9055 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
9056 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
9057 if(gameInfo.variant == VariantKnightmate)
9058 king += (int) WhiteUnicorn - (int) WhiteKing;
9060 /* Code added by Tord: */
9061 /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
9062 if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
9063 board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
9064 board[fromY][fromX] = EmptySquare;
9065 board[toY][toX] = EmptySquare;
9066 if((toX > fromX) != (piece == WhiteRook)) {
9067 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
9069 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
9071 } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
9072 board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
9073 board[fromY][fromX] = EmptySquare;
9074 board[toY][toX] = EmptySquare;
9075 if((toX > fromX) != (piece == BlackRook)) {
9076 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
9078 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
9080 /* End of code added by Tord */
9082 } else if (board[fromY][fromX] == king
9083 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9084 && toY == fromY && toX > fromX+1) {
9085 board[fromY][fromX] = EmptySquare;
9086 board[toY][toX] = king;
9087 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9088 board[fromY][BOARD_RGHT-1] = EmptySquare;
9089 } else if (board[fromY][fromX] == king
9090 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9091 && toY == fromY && toX < fromX-1) {
9092 board[fromY][fromX] = EmptySquare;
9093 board[toY][toX] = king;
9094 board[toY][toX+1] = board[fromY][BOARD_LEFT];
9095 board[fromY][BOARD_LEFT] = EmptySquare;
9096 } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
9097 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9098 && toY >= BOARD_HEIGHT-promoRank && promoChar // defaulting to Q is done elsewhere
9100 /* white pawn promotion */
9101 board[toY][toX] = CharToPiece(ToUpper(promoChar));
9102 if(gameInfo.variant==VariantBughouse ||
9103 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9104 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9105 board[fromY][fromX] = EmptySquare;
9106 } else if ((fromY >= BOARD_HEIGHT>>1)
9108 && gameInfo.variant != VariantXiangqi
9109 && gameInfo.variant != VariantBerolina
9110 && (board[fromY][fromX] == WhitePawn)
9111 && (board[toY][toX] == EmptySquare)) {
9112 board[fromY][fromX] = EmptySquare;
9113 board[toY][toX] = WhitePawn;
9114 captured = board[toY - 1][toX];
9115 board[toY - 1][toX] = EmptySquare;
9116 } else if ((fromY == BOARD_HEIGHT-4)
9118 && gameInfo.variant == VariantBerolina
9119 && (board[fromY][fromX] == WhitePawn)
9120 && (board[toY][toX] == EmptySquare)) {
9121 board[fromY][fromX] = EmptySquare;
9122 board[toY][toX] = WhitePawn;
9123 if(oldEP & EP_BEROLIN_A) {
9124 captured = board[fromY][fromX-1];
9125 board[fromY][fromX-1] = EmptySquare;
9126 }else{ captured = board[fromY][fromX+1];
9127 board[fromY][fromX+1] = EmptySquare;
9129 } else if (board[fromY][fromX] == king
9130 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9131 && toY == fromY && toX > fromX+1) {
9132 board[fromY][fromX] = EmptySquare;
9133 board[toY][toX] = king;
9134 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
9135 board[fromY][BOARD_RGHT-1] = EmptySquare;
9136 } else if (board[fromY][fromX] == king
9137 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
9138 && toY == fromY && toX < fromX-1) {
9139 board[fromY][fromX] = EmptySquare;
9140 board[toY][toX] = king;
9141 board[toY][toX+1] = board[fromY][BOARD_LEFT];
9142 board[fromY][BOARD_LEFT] = EmptySquare;
9143 } else if (fromY == 7 && fromX == 3
9144 && board[fromY][fromX] == BlackKing
9145 && toY == 7 && toX == 5) {
9146 board[fromY][fromX] = EmptySquare;
9147 board[toY][toX] = BlackKing;
9148 board[fromY][7] = EmptySquare;
9149 board[toY][4] = BlackRook;
9150 } else if (fromY == 7 && fromX == 3
9151 && board[fromY][fromX] == BlackKing
9152 && toY == 7 && toX == 1) {
9153 board[fromY][fromX] = EmptySquare;
9154 board[toY][toX] = BlackKing;
9155 board[fromY][0] = EmptySquare;
9156 board[toY][2] = BlackRook;
9157 } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
9158 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
9159 && toY < promoRank && promoChar
9161 /* black pawn promotion */
9162 board[toY][toX] = CharToPiece(ToLower(promoChar));
9163 if(gameInfo.variant==VariantBughouse ||
9164 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
9165 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
9166 board[fromY][fromX] = EmptySquare;
9167 } else if ((fromY < BOARD_HEIGHT>>1)
9169 && gameInfo.variant != VariantXiangqi
9170 && gameInfo.variant != VariantBerolina
9171 && (board[fromY][fromX] == BlackPawn)
9172 && (board[toY][toX] == EmptySquare)) {
9173 board[fromY][fromX] = EmptySquare;
9174 board[toY][toX] = BlackPawn;
9175 captured = board[toY + 1][toX];
9176 board[toY + 1][toX] = EmptySquare;
9177 } else if ((fromY == 3)
9179 && gameInfo.variant == VariantBerolina
9180 && (board[fromY][fromX] == BlackPawn)
9181 && (board[toY][toX] == EmptySquare)) {
9182 board[fromY][fromX] = EmptySquare;
9183 board[toY][toX] = BlackPawn;
9184 if(oldEP & EP_BEROLIN_A) {
9185 captured = board[fromY][fromX-1];
9186 board[fromY][fromX-1] = EmptySquare;
9187 }else{ captured = board[fromY][fromX+1];
9188 board[fromY][fromX+1] = EmptySquare;
9191 board[toY][toX] = board[fromY][fromX];
9192 board[fromY][fromX] = EmptySquare;
9196 if (gameInfo.holdingsWidth != 0) {
9198 /* !!A lot more code needs to be written to support holdings */
9199 /* [HGM] OK, so I have written it. Holdings are stored in the */
9200 /* penultimate board files, so they are automaticlly stored */
9201 /* in the game history. */
9202 if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9203 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9204 /* Delete from holdings, by decreasing count */
9205 /* and erasing image if necessary */
9206 p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9207 if(p < (int) BlackPawn) { /* white drop */
9208 p -= (int)WhitePawn;
9209 p = PieceToNumber((ChessSquare)p);
9210 if(p >= gameInfo.holdingsSize) p = 0;
9211 if(--board[p][BOARD_WIDTH-2] <= 0)
9212 board[p][BOARD_WIDTH-1] = EmptySquare;
9213 if((int)board[p][BOARD_WIDTH-2] < 0)
9214 board[p][BOARD_WIDTH-2] = 0;
9215 } else { /* black drop */
9216 p -= (int)BlackPawn;
9217 p = PieceToNumber((ChessSquare)p);
9218 if(p >= gameInfo.holdingsSize) p = 0;
9219 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9220 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9221 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9222 board[BOARD_HEIGHT-1-p][1] = 0;
9225 if (captured != EmptySquare && gameInfo.holdingsSize > 0
9226 && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess ) {
9227 /* [HGM] holdings: Add to holdings, if holdings exist */
9228 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) {
9229 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9230 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9233 if (p >= (int) BlackPawn) {
9234 p -= (int)BlackPawn;
9235 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9236 /* in Shogi restore piece to its original first */
9237 captured = (ChessSquare) (DEMOTED captured);
9240 p = PieceToNumber((ChessSquare)p);
9241 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9242 board[p][BOARD_WIDTH-2]++;
9243 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9245 p -= (int)WhitePawn;
9246 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9247 captured = (ChessSquare) (DEMOTED captured);
9250 p = PieceToNumber((ChessSquare)p);
9251 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9252 board[BOARD_HEIGHT-1-p][1]++;
9253 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9256 } else if (gameInfo.variant == VariantAtomic) {
9257 if (captured != EmptySquare) {
9259 for (y = toY-1; y <= toY+1; y++) {
9260 for (x = toX-1; x <= toX+1; x++) {
9261 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9262 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9263 board[y][x] = EmptySquare;
9267 board[toY][toX] = EmptySquare;
9270 if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9271 board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9273 if(promoChar == '+') {
9274 /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9275 board[toY][toX] = (ChessSquare) (PROMOTED piece);
9276 } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9277 board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9279 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
9280 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9281 // [HGM] superchess: take promotion piece out of holdings
9282 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9283 if((int)piece < (int)BlackPawn) { // determine stm from piece color
9284 if(!--board[k][BOARD_WIDTH-2])
9285 board[k][BOARD_WIDTH-1] = EmptySquare;
9287 if(!--board[BOARD_HEIGHT-1-k][1])
9288 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9294 /* Updates forwardMostMove */
9296 MakeMove(fromX, fromY, toX, toY, promoChar)
9297 int fromX, fromY, toX, toY;
9300 // forwardMostMove++; // [HGM] bare: moved downstream
9302 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9303 int timeLeft; static int lastLoadFlag=0; int king, piece;
9304 piece = boards[forwardMostMove][fromY][fromX];
9305 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9306 if(gameInfo.variant == VariantKnightmate)
9307 king += (int) WhiteUnicorn - (int) WhiteKing;
9308 if(forwardMostMove == 0) {
9310 fprintf(serverMoves, "%s;", second.tidy);
9311 fprintf(serverMoves, "%s;", first.tidy);
9312 if(!blackPlaysFirst)
9313 fprintf(serverMoves, "%s;", second.tidy);
9314 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9315 lastLoadFlag = loadFlag;
9317 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9318 // print castling suffix
9319 if( toY == fromY && piece == king ) {
9321 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9323 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9326 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9327 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
9328 boards[forwardMostMove][toY][toX] == EmptySquare
9329 && fromX != toX && fromY != toY)
9330 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9332 if(promoChar != NULLCHAR)
9333 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
9335 fprintf(serverMoves, "/%d/%d",
9336 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9337 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9338 else timeLeft = blackTimeRemaining/1000;
9339 fprintf(serverMoves, "/%d", timeLeft);
9341 fflush(serverMoves);
9344 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
9345 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
9349 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9350 if (commentList[forwardMostMove+1] != NULL) {
9351 free(commentList[forwardMostMove+1]);
9352 commentList[forwardMostMove+1] = NULL;
9354 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9355 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9356 // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9357 SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9358 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9359 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9360 gameInfo.result = GameUnfinished;
9361 if (gameInfo.resultDetails != NULL) {
9362 free(gameInfo.resultDetails);
9363 gameInfo.resultDetails = NULL;
9365 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9366 moveList[forwardMostMove - 1]);
9367 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
9368 PosFlags(forwardMostMove - 1),
9369 fromY, fromX, toY, toX, promoChar,
9370 parseList[forwardMostMove - 1]);
9371 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9377 if(gameInfo.variant != VariantShogi)
9378 strcat(parseList[forwardMostMove - 1], "+");
9382 strcat(parseList[forwardMostMove - 1], "#");
9385 if (appData.debugMode) {
9386 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9391 /* Updates currentMove if not pausing */
9393 ShowMove(fromX, fromY, toX, toY)
9395 int instant = (gameMode == PlayFromGameFile) ?
9396 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9397 if(appData.noGUI) return;
9398 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9400 if (forwardMostMove == currentMove + 1) {
9401 AnimateMove(boards[forwardMostMove - 1],
9402 fromX, fromY, toX, toY);
9404 if (appData.highlightLastMove) {
9405 SetHighlights(fromX, fromY, toX, toY);
9408 currentMove = forwardMostMove;
9411 if (instant) return;
9413 DisplayMove(currentMove - 1);
9414 DrawPosition(FALSE, boards[currentMove]);
9415 DisplayBothClocks();
9416 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9417 DisplayBook(currentMove);
9420 void SendEgtPath(ChessProgramState *cps)
9421 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9422 char buf[MSG_SIZ], name[MSG_SIZ], *p;
9424 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9427 char c, *q = name+1, *r, *s;
9429 name[0] = ','; // extract next format name from feature and copy with prefixed ','
9430 while(*p && *p != ',') *q++ = *p++;
9432 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9433 strcmp(name, ",nalimov:") == 0 ) {
9434 // take nalimov path from the menu-changeable option first, if it is defined
9435 snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9436 SendToProgram(buf,cps); // send egtbpath command for nalimov
9438 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9439 (s = StrStr(appData.egtFormats, name)) != NULL) {
9440 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9441 s = r = StrStr(s, ":") + 1; // beginning of path info
9442 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9443 c = *r; *r = 0; // temporarily null-terminate path info
9444 *--q = 0; // strip of trailig ':' from name
9445 snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9447 SendToProgram(buf,cps); // send egtbpath command for this format
9449 if(*p == ',') p++; // read away comma to position for next format name
9454 InitChessProgram(cps, setup)
9455 ChessProgramState *cps;
9456 int setup; /* [HGM] needed to setup FRC opening position */
9458 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9459 if (appData.noChessProgram) return;
9460 hintRequested = FALSE;
9461 bookRequested = FALSE;
9463 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9464 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9465 if(cps->memSize) { /* [HGM] memory */
9466 snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9467 SendToProgram(buf, cps);
9469 SendEgtPath(cps); /* [HGM] EGT */
9470 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9471 snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9472 SendToProgram(buf, cps);
9475 SendToProgram(cps->initString, cps);
9476 if (gameInfo.variant != VariantNormal &&
9477 gameInfo.variant != VariantLoadable
9478 /* [HGM] also send variant if board size non-standard */
9479 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9481 char *v = VariantName(gameInfo.variant);
9482 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9483 /* [HGM] in protocol 1 we have to assume all variants valid */
9484 snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9485 DisplayFatalError(buf, 0, 1);
9489 /* [HGM] make prefix for non-standard board size. Awkward testing... */
9490 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9491 if( gameInfo.variant == VariantXiangqi )
9492 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9493 if( gameInfo.variant == VariantShogi )
9494 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9495 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9496 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9497 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9498 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9499 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9500 if( gameInfo.variant == VariantCourier )
9501 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9502 if( gameInfo.variant == VariantSuper )
9503 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9504 if( gameInfo.variant == VariantGreat )
9505 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9506 if( gameInfo.variant == VariantSChess )
9507 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9508 if( gameInfo.variant == VariantGrand )
9509 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 7;
9512 snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9513 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9514 /* [HGM] varsize: try first if this defiant size variant is specifically known */
9515 if(StrStr(cps->variants, b) == NULL) {
9516 // specific sized variant not known, check if general sizing allowed
9517 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9518 if(StrStr(cps->variants, "boardsize") == NULL) {
9519 snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9520 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9521 DisplayFatalError(buf, 0, 1);
9524 /* [HGM] here we really should compare with the maximum supported board size */
9527 } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9528 snprintf(buf, MSG_SIZ, "variant %s\n", b);
9529 SendToProgram(buf, cps);
9531 currentlyInitializedVariant = gameInfo.variant;
9533 /* [HGM] send opening position in FRC to first engine */
9535 SendToProgram("force\n", cps);
9537 /* engine is now in force mode! Set flag to wake it up after first move. */
9538 setboardSpoiledMachineBlack = 1;
9542 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9543 SendToProgram(buf, cps);
9545 cps->maybeThinking = FALSE;
9546 cps->offeredDraw = 0;
9547 if (!appData.icsActive) {
9548 SendTimeControl(cps, movesPerSession, timeControl,
9549 timeIncrement, appData.searchDepth,
9552 if (appData.showThinking
9553 // [HGM] thinking: four options require thinking output to be sent
9554 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9556 SendToProgram("post\n", cps);
9558 SendToProgram("hard\n", cps);
9559 if (!appData.ponderNextMove) {
9560 /* Warning: "easy" is a toggle in GNU Chess, so don't send
9561 it without being sure what state we are in first. "hard"
9562 is not a toggle, so that one is OK.
9564 SendToProgram("easy\n", cps);
9567 snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9568 SendToProgram(buf, cps);
9570 cps->initDone = TRUE;
9571 ClearEngineOutputPane(cps == &second);
9576 StartChessProgram(cps)
9577 ChessProgramState *cps;
9582 if (appData.noChessProgram) return;
9583 cps->initDone = FALSE;
9585 if (strcmp(cps->host, "localhost") == 0) {
9586 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9587 } else if (*appData.remoteShell == NULLCHAR) {
9588 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9590 if (*appData.remoteUser == NULLCHAR) {
9591 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9594 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9595 cps->host, appData.remoteUser, cps->program);
9597 err = StartChildProcess(buf, "", &cps->pr);
9601 snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9602 DisplayError(buf, err); // [HGM] bit of a rough kludge: ignore failure, (which XBoard would do anyway), and let I/O discover it
9603 if(cps != &first) return;
9604 appData.noChessProgram = TRUE;
9607 // DisplayFatalError(buf, err, 1);
9608 // cps->pr = NoProc;
9613 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9614 if (cps->protocolVersion > 1) {
9615 snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9616 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9617 cps->comboCnt = 0; // and values of combo boxes
9618 SendToProgram(buf, cps);
9620 SendToProgram("xboard\n", cps);
9625 TwoMachinesEventIfReady P((void))
9627 static int curMess = 0;
9628 if (first.lastPing != first.lastPong) {
9629 if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9630 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9633 if (second.lastPing != second.lastPong) {
9634 if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9635 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9638 DisplayMessage("", ""); curMess = 0;
9644 MakeName(char *template)
9648 static char buf[MSG_SIZ];
9652 clock = time((time_t *)NULL);
9653 tm = localtime(&clock);
9655 while(*p++ = *template++) if(p[-1] == '%') {
9656 switch(*template++) {
9657 case 0: *p = 0; return buf;
9658 case 'Y': i = tm->tm_year+1900; break;
9659 case 'y': i = tm->tm_year-100; break;
9660 case 'M': i = tm->tm_mon+1; break;
9661 case 'd': i = tm->tm_mday; break;
9662 case 'h': i = tm->tm_hour; break;
9663 case 'm': i = tm->tm_min; break;
9664 case 's': i = tm->tm_sec; break;
9667 snprintf(p-1, MSG_SIZ-10 - (p - buf), "%02d", i); p += strlen(p);
9673 CountPlayers(char *p)
9676 while(p = strchr(p, '\n')) p++, n++; // count participants
9681 WriteTourneyFile(char *results)
9682 { // write tournament parameters on tourneyFile; on success return the stream pointer for closing
9683 FILE *f = fopen(appData.tourneyFile, "w");
9684 if(f == NULL) DisplayError(_("Could not write on tourney file"), 0); else {
9685 // create a file with tournament description
9686 fprintf(f, "-participants {%s}\n", appData.participants);
9687 fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9688 fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9689 fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9690 fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9691 fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9692 fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9693 fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9694 fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9695 fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9696 fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9697 fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9699 fprintf(f, "-searchTime \"%d:%02d\"\n", searchTime/60, searchTime%60);
9701 fprintf(f, "-mps %d\n", appData.movesPerSession);
9702 fprintf(f, "-tc %s\n", appData.timeControl);
9703 fprintf(f, "-inc %.2f\n", appData.timeIncrement);
9705 fprintf(f, "-results \"%s\"\n", results);
9711 CreateTourney(char *name)
9714 if(name[0] == NULLCHAR) {
9715 if(appData.participants[0])
9716 DisplayError(_("You must supply a tournament file,\nfor storing the tourney progress"), 0);
9719 f = fopen(name, "r");
9720 if(f) { // file exists
9721 ASSIGN(appData.tourneyFile, name);
9722 ParseArgsFromFile(f); // parse it
9724 if(!appData.participants[0]) return 0; // ignore tourney file if non-existing & no participants
9725 if(CountPlayers(appData.participants) < (appData.tourneyType>0 ? appData.tourneyType+1 : 2)) {
9726 DisplayError(_("Not enough participants"), 0);
9729 ASSIGN(appData.tourneyFile, name);
9730 if(appData.tourneyType < 0) appData.defaultMatchGames = 1; // Swiss forces games/pairing = 1
9731 if((f = WriteTourneyFile("")) == NULL) return 0;
9734 appData.noChessProgram = FALSE;
9735 appData.clockMode = TRUE;
9740 #define MAXENGINES 1000
9741 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9743 void NamesToList(char *names, char **engineList, char **engineMnemonic)
9745 char buf[MSG_SIZ], *p, *q;
9749 while(*p && *p != '\n') *q++ = *p++;
9751 if(engineList[i]) free(engineList[i]);
9752 engineList[i] = strdup(buf);
9754 TidyProgramName(engineList[i], "localhost", buf);
9755 if(engineMnemonic[i]) free(engineMnemonic[i]);
9756 if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9758 sscanf(q + 8, "%s", buf + strlen(buf));
9761 engineMnemonic[i] = strdup(buf);
9763 if(i > MAXENGINES - 2) break;
9765 engineList[i] = NULL;
9768 // following implemented as macro to avoid type limitations
9769 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9771 void SwapEngines(int n)
9772 { // swap settings for first engine and other engine (so far only some selected options)
9777 SWAP(chessProgram, p)
9779 SWAP(hasOwnBookUCI, h)
9780 SWAP(protocolVersion, h)
9782 SWAP(scoreIsAbsolute, h)
9790 SetPlayer(int player)
9791 { // [HGM] find the engine line of the partcipant given by number, and parse its options.
9793 char buf[MSG_SIZ], *engineName, *p = appData.participants;
9794 for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9795 engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9796 for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9798 snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9799 ParseArgsFromString(resetOptions); appData.fenOverride[0] = NULL;
9800 ParseArgsFromString(buf);
9806 Pairing(int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9807 { // determine players from game number
9808 int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle=1, pairingsPerRound=1;
9810 if(appData.tourneyType == 0) {
9811 roundsPerCycle = (nPlayers - 1) | 1;
9812 pairingsPerRound = nPlayers / 2;
9813 } else if(appData.tourneyType > 0) {
9814 roundsPerCycle = nPlayers - appData.tourneyType;
9815 pairingsPerRound = appData.tourneyType;
9817 gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9818 gamesPerCycle = gamesPerRound * roundsPerCycle;
9819 appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9820 curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9821 curRound = nr / gamesPerRound; nr %= gamesPerRound;
9822 curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9823 matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9824 roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9826 if(appData.cycleSync) *syncInterval = gamesPerCycle;
9827 if(appData.roundSync) *syncInterval = gamesPerRound;
9829 if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9831 if(appData.tourneyType == 0) {
9832 if(curPairing == (nPlayers-1)/2 ) {
9833 *whitePlayer = curRound;
9834 *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9836 *whitePlayer = curRound - (nPlayers-1)/2 + curPairing;
9837 if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9838 *blackPlayer = curRound + (nPlayers-1)/2 - curPairing;
9839 if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9841 } else if(appData.tourneyType > 0) {
9842 *whitePlayer = curPairing;
9843 *blackPlayer = curRound + appData.tourneyType;
9846 // take care of white/black alternation per round.
9847 // For cycles and games this is already taken care of by default, derived from matchGame!
9848 return curRound & 1;
9852 NextTourneyGame(int nr, int *swapColors)
9853 { // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
9855 int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers;
9857 if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
9858 tf = fopen(appData.tourneyFile, "r");
9859 if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
9860 ParseArgsFromFile(tf); fclose(tf);
9861 InitTimeControls(); // TC might be altered from tourney file
9863 nPlayers = CountPlayers(appData.participants); // count participants
9864 if(appData.tourneyType < 0) syncInterval = nPlayers/2; else
9865 *swapColors = Pairing(nr<0 ? 0 : nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
9868 p = q = appData.results;
9869 while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
9870 if(firstBusy/syncInterval < (nextGame/syncInterval)) {
9871 DisplayMessage(_("Waiting for other game(s)"),"");
9872 waitingForGame = TRUE;
9873 ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
9876 waitingForGame = FALSE;
9879 if(appData.tourneyType < 0) {
9880 if(nr>=0 && !pairingReceived) {
9882 if(pairing.pr == NoProc) {
9883 if(!appData.pairingEngine[0]) {
9884 DisplayFatalError(_("No pairing engine specified"), 0, 1);
9887 StartChessProgram(&pairing); // starts the pairing engine
9889 snprintf(buf, 1<<16, "results %d %s\n", nPlayers, appData.results);
9890 SendToProgram(buf, &pairing);
9891 snprintf(buf, 1<<16, "pairing %d\n", nr+1);
9892 SendToProgram(buf, &pairing);
9893 return 0; // wait for pairing engine to answer (which causes NextTourneyGame to be called again...
9895 pairingReceived = 0; // ... so we continue here
9897 appData.matchGames = appData.tourneyCycles * syncInterval - 1;
9898 whitePlayer = savedWhitePlayer-1; blackPlayer = savedBlackPlayer-1;
9899 matchGame = 1; roundNr = nr / syncInterval + 1;
9902 if(first.pr != NoProc) return 1; // engines already loaded
9904 // redefine engines, engine dir, etc.
9905 NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
9906 SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
9908 SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
9909 SwapEngines(1); // and make that valid for second engine by swapping
9910 InitEngine(&first, 0); // initialize ChessProgramStates based on new settings.
9911 InitEngine(&second, 1);
9912 CommonEngineInit(); // after this TwoMachinesEvent will create correct engine processes
9913 UpdateLogos(FALSE); // leave display to ModeHiglight()
9919 { // performs game initialization that does not invoke engines, and then tries to start the game
9920 int firstWhite, swapColors = 0;
9921 if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
9922 firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
9923 firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
9924 first.twoMachinesColor = firstWhite ? "white\n" : "black\n"; // perform actual color assignement
9925 second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
9926 appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
9927 Reset(FALSE, first.pr != NoProc);
9928 appData.noChessProgram = FALSE;
9929 if(!LoadGameOrPosition(matchGame)) return; // setup game; abort when bad game/pos file
9933 void UserAdjudicationEvent( int result )
9935 ChessMove gameResult = GameIsDrawn;
9938 gameResult = WhiteWins;
9940 else if( result < 0 ) {
9941 gameResult = BlackWins;
9944 if( gameMode == TwoMachinesPlay ) {
9945 GameEnds( gameResult, "User adjudication", GE_XBOARD );
9950 // [HGM] save: calculate checksum of game to make games easily identifiable
9951 int StringCheckSum(char *s)
9954 if(s==NULL) return 0;
9955 while(*s) i = i*259 + *s++;
9962 for(i=backwardMostMove; i<forwardMostMove; i++) {
9963 sum += pvInfoList[i].depth;
9964 sum += StringCheckSum(parseList[i]);
9965 sum += StringCheckSum(commentList[i]);
9968 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9969 return sum + StringCheckSum(commentList[i]);
9970 } // end of save patch
9973 GameEnds(result, resultDetails, whosays)
9975 char *resultDetails;
9978 GameMode nextGameMode;
9980 char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
9982 if(endingGame) return; /* [HGM] crash: forbid recursion */
9984 if(twoBoards) { // [HGM] dual: switch back to one board
9985 twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9986 DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9988 if (appData.debugMode) {
9989 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9990 result, resultDetails ? resultDetails : "(null)", whosays);
9993 fromX = fromY = -1; // [HGM] abort any move the user is entering.
9995 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9996 /* If we are playing on ICS, the server decides when the
9997 game is over, but the engine can offer to draw, claim
10001 if (appData.zippyPlay && first.initDone) {
10002 if (result == GameIsDrawn) {
10003 /* In case draw still needs to be claimed */
10004 SendToICS(ics_prefix);
10005 SendToICS("draw\n");
10006 } else if (StrCaseStr(resultDetails, "resign")) {
10007 SendToICS(ics_prefix);
10008 SendToICS("resign\n");
10012 endingGame = 0; /* [HGM] crash */
10016 /* If we're loading the game from a file, stop */
10017 if (whosays == GE_FILE) {
10018 (void) StopLoadGameTimer();
10022 /* Cancel draw offers */
10023 first.offeredDraw = second.offeredDraw = 0;
10025 /* If this is an ICS game, only ICS can really say it's done;
10026 if not, anyone can. */
10027 isIcsGame = (gameMode == IcsPlayingWhite ||
10028 gameMode == IcsPlayingBlack ||
10029 gameMode == IcsObserving ||
10030 gameMode == IcsExamining);
10032 if (!isIcsGame || whosays == GE_ICS) {
10033 /* OK -- not an ICS game, or ICS said it was done */
10035 if (!isIcsGame && !appData.noChessProgram)
10036 SetUserThinkingEnables();
10038 /* [HGM] if a machine claims the game end we verify this claim */
10039 if(gameMode == TwoMachinesPlay && appData.testClaims) {
10040 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
10042 ChessMove trueResult = (ChessMove) -1;
10044 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
10045 first.twoMachinesColor[0] :
10046 second.twoMachinesColor[0] ;
10048 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
10049 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
10050 /* [HGM] verify: engine mate claims accepted if they were flagged */
10051 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
10053 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
10054 /* [HGM] verify: engine mate claims accepted if they were flagged */
10055 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
10057 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
10058 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
10061 // now verify win claims, but not in drop games, as we don't understand those yet
10062 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10063 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand) &&
10064 (result == WhiteWins && claimer == 'w' ||
10065 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
10066 if (appData.debugMode) {
10067 fprintf(debugFP, "result=%d sp=%d move=%d\n",
10068 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
10070 if(result != trueResult) {
10071 snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
10072 result = claimer == 'w' ? BlackWins : WhiteWins;
10073 resultDetails = buf;
10076 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
10077 && (forwardMostMove <= backwardMostMove ||
10078 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
10079 (claimer=='b')==(forwardMostMove&1))
10081 /* [HGM] verify: draws that were not flagged are false claims */
10082 snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
10083 result = claimer == 'w' ? BlackWins : WhiteWins;
10084 resultDetails = buf;
10086 /* (Claiming a loss is accepted no questions asked!) */
10088 /* [HGM] bare: don't allow bare King to win */
10089 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
10090 || gameInfo.variant == VariantGreat || gameInfo.variant == VariantGrand)
10091 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
10092 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
10093 && result != GameIsDrawn)
10094 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
10095 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
10096 int p = (signed char)boards[forwardMostMove][i][j] - color;
10097 if(p >= 0 && p <= (int)WhiteKing) k++;
10099 if (appData.debugMode) {
10100 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
10101 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
10104 result = GameIsDrawn;
10105 snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
10106 resultDetails = buf;
10112 if(serverMoves != NULL && !loadFlag) { char c = '=';
10113 if(result==WhiteWins) c = '+';
10114 if(result==BlackWins) c = '-';
10115 if(resultDetails != NULL)
10116 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
10118 if (resultDetails != NULL) {
10119 gameInfo.result = result;
10120 gameInfo.resultDetails = StrSave(resultDetails);
10122 /* display last move only if game was not loaded from file */
10123 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
10124 DisplayMove(currentMove - 1);
10126 if (forwardMostMove != 0) {
10127 if (gameMode != PlayFromGameFile && gameMode != EditGame
10128 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
10130 if (*appData.saveGameFile != NULLCHAR) {
10131 SaveGameToFile(appData.saveGameFile, TRUE);
10132 } else if (appData.autoSaveGames) {
10135 if (*appData.savePositionFile != NULLCHAR) {
10136 SavePositionToFile(appData.savePositionFile);
10141 /* Tell program how game ended in case it is learning */
10142 /* [HGM] Moved this to after saving the PGN, just in case */
10143 /* engine died and we got here through time loss. In that */
10144 /* case we will get a fatal error writing the pipe, which */
10145 /* would otherwise lose us the PGN. */
10146 /* [HGM] crash: not needed anymore, but doesn't hurt; */
10147 /* output during GameEnds should never be fatal anymore */
10148 if (gameMode == MachinePlaysWhite ||
10149 gameMode == MachinePlaysBlack ||
10150 gameMode == TwoMachinesPlay ||
10151 gameMode == IcsPlayingWhite ||
10152 gameMode == IcsPlayingBlack ||
10153 gameMode == BeginningOfGame) {
10155 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
10157 if (first.pr != NoProc) {
10158 SendToProgram(buf, &first);
10160 if (second.pr != NoProc &&
10161 gameMode == TwoMachinesPlay) {
10162 SendToProgram(buf, &second);
10167 if (appData.icsActive) {
10168 if (appData.quietPlay &&
10169 (gameMode == IcsPlayingWhite ||
10170 gameMode == IcsPlayingBlack)) {
10171 SendToICS(ics_prefix);
10172 SendToICS("set shout 1\n");
10174 nextGameMode = IcsIdle;
10175 ics_user_moved = FALSE;
10176 /* clean up premove. It's ugly when the game has ended and the
10177 * premove highlights are still on the board.
10180 gotPremove = FALSE;
10181 ClearPremoveHighlights();
10182 DrawPosition(FALSE, boards[currentMove]);
10184 if (whosays == GE_ICS) {
10187 if (gameMode == IcsPlayingWhite)
10189 else if(gameMode == IcsPlayingBlack)
10190 PlayIcsLossSound();
10193 if (gameMode == IcsPlayingBlack)
10195 else if(gameMode == IcsPlayingWhite)
10196 PlayIcsLossSound();
10199 PlayIcsDrawSound();
10202 PlayIcsUnfinishedSound();
10205 } else if (gameMode == EditGame ||
10206 gameMode == PlayFromGameFile ||
10207 gameMode == AnalyzeMode ||
10208 gameMode == AnalyzeFile) {
10209 nextGameMode = gameMode;
10211 nextGameMode = EndOfGame;
10216 nextGameMode = gameMode;
10219 if (appData.noChessProgram) {
10220 gameMode = nextGameMode;
10222 endingGame = 0; /* [HGM] crash */
10227 /* Put first chess program into idle state */
10228 if (first.pr != NoProc &&
10229 (gameMode == MachinePlaysWhite ||
10230 gameMode == MachinePlaysBlack ||
10231 gameMode == TwoMachinesPlay ||
10232 gameMode == IcsPlayingWhite ||
10233 gameMode == IcsPlayingBlack ||
10234 gameMode == BeginningOfGame)) {
10235 SendToProgram("force\n", &first);
10236 if (first.usePing) {
10238 snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
10239 SendToProgram(buf, &first);
10242 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10243 /* Kill off first chess program */
10244 if (first.isr != NULL)
10245 RemoveInputSource(first.isr);
10248 if (first.pr != NoProc) {
10250 DoSleep( appData.delayBeforeQuit );
10251 SendToProgram("quit\n", &first);
10252 DoSleep( appData.delayAfterQuit );
10253 DestroyChildProcess(first.pr, first.useSigterm);
10257 if (second.reuse) {
10258 /* Put second chess program into idle state */
10259 if (second.pr != NoProc &&
10260 gameMode == TwoMachinesPlay) {
10261 SendToProgram("force\n", &second);
10262 if (second.usePing) {
10264 snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
10265 SendToProgram(buf, &second);
10268 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
10269 /* Kill off second chess program */
10270 if (second.isr != NULL)
10271 RemoveInputSource(second.isr);
10274 if (second.pr != NoProc) {
10275 DoSleep( appData.delayBeforeQuit );
10276 SendToProgram("quit\n", &second);
10277 DoSleep( appData.delayAfterQuit );
10278 DestroyChildProcess(second.pr, second.useSigterm);
10280 second.pr = NoProc;
10283 if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10284 char resChar = '=';
10288 if (first.twoMachinesColor[0] == 'w') {
10291 second.matchWins++;
10296 if (first.twoMachinesColor[0] == 'b') {
10299 second.matchWins++;
10302 case GameUnfinished:
10308 if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10309 if(appData.tourneyFile[0]){ // [HGM] we are in a tourney; update tourney file with game result
10310 ReserveGame(nextGame, resChar); // sets nextGame
10311 if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10312 else ranking = strdup("busy"); //suppress popup when aborted but not finished
10313 } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10315 if (nextGame <= appData.matchGames && !abortMatch) {
10316 gameMode = nextGameMode;
10317 matchGame = nextGame; // this will be overruled in tourney mode!
10318 GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10319 ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10320 endingGame = 0; /* [HGM] crash */
10323 gameMode = nextGameMode;
10324 snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10325 first.tidy, second.tidy,
10326 first.matchWins, second.matchWins,
10327 appData.matchGames - (first.matchWins + second.matchWins));
10328 if(!appData.tourneyFile[0]) matchGame++, DisplayTwoMachinesTitle(); // [HGM] update result in window title
10329 popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10330 if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10331 first.twoMachinesColor = "black\n";
10332 second.twoMachinesColor = "white\n";
10334 first.twoMachinesColor = "white\n";
10335 second.twoMachinesColor = "black\n";
10339 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10340 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10342 gameMode = nextGameMode;
10344 endingGame = 0; /* [HGM] crash */
10345 if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10346 if(matchMode == TRUE) { // match through command line: exit with or without popup
10348 ToNrEvent(forwardMostMove);
10349 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10351 } else DisplayFatalError(buf, 0, 0);
10352 } else { // match through menu; just stop, with or without popup
10353 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10356 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10357 } else DisplayNote(buf);
10359 if(ranking) free(ranking);
10363 /* Assumes program was just initialized (initString sent).
10364 Leaves program in force mode. */
10366 FeedMovesToProgram(cps, upto)
10367 ChessProgramState *cps;
10372 if (appData.debugMode)
10373 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10374 startedFromSetupPosition ? "position and " : "",
10375 backwardMostMove, upto, cps->which);
10376 if(currentlyInitializedVariant != gameInfo.variant) {
10378 // [HGM] variantswitch: make engine aware of new variant
10379 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10380 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10381 snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10382 SendToProgram(buf, cps);
10383 currentlyInitializedVariant = gameInfo.variant;
10385 SendToProgram("force\n", cps);
10386 if (startedFromSetupPosition) {
10387 SendBoard(cps, backwardMostMove);
10388 if (appData.debugMode) {
10389 fprintf(debugFP, "feedMoves\n");
10392 for (i = backwardMostMove; i < upto; i++) {
10393 SendMoveToProgram(i, cps);
10399 ResurrectChessProgram()
10401 /* The chess program may have exited.
10402 If so, restart it and feed it all the moves made so far. */
10403 static int doInit = 0;
10405 if (appData.noChessProgram) return 1;
10407 if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10408 if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10409 if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10410 doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10412 if (first.pr != NoProc) return 1;
10413 StartChessProgram(&first);
10415 InitChessProgram(&first, FALSE);
10416 FeedMovesToProgram(&first, currentMove);
10418 if (!first.sendTime) {
10419 /* can't tell gnuchess what its clock should read,
10420 so we bow to its notion. */
10422 timeRemaining[0][currentMove] = whiteTimeRemaining;
10423 timeRemaining[1][currentMove] = blackTimeRemaining;
10426 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10427 appData.icsEngineAnalyze) && first.analysisSupport) {
10428 SendToProgram("analyze\n", &first);
10429 first.analyzing = TRUE;
10435 * Button procedures
10438 Reset(redraw, init)
10443 if (appData.debugMode) {
10444 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10445 redraw, init, gameMode);
10447 CleanupTail(); // [HGM] vari: delete any stored variations
10448 pausing = pauseExamInvalid = FALSE;
10449 startedFromSetupPosition = blackPlaysFirst = FALSE;
10451 whiteFlag = blackFlag = FALSE;
10452 userOfferedDraw = FALSE;
10453 hintRequested = bookRequested = FALSE;
10454 first.maybeThinking = FALSE;
10455 second.maybeThinking = FALSE;
10456 first.bookSuspend = FALSE; // [HGM] book
10457 second.bookSuspend = FALSE;
10458 thinkOutput[0] = NULLCHAR;
10459 lastHint[0] = NULLCHAR;
10460 ClearGameInfo(&gameInfo);
10461 gameInfo.variant = StringToVariant(appData.variant);
10462 ics_user_moved = ics_clock_paused = FALSE;
10463 ics_getting_history = H_FALSE;
10465 white_holding[0] = black_holding[0] = NULLCHAR;
10466 ClearProgramStats();
10467 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10471 flipView = appData.flipView;
10472 ClearPremoveHighlights();
10473 gotPremove = FALSE;
10474 alarmSounded = FALSE;
10476 GameEnds(EndOfFile, NULL, GE_PLAYER);
10477 if(appData.serverMovesName != NULL) {
10478 /* [HGM] prepare to make moves file for broadcasting */
10479 clock_t t = clock();
10480 if(serverMoves != NULL) fclose(serverMoves);
10481 serverMoves = fopen(appData.serverMovesName, "r");
10482 if(serverMoves != NULL) {
10483 fclose(serverMoves);
10484 /* delay 15 sec before overwriting, so all clients can see end */
10485 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10487 serverMoves = fopen(appData.serverMovesName, "w");
10491 gameMode = BeginningOfGame;
10493 if(appData.icsActive) gameInfo.variant = VariantNormal;
10494 currentMove = forwardMostMove = backwardMostMove = 0;
10495 InitPosition(redraw);
10496 for (i = 0; i < MAX_MOVES; i++) {
10497 if (commentList[i] != NULL) {
10498 free(commentList[i]);
10499 commentList[i] = NULL;
10503 timeRemaining[0][0] = whiteTimeRemaining;
10504 timeRemaining[1][0] = blackTimeRemaining;
10506 if (first.pr == NULL) {
10507 StartChessProgram(&first);
10510 InitChessProgram(&first, startedFromSetupPosition);
10513 DisplayMessage("", "");
10514 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10515 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10522 if (!AutoPlayOneMove())
10524 if (matchMode || appData.timeDelay == 0)
10526 if (appData.timeDelay < 0)
10528 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10537 int fromX, fromY, toX, toY;
10539 if (appData.debugMode) {
10540 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10543 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10546 if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10547 pvInfoList[currentMove].depth = programStats.depth;
10548 pvInfoList[currentMove].score = programStats.score;
10549 pvInfoList[currentMove].time = 0;
10550 if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10553 if (currentMove >= forwardMostMove) {
10554 if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10555 gameMode = EditGame;
10558 /* [AS] Clear current move marker at the end of a game */
10559 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10564 toX = moveList[currentMove][2] - AAA;
10565 toY = moveList[currentMove][3] - ONE;
10567 if (moveList[currentMove][1] == '@') {
10568 if (appData.highlightLastMove) {
10569 SetHighlights(-1, -1, toX, toY);
10572 fromX = moveList[currentMove][0] - AAA;
10573 fromY = moveList[currentMove][1] - ONE;
10575 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10577 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10579 if (appData.highlightLastMove) {
10580 SetHighlights(fromX, fromY, toX, toY);
10583 DisplayMove(currentMove);
10584 SendMoveToProgram(currentMove++, &first);
10585 DisplayBothClocks();
10586 DrawPosition(FALSE, boards[currentMove]);
10587 // [HGM] PV info: always display, routine tests if empty
10588 DisplayComment(currentMove - 1, commentList[currentMove]);
10594 LoadGameOneMove(readAhead)
10595 ChessMove readAhead;
10597 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10598 char promoChar = NULLCHAR;
10599 ChessMove moveType;
10600 char move[MSG_SIZ];
10603 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10604 gameMode != AnalyzeMode && gameMode != Training) {
10609 yyboardindex = forwardMostMove;
10610 if (readAhead != EndOfFile) {
10611 moveType = readAhead;
10613 if (gameFileFP == NULL)
10615 moveType = (ChessMove) Myylex();
10619 switch (moveType) {
10621 if (appData.debugMode)
10622 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10625 /* append the comment but don't display it */
10626 AppendComment(currentMove, p, FALSE);
10629 case WhiteCapturesEnPassant:
10630 case BlackCapturesEnPassant:
10631 case WhitePromotion:
10632 case BlackPromotion:
10633 case WhiteNonPromotion:
10634 case BlackNonPromotion:
10636 case WhiteKingSideCastle:
10637 case WhiteQueenSideCastle:
10638 case BlackKingSideCastle:
10639 case BlackQueenSideCastle:
10640 case WhiteKingSideCastleWild:
10641 case WhiteQueenSideCastleWild:
10642 case BlackKingSideCastleWild:
10643 case BlackQueenSideCastleWild:
10645 case WhiteHSideCastleFR:
10646 case WhiteASideCastleFR:
10647 case BlackHSideCastleFR:
10648 case BlackASideCastleFR:
10650 if (appData.debugMode)
10651 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10652 fromX = currentMoveString[0] - AAA;
10653 fromY = currentMoveString[1] - ONE;
10654 toX = currentMoveString[2] - AAA;
10655 toY = currentMoveString[3] - ONE;
10656 promoChar = currentMoveString[4];
10661 if (appData.debugMode)
10662 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10663 fromX = moveType == WhiteDrop ?
10664 (int) CharToPiece(ToUpper(currentMoveString[0])) :
10665 (int) CharToPiece(ToLower(currentMoveString[0]));
10667 toX = currentMoveString[2] - AAA;
10668 toY = currentMoveString[3] - ONE;
10674 case GameUnfinished:
10675 if (appData.debugMode)
10676 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10677 p = strchr(yy_text, '{');
10678 if (p == NULL) p = strchr(yy_text, '(');
10681 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10683 q = strchr(p, *p == '{' ? '}' : ')');
10684 if (q != NULL) *q = NULLCHAR;
10687 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10688 GameEnds(moveType, p, GE_FILE);
10690 if (cmailMsgLoaded) {
10692 flipView = WhiteOnMove(currentMove);
10693 if (moveType == GameUnfinished) flipView = !flipView;
10694 if (appData.debugMode)
10695 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10700 if (appData.debugMode)
10701 fprintf(debugFP, "Parser hit end of file\n");
10702 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10708 if (WhiteOnMove(currentMove)) {
10709 GameEnds(BlackWins, "Black mates", GE_FILE);
10711 GameEnds(WhiteWins, "White mates", GE_FILE);
10715 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10721 case MoveNumberOne:
10722 if (lastLoadGameStart == GNUChessGame) {
10723 /* GNUChessGames have numbers, but they aren't move numbers */
10724 if (appData.debugMode)
10725 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10726 yy_text, (int) moveType);
10727 return LoadGameOneMove(EndOfFile); /* tail recursion */
10729 /* else fall thru */
10734 /* Reached start of next game in file */
10735 if (appData.debugMode)
10736 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10737 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10743 if (WhiteOnMove(currentMove)) {
10744 GameEnds(BlackWins, "Black mates", GE_FILE);
10746 GameEnds(WhiteWins, "White mates", GE_FILE);
10750 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10756 case PositionDiagram: /* should not happen; ignore */
10757 case ElapsedTime: /* ignore */
10758 case NAG: /* ignore */
10759 if (appData.debugMode)
10760 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10761 yy_text, (int) moveType);
10762 return LoadGameOneMove(EndOfFile); /* tail recursion */
10765 if (appData.testLegality) {
10766 if (appData.debugMode)
10767 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10768 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10769 (forwardMostMove / 2) + 1,
10770 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10771 DisplayError(move, 0);
10774 if (appData.debugMode)
10775 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10776 yy_text, currentMoveString);
10777 fromX = currentMoveString[0] - AAA;
10778 fromY = currentMoveString[1] - ONE;
10779 toX = currentMoveString[2] - AAA;
10780 toY = currentMoveString[3] - ONE;
10781 promoChar = currentMoveString[4];
10785 case AmbiguousMove:
10786 if (appData.debugMode)
10787 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10788 snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10789 (forwardMostMove / 2) + 1,
10790 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10791 DisplayError(move, 0);
10796 case ImpossibleMove:
10797 if (appData.debugMode)
10798 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10799 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10800 (forwardMostMove / 2) + 1,
10801 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10802 DisplayError(move, 0);
10808 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10809 DrawPosition(FALSE, boards[currentMove]);
10810 DisplayBothClocks();
10811 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10812 DisplayComment(currentMove - 1, commentList[currentMove]);
10814 (void) StopLoadGameTimer();
10816 cmailOldMove = forwardMostMove;
10819 /* currentMoveString is set as a side-effect of yylex */
10821 thinkOutput[0] = NULLCHAR;
10822 MakeMove(fromX, fromY, toX, toY, promoChar);
10823 currentMove = forwardMostMove;
10828 /* Load the nth game from the given file */
10830 LoadGameFromFile(filename, n, title, useList)
10834 /*Boolean*/ int useList;
10839 if (strcmp(filename, "-") == 0) {
10843 f = fopen(filename, "rb");
10845 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10846 DisplayError(buf, errno);
10850 if (fseek(f, 0, 0) == -1) {
10851 /* f is not seekable; probably a pipe */
10854 if (useList && n == 0) {
10855 int error = GameListBuild(f);
10857 DisplayError(_("Cannot build game list"), error);
10858 } else if (!ListEmpty(&gameList) &&
10859 ((ListGame *) gameList.tailPred)->number > 1) {
10860 GameListPopUp(f, title);
10867 return LoadGame(f, n, title, FALSE);
10872 MakeRegisteredMove()
10874 int fromX, fromY, toX, toY;
10876 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10877 switch (cmailMoveType[lastLoadGameNumber - 1]) {
10880 if (appData.debugMode)
10881 fprintf(debugFP, "Restoring %s for game %d\n",
10882 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10884 thinkOutput[0] = NULLCHAR;
10885 safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10886 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10887 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10888 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10889 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10890 promoChar = cmailMove[lastLoadGameNumber - 1][4];
10891 MakeMove(fromX, fromY, toX, toY, promoChar);
10892 ShowMove(fromX, fromY, toX, toY);
10894 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10901 if (WhiteOnMove(currentMove)) {
10902 GameEnds(BlackWins, "Black mates", GE_PLAYER);
10904 GameEnds(WhiteWins, "White mates", GE_PLAYER);
10909 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10916 if (WhiteOnMove(currentMove)) {
10917 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10919 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10924 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10935 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10937 CmailLoadGame(f, gameNumber, title, useList)
10945 if (gameNumber > nCmailGames) {
10946 DisplayError(_("No more games in this message"), 0);
10949 if (f == lastLoadGameFP) {
10950 int offset = gameNumber - lastLoadGameNumber;
10952 cmailMsg[0] = NULLCHAR;
10953 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10954 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10955 nCmailMovesRegistered--;
10957 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10958 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10959 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10962 if (! RegisterMove()) return FALSE;
10966 retVal = LoadGame(f, gameNumber, title, useList);
10968 /* Make move registered during previous look at this game, if any */
10969 MakeRegisteredMove();
10971 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10972 commentList[currentMove]
10973 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10974 DisplayComment(currentMove - 1, commentList[currentMove]);
10980 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10985 int gameNumber = lastLoadGameNumber + offset;
10986 if (lastLoadGameFP == NULL) {
10987 DisplayError(_("No game has been loaded yet"), 0);
10990 if (gameNumber <= 0) {
10991 DisplayError(_("Can't back up any further"), 0);
10994 if (cmailMsgLoaded) {
10995 return CmailLoadGame(lastLoadGameFP, gameNumber,
10996 lastLoadGameTitle, lastLoadGameUseList);
10998 return LoadGame(lastLoadGameFP, gameNumber,
10999 lastLoadGameTitle, lastLoadGameUseList);
11005 /* Load the nth game from open file f */
11007 LoadGame(f, gameNumber, title, useList)
11015 int gn = gameNumber;
11016 ListGame *lg = NULL;
11017 int numPGNTags = 0;
11019 GameMode oldGameMode;
11020 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
11022 if (appData.debugMode)
11023 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
11025 if (gameMode == Training )
11026 SetTrainingModeOff();
11028 oldGameMode = gameMode;
11029 if (gameMode != BeginningOfGame) {
11030 Reset(FALSE, TRUE);
11034 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
11035 fclose(lastLoadGameFP);
11039 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
11042 fseek(f, lg->offset, 0);
11043 GameListHighlight(gameNumber);
11047 DisplayError(_("Game number out of range"), 0);
11052 if (fseek(f, 0, 0) == -1) {
11053 if (f == lastLoadGameFP ?
11054 gameNumber == lastLoadGameNumber + 1 :
11058 DisplayError(_("Can't seek on game file"), 0);
11063 lastLoadGameFP = f;
11064 lastLoadGameNumber = gameNumber;
11065 safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
11066 lastLoadGameUseList = useList;
11070 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
11071 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
11072 lg->gameInfo.black);
11074 } else if (*title != NULLCHAR) {
11075 if (gameNumber > 1) {
11076 snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
11079 DisplayTitle(title);
11083 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
11084 gameMode = PlayFromGameFile;
11088 currentMove = forwardMostMove = backwardMostMove = 0;
11089 CopyBoard(boards[0], initialPosition);
11093 * Skip the first gn-1 games in the file.
11094 * Also skip over anything that precedes an identifiable
11095 * start of game marker, to avoid being confused by
11096 * garbage at the start of the file. Currently
11097 * recognized start of game markers are the move number "1",
11098 * the pattern "gnuchess .* game", the pattern
11099 * "^[#;%] [^ ]* game file", and a PGN tag block.
11100 * A game that starts with one of the latter two patterns
11101 * will also have a move number 1, possibly
11102 * following a position diagram.
11103 * 5-4-02: Let's try being more lenient and allowing a game to
11104 * start with an unnumbered move. Does that break anything?
11106 cm = lastLoadGameStart = EndOfFile;
11108 yyboardindex = forwardMostMove;
11109 cm = (ChessMove) Myylex();
11112 if (cmailMsgLoaded) {
11113 nCmailGames = CMAIL_MAX_GAMES - gn;
11116 DisplayError(_("Game not found in file"), 0);
11123 lastLoadGameStart = cm;
11126 case MoveNumberOne:
11127 switch (lastLoadGameStart) {
11132 case MoveNumberOne:
11134 gn--; /* count this game */
11135 lastLoadGameStart = cm;
11144 switch (lastLoadGameStart) {
11147 case MoveNumberOne:
11149 gn--; /* count this game */
11150 lastLoadGameStart = cm;
11153 lastLoadGameStart = cm; /* game counted already */
11161 yyboardindex = forwardMostMove;
11162 cm = (ChessMove) Myylex();
11163 } while (cm == PGNTag || cm == Comment);
11170 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
11171 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
11172 != CMAIL_OLD_RESULT) {
11174 cmailResult[ CMAIL_MAX_GAMES
11175 - gn - 1] = CMAIL_OLD_RESULT;
11181 /* Only a NormalMove can be at the start of a game
11182 * without a position diagram. */
11183 if (lastLoadGameStart == EndOfFile ) {
11185 lastLoadGameStart = MoveNumberOne;
11194 if (appData.debugMode)
11195 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
11197 if (cm == XBoardGame) {
11198 /* Skip any header junk before position diagram and/or move 1 */
11200 yyboardindex = forwardMostMove;
11201 cm = (ChessMove) Myylex();
11203 if (cm == EndOfFile ||
11204 cm == GNUChessGame || cm == XBoardGame) {
11205 /* Empty game; pretend end-of-file and handle later */
11210 if (cm == MoveNumberOne || cm == PositionDiagram ||
11211 cm == PGNTag || cm == Comment)
11214 } else if (cm == GNUChessGame) {
11215 if (gameInfo.event != NULL) {
11216 free(gameInfo.event);
11218 gameInfo.event = StrSave(yy_text);
11221 startedFromSetupPosition = FALSE;
11222 while (cm == PGNTag) {
11223 if (appData.debugMode)
11224 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
11225 err = ParsePGNTag(yy_text, &gameInfo);
11226 if (!err) numPGNTags++;
11228 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
11229 if(gameInfo.variant != oldVariant) {
11230 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
11231 ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
11232 InitPosition(TRUE);
11233 oldVariant = gameInfo.variant;
11234 if (appData.debugMode)
11235 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
11239 if (gameInfo.fen != NULL) {
11240 Board initial_position;
11241 startedFromSetupPosition = TRUE;
11242 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
11244 DisplayError(_("Bad FEN position in file"), 0);
11247 CopyBoard(boards[0], initial_position);
11248 if (blackPlaysFirst) {
11249 currentMove = forwardMostMove = backwardMostMove = 1;
11250 CopyBoard(boards[1], initial_position);
11251 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11252 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11253 timeRemaining[0][1] = whiteTimeRemaining;
11254 timeRemaining[1][1] = blackTimeRemaining;
11255 if (commentList[0] != NULL) {
11256 commentList[1] = commentList[0];
11257 commentList[0] = NULL;
11260 currentMove = forwardMostMove = backwardMostMove = 0;
11262 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
11264 initialRulePlies = FENrulePlies;
11265 for( i=0; i< nrCastlingRights; i++ )
11266 initialRights[i] = initial_position[CASTLING][i];
11268 yyboardindex = forwardMostMove;
11269 free(gameInfo.fen);
11270 gameInfo.fen = NULL;
11273 yyboardindex = forwardMostMove;
11274 cm = (ChessMove) Myylex();
11276 /* Handle comments interspersed among the tags */
11277 while (cm == Comment) {
11279 if (appData.debugMode)
11280 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11282 AppendComment(currentMove, p, FALSE);
11283 yyboardindex = forwardMostMove;
11284 cm = (ChessMove) Myylex();
11288 /* don't rely on existence of Event tag since if game was
11289 * pasted from clipboard the Event tag may not exist
11291 if (numPGNTags > 0){
11293 if (gameInfo.variant == VariantNormal) {
11294 VariantClass v = StringToVariant(gameInfo.event);
11295 // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11296 if(v < VariantShogi) gameInfo.variant = v;
11299 if( appData.autoDisplayTags ) {
11300 tags = PGNTags(&gameInfo);
11301 TagsPopUp(tags, CmailMsg());
11306 /* Make something up, but don't display it now */
11311 if (cm == PositionDiagram) {
11314 Board initial_position;
11316 if (appData.debugMode)
11317 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11319 if (!startedFromSetupPosition) {
11321 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11322 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11333 initial_position[i][j++] = CharToPiece(*p);
11336 while (*p == ' ' || *p == '\t' ||
11337 *p == '\n' || *p == '\r') p++;
11339 if (strncmp(p, "black", strlen("black"))==0)
11340 blackPlaysFirst = TRUE;
11342 blackPlaysFirst = FALSE;
11343 startedFromSetupPosition = TRUE;
11345 CopyBoard(boards[0], initial_position);
11346 if (blackPlaysFirst) {
11347 currentMove = forwardMostMove = backwardMostMove = 1;
11348 CopyBoard(boards[1], initial_position);
11349 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11350 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11351 timeRemaining[0][1] = whiteTimeRemaining;
11352 timeRemaining[1][1] = blackTimeRemaining;
11353 if (commentList[0] != NULL) {
11354 commentList[1] = commentList[0];
11355 commentList[0] = NULL;
11358 currentMove = forwardMostMove = backwardMostMove = 0;
11361 yyboardindex = forwardMostMove;
11362 cm = (ChessMove) Myylex();
11365 if (first.pr == NoProc) {
11366 StartChessProgram(&first);
11368 InitChessProgram(&first, FALSE);
11369 SendToProgram("force\n", &first);
11370 if (startedFromSetupPosition) {
11371 SendBoard(&first, forwardMostMove);
11372 if (appData.debugMode) {
11373 fprintf(debugFP, "Load Game\n");
11375 DisplayBothClocks();
11378 /* [HGM] server: flag to write setup moves in broadcast file as one */
11379 loadFlag = appData.suppressLoadMoves;
11381 while (cm == Comment) {
11383 if (appData.debugMode)
11384 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11386 AppendComment(currentMove, p, FALSE);
11387 yyboardindex = forwardMostMove;
11388 cm = (ChessMove) Myylex();
11391 if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11392 cm == WhiteWins || cm == BlackWins ||
11393 cm == GameIsDrawn || cm == GameUnfinished) {
11394 DisplayMessage("", _("No moves in game"));
11395 if (cmailMsgLoaded) {
11396 if (appData.debugMode)
11397 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11401 DrawPosition(FALSE, boards[currentMove]);
11402 DisplayBothClocks();
11403 gameMode = EditGame;
11410 // [HGM] PV info: routine tests if comment empty
11411 if (!matchMode && (pausing || appData.timeDelay != 0)) {
11412 DisplayComment(currentMove - 1, commentList[currentMove]);
11414 if (!matchMode && appData.timeDelay != 0)
11415 DrawPosition(FALSE, boards[currentMove]);
11417 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11418 programStats.ok_to_send = 1;
11421 /* if the first token after the PGN tags is a move
11422 * and not move number 1, retrieve it from the parser
11424 if (cm != MoveNumberOne)
11425 LoadGameOneMove(cm);
11427 /* load the remaining moves from the file */
11428 while (LoadGameOneMove(EndOfFile)) {
11429 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11430 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11433 /* rewind to the start of the game */
11434 currentMove = backwardMostMove;
11436 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11438 if (oldGameMode == AnalyzeFile ||
11439 oldGameMode == AnalyzeMode) {
11440 AnalyzeFileEvent();
11443 if (matchMode || appData.timeDelay == 0) {
11445 gameMode = EditGame;
11447 } else if (appData.timeDelay > 0) {
11448 AutoPlayGameLoop();
11451 if (appData.debugMode)
11452 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11454 loadFlag = 0; /* [HGM] true game starts */
11458 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11460 ReloadPosition(offset)
11463 int positionNumber = lastLoadPositionNumber + offset;
11464 if (lastLoadPositionFP == NULL) {
11465 DisplayError(_("No position has been loaded yet"), 0);
11468 if (positionNumber <= 0) {
11469 DisplayError(_("Can't back up any further"), 0);
11472 return LoadPosition(lastLoadPositionFP, positionNumber,
11473 lastLoadPositionTitle);
11476 /* Load the nth position from the given file */
11478 LoadPositionFromFile(filename, n, title)
11486 if (strcmp(filename, "-") == 0) {
11487 return LoadPosition(stdin, n, "stdin");
11489 f = fopen(filename, "rb");
11491 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11492 DisplayError(buf, errno);
11495 return LoadPosition(f, n, title);
11500 /* Load the nth position from the given open file, and close it */
11502 LoadPosition(f, positionNumber, title)
11504 int positionNumber;
11507 char *p, line[MSG_SIZ];
11508 Board initial_position;
11509 int i, j, fenMode, pn;
11511 if (gameMode == Training )
11512 SetTrainingModeOff();
11514 if (gameMode != BeginningOfGame) {
11515 Reset(FALSE, TRUE);
11517 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
11518 fclose(lastLoadPositionFP);
11520 if (positionNumber == 0) positionNumber = 1;
11521 lastLoadPositionFP = f;
11522 lastLoadPositionNumber = positionNumber;
11523 safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
11524 if (first.pr == NoProc) {
11525 StartChessProgram(&first);
11526 InitChessProgram(&first, FALSE);
11528 pn = positionNumber;
11529 if (positionNumber < 0) {
11530 /* Negative position number means to seek to that byte offset */
11531 if (fseek(f, -positionNumber, 0) == -1) {
11532 DisplayError(_("Can't seek on position file"), 0);
11537 if (fseek(f, 0, 0) == -1) {
11538 if (f == lastLoadPositionFP ?
11539 positionNumber == lastLoadPositionNumber + 1 :
11540 positionNumber == 1) {
11543 DisplayError(_("Can't seek on position file"), 0);
11548 /* See if this file is FEN or old-style xboard */
11549 if (fgets(line, MSG_SIZ, f) == NULL) {
11550 DisplayError(_("Position not found in file"), 0);
11553 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
11554 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
11557 if (fenMode || line[0] == '#') pn--;
11559 /* skip positions before number pn */
11560 if (fgets(line, MSG_SIZ, f) == NULL) {
11562 DisplayError(_("Position not found in file"), 0);
11565 if (fenMode || line[0] == '#') pn--;
11570 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
11571 DisplayError(_("Bad FEN position in file"), 0);
11575 (void) fgets(line, MSG_SIZ, f);
11576 (void) fgets(line, MSG_SIZ, f);
11578 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11579 (void) fgets(line, MSG_SIZ, f);
11580 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
11583 initial_position[i][j++] = CharToPiece(*p);
11587 blackPlaysFirst = FALSE;
11589 (void) fgets(line, MSG_SIZ, f);
11590 if (strncmp(line, "black", strlen("black"))==0)
11591 blackPlaysFirst = TRUE;
11594 startedFromSetupPosition = TRUE;
11596 SendToProgram("force\n", &first);
11597 CopyBoard(boards[0], initial_position);
11598 if (blackPlaysFirst) {
11599 currentMove = forwardMostMove = backwardMostMove = 1;
11600 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11601 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11602 CopyBoard(boards[1], initial_position);
11603 DisplayMessage("", _("Black to play"));
11605 currentMove = forwardMostMove = backwardMostMove = 0;
11606 DisplayMessage("", _("White to play"));
11608 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
11609 SendBoard(&first, forwardMostMove);
11610 if (appData.debugMode) {
11612 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
11613 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
11614 fprintf(debugFP, "Load Position\n");
11617 if (positionNumber > 1) {
11618 snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
11619 DisplayTitle(line);
11621 DisplayTitle(title);
11623 gameMode = EditGame;
11626 timeRemaining[0][1] = whiteTimeRemaining;
11627 timeRemaining[1][1] = blackTimeRemaining;
11628 DrawPosition(FALSE, boards[currentMove]);
11635 CopyPlayerNameIntoFileName(dest, src)
11638 while (*src != NULLCHAR && *src != ',') {
11643 *(*dest)++ = *src++;
11648 char *DefaultFileName(ext)
11651 static char def[MSG_SIZ];
11654 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
11656 CopyPlayerNameIntoFileName(&p, gameInfo.white);
11658 CopyPlayerNameIntoFileName(&p, gameInfo.black);
11660 safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
11667 /* Save the current game to the given file */
11669 SaveGameToFile(filename, append)
11677 if (strcmp(filename, "-") == 0) {
11678 return SaveGame(stdout, 0, NULL);
11680 f = fopen(filename, append ? "a" : "w");
11682 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11683 DisplayError(buf, errno);
11686 safeStrCpy(buf, lastMsg, MSG_SIZ);
11687 DisplayMessage(_("Waiting for access to save file"), "");
11688 flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
11689 DisplayMessage(_("Saving game"), "");
11690 if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError("Bad Seek", errno); // better safe than sorry...
11691 result = SaveGame(f, 0, NULL);
11692 DisplayMessage(buf, "");
11702 static char buf[MSG_SIZ];
11705 p = strchr(str, ' ');
11706 if (p == NULL) return str;
11707 strncpy(buf, str, p - str);
11708 buf[p - str] = NULLCHAR;
11712 #define PGN_MAX_LINE 75
11714 #define PGN_SIDE_WHITE 0
11715 #define PGN_SIDE_BLACK 1
11718 static int FindFirstMoveOutOfBook( int side )
11722 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
11723 int index = backwardMostMove;
11724 int has_book_hit = 0;
11726 if( (index % 2) != side ) {
11730 while( index < forwardMostMove ) {
11731 /* Check to see if engine is in book */
11732 int depth = pvInfoList[index].depth;
11733 int score = pvInfoList[index].score;
11739 else if( score == 0 && depth == 63 ) {
11740 in_book = 1; /* Zappa */
11742 else if( score == 2 && depth == 99 ) {
11743 in_book = 1; /* Abrok */
11746 has_book_hit += in_book;
11762 void GetOutOfBookInfo( char * buf )
11766 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11768 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
11769 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
11773 if( oob[0] >= 0 || oob[1] >= 0 ) {
11774 for( i=0; i<2; i++ ) {
11778 if( i > 0 && oob[0] >= 0 ) {
11779 strcat( buf, " " );
11782 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
11783 sprintf( buf+strlen(buf), "%s%.2f",
11784 pvInfoList[idx].score >= 0 ? "+" : "",
11785 pvInfoList[idx].score / 100.0 );
11791 /* Save game in PGN style and close the file */
11796 int i, offset, linelen, newblock;
11800 int movelen, numlen, blank;
11801 char move_buffer[100]; /* [AS] Buffer for move+PV info */
11803 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11805 tm = time((time_t *) NULL);
11807 PrintPGNTags(f, &gameInfo);
11809 if (backwardMostMove > 0 || startedFromSetupPosition) {
11810 char *fen = PositionToFEN(backwardMostMove, NULL);
11811 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
11812 fprintf(f, "\n{--------------\n");
11813 PrintPosition(f, backwardMostMove);
11814 fprintf(f, "--------------}\n");
11818 /* [AS] Out of book annotation */
11819 if( appData.saveOutOfBookInfo ) {
11822 GetOutOfBookInfo( buf );
11824 if( buf[0] != '\0' ) {
11825 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
11832 i = backwardMostMove;
11836 while (i < forwardMostMove) {
11837 /* Print comments preceding this move */
11838 if (commentList[i] != NULL) {
11839 if (linelen > 0) fprintf(f, "\n");
11840 fprintf(f, "%s", commentList[i]);
11845 /* Format move number */
11847 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
11850 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
11852 numtext[0] = NULLCHAR;
11854 numlen = strlen(numtext);
11857 /* Print move number */
11858 blank = linelen > 0 && numlen > 0;
11859 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
11868 fprintf(f, "%s", numtext);
11872 safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
11873 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
11876 blank = linelen > 0 && movelen > 0;
11877 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11886 fprintf(f, "%s", move_buffer);
11887 linelen += movelen;
11889 /* [AS] Add PV info if present */
11890 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
11891 /* [HGM] add time */
11892 char buf[MSG_SIZ]; int seconds;
11894 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
11900 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
11903 seconds = (seconds + 4)/10; // round to full seconds
11905 snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
11907 snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
11910 snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
11911 pvInfoList[i].score >= 0 ? "+" : "",
11912 pvInfoList[i].score / 100.0,
11913 pvInfoList[i].depth,
11916 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11918 /* Print score/depth */
11919 blank = linelen > 0 && movelen > 0;
11920 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11929 fprintf(f, "%s", move_buffer);
11930 linelen += movelen;
11936 /* Start a new line */
11937 if (linelen > 0) fprintf(f, "\n");
11939 /* Print comments after last move */
11940 if (commentList[i] != NULL) {
11941 fprintf(f, "%s\n", commentList[i]);
11945 if (gameInfo.resultDetails != NULL &&
11946 gameInfo.resultDetails[0] != NULLCHAR) {
11947 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11948 PGNResult(gameInfo.result));
11950 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11954 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11958 /* Save game in old style and close the file */
11960 SaveGameOldStyle(f)
11966 tm = time((time_t *) NULL);
11968 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11971 if (backwardMostMove > 0 || startedFromSetupPosition) {
11972 fprintf(f, "\n[--------------\n");
11973 PrintPosition(f, backwardMostMove);
11974 fprintf(f, "--------------]\n");
11979 i = backwardMostMove;
11980 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11982 while (i < forwardMostMove) {
11983 if (commentList[i] != NULL) {
11984 fprintf(f, "[%s]\n", commentList[i]);
11987 if ((i % 2) == 1) {
11988 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
11991 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
11993 if (commentList[i] != NULL) {
11997 if (i >= forwardMostMove) {
12001 fprintf(f, "%s\n", parseList[i]);
12006 if (commentList[i] != NULL) {
12007 fprintf(f, "[%s]\n", commentList[i]);
12010 /* This isn't really the old style, but it's close enough */
12011 if (gameInfo.resultDetails != NULL &&
12012 gameInfo.resultDetails[0] != NULLCHAR) {
12013 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
12014 gameInfo.resultDetails);
12016 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
12023 /* Save the current game to open file f and close the file */
12025 SaveGame(f, dummy, dummy2)
12030 if (gameMode == EditPosition) EditPositionDone(TRUE);
12031 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
12032 if (appData.oldSaveStyle)
12033 return SaveGameOldStyle(f);
12035 return SaveGamePGN(f);
12038 /* Save the current position to the given file */
12040 SavePositionToFile(filename)
12046 if (strcmp(filename, "-") == 0) {
12047 return SavePosition(stdout, 0, NULL);
12049 f = fopen(filename, "a");
12051 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
12052 DisplayError(buf, errno);
12055 safeStrCpy(buf, lastMsg, MSG_SIZ);
12056 DisplayMessage(_("Waiting for access to save file"), "");
12057 flock(fileno(f), LOCK_EX); // [HGM] lock
12058 DisplayMessage(_("Saving position"), "");
12059 lseek(fileno(f), 0, SEEK_END); // better safe than sorry...
12060 SavePosition(f, 0, NULL);
12061 DisplayMessage(buf, "");
12067 /* Save the current position to the given open file and close the file */
12069 SavePosition(f, dummy, dummy2)
12077 if (gameMode == EditPosition) EditPositionDone(TRUE);
12078 if (appData.oldSaveStyle) {
12079 tm = time((time_t *) NULL);
12081 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
12083 fprintf(f, "[--------------\n");
12084 PrintPosition(f, currentMove);
12085 fprintf(f, "--------------]\n");
12087 fen = PositionToFEN(currentMove, NULL);
12088 fprintf(f, "%s\n", fen);
12096 ReloadCmailMsgEvent(unregister)
12100 static char *inFilename = NULL;
12101 static char *outFilename;
12103 struct stat inbuf, outbuf;
12106 /* Any registered moves are unregistered if unregister is set, */
12107 /* i.e. invoked by the signal handler */
12109 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12110 cmailMoveRegistered[i] = FALSE;
12111 if (cmailCommentList[i] != NULL) {
12112 free(cmailCommentList[i]);
12113 cmailCommentList[i] = NULL;
12116 nCmailMovesRegistered = 0;
12119 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
12120 cmailResult[i] = CMAIL_NOT_RESULT;
12124 if (inFilename == NULL) {
12125 /* Because the filenames are static they only get malloced once */
12126 /* and they never get freed */
12127 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
12128 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
12130 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
12131 sprintf(outFilename, "%s.out", appData.cmailGameName);
12134 status = stat(outFilename, &outbuf);
12136 cmailMailedMove = FALSE;
12138 status = stat(inFilename, &inbuf);
12139 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
12142 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
12143 counts the games, notes how each one terminated, etc.
12145 It would be nice to remove this kludge and instead gather all
12146 the information while building the game list. (And to keep it
12147 in the game list nodes instead of having a bunch of fixed-size
12148 parallel arrays.) Note this will require getting each game's
12149 termination from the PGN tags, as the game list builder does
12150 not process the game moves. --mann
12152 cmailMsgLoaded = TRUE;
12153 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
12155 /* Load first game in the file or popup game menu */
12156 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
12158 #endif /* !WIN32 */
12166 char string[MSG_SIZ];
12168 if ( cmailMailedMove
12169 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
12170 return TRUE; /* Allow free viewing */
12173 /* Unregister move to ensure that we don't leave RegisterMove */
12174 /* with the move registered when the conditions for registering no */
12176 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
12177 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
12178 nCmailMovesRegistered --;
12180 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
12182 free(cmailCommentList[lastLoadGameNumber - 1]);
12183 cmailCommentList[lastLoadGameNumber - 1] = NULL;
12187 if (cmailOldMove == -1) {
12188 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
12192 if (currentMove > cmailOldMove + 1) {
12193 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
12197 if (currentMove < cmailOldMove) {
12198 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
12202 if (forwardMostMove > currentMove) {
12203 /* Silently truncate extra moves */
12207 if ( (currentMove == cmailOldMove + 1)
12208 || ( (currentMove == cmailOldMove)
12209 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
12210 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
12211 if (gameInfo.result != GameUnfinished) {
12212 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
12215 if (commentList[currentMove] != NULL) {
12216 cmailCommentList[lastLoadGameNumber - 1]
12217 = StrSave(commentList[currentMove]);
12219 safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
12221 if (appData.debugMode)
12222 fprintf(debugFP, "Saving %s for game %d\n",
12223 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
12225 snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
12227 f = fopen(string, "w");
12228 if (appData.oldSaveStyle) {
12229 SaveGameOldStyle(f); /* also closes the file */
12231 snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
12232 f = fopen(string, "w");
12233 SavePosition(f, 0, NULL); /* also closes the file */
12235 fprintf(f, "{--------------\n");
12236 PrintPosition(f, currentMove);
12237 fprintf(f, "--------------}\n\n");
12239 SaveGame(f, 0, NULL); /* also closes the file*/
12242 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
12243 nCmailMovesRegistered ++;
12244 } else if (nCmailGames == 1) {
12245 DisplayError(_("You have not made a move yet"), 0);
12256 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
12257 FILE *commandOutput;
12258 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
12259 int nBytes = 0; /* Suppress warnings on uninitialized variables */
12265 if (! cmailMsgLoaded) {
12266 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
12270 if (nCmailGames == nCmailResults) {
12271 DisplayError(_("No unfinished games"), 0);
12275 #if CMAIL_PROHIBIT_REMAIL
12276 if (cmailMailedMove) {
12277 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);
12278 DisplayError(msg, 0);
12283 if (! (cmailMailedMove || RegisterMove())) return;
12285 if ( cmailMailedMove
12286 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12287 snprintf(string, MSG_SIZ, partCommandString,
12288 appData.debugMode ? " -v" : "", appData.cmailGameName);
12289 commandOutput = popen(string, "r");
12291 if (commandOutput == NULL) {
12292 DisplayError(_("Failed to invoke cmail"), 0);
12294 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12295 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12297 if (nBuffers > 1) {
12298 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12299 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12300 nBytes = MSG_SIZ - 1;
12302 (void) memcpy(msg, buffer, nBytes);
12304 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12306 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12307 cmailMailedMove = TRUE; /* Prevent >1 moves */
12310 for (i = 0; i < nCmailGames; i ++) {
12311 if (cmailResult[i] == CMAIL_NOT_RESULT) {
12316 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12318 snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12320 appData.cmailGameName,
12322 LoadGameFromFile(buffer, 1, buffer, FALSE);
12323 cmailMsgLoaded = FALSE;
12327 DisplayInformation(msg);
12328 pclose(commandOutput);
12331 if ((*cmailMsg) != '\0') {
12332 DisplayInformation(cmailMsg);
12337 #endif /* !WIN32 */
12346 int prependComma = 0;
12348 char string[MSG_SIZ]; /* Space for game-list */
12351 if (!cmailMsgLoaded) return "";
12353 if (cmailMailedMove) {
12354 snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12356 /* Create a list of games left */
12357 snprintf(string, MSG_SIZ, "[");
12358 for (i = 0; i < nCmailGames; i ++) {
12359 if (! ( cmailMoveRegistered[i]
12360 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12361 if (prependComma) {
12362 snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12364 snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12368 strcat(string, number);
12371 strcat(string, "]");
12373 if (nCmailMovesRegistered + nCmailResults == 0) {
12374 switch (nCmailGames) {
12376 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12380 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12384 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12389 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12391 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12396 if (nCmailResults == nCmailGames) {
12397 snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12399 snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12404 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12416 if (gameMode == Training)
12417 SetTrainingModeOff();
12420 cmailMsgLoaded = FALSE;
12421 if (appData.icsActive) {
12422 SendToICS(ics_prefix);
12423 SendToICS("refresh\n");
12433 /* Give up on clean exit */
12437 /* Keep trying for clean exit */
12441 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12443 if (telnetISR != NULL) {
12444 RemoveInputSource(telnetISR);
12446 if (icsPR != NoProc) {
12447 DestroyChildProcess(icsPR, TRUE);
12450 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12451 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12453 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12454 /* make sure this other one finishes before killing it! */
12455 if(endingGame) { int count = 0;
12456 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12457 while(endingGame && count++ < 10) DoSleep(1);
12458 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12461 /* Kill off chess programs */
12462 if (first.pr != NoProc) {
12465 DoSleep( appData.delayBeforeQuit );
12466 SendToProgram("quit\n", &first);
12467 DoSleep( appData.delayAfterQuit );
12468 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12470 if (second.pr != NoProc) {
12471 DoSleep( appData.delayBeforeQuit );
12472 SendToProgram("quit\n", &second);
12473 DoSleep( appData.delayAfterQuit );
12474 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12476 if (first.isr != NULL) {
12477 RemoveInputSource(first.isr);
12479 if (second.isr != NULL) {
12480 RemoveInputSource(second.isr);
12483 if (pairing.pr != NoProc) SendToProgram("quit\n", &pairing);
12484 if (pairing.isr != NULL) RemoveInputSource(pairing.isr);
12486 ShutDownFrontEnd();
12493 if (appData.debugMode)
12494 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
12498 if (gameMode == MachinePlaysWhite ||
12499 gameMode == MachinePlaysBlack) {
12502 DisplayBothClocks();
12504 if (gameMode == PlayFromGameFile) {
12505 if (appData.timeDelay >= 0)
12506 AutoPlayGameLoop();
12507 } else if (gameMode == IcsExamining && pauseExamInvalid) {
12508 Reset(FALSE, TRUE);
12509 SendToICS(ics_prefix);
12510 SendToICS("refresh\n");
12511 } else if (currentMove < forwardMostMove) {
12512 ForwardInner(forwardMostMove);
12514 pauseExamInvalid = FALSE;
12516 switch (gameMode) {
12520 pauseExamForwardMostMove = forwardMostMove;
12521 pauseExamInvalid = FALSE;
12524 case IcsPlayingWhite:
12525 case IcsPlayingBlack:
12529 case PlayFromGameFile:
12530 (void) StopLoadGameTimer();
12534 case BeginningOfGame:
12535 if (appData.icsActive) return;
12536 /* else fall through */
12537 case MachinePlaysWhite:
12538 case MachinePlaysBlack:
12539 case TwoMachinesPlay:
12540 if (forwardMostMove == 0)
12541 return; /* don't pause if no one has moved */
12542 if ((gameMode == MachinePlaysWhite &&
12543 !WhiteOnMove(forwardMostMove)) ||
12544 (gameMode == MachinePlaysBlack &&
12545 WhiteOnMove(forwardMostMove))) {
12558 char title[MSG_SIZ];
12560 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
12561 safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
12563 snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
12564 WhiteOnMove(currentMove - 1) ? " " : ".. ",
12565 parseList[currentMove - 1]);
12568 EditCommentPopUp(currentMove, title, commentList[currentMove]);
12575 char *tags = PGNTags(&gameInfo);
12577 EditTagsPopUp(tags, NULL);
12584 if (appData.noChessProgram || gameMode == AnalyzeMode)
12587 if (gameMode != AnalyzeFile) {
12588 if (!appData.icsEngineAnalyze) {
12590 if (gameMode != EditGame) return;
12592 ResurrectChessProgram();
12593 SendToProgram("analyze\n", &first);
12594 first.analyzing = TRUE;
12595 /*first.maybeThinking = TRUE;*/
12596 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12597 EngineOutputPopUp();
12599 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
12604 StartAnalysisClock();
12605 GetTimeMark(&lastNodeCountTime);
12612 if (appData.noChessProgram || gameMode == AnalyzeFile)
12615 if (gameMode != AnalyzeMode) {
12617 if (gameMode != EditGame) return;
12618 ResurrectChessProgram();
12619 SendToProgram("analyze\n", &first);
12620 first.analyzing = TRUE;
12621 /*first.maybeThinking = TRUE;*/
12622 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12623 EngineOutputPopUp();
12625 gameMode = AnalyzeFile;
12630 StartAnalysisClock();
12631 GetTimeMark(&lastNodeCountTime);
12636 MachineWhiteEvent()
12639 char *bookHit = NULL;
12641 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
12645 if (gameMode == PlayFromGameFile ||
12646 gameMode == TwoMachinesPlay ||
12647 gameMode == Training ||
12648 gameMode == AnalyzeMode ||
12649 gameMode == EndOfGame)
12652 if (gameMode == EditPosition)
12653 EditPositionDone(TRUE);
12655 if (!WhiteOnMove(currentMove)) {
12656 DisplayError(_("It is not White's turn"), 0);
12660 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12663 if (gameMode == EditGame || gameMode == AnalyzeMode ||
12664 gameMode == AnalyzeFile)
12667 ResurrectChessProgram(); /* in case it isn't running */
12668 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
12669 gameMode = MachinePlaysWhite;
12672 gameMode = MachinePlaysWhite;
12676 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12678 if (first.sendName) {
12679 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
12680 SendToProgram(buf, &first);
12682 if (first.sendTime) {
12683 if (first.useColors) {
12684 SendToProgram("black\n", &first); /*gnu kludge*/
12686 SendTimeRemaining(&first, TRUE);
12688 if (first.useColors) {
12689 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
12691 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12692 SetMachineThinkingEnables();
12693 first.maybeThinking = TRUE;
12697 if (appData.autoFlipView && !flipView) {
12698 flipView = !flipView;
12699 DrawPosition(FALSE, NULL);
12700 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
12703 if(bookHit) { // [HGM] book: simulate book reply
12704 static char bookMove[MSG_SIZ]; // a bit generous?
12706 programStats.nodes = programStats.depth = programStats.time =
12707 programStats.score = programStats.got_only_move = 0;
12708 sprintf(programStats.movelist, "%s (xbook)", bookHit);
12710 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12711 strcat(bookMove, bookHit);
12712 HandleMachineMove(bookMove, &first);
12717 MachineBlackEvent()
12720 char *bookHit = NULL;
12722 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
12726 if (gameMode == PlayFromGameFile ||
12727 gameMode == TwoMachinesPlay ||
12728 gameMode == Training ||
12729 gameMode == AnalyzeMode ||
12730 gameMode == EndOfGame)
12733 if (gameMode == EditPosition)
12734 EditPositionDone(TRUE);
12736 if (WhiteOnMove(currentMove)) {
12737 DisplayError(_("It is not Black's turn"), 0);
12741 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12744 if (gameMode == EditGame || gameMode == AnalyzeMode ||
12745 gameMode == AnalyzeFile)
12748 ResurrectChessProgram(); /* in case it isn't running */
12749 gameMode = MachinePlaysBlack;
12753 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12755 if (first.sendName) {
12756 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
12757 SendToProgram(buf, &first);
12759 if (first.sendTime) {
12760 if (first.useColors) {
12761 SendToProgram("white\n", &first); /*gnu kludge*/
12763 SendTimeRemaining(&first, FALSE);
12765 if (first.useColors) {
12766 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
12768 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12769 SetMachineThinkingEnables();
12770 first.maybeThinking = TRUE;
12773 if (appData.autoFlipView && flipView) {
12774 flipView = !flipView;
12775 DrawPosition(FALSE, NULL);
12776 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
12778 if(bookHit) { // [HGM] book: simulate book reply
12779 static char bookMove[MSG_SIZ]; // a bit generous?
12781 programStats.nodes = programStats.depth = programStats.time =
12782 programStats.score = programStats.got_only_move = 0;
12783 sprintf(programStats.movelist, "%s (xbook)", bookHit);
12785 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12786 strcat(bookMove, bookHit);
12787 HandleMachineMove(bookMove, &first);
12793 DisplayTwoMachinesTitle()
12796 if (appData.matchGames > 0) {
12797 if(appData.tourneyFile[0]) {
12798 snprintf(buf, MSG_SIZ, "%s vs. %s (%d/%d%s)",
12799 gameInfo.white, gameInfo.black,
12800 nextGame+1, appData.matchGames+1,
12801 appData.tourneyType>0 ? "gt" : appData.tourneyType<0 ? "sw" : "rr");
12803 if (first.twoMachinesColor[0] == 'w') {
12804 snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12805 gameInfo.white, gameInfo.black,
12806 first.matchWins, second.matchWins,
12807 matchGame - 1 - (first.matchWins + second.matchWins));
12809 snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12810 gameInfo.white, gameInfo.black,
12811 second.matchWins, first.matchWins,
12812 matchGame - 1 - (first.matchWins + second.matchWins));
12815 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12821 SettingsMenuIfReady()
12823 if (second.lastPing != second.lastPong) {
12824 DisplayMessage("", _("Waiting for second chess program"));
12825 ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
12829 DisplayMessage("", "");
12830 SettingsPopUp(&second);
12834 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
12837 if (cps->pr == NULL) {
12838 StartChessProgram(cps);
12839 if (cps->protocolVersion == 1) {
12842 /* kludge: allow timeout for initial "feature" command */
12844 snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
12845 DisplayMessage("", buf);
12846 ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
12854 TwoMachinesEvent P((void))
12858 ChessProgramState *onmove;
12859 char *bookHit = NULL;
12860 static int stalling = 0;
12864 if (appData.noChessProgram) return;
12866 switch (gameMode) {
12867 case TwoMachinesPlay:
12869 case MachinePlaysWhite:
12870 case MachinePlaysBlack:
12871 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12872 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12876 case BeginningOfGame:
12877 case PlayFromGameFile:
12880 if (gameMode != EditGame) return;
12883 EditPositionDone(TRUE);
12894 // forwardMostMove = currentMove;
12895 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
12897 if(!ResurrectChessProgram()) return; /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
12899 if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
12900 if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
12901 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12905 InitChessProgram(&second, FALSE); // unbalances ping of second engine
12906 SendToProgram("force\n", &second);
12908 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12911 GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
12912 if(appData.matchPause>10000 || appData.matchPause<10)
12913 appData.matchPause = 10000; /* [HGM] make pause adjustable */
12914 wait = SubtractTimeMarks(&now, &pauseStart);
12915 if(wait < appData.matchPause) {
12916 ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
12920 DisplayMessage("", "");
12921 if (startedFromSetupPosition) {
12922 SendBoard(&second, backwardMostMove);
12923 if (appData.debugMode) {
12924 fprintf(debugFP, "Two Machines\n");
12927 for (i = backwardMostMove; i < forwardMostMove; i++) {
12928 SendMoveToProgram(i, &second);
12931 gameMode = TwoMachinesPlay;
12933 ModeHighlight(); // [HGM] logo: this triggers display update of logos
12935 DisplayTwoMachinesTitle();
12937 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
12942 if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
12943 SendToProgram(first.computerString, &first);
12944 if (first.sendName) {
12945 snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
12946 SendToProgram(buf, &first);
12948 SendToProgram(second.computerString, &second);
12949 if (second.sendName) {
12950 snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12951 SendToProgram(buf, &second);
12955 if (!first.sendTime || !second.sendTime) {
12956 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12957 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12959 if (onmove->sendTime) {
12960 if (onmove->useColors) {
12961 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12963 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12965 if (onmove->useColors) {
12966 SendToProgram(onmove->twoMachinesColor, onmove);
12968 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12969 // SendToProgram("go\n", onmove);
12970 onmove->maybeThinking = TRUE;
12971 SetMachineThinkingEnables();
12975 if(bookHit) { // [HGM] book: simulate book reply
12976 static char bookMove[MSG_SIZ]; // a bit generous?
12978 programStats.nodes = programStats.depth = programStats.time =
12979 programStats.score = programStats.got_only_move = 0;
12980 sprintf(programStats.movelist, "%s (xbook)", bookHit);
12982 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12983 strcat(bookMove, bookHit);
12984 savedMessage = bookMove; // args for deferred call
12985 savedState = onmove;
12986 ScheduleDelayedEvent(DeferredBookMove, 1);
12993 if (gameMode == Training) {
12994 SetTrainingModeOff();
12995 gameMode = PlayFromGameFile;
12996 DisplayMessage("", _("Training mode off"));
12998 gameMode = Training;
12999 animateTraining = appData.animate;
13001 /* make sure we are not already at the end of the game */
13002 if (currentMove < forwardMostMove) {
13003 SetTrainingModeOn();
13004 DisplayMessage("", _("Training mode on"));
13006 gameMode = PlayFromGameFile;
13007 DisplayError(_("Already at end of game"), 0);
13016 if (!appData.icsActive) return;
13017 switch (gameMode) {
13018 case IcsPlayingWhite:
13019 case IcsPlayingBlack:
13022 case BeginningOfGame:
13030 EditPositionDone(TRUE);
13043 gameMode = IcsIdle;
13054 switch (gameMode) {
13056 SetTrainingModeOff();
13058 case MachinePlaysWhite:
13059 case MachinePlaysBlack:
13060 case BeginningOfGame:
13061 SendToProgram("force\n", &first);
13062 SetUserThinkingEnables();
13064 case PlayFromGameFile:
13065 (void) StopLoadGameTimer();
13066 if (gameFileFP != NULL) {
13071 EditPositionDone(TRUE);
13076 SendToProgram("force\n", &first);
13078 case TwoMachinesPlay:
13079 GameEnds(EndOfFile, NULL, GE_PLAYER);
13080 ResurrectChessProgram();
13081 SetUserThinkingEnables();
13084 ResurrectChessProgram();
13086 case IcsPlayingBlack:
13087 case IcsPlayingWhite:
13088 DisplayError(_("Warning: You are still playing a game"), 0);
13091 DisplayError(_("Warning: You are still observing a game"), 0);
13094 DisplayError(_("Warning: You are still examining a game"), 0);
13105 first.offeredDraw = second.offeredDraw = 0;
13107 if (gameMode == PlayFromGameFile) {
13108 whiteTimeRemaining = timeRemaining[0][currentMove];
13109 blackTimeRemaining = timeRemaining[1][currentMove];
13113 if (gameMode == MachinePlaysWhite ||
13114 gameMode == MachinePlaysBlack ||
13115 gameMode == TwoMachinesPlay ||
13116 gameMode == EndOfGame) {
13117 i = forwardMostMove;
13118 while (i > currentMove) {
13119 SendToProgram("undo\n", &first);
13122 whiteTimeRemaining = timeRemaining[0][currentMove];
13123 blackTimeRemaining = timeRemaining[1][currentMove];
13124 DisplayBothClocks();
13125 if (whiteFlag || blackFlag) {
13126 whiteFlag = blackFlag = 0;
13131 gameMode = EditGame;
13138 EditPositionEvent()
13140 if (gameMode == EditPosition) {
13146 if (gameMode != EditGame) return;
13148 gameMode = EditPosition;
13151 if (currentMove > 0)
13152 CopyBoard(boards[0], boards[currentMove]);
13154 blackPlaysFirst = !WhiteOnMove(currentMove);
13156 currentMove = forwardMostMove = backwardMostMove = 0;
13157 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13164 /* [DM] icsEngineAnalyze - possible call from other functions */
13165 if (appData.icsEngineAnalyze) {
13166 appData.icsEngineAnalyze = FALSE;
13168 DisplayMessage("",_("Close ICS engine analyze..."));
13170 if (first.analysisSupport && first.analyzing) {
13171 SendToProgram("exit\n", &first);
13172 first.analyzing = FALSE;
13174 thinkOutput[0] = NULLCHAR;
13178 EditPositionDone(Boolean fakeRights)
13180 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
13182 startedFromSetupPosition = TRUE;
13183 InitChessProgram(&first, FALSE);
13184 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
13185 boards[0][EP_STATUS] = EP_NONE;
13186 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
13187 if(boards[0][0][BOARD_WIDTH>>1] == king) {
13188 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
13189 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
13190 } else boards[0][CASTLING][2] = NoRights;
13191 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
13192 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
13193 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
13194 } else boards[0][CASTLING][5] = NoRights;
13196 SendToProgram("force\n", &first);
13197 if (blackPlaysFirst) {
13198 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
13199 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
13200 currentMove = forwardMostMove = backwardMostMove = 1;
13201 CopyBoard(boards[1], boards[0]);
13203 currentMove = forwardMostMove = backwardMostMove = 0;
13205 SendBoard(&first, forwardMostMove);
13206 if (appData.debugMode) {
13207 fprintf(debugFP, "EditPosDone\n");
13210 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
13211 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
13212 gameMode = EditGame;
13214 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
13215 ClearHighlights(); /* [AS] */
13218 /* Pause for `ms' milliseconds */
13219 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13229 } while (SubtractTimeMarks(&m2, &m1) < ms);
13232 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
13234 SendMultiLineToICS(buf)
13237 char temp[MSG_SIZ+1], *p;
13244 strncpy(temp, buf, len);
13249 if (*p == '\n' || *p == '\r')
13254 strcat(temp, "\n");
13256 SendToPlayer(temp, strlen(temp));
13260 SetWhiteToPlayEvent()
13262 if (gameMode == EditPosition) {
13263 blackPlaysFirst = FALSE;
13264 DisplayBothClocks(); /* works because currentMove is 0 */
13265 } else if (gameMode == IcsExamining) {
13266 SendToICS(ics_prefix);
13267 SendToICS("tomove white\n");
13272 SetBlackToPlayEvent()
13274 if (gameMode == EditPosition) {
13275 blackPlaysFirst = TRUE;
13276 currentMove = 1; /* kludge */
13277 DisplayBothClocks();
13279 } else if (gameMode == IcsExamining) {
13280 SendToICS(ics_prefix);
13281 SendToICS("tomove black\n");
13286 EditPositionMenuEvent(selection, x, y)
13287 ChessSquare selection;
13291 ChessSquare piece = boards[0][y][x];
13293 if (gameMode != EditPosition && gameMode != IcsExamining) return;
13295 switch (selection) {
13297 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13298 SendToICS(ics_prefix);
13299 SendToICS("bsetup clear\n");
13300 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13301 SendToICS(ics_prefix);
13302 SendToICS("clearboard\n");
13304 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13305 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13306 for (y = 0; y < BOARD_HEIGHT; y++) {
13307 if (gameMode == IcsExamining) {
13308 if (boards[currentMove][y][x] != EmptySquare) {
13309 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13314 boards[0][y][x] = p;
13319 if (gameMode == EditPosition) {
13320 DrawPosition(FALSE, boards[0]);
13325 SetWhiteToPlayEvent();
13329 SetBlackToPlayEvent();
13333 if (gameMode == IcsExamining) {
13334 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13335 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13338 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13339 if(x == BOARD_LEFT-2) {
13340 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13341 boards[0][y][1] = 0;
13343 if(x == BOARD_RGHT+1) {
13344 if(y >= gameInfo.holdingsSize) break;
13345 boards[0][y][BOARD_WIDTH-2] = 0;
13348 boards[0][y][x] = EmptySquare;
13349 DrawPosition(FALSE, boards[0]);
13354 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13355 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
13356 selection = (ChessSquare) (PROMOTED piece);
13357 } else if(piece == EmptySquare) selection = WhiteSilver;
13358 else selection = (ChessSquare)((int)piece - 1);
13362 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13363 piece > (int)BlackMan && piece <= (int)BlackKing ) {
13364 selection = (ChessSquare) (DEMOTED piece);
13365 } else if(piece == EmptySquare) selection = BlackSilver;
13366 else selection = (ChessSquare)((int)piece + 1);
13371 if(gameInfo.variant == VariantShatranj ||
13372 gameInfo.variant == VariantXiangqi ||
13373 gameInfo.variant == VariantCourier ||
13374 gameInfo.variant == VariantMakruk )
13375 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13380 if(gameInfo.variant == VariantXiangqi)
13381 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13382 if(gameInfo.variant == VariantKnightmate)
13383 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13386 if (gameMode == IcsExamining) {
13387 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13388 snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13389 PieceToChar(selection), AAA + x, ONE + y);
13392 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13394 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13395 n = PieceToNumber(selection - BlackPawn);
13396 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13397 boards[0][BOARD_HEIGHT-1-n][0] = selection;
13398 boards[0][BOARD_HEIGHT-1-n][1]++;
13400 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13401 n = PieceToNumber(selection);
13402 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13403 boards[0][n][BOARD_WIDTH-1] = selection;
13404 boards[0][n][BOARD_WIDTH-2]++;
13407 boards[0][y][x] = selection;
13408 DrawPosition(TRUE, boards[0]);
13416 DropMenuEvent(selection, x, y)
13417 ChessSquare selection;
13420 ChessMove moveType;
13422 switch (gameMode) {
13423 case IcsPlayingWhite:
13424 case MachinePlaysBlack:
13425 if (!WhiteOnMove(currentMove)) {
13426 DisplayMoveError(_("It is Black's turn"));
13429 moveType = WhiteDrop;
13431 case IcsPlayingBlack:
13432 case MachinePlaysWhite:
13433 if (WhiteOnMove(currentMove)) {
13434 DisplayMoveError(_("It is White's turn"));
13437 moveType = BlackDrop;
13440 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13446 if (moveType == BlackDrop && selection < BlackPawn) {
13447 selection = (ChessSquare) ((int) selection
13448 + (int) BlackPawn - (int) WhitePawn);
13450 if (boards[currentMove][y][x] != EmptySquare) {
13451 DisplayMoveError(_("That square is occupied"));
13455 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13461 /* Accept a pending offer of any kind from opponent */
13463 if (appData.icsActive) {
13464 SendToICS(ics_prefix);
13465 SendToICS("accept\n");
13466 } else if (cmailMsgLoaded) {
13467 if (currentMove == cmailOldMove &&
13468 commentList[cmailOldMove] != NULL &&
13469 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13470 "Black offers a draw" : "White offers a draw")) {
13472 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13473 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13475 DisplayError(_("There is no pending offer on this move"), 0);
13476 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13479 /* Not used for offers from chess program */
13486 /* Decline a pending offer of any kind from opponent */
13488 if (appData.icsActive) {
13489 SendToICS(ics_prefix);
13490 SendToICS("decline\n");
13491 } else if (cmailMsgLoaded) {
13492 if (currentMove == cmailOldMove &&
13493 commentList[cmailOldMove] != NULL &&
13494 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13495 "Black offers a draw" : "White offers a draw")) {
13497 AppendComment(cmailOldMove, "Draw declined", TRUE);
13498 DisplayComment(cmailOldMove - 1, "Draw declined");
13501 DisplayError(_("There is no pending offer on this move"), 0);
13504 /* Not used for offers from chess program */
13511 /* Issue ICS rematch command */
13512 if (appData.icsActive) {
13513 SendToICS(ics_prefix);
13514 SendToICS("rematch\n");
13521 /* Call your opponent's flag (claim a win on time) */
13522 if (appData.icsActive) {
13523 SendToICS(ics_prefix);
13524 SendToICS("flag\n");
13526 switch (gameMode) {
13529 case MachinePlaysWhite:
13532 GameEnds(GameIsDrawn, "Both players ran out of time",
13535 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
13537 DisplayError(_("Your opponent is not out of time"), 0);
13540 case MachinePlaysBlack:
13543 GameEnds(GameIsDrawn, "Both players ran out of time",
13546 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
13548 DisplayError(_("Your opponent is not out of time"), 0);
13556 ClockClick(int which)
13557 { // [HGM] code moved to back-end from winboard.c
13558 if(which) { // black clock
13559 if (gameMode == EditPosition || gameMode == IcsExamining) {
13560 if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13561 SetBlackToPlayEvent();
13562 } else if (gameMode == EditGame || shiftKey) {
13563 AdjustClock(which, -1);
13564 } else if (gameMode == IcsPlayingWhite ||
13565 gameMode == MachinePlaysBlack) {
13568 } else { // white clock
13569 if (gameMode == EditPosition || gameMode == IcsExamining) {
13570 if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13571 SetWhiteToPlayEvent();
13572 } else if (gameMode == EditGame || shiftKey) {
13573 AdjustClock(which, -1);
13574 } else if (gameMode == IcsPlayingBlack ||
13575 gameMode == MachinePlaysWhite) {
13584 /* Offer draw or accept pending draw offer from opponent */
13586 if (appData.icsActive) {
13587 /* Note: tournament rules require draw offers to be
13588 made after you make your move but before you punch
13589 your clock. Currently ICS doesn't let you do that;
13590 instead, you immediately punch your clock after making
13591 a move, but you can offer a draw at any time. */
13593 SendToICS(ics_prefix);
13594 SendToICS("draw\n");
13595 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
13596 } else if (cmailMsgLoaded) {
13597 if (currentMove == cmailOldMove &&
13598 commentList[cmailOldMove] != NULL &&
13599 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13600 "Black offers a draw" : "White offers a draw")) {
13601 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13602 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13603 } else if (currentMove == cmailOldMove + 1) {
13604 char *offer = WhiteOnMove(cmailOldMove) ?
13605 "White offers a draw" : "Black offers a draw";
13606 AppendComment(currentMove, offer, TRUE);
13607 DisplayComment(currentMove - 1, offer);
13608 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
13610 DisplayError(_("You must make your move before offering a draw"), 0);
13611 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13613 } else if (first.offeredDraw) {
13614 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
13616 if (first.sendDrawOffers) {
13617 SendToProgram("draw\n", &first);
13618 userOfferedDraw = TRUE;
13626 /* Offer Adjourn or accept pending Adjourn offer from opponent */
13628 if (appData.icsActive) {
13629 SendToICS(ics_prefix);
13630 SendToICS("adjourn\n");
13632 /* Currently GNU Chess doesn't offer or accept Adjourns */
13640 /* Offer Abort or accept pending Abort offer from opponent */
13642 if (appData.icsActive) {
13643 SendToICS(ics_prefix);
13644 SendToICS("abort\n");
13646 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
13653 /* Resign. You can do this even if it's not your turn. */
13655 if (appData.icsActive) {
13656 SendToICS(ics_prefix);
13657 SendToICS("resign\n");
13659 switch (gameMode) {
13660 case MachinePlaysWhite:
13661 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13663 case MachinePlaysBlack:
13664 GameEnds(BlackWins, "White resigns", GE_PLAYER);
13667 if (cmailMsgLoaded) {
13669 if (WhiteOnMove(cmailOldMove)) {
13670 GameEnds(BlackWins, "White resigns", GE_PLAYER);
13672 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13674 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
13685 StopObservingEvent()
13687 /* Stop observing current games */
13688 SendToICS(ics_prefix);
13689 SendToICS("unobserve\n");
13693 StopExaminingEvent()
13695 /* Stop observing current game */
13696 SendToICS(ics_prefix);
13697 SendToICS("unexamine\n");
13701 ForwardInner(target)
13706 if (appData.debugMode)
13707 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
13708 target, currentMove, forwardMostMove);
13710 if (gameMode == EditPosition)
13713 if (gameMode == PlayFromGameFile && !pausing)
13716 if (gameMode == IcsExamining && pausing)
13717 limit = pauseExamForwardMostMove;
13719 limit = forwardMostMove;
13721 if (target > limit) target = limit;
13723 if (target > 0 && moveList[target - 1][0]) {
13724 int fromX, fromY, toX, toY;
13725 toX = moveList[target - 1][2] - AAA;
13726 toY = moveList[target - 1][3] - ONE;
13727 if (moveList[target - 1][1] == '@') {
13728 if (appData.highlightLastMove) {
13729 SetHighlights(-1, -1, toX, toY);
13732 fromX = moveList[target - 1][0] - AAA;
13733 fromY = moveList[target - 1][1] - ONE;
13734 if (target == currentMove + 1) {
13735 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
13737 if (appData.highlightLastMove) {
13738 SetHighlights(fromX, fromY, toX, toY);
13742 if (gameMode == EditGame || gameMode == AnalyzeMode ||
13743 gameMode == Training || gameMode == PlayFromGameFile ||
13744 gameMode == AnalyzeFile) {
13745 while (currentMove < target) {
13746 SendMoveToProgram(currentMove++, &first);
13749 currentMove = target;
13752 if (gameMode == EditGame || gameMode == EndOfGame) {
13753 whiteTimeRemaining = timeRemaining[0][currentMove];
13754 blackTimeRemaining = timeRemaining[1][currentMove];
13756 DisplayBothClocks();
13757 DisplayMove(currentMove - 1);
13758 DrawPosition(FALSE, boards[currentMove]);
13759 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13760 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
13761 DisplayComment(currentMove - 1, commentList[currentMove]);
13763 DisplayBook(currentMove);
13770 if (gameMode == IcsExamining && !pausing) {
13771 SendToICS(ics_prefix);
13772 SendToICS("forward\n");
13774 ForwardInner(currentMove + 1);
13781 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13782 /* to optimze, we temporarily turn off analysis mode while we feed
13783 * the remaining moves to the engine. Otherwise we get analysis output
13786 if (first.analysisSupport) {
13787 SendToProgram("exit\nforce\n", &first);
13788 first.analyzing = FALSE;
13792 if (gameMode == IcsExamining && !pausing) {
13793 SendToICS(ics_prefix);
13794 SendToICS("forward 999999\n");
13796 ForwardInner(forwardMostMove);
13799 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13800 /* we have fed all the moves, so reactivate analysis mode */
13801 SendToProgram("analyze\n", &first);
13802 first.analyzing = TRUE;
13803 /*first.maybeThinking = TRUE;*/
13804 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13809 BackwardInner(target)
13812 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
13814 if (appData.debugMode)
13815 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
13816 target, currentMove, forwardMostMove);
13818 if (gameMode == EditPosition) return;
13819 if (currentMove <= backwardMostMove) {
13821 DrawPosition(full_redraw, boards[currentMove]);
13824 if (gameMode == PlayFromGameFile && !pausing)
13827 if (moveList[target][0]) {
13828 int fromX, fromY, toX, toY;
13829 toX = moveList[target][2] - AAA;
13830 toY = moveList[target][3] - ONE;
13831 if (moveList[target][1] == '@') {
13832 if (appData.highlightLastMove) {
13833 SetHighlights(-1, -1, toX, toY);
13836 fromX = moveList[target][0] - AAA;
13837 fromY = moveList[target][1] - ONE;
13838 if (target == currentMove - 1) {
13839 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
13841 if (appData.highlightLastMove) {
13842 SetHighlights(fromX, fromY, toX, toY);
13846 if (gameMode == EditGame || gameMode==AnalyzeMode ||
13847 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
13848 while (currentMove > target) {
13849 SendToProgram("undo\n", &first);
13853 currentMove = target;
13856 if (gameMode == EditGame || gameMode == EndOfGame) {
13857 whiteTimeRemaining = timeRemaining[0][currentMove];
13858 blackTimeRemaining = timeRemaining[1][currentMove];
13860 DisplayBothClocks();
13861 DisplayMove(currentMove - 1);
13862 DrawPosition(full_redraw, boards[currentMove]);
13863 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13864 // [HGM] PV info: routine tests if comment empty
13865 DisplayComment(currentMove - 1, commentList[currentMove]);
13866 DisplayBook(currentMove);
13872 if (gameMode == IcsExamining && !pausing) {
13873 SendToICS(ics_prefix);
13874 SendToICS("backward\n");
13876 BackwardInner(currentMove - 1);
13883 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13884 /* to optimize, we temporarily turn off analysis mode while we undo
13885 * all the moves. Otherwise we get analysis output after each undo.
13887 if (first.analysisSupport) {
13888 SendToProgram("exit\nforce\n", &first);
13889 first.analyzing = FALSE;
13893 if (gameMode == IcsExamining && !pausing) {
13894 SendToICS(ics_prefix);
13895 SendToICS("backward 999999\n");
13897 BackwardInner(backwardMostMove);
13900 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13901 /* we have fed all the moves, so reactivate analysis mode */
13902 SendToProgram("analyze\n", &first);
13903 first.analyzing = TRUE;
13904 /*first.maybeThinking = TRUE;*/
13905 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13912 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
13913 if (to >= forwardMostMove) to = forwardMostMove;
13914 if (to <= backwardMostMove) to = backwardMostMove;
13915 if (to < currentMove) {
13923 RevertEvent(Boolean annotate)
13925 if(PopTail(annotate)) { // [HGM] vari: restore old game tail
13928 if (gameMode != IcsExamining) {
13929 DisplayError(_("You are not examining a game"), 0);
13933 DisplayError(_("You can't revert while pausing"), 0);
13936 SendToICS(ics_prefix);
13937 SendToICS("revert\n");
13943 switch (gameMode) {
13944 case MachinePlaysWhite:
13945 case MachinePlaysBlack:
13946 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13947 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13950 if (forwardMostMove < 2) return;
13951 currentMove = forwardMostMove = forwardMostMove - 2;
13952 whiteTimeRemaining = timeRemaining[0][currentMove];
13953 blackTimeRemaining = timeRemaining[1][currentMove];
13954 DisplayBothClocks();
13955 DisplayMove(currentMove - 1);
13956 ClearHighlights();/*!! could figure this out*/
13957 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
13958 SendToProgram("remove\n", &first);
13959 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
13962 case BeginningOfGame:
13966 case IcsPlayingWhite:
13967 case IcsPlayingBlack:
13968 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
13969 SendToICS(ics_prefix);
13970 SendToICS("takeback 2\n");
13972 SendToICS(ics_prefix);
13973 SendToICS("takeback 1\n");
13982 ChessProgramState *cps;
13984 switch (gameMode) {
13985 case MachinePlaysWhite:
13986 if (!WhiteOnMove(forwardMostMove)) {
13987 DisplayError(_("It is your turn"), 0);
13992 case MachinePlaysBlack:
13993 if (WhiteOnMove(forwardMostMove)) {
13994 DisplayError(_("It is your turn"), 0);
13999 case TwoMachinesPlay:
14000 if (WhiteOnMove(forwardMostMove) ==
14001 (first.twoMachinesColor[0] == 'w')) {
14007 case BeginningOfGame:
14011 SendToProgram("?\n", cps);
14015 TruncateGameEvent()
14018 if (gameMode != EditGame) return;
14025 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
14026 if (forwardMostMove > currentMove) {
14027 if (gameInfo.resultDetails != NULL) {
14028 free(gameInfo.resultDetails);
14029 gameInfo.resultDetails = NULL;
14030 gameInfo.result = GameUnfinished;
14032 forwardMostMove = currentMove;
14033 HistorySet(parseList, backwardMostMove, forwardMostMove,
14041 if (appData.noChessProgram) return;
14042 switch (gameMode) {
14043 case MachinePlaysWhite:
14044 if (WhiteOnMove(forwardMostMove)) {
14045 DisplayError(_("Wait until your turn"), 0);
14049 case BeginningOfGame:
14050 case MachinePlaysBlack:
14051 if (!WhiteOnMove(forwardMostMove)) {
14052 DisplayError(_("Wait until your turn"), 0);
14057 DisplayError(_("No hint available"), 0);
14060 SendToProgram("hint\n", &first);
14061 hintRequested = TRUE;
14067 if (appData.noChessProgram) return;
14068 switch (gameMode) {
14069 case MachinePlaysWhite:
14070 if (WhiteOnMove(forwardMostMove)) {
14071 DisplayError(_("Wait until your turn"), 0);
14075 case BeginningOfGame:
14076 case MachinePlaysBlack:
14077 if (!WhiteOnMove(forwardMostMove)) {
14078 DisplayError(_("Wait until your turn"), 0);
14083 EditPositionDone(TRUE);
14085 case TwoMachinesPlay:
14090 SendToProgram("bk\n", &first);
14091 bookOutput[0] = NULLCHAR;
14092 bookRequested = TRUE;
14098 char *tags = PGNTags(&gameInfo);
14099 TagsPopUp(tags, CmailMsg());
14103 /* end button procedures */
14106 PrintPosition(fp, move)
14112 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
14113 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
14114 char c = PieceToChar(boards[move][i][j]);
14115 fputc(c == 'x' ? '.' : c, fp);
14116 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
14119 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
14120 fprintf(fp, "white to play\n");
14122 fprintf(fp, "black to play\n");
14129 if (gameInfo.white != NULL) {
14130 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
14136 /* Find last component of program's own name, using some heuristics */
14138 TidyProgramName(prog, host, buf)
14139 char *prog, *host, buf[MSG_SIZ];
14142 int local = (strcmp(host, "localhost") == 0);
14143 while (!local && (p = strchr(prog, ';')) != NULL) {
14145 while (*p == ' ') p++;
14148 if (*prog == '"' || *prog == '\'') {
14149 q = strchr(prog + 1, *prog);
14151 q = strchr(prog, ' ');
14153 if (q == NULL) q = prog + strlen(prog);
14155 while (p >= prog && *p != '/' && *p != '\\') p--;
14157 if(p == prog && *p == '"') p++;
14158 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
14159 memcpy(buf, p, q - p);
14160 buf[q - p] = NULLCHAR;
14168 TimeControlTagValue()
14171 if (!appData.clockMode) {
14172 safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
14173 } else if (movesPerSession > 0) {
14174 snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
14175 } else if (timeIncrement == 0) {
14176 snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
14178 snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
14180 return StrSave(buf);
14186 /* This routine is used only for certain modes */
14187 VariantClass v = gameInfo.variant;
14188 ChessMove r = GameUnfinished;
14191 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
14192 r = gameInfo.result;
14193 p = gameInfo.resultDetails;
14194 gameInfo.resultDetails = NULL;
14196 ClearGameInfo(&gameInfo);
14197 gameInfo.variant = v;
14199 switch (gameMode) {
14200 case MachinePlaysWhite:
14201 gameInfo.event = StrSave( appData.pgnEventHeader );
14202 gameInfo.site = StrSave(HostName());
14203 gameInfo.date = PGNDate();
14204 gameInfo.round = StrSave("-");
14205 gameInfo.white = StrSave(first.tidy);
14206 gameInfo.black = StrSave(UserName());
14207 gameInfo.timeControl = TimeControlTagValue();
14210 case MachinePlaysBlack:
14211 gameInfo.event = StrSave( appData.pgnEventHeader );
14212 gameInfo.site = StrSave(HostName());
14213 gameInfo.date = PGNDate();
14214 gameInfo.round = StrSave("-");
14215 gameInfo.white = StrSave(UserName());
14216 gameInfo.black = StrSave(first.tidy);
14217 gameInfo.timeControl = TimeControlTagValue();
14220 case TwoMachinesPlay:
14221 gameInfo.event = StrSave( appData.pgnEventHeader );
14222 gameInfo.site = StrSave(HostName());
14223 gameInfo.date = PGNDate();
14226 snprintf(buf, MSG_SIZ, "%d", roundNr);
14227 gameInfo.round = StrSave(buf);
14229 gameInfo.round = StrSave("-");
14231 if (first.twoMachinesColor[0] == 'w') {
14232 gameInfo.white = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14233 gameInfo.black = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14235 gameInfo.white = StrSave(appData.pgnName[1][0] ? appData.pgnName[1] : second.tidy);
14236 gameInfo.black = StrSave(appData.pgnName[0][0] ? appData.pgnName[0] : first.tidy);
14238 gameInfo.timeControl = TimeControlTagValue();
14242 gameInfo.event = StrSave("Edited game");
14243 gameInfo.site = StrSave(HostName());
14244 gameInfo.date = PGNDate();
14245 gameInfo.round = StrSave("-");
14246 gameInfo.white = StrSave("-");
14247 gameInfo.black = StrSave("-");
14248 gameInfo.result = r;
14249 gameInfo.resultDetails = p;
14253 gameInfo.event = StrSave("Edited position");
14254 gameInfo.site = StrSave(HostName());
14255 gameInfo.date = PGNDate();
14256 gameInfo.round = StrSave("-");
14257 gameInfo.white = StrSave("-");
14258 gameInfo.black = StrSave("-");
14261 case IcsPlayingWhite:
14262 case IcsPlayingBlack:
14267 case PlayFromGameFile:
14268 gameInfo.event = StrSave("Game from non-PGN file");
14269 gameInfo.site = StrSave(HostName());
14270 gameInfo.date = PGNDate();
14271 gameInfo.round = StrSave("-");
14272 gameInfo.white = StrSave("?");
14273 gameInfo.black = StrSave("?");
14282 ReplaceComment(index, text)
14290 if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
14291 pvInfoList[index-1].depth == len &&
14292 fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14293 (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14294 while (*text == '\n') text++;
14295 len = strlen(text);
14296 while (len > 0 && text[len - 1] == '\n') len--;
14298 if (commentList[index] != NULL)
14299 free(commentList[index]);
14302 commentList[index] = NULL;
14305 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14306 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14307 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14308 commentList[index] = (char *) malloc(len + 2);
14309 strncpy(commentList[index], text, len);
14310 commentList[index][len] = '\n';
14311 commentList[index][len + 1] = NULLCHAR;
14313 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14315 commentList[index] = (char *) malloc(len + 7);
14316 safeStrCpy(commentList[index], "{\n", 3);
14317 safeStrCpy(commentList[index]+2, text, len+1);
14318 commentList[index][len+2] = NULLCHAR;
14319 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14320 strcat(commentList[index], "\n}\n");
14334 if (ch == '\r') continue;
14336 } while (ch != '\0');
14340 AppendComment(index, text, addBraces)
14343 Boolean addBraces; // [HGM] braces: tells if we should add {}
14348 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14349 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14352 while (*text == '\n') text++;
14353 len = strlen(text);
14354 while (len > 0 && text[len - 1] == '\n') len--;
14356 if (len == 0) return;
14358 if (commentList[index] != NULL) {
14359 old = commentList[index];
14360 oldlen = strlen(old);
14361 while(commentList[index][oldlen-1] == '\n')
14362 commentList[index][--oldlen] = NULLCHAR;
14363 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14364 safeStrCpy(commentList[index], old, oldlen + len + 6);
14366 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14367 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14368 if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14369 while (*text == '\n') { text++; len--; }
14370 commentList[index][--oldlen] = NULLCHAR;
14372 if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14373 else strcat(commentList[index], "\n");
14374 strcat(commentList[index], text);
14375 if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
14376 else strcat(commentList[index], "\n");
14378 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14380 safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14381 else commentList[index][0] = NULLCHAR;
14382 strcat(commentList[index], text);
14383 strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14384 if(addBraces == TRUE) strcat(commentList[index], "}\n");
14388 static char * FindStr( char * text, char * sub_text )
14390 char * result = strstr( text, sub_text );
14392 if( result != NULL ) {
14393 result += strlen( sub_text );
14399 /* [AS] Try to extract PV info from PGN comment */
14400 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14401 char *GetInfoFromComment( int index, char * text )
14403 char * sep = text, *p;
14405 if( text != NULL && index > 0 ) {
14408 int time = -1, sec = 0, deci;
14409 char * s_eval = FindStr( text, "[%eval " );
14410 char * s_emt = FindStr( text, "[%emt " );
14412 if( s_eval != NULL || s_emt != NULL ) {
14416 if( s_eval != NULL ) {
14417 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14421 if( delim != ']' ) {
14426 if( s_emt != NULL ) {
14431 /* We expect something like: [+|-]nnn.nn/dd */
14434 if(*text != '{') return text; // [HGM] braces: must be normal comment
14436 sep = strchr( text, '/' );
14437 if( sep == NULL || sep < (text+4) ) {
14442 if(p[1] == '(') { // comment starts with PV
14443 p = strchr(p, ')'); // locate end of PV
14444 if(p == NULL || sep < p+5) return text;
14445 // at this point we have something like "{(.*) +0.23/6 ..."
14446 p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14447 *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14448 // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14450 time = -1; sec = -1; deci = -1;
14451 if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14452 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14453 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14454 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
14458 if( score_lo < 0 || score_lo >= 100 ) {
14462 if(sec >= 0) time = 600*time + 10*sec; else
14463 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14465 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14467 /* [HGM] PV time: now locate end of PV info */
14468 while( *++sep >= '0' && *sep <= '9'); // strip depth
14470 while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14472 while( *++sep >= '0' && *sep <= '9'); // strip seconds
14474 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14475 while(*sep == ' ') sep++;
14486 pvInfoList[index-1].depth = depth;
14487 pvInfoList[index-1].score = score;
14488 pvInfoList[index-1].time = 10*time; // centi-sec
14489 if(*sep == '}') *sep = 0; else *--sep = '{';
14490 if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
14496 SendToProgram(message, cps)
14498 ChessProgramState *cps;
14500 int count, outCount, error;
14503 if (cps->pr == NULL) return;
14506 if (appData.debugMode) {
14509 fprintf(debugFP, "%ld >%-6s: %s",
14510 SubtractTimeMarks(&now, &programStartTime),
14511 cps->which, message);
14514 count = strlen(message);
14515 outCount = OutputToProcess(cps->pr, message, count, &error);
14516 if (outCount < count && !exiting
14517 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
14518 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14519 snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
14520 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14521 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14522 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14523 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14524 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14526 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14527 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14528 gameInfo.result = res;
14530 gameInfo.resultDetails = StrSave(buf);
14532 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14533 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14538 ReceiveFromProgram(isr, closure, message, count, error)
14539 InputSourceRef isr;
14547 ChessProgramState *cps = (ChessProgramState *)closure;
14549 if (isr != cps->isr) return; /* Killed intentionally */
14552 RemoveInputSource(cps->isr);
14553 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14554 snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
14555 _(cps->which), cps->program);
14556 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14557 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14558 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14559 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14560 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14562 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14563 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14564 gameInfo.result = res;
14566 gameInfo.resultDetails = StrSave(buf);
14568 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14569 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
14571 snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
14572 _(cps->which), cps->program);
14573 RemoveInputSource(cps->isr);
14575 /* [AS] Program is misbehaving badly... kill it */
14576 if( count == -2 ) {
14577 DestroyChildProcess( cps->pr, 9 );
14581 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14586 if ((end_str = strchr(message, '\r')) != NULL)
14587 *end_str = NULLCHAR;
14588 if ((end_str = strchr(message, '\n')) != NULL)
14589 *end_str = NULLCHAR;
14591 if (appData.debugMode) {
14592 TimeMark now; int print = 1;
14593 char *quote = ""; char c; int i;
14595 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
14596 char start = message[0];
14597 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
14598 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
14599 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
14600 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
14601 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
14602 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
14603 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
14604 sscanf(message, "setboard %c", &c)!=1 && sscanf(message, "setup %c", &c)!=1 &&
14605 sscanf(message, "hint: %c", &c)!=1 &&
14606 sscanf(message, "pong %c", &c)!=1 && start != '#') {
14607 quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
14608 print = (appData.engineComments >= 2);
14610 message[0] = start; // restore original message
14614 fprintf(debugFP, "%ld <%-6s: %s%s\n",
14615 SubtractTimeMarks(&now, &programStartTime), cps->which,
14621 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
14622 if (appData.icsEngineAnalyze) {
14623 if (strstr(message, "whisper") != NULL ||
14624 strstr(message, "kibitz") != NULL ||
14625 strstr(message, "tellics") != NULL) return;
14628 HandleMachineMove(message, cps);
14633 SendTimeControl(cps, mps, tc, inc, sd, st)
14634 ChessProgramState *cps;
14635 int mps, inc, sd, st;
14641 if( timeControl_2 > 0 ) {
14642 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
14643 tc = timeControl_2;
14646 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
14647 inc /= cps->timeOdds;
14648 st /= cps->timeOdds;
14650 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
14653 /* Set exact time per move, normally using st command */
14654 if (cps->stKludge) {
14655 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
14657 if (seconds == 0) {
14658 snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
14660 snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
14663 snprintf(buf, MSG_SIZ, "st %d\n", st);
14666 /* Set conventional or incremental time control, using level command */
14667 if (seconds == 0) {
14668 /* Note old gnuchess bug -- minutes:seconds used to not work.
14669 Fixed in later versions, but still avoid :seconds
14670 when seconds is 0. */
14671 snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
14673 snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
14674 seconds, inc/1000.);
14677 SendToProgram(buf, cps);
14679 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
14680 /* Orthogonally, limit search to given depth */
14682 if (cps->sdKludge) {
14683 snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
14685 snprintf(buf, MSG_SIZ, "sd %d\n", sd);
14687 SendToProgram(buf, cps);
14690 if(cps->nps >= 0) { /* [HGM] nps */
14691 if(cps->supportsNPS == FALSE)
14692 cps->nps = -1; // don't use if engine explicitly says not supported!
14694 snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
14695 SendToProgram(buf, cps);
14700 ChessProgramState *WhitePlayer()
14701 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
14703 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
14704 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
14710 SendTimeRemaining(cps, machineWhite)
14711 ChessProgramState *cps;
14712 int /*boolean*/ machineWhite;
14714 char message[MSG_SIZ];
14717 /* Note: this routine must be called when the clocks are stopped
14718 or when they have *just* been set or switched; otherwise
14719 it will be off by the time since the current tick started.
14721 if (machineWhite) {
14722 time = whiteTimeRemaining / 10;
14723 otime = blackTimeRemaining / 10;
14725 time = blackTimeRemaining / 10;
14726 otime = whiteTimeRemaining / 10;
14728 /* [HGM] translate opponent's time by time-odds factor */
14729 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
14730 if (appData.debugMode) {
14731 fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
14734 if (time <= 0) time = 1;
14735 if (otime <= 0) otime = 1;
14737 snprintf(message, MSG_SIZ, "time %ld\n", time);
14738 SendToProgram(message, cps);
14740 snprintf(message, MSG_SIZ, "otim %ld\n", otime);
14741 SendToProgram(message, cps);
14745 BoolFeature(p, name, loc, cps)
14749 ChessProgramState *cps;
14752 int len = strlen(name);
14755 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14757 sscanf(*p, "%d", &val);
14759 while (**p && **p != ' ')
14761 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14762 SendToProgram(buf, cps);
14769 IntFeature(p, name, loc, cps)
14773 ChessProgramState *cps;
14776 int len = strlen(name);
14777 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14779 sscanf(*p, "%d", loc);
14780 while (**p && **p != ' ') (*p)++;
14781 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14782 SendToProgram(buf, cps);
14789 StringFeature(p, name, loc, cps)
14793 ChessProgramState *cps;
14796 int len = strlen(name);
14797 if (strncmp((*p), name, len) == 0
14798 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
14800 sscanf(*p, "%[^\"]", loc);
14801 while (**p && **p != '\"') (*p)++;
14802 if (**p == '\"') (*p)++;
14803 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14804 SendToProgram(buf, cps);
14811 ParseOption(Option *opt, ChessProgramState *cps)
14812 // [HGM] options: process the string that defines an engine option, and determine
14813 // name, type, default value, and allowed value range
14815 char *p, *q, buf[MSG_SIZ];
14816 int n, min = (-1)<<31, max = 1<<31, def;
14818 if(p = strstr(opt->name, " -spin ")) {
14819 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14820 if(max < min) max = min; // enforce consistency
14821 if(def < min) def = min;
14822 if(def > max) def = max;
14827 } else if((p = strstr(opt->name, " -slider "))) {
14828 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
14829 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14830 if(max < min) max = min; // enforce consistency
14831 if(def < min) def = min;
14832 if(def > max) def = max;
14836 opt->type = Spin; // Slider;
14837 } else if((p = strstr(opt->name, " -string "))) {
14838 opt->textValue = p+9;
14839 opt->type = TextBox;
14840 } else if((p = strstr(opt->name, " -file "))) {
14841 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14842 opt->textValue = p+7;
14843 opt->type = FileName; // FileName;
14844 } else if((p = strstr(opt->name, " -path "))) {
14845 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14846 opt->textValue = p+7;
14847 opt->type = PathName; // PathName;
14848 } else if(p = strstr(opt->name, " -check ")) {
14849 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
14850 opt->value = (def != 0);
14851 opt->type = CheckBox;
14852 } else if(p = strstr(opt->name, " -combo ")) {
14853 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
14854 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
14855 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
14856 opt->value = n = 0;
14857 while(q = StrStr(q, " /// ")) {
14858 n++; *q = 0; // count choices, and null-terminate each of them
14860 if(*q == '*') { // remember default, which is marked with * prefix
14864 cps->comboList[cps->comboCnt++] = q;
14866 cps->comboList[cps->comboCnt++] = NULL;
14868 opt->type = ComboBox;
14869 } else if(p = strstr(opt->name, " -button")) {
14870 opt->type = Button;
14871 } else if(p = strstr(opt->name, " -save")) {
14872 opt->type = SaveButton;
14873 } else return FALSE;
14874 *p = 0; // terminate option name
14875 // now look if the command-line options define a setting for this engine option.
14876 if(cps->optionSettings && cps->optionSettings[0])
14877 p = strstr(cps->optionSettings, opt->name); else p = NULL;
14878 if(p && (p == cps->optionSettings || p[-1] == ',')) {
14879 snprintf(buf, MSG_SIZ, "option %s", p);
14880 if(p = strstr(buf, ",")) *p = 0;
14881 if(q = strchr(buf, '=')) switch(opt->type) {
14883 for(n=0; n<opt->max; n++)
14884 if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
14887 safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
14891 opt->value = atoi(q+1);
14896 SendToProgram(buf, cps);
14902 FeatureDone(cps, val)
14903 ChessProgramState* cps;
14906 DelayedEventCallback cb = GetDelayedEvent();
14907 if ((cb == InitBackEnd3 && cps == &first) ||
14908 (cb == SettingsMenuIfReady && cps == &second) ||
14909 (cb == LoadEngine) ||
14910 (cb == TwoMachinesEventIfReady)) {
14911 CancelDelayedEvent();
14912 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
14914 cps->initDone = val;
14917 /* Parse feature command from engine */
14919 ParseFeatures(args, cps)
14921 ChessProgramState *cps;
14929 while (*p == ' ') p++;
14930 if (*p == NULLCHAR) return;
14932 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
14933 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
14934 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
14935 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
14936 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
14937 if (BoolFeature(&p, "reuse", &val, cps)) {
14938 /* Engine can disable reuse, but can't enable it if user said no */
14939 if (!val) cps->reuse = FALSE;
14942 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
14943 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
14944 if (gameMode == TwoMachinesPlay) {
14945 DisplayTwoMachinesTitle();
14951 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
14952 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
14953 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
14954 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
14955 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
14956 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
14957 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
14958 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
14959 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
14960 if (IntFeature(&p, "done", &val, cps)) {
14961 FeatureDone(cps, val);
14964 /* Added by Tord: */
14965 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
14966 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
14967 /* End of additions by Tord */
14969 /* [HGM] added features: */
14970 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
14971 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
14972 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
14973 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
14974 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14975 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
14976 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
14977 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
14978 snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
14979 SendToProgram(buf, cps);
14982 if(cps->nrOptions >= MAX_OPTIONS) {
14984 snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
14985 DisplayError(buf, 0);
14989 /* End of additions by HGM */
14991 /* unknown feature: complain and skip */
14993 while (*q && *q != '=') q++;
14994 snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
14995 SendToProgram(buf, cps);
15001 while (*p && *p != '\"') p++;
15002 if (*p == '\"') p++;
15004 while (*p && *p != ' ') p++;
15012 PeriodicUpdatesEvent(newState)
15015 if (newState == appData.periodicUpdates)
15018 appData.periodicUpdates=newState;
15020 /* Display type changes, so update it now */
15021 // DisplayAnalysis();
15023 /* Get the ball rolling again... */
15025 AnalysisPeriodicEvent(1);
15026 StartAnalysisClock();
15031 PonderNextMoveEvent(newState)
15034 if (newState == appData.ponderNextMove) return;
15035 if (gameMode == EditPosition) EditPositionDone(TRUE);
15037 SendToProgram("hard\n", &first);
15038 if (gameMode == TwoMachinesPlay) {
15039 SendToProgram("hard\n", &second);
15042 SendToProgram("easy\n", &first);
15043 thinkOutput[0] = NULLCHAR;
15044 if (gameMode == TwoMachinesPlay) {
15045 SendToProgram("easy\n", &second);
15048 appData.ponderNextMove = newState;
15052 NewSettingEvent(option, feature, command, value)
15054 int option, value, *feature;
15058 if (gameMode == EditPosition) EditPositionDone(TRUE);
15059 snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
15060 if(feature == NULL || *feature) SendToProgram(buf, &first);
15061 if (gameMode == TwoMachinesPlay) {
15062 if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
15067 ShowThinkingEvent()
15068 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
15070 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
15071 int newState = appData.showThinking
15072 // [HGM] thinking: other features now need thinking output as well
15073 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
15075 if (oldState == newState) return;
15076 oldState = newState;
15077 if (gameMode == EditPosition) EditPositionDone(TRUE);
15079 SendToProgram("post\n", &first);
15080 if (gameMode == TwoMachinesPlay) {
15081 SendToProgram("post\n", &second);
15084 SendToProgram("nopost\n", &first);
15085 thinkOutput[0] = NULLCHAR;
15086 if (gameMode == TwoMachinesPlay) {
15087 SendToProgram("nopost\n", &second);
15090 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
15094 AskQuestionEvent(title, question, replyPrefix, which)
15095 char *title; char *question; char *replyPrefix; char *which;
15097 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
15098 if (pr == NoProc) return;
15099 AskQuestion(title, question, replyPrefix, pr);
15103 TypeInEvent(char firstChar)
15105 if ((gameMode == BeginningOfGame && !appData.icsActive) ||
15106 gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
15107 gameMode == AnalyzeMode || gameMode == EditGame ||
15108 gameMode == EditPosition || gameMode == IcsExamining ||
15109 gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
15110 isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
15111 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
15112 gameMode == IcsObserving || gameMode == TwoMachinesPlay ) ||
15113 gameMode == Training) PopUpMoveDialog(firstChar);
15117 TypeInDoneEvent(char *move)
15120 int n, fromX, fromY, toX, toY;
15122 ChessMove moveType;
15125 if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
15126 EditPositionPasteFEN(move);
15129 // [HGM] movenum: allow move number to be typed in any mode
15130 if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
15135 if (gameMode != EditGame && currentMove != forwardMostMove &&
15136 gameMode != Training) {
15137 DisplayMoveError(_("Displayed move is not current"));
15139 int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
15140 &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
15141 if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
15142 if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
15143 &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
15144 UserMoveEvent(fromX, fromY, toX, toY, promoChar);
15146 DisplayMoveError(_("Could not parse move"));
15152 DisplayMove(moveNumber)
15155 char message[MSG_SIZ];
15157 char cpThinkOutput[MSG_SIZ];
15159 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
15161 if (moveNumber == forwardMostMove - 1 ||
15162 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
15164 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
15166 if (strchr(cpThinkOutput, '\n')) {
15167 *strchr(cpThinkOutput, '\n') = NULLCHAR;
15170 *cpThinkOutput = NULLCHAR;
15173 /* [AS] Hide thinking from human user */
15174 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
15175 *cpThinkOutput = NULLCHAR;
15176 if( thinkOutput[0] != NULLCHAR ) {
15179 for( i=0; i<=hiddenThinkOutputState; i++ ) {
15180 cpThinkOutput[i] = '.';
15182 cpThinkOutput[i] = NULLCHAR;
15183 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
15187 if (moveNumber == forwardMostMove - 1 &&
15188 gameInfo.resultDetails != NULL) {
15189 if (gameInfo.resultDetails[0] == NULLCHAR) {
15190 snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
15192 snprintf(res, MSG_SIZ, " {%s} %s",
15193 T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
15199 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15200 DisplayMessage(res, cpThinkOutput);
15202 snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
15203 WhiteOnMove(moveNumber) ? " " : ".. ",
15204 parseList[moveNumber], res);
15205 DisplayMessage(message, cpThinkOutput);
15210 DisplayComment(moveNumber, text)
15214 char title[MSG_SIZ];
15216 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
15217 safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
15219 snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
15220 WhiteOnMove(moveNumber) ? " " : ".. ",
15221 parseList[moveNumber]);
15223 if (text != NULL && (appData.autoDisplayComment || commentUp))
15224 CommentPopUp(title, text);
15227 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
15228 * might be busy thinking or pondering. It can be omitted if your
15229 * gnuchess is configured to stop thinking immediately on any user
15230 * input. However, that gnuchess feature depends on the FIONREAD
15231 * ioctl, which does not work properly on some flavors of Unix.
15235 ChessProgramState *cps;
15238 if (!cps->useSigint) return;
15239 if (appData.noChessProgram || (cps->pr == NoProc)) return;
15240 switch (gameMode) {
15241 case MachinePlaysWhite:
15242 case MachinePlaysBlack:
15243 case TwoMachinesPlay:
15244 case IcsPlayingWhite:
15245 case IcsPlayingBlack:
15248 /* Skip if we know it isn't thinking */
15249 if (!cps->maybeThinking) return;
15250 if (appData.debugMode)
15251 fprintf(debugFP, "Interrupting %s\n", cps->which);
15252 InterruptChildProcess(cps->pr);
15253 cps->maybeThinking = FALSE;
15258 #endif /*ATTENTION*/
15264 if (whiteTimeRemaining <= 0) {
15267 if (appData.icsActive) {
15268 if (appData.autoCallFlag &&
15269 gameMode == IcsPlayingBlack && !blackFlag) {
15270 SendToICS(ics_prefix);
15271 SendToICS("flag\n");
15275 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15277 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
15278 if (appData.autoCallFlag) {
15279 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15286 if (blackTimeRemaining <= 0) {
15289 if (appData.icsActive) {
15290 if (appData.autoCallFlag &&
15291 gameMode == IcsPlayingWhite && !whiteFlag) {
15292 SendToICS(ics_prefix);
15293 SendToICS("flag\n");
15297 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15299 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15300 if (appData.autoCallFlag) {
15301 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15314 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15315 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15318 * add time to clocks when time control is achieved ([HGM] now also used for increment)
15320 if ( !WhiteOnMove(forwardMostMove) ) {
15321 /* White made time control */
15322 lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15323 whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15324 /* [HGM] time odds: correct new time quota for time odds! */
15325 / WhitePlayer()->timeOdds;
15326 lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15328 lastBlack -= blackTimeRemaining;
15329 /* Black made time control */
15330 blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15331 / WhitePlayer()->other->timeOdds;
15332 lastWhite = whiteTimeRemaining;
15337 DisplayBothClocks()
15339 int wom = gameMode == EditPosition ?
15340 !blackPlaysFirst : WhiteOnMove(currentMove);
15341 DisplayWhiteClock(whiteTimeRemaining, wom);
15342 DisplayBlackClock(blackTimeRemaining, !wom);
15346 /* Timekeeping seems to be a portability nightmare. I think everyone
15347 has ftime(), but I'm really not sure, so I'm including some ifdefs
15348 to use other calls if you don't. Clocks will be less accurate if
15349 you have neither ftime nor gettimeofday.
15352 /* VS 2008 requires the #include outside of the function */
15353 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15354 #include <sys/timeb.h>
15357 /* Get the current time as a TimeMark */
15362 #if HAVE_GETTIMEOFDAY
15364 struct timeval timeVal;
15365 struct timezone timeZone;
15367 gettimeofday(&timeVal, &timeZone);
15368 tm->sec = (long) timeVal.tv_sec;
15369 tm->ms = (int) (timeVal.tv_usec / 1000L);
15371 #else /*!HAVE_GETTIMEOFDAY*/
15374 // include <sys/timeb.h> / moved to just above start of function
15375 struct timeb timeB;
15378 tm->sec = (long) timeB.time;
15379 tm->ms = (int) timeB.millitm;
15381 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15382 tm->sec = (long) time(NULL);
15388 /* Return the difference in milliseconds between two
15389 time marks. We assume the difference will fit in a long!
15392 SubtractTimeMarks(tm2, tm1)
15393 TimeMark *tm2, *tm1;
15395 return 1000L*(tm2->sec - tm1->sec) +
15396 (long) (tm2->ms - tm1->ms);
15401 * Code to manage the game clocks.
15403 * In tournament play, black starts the clock and then white makes a move.
15404 * We give the human user a slight advantage if he is playing white---the
15405 * clocks don't run until he makes his first move, so it takes zero time.
15406 * Also, we don't account for network lag, so we could get out of sync
15407 * with GNU Chess's clock -- but then, referees are always right.
15410 static TimeMark tickStartTM;
15411 static long intendedTickLength;
15414 NextTickLength(timeRemaining)
15415 long timeRemaining;
15417 long nominalTickLength, nextTickLength;
15419 if (timeRemaining > 0L && timeRemaining <= 10000L)
15420 nominalTickLength = 100L;
15422 nominalTickLength = 1000L;
15423 nextTickLength = timeRemaining % nominalTickLength;
15424 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15426 return nextTickLength;
15429 /* Adjust clock one minute up or down */
15431 AdjustClock(Boolean which, int dir)
15433 if(which) blackTimeRemaining += 60000*dir;
15434 else whiteTimeRemaining += 60000*dir;
15435 DisplayBothClocks();
15438 /* Stop clocks and reset to a fresh time control */
15442 (void) StopClockTimer();
15443 if (appData.icsActive) {
15444 whiteTimeRemaining = blackTimeRemaining = 0;
15445 } else if (searchTime) {
15446 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15447 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15448 } else { /* [HGM] correct new time quote for time odds */
15449 whiteTC = blackTC = fullTimeControlString;
15450 whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15451 blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15453 if (whiteFlag || blackFlag) {
15455 whiteFlag = blackFlag = FALSE;
15457 lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15458 DisplayBothClocks();
15461 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15463 /* Decrement running clock by amount of time that has passed */
15467 long timeRemaining;
15468 long lastTickLength, fudge;
15471 if (!appData.clockMode) return;
15472 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15476 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15478 /* Fudge if we woke up a little too soon */
15479 fudge = intendedTickLength - lastTickLength;
15480 if (fudge < 0 || fudge > FUDGE) fudge = 0;
15482 if (WhiteOnMove(forwardMostMove)) {
15483 if(whiteNPS >= 0) lastTickLength = 0;
15484 timeRemaining = whiteTimeRemaining -= lastTickLength;
15485 if(timeRemaining < 0 && !appData.icsActive) {
15486 GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15487 if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15488 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15489 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15492 DisplayWhiteClock(whiteTimeRemaining - fudge,
15493 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15495 if(blackNPS >= 0) lastTickLength = 0;
15496 timeRemaining = blackTimeRemaining -= lastTickLength;
15497 if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15498 GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15500 blackStartMove = forwardMostMove;
15501 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
15504 DisplayBlackClock(blackTimeRemaining - fudge,
15505 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15507 if (CheckFlags()) return;
15510 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
15511 StartClockTimer(intendedTickLength);
15513 /* if the time remaining has fallen below the alarm threshold, sound the
15514 * alarm. if the alarm has sounded and (due to a takeback or time control
15515 * with increment) the time remaining has increased to a level above the
15516 * threshold, reset the alarm so it can sound again.
15519 if (appData.icsActive && appData.icsAlarm) {
15521 /* make sure we are dealing with the user's clock */
15522 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
15523 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
15526 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
15527 alarmSounded = FALSE;
15528 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
15530 alarmSounded = TRUE;
15536 /* A player has just moved, so stop the previously running
15537 clock and (if in clock mode) start the other one.
15538 We redisplay both clocks in case we're in ICS mode, because
15539 ICS gives us an update to both clocks after every move.
15540 Note that this routine is called *after* forwardMostMove
15541 is updated, so the last fractional tick must be subtracted
15542 from the color that is *not* on move now.
15545 SwitchClocks(int newMoveNr)
15547 long lastTickLength;
15549 int flagged = FALSE;
15553 if (StopClockTimer() && appData.clockMode) {
15554 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15555 if (!WhiteOnMove(forwardMostMove)) {
15556 if(blackNPS >= 0) lastTickLength = 0;
15557 blackTimeRemaining -= lastTickLength;
15558 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15559 // if(pvInfoList[forwardMostMove].time == -1)
15560 pvInfoList[forwardMostMove].time = // use GUI time
15561 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
15563 if(whiteNPS >= 0) lastTickLength = 0;
15564 whiteTimeRemaining -= lastTickLength;
15565 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15566 // if(pvInfoList[forwardMostMove].time == -1)
15567 pvInfoList[forwardMostMove].time =
15568 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
15570 flagged = CheckFlags();
15572 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
15573 CheckTimeControl();
15575 if (flagged || !appData.clockMode) return;
15577 switch (gameMode) {
15578 case MachinePlaysBlack:
15579 case MachinePlaysWhite:
15580 case BeginningOfGame:
15581 if (pausing) return;
15585 case PlayFromGameFile:
15593 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
15594 if(WhiteOnMove(forwardMostMove))
15595 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15596 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15600 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15601 whiteTimeRemaining : blackTimeRemaining);
15602 StartClockTimer(intendedTickLength);
15606 /* Stop both clocks */
15610 long lastTickLength;
15613 if (!StopClockTimer()) return;
15614 if (!appData.clockMode) return;
15618 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15619 if (WhiteOnMove(forwardMostMove)) {
15620 if(whiteNPS >= 0) lastTickLength = 0;
15621 whiteTimeRemaining -= lastTickLength;
15622 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
15624 if(blackNPS >= 0) lastTickLength = 0;
15625 blackTimeRemaining -= lastTickLength;
15626 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
15631 /* Start clock of player on move. Time may have been reset, so
15632 if clock is already running, stop and restart it. */
15636 (void) StopClockTimer(); /* in case it was running already */
15637 DisplayBothClocks();
15638 if (CheckFlags()) return;
15640 if (!appData.clockMode) return;
15641 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
15643 GetTimeMark(&tickStartTM);
15644 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15645 whiteTimeRemaining : blackTimeRemaining);
15647 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
15648 whiteNPS = blackNPS = -1;
15649 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
15650 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
15651 whiteNPS = first.nps;
15652 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
15653 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
15654 blackNPS = first.nps;
15655 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
15656 whiteNPS = second.nps;
15657 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
15658 blackNPS = second.nps;
15659 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
15661 StartClockTimer(intendedTickLength);
15668 long second, minute, hour, day;
15670 static char buf[32];
15672 if (ms > 0 && ms <= 9900) {
15673 /* convert milliseconds to tenths, rounding up */
15674 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
15676 snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
15680 /* convert milliseconds to seconds, rounding up */
15681 /* use floating point to avoid strangeness of integer division
15682 with negative dividends on many machines */
15683 second = (long) floor(((double) (ms + 999L)) / 1000.0);
15690 day = second / (60 * 60 * 24);
15691 second = second % (60 * 60 * 24);
15692 hour = second / (60 * 60);
15693 second = second % (60 * 60);
15694 minute = second / 60;
15695 second = second % 60;
15698 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
15699 sign, day, hour, minute, second);
15701 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
15703 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
15710 * This is necessary because some C libraries aren't ANSI C compliant yet.
15713 StrStr(string, match)
15714 char *string, *match;
15718 length = strlen(match);
15720 for (i = strlen(string) - length; i >= 0; i--, string++)
15721 if (!strncmp(match, string, length))
15728 StrCaseStr(string, match)
15729 char *string, *match;
15733 length = strlen(match);
15735 for (i = strlen(string) - length; i >= 0; i--, string++) {
15736 for (j = 0; j < length; j++) {
15737 if (ToLower(match[j]) != ToLower(string[j]))
15740 if (j == length) return string;
15754 c1 = ToLower(*s1++);
15755 c2 = ToLower(*s2++);
15756 if (c1 > c2) return 1;
15757 if (c1 < c2) return -1;
15758 if (c1 == NULLCHAR) return 0;
15767 return isupper(c) ? tolower(c) : c;
15775 return islower(c) ? toupper(c) : c;
15777 #endif /* !_amigados */
15785 if ((ret = (char *) malloc(strlen(s) + 1)))
15787 safeStrCpy(ret, s, strlen(s)+1);
15793 StrSavePtr(s, savePtr)
15794 char *s, **savePtr;
15799 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
15800 safeStrCpy(*savePtr, s, strlen(s)+1);
15812 clock = time((time_t *)NULL);
15813 tm = localtime(&clock);
15814 snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
15815 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
15816 return StrSave(buf);
15821 PositionToFEN(move, overrideCastling)
15823 char *overrideCastling;
15825 int i, j, fromX, fromY, toX, toY;
15832 whiteToPlay = (gameMode == EditPosition) ?
15833 !blackPlaysFirst : (move % 2 == 0);
15836 /* Piece placement data */
15837 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15838 if(MSG_SIZ - (p - buf) < BOARD_RGHT - BOARD_LEFT + 20) { *p = 0; return StrSave(buf); }
15840 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15841 if (boards[move][i][j] == EmptySquare) {
15843 } else { ChessSquare piece = boards[move][i][j];
15844 if (emptycount > 0) {
15845 if(emptycount<10) /* [HGM] can be >= 10 */
15846 *p++ = '0' + emptycount;
15847 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15850 if(PieceToChar(piece) == '+') {
15851 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
15853 piece = (ChessSquare)(DEMOTED piece);
15855 *p++ = PieceToChar(piece);
15857 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
15858 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
15863 if (emptycount > 0) {
15864 if(emptycount<10) /* [HGM] can be >= 10 */
15865 *p++ = '0' + emptycount;
15866 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15873 /* [HGM] print Crazyhouse or Shogi holdings */
15874 if( gameInfo.holdingsWidth ) {
15875 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
15877 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
15878 piece = boards[move][i][BOARD_WIDTH-1];
15879 if( piece != EmptySquare )
15880 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
15881 *p++ = PieceToChar(piece);
15883 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
15884 piece = boards[move][BOARD_HEIGHT-i-1][0];
15885 if( piece != EmptySquare )
15886 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
15887 *p++ = PieceToChar(piece);
15890 if( q == p ) *p++ = '-';
15896 *p++ = whiteToPlay ? 'w' : 'b';
15899 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
15900 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
15902 if(nrCastlingRights) {
15904 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
15905 /* [HGM] write directly from rights */
15906 if(boards[move][CASTLING][2] != NoRights &&
15907 boards[move][CASTLING][0] != NoRights )
15908 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
15909 if(boards[move][CASTLING][2] != NoRights &&
15910 boards[move][CASTLING][1] != NoRights )
15911 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
15912 if(boards[move][CASTLING][5] != NoRights &&
15913 boards[move][CASTLING][3] != NoRights )
15914 *p++ = boards[move][CASTLING][3] + AAA;
15915 if(boards[move][CASTLING][5] != NoRights &&
15916 boards[move][CASTLING][4] != NoRights )
15917 *p++ = boards[move][CASTLING][4] + AAA;
15920 /* [HGM] write true castling rights */
15921 if( nrCastlingRights == 6 ) {
15922 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
15923 boards[move][CASTLING][2] != NoRights ) *p++ = 'K';
15924 if(boards[move][CASTLING][1] == BOARD_LEFT &&
15925 boards[move][CASTLING][2] != NoRights ) *p++ = 'Q';
15926 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
15927 boards[move][CASTLING][5] != NoRights ) *p++ = 'k';
15928 if(boards[move][CASTLING][4] == BOARD_LEFT &&
15929 boards[move][CASTLING][5] != NoRights ) *p++ = 'q';
15932 if (q == p) *p++ = '-'; /* No castling rights */
15936 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
15937 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15938 /* En passant target square */
15939 if (move > backwardMostMove) {
15940 fromX = moveList[move - 1][0] - AAA;
15941 fromY = moveList[move - 1][1] - ONE;
15942 toX = moveList[move - 1][2] - AAA;
15943 toY = moveList[move - 1][3] - ONE;
15944 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
15945 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
15946 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
15948 /* 2-square pawn move just happened */
15950 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15954 } else if(move == backwardMostMove) {
15955 // [HGM] perhaps we should always do it like this, and forget the above?
15956 if((signed char)boards[move][EP_STATUS] >= 0) {
15957 *p++ = boards[move][EP_STATUS] + AAA;
15958 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15969 /* [HGM] find reversible plies */
15970 { int i = 0, j=move;
15972 if (appData.debugMode) { int k;
15973 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
15974 for(k=backwardMostMove; k<=forwardMostMove; k++)
15975 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
15979 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
15980 if( j == backwardMostMove ) i += initialRulePlies;
15981 sprintf(p, "%d ", i);
15982 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
15984 /* Fullmove number */
15985 sprintf(p, "%d", (move / 2) + 1);
15987 return StrSave(buf);
15991 ParseFEN(board, blackPlaysFirst, fen)
15993 int *blackPlaysFirst;
16003 /* [HGM] by default clear Crazyhouse holdings, if present */
16004 if(gameInfo.holdingsWidth) {
16005 for(i=0; i<BOARD_HEIGHT; i++) {
16006 board[i][0] = EmptySquare; /* black holdings */
16007 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
16008 board[i][1] = (ChessSquare) 0; /* black counts */
16009 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
16013 /* Piece placement data */
16014 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
16017 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
16018 if (*p == '/') p++;
16019 emptycount = gameInfo.boardWidth - j;
16020 while (emptycount--)
16021 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16023 #if(BOARD_FILES >= 10)
16024 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
16025 p++; emptycount=10;
16026 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16027 while (emptycount--)
16028 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16030 } else if (isdigit(*p)) {
16031 emptycount = *p++ - '0';
16032 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
16033 if (j + emptycount > gameInfo.boardWidth) return FALSE;
16034 while (emptycount--)
16035 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
16036 } else if (*p == '+' || isalpha(*p)) {
16037 if (j >= gameInfo.boardWidth) return FALSE;
16039 piece = CharToPiece(*++p);
16040 if(piece == EmptySquare) return FALSE; /* unknown piece */
16041 piece = (ChessSquare) (PROMOTED piece ); p++;
16042 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
16043 } else piece = CharToPiece(*p++);
16045 if(piece==EmptySquare) return FALSE; /* unknown piece */
16046 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
16047 piece = (ChessSquare) (PROMOTED piece);
16048 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
16051 board[i][(j++)+gameInfo.holdingsWidth] = piece;
16057 while (*p == '/' || *p == ' ') p++;
16059 /* [HGM] look for Crazyhouse holdings here */
16060 while(*p==' ') p++;
16061 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
16063 if(*p == '-' ) p++; /* empty holdings */ else {
16064 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
16065 /* if we would allow FEN reading to set board size, we would */
16066 /* have to add holdings and shift the board read so far here */
16067 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
16069 if((int) piece >= (int) BlackPawn ) {
16070 i = (int)piece - (int)BlackPawn;
16071 i = PieceToNumber((ChessSquare)i);
16072 if( i >= gameInfo.holdingsSize ) return FALSE;
16073 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
16074 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
16076 i = (int)piece - (int)WhitePawn;
16077 i = PieceToNumber((ChessSquare)i);
16078 if( i >= gameInfo.holdingsSize ) return FALSE;
16079 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
16080 board[i][BOARD_WIDTH-2]++; /* black holdings */
16087 while(*p == ' ') p++;
16091 if(appData.colorNickNames) {
16092 if( c == appData.colorNickNames[0] ) c = 'w'; else
16093 if( c == appData.colorNickNames[1] ) c = 'b';
16097 *blackPlaysFirst = FALSE;
16100 *blackPlaysFirst = TRUE;
16106 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
16107 /* return the extra info in global variiables */
16109 /* set defaults in case FEN is incomplete */
16110 board[EP_STATUS] = EP_UNKNOWN;
16111 for(i=0; i<nrCastlingRights; i++ ) {
16112 board[CASTLING][i] =
16113 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
16114 } /* assume possible unless obviously impossible */
16115 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
16116 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
16117 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
16118 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
16119 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
16120 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
16121 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
16122 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
16125 while(*p==' ') p++;
16126 if(nrCastlingRights) {
16127 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
16128 /* castling indicator present, so default becomes no castlings */
16129 for(i=0; i<nrCastlingRights; i++ ) {
16130 board[CASTLING][i] = NoRights;
16133 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
16134 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
16135 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
16136 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
16137 char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
16139 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
16140 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
16141 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
16143 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
16144 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
16145 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
16146 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
16147 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
16148 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
16151 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
16152 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
16153 board[CASTLING][2] = whiteKingFile;
16156 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
16157 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
16158 board[CASTLING][2] = whiteKingFile;
16161 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
16162 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
16163 board[CASTLING][5] = blackKingFile;
16166 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
16167 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
16168 board[CASTLING][5] = blackKingFile;
16171 default: /* FRC castlings */
16172 if(c >= 'a') { /* black rights */
16173 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16174 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
16175 if(i == BOARD_RGHT) break;
16176 board[CASTLING][5] = i;
16178 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
16179 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
16181 board[CASTLING][3] = c;
16183 board[CASTLING][4] = c;
16184 } else { /* white rights */
16185 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
16186 if(board[0][i] == WhiteKing) break;
16187 if(i == BOARD_RGHT) break;
16188 board[CASTLING][2] = i;
16189 c -= AAA - 'a' + 'A';
16190 if(board[0][c] >= WhiteKing) break;
16192 board[CASTLING][0] = c;
16194 board[CASTLING][1] = c;
16198 for(i=0; i<nrCastlingRights; i++)
16199 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
16200 if (appData.debugMode) {
16201 fprintf(debugFP, "FEN castling rights:");
16202 for(i=0; i<nrCastlingRights; i++)
16203 fprintf(debugFP, " %d", board[CASTLING][i]);
16204 fprintf(debugFP, "\n");
16207 while(*p==' ') p++;
16210 /* read e.p. field in games that know e.p. capture */
16211 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
16212 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
16214 p++; board[EP_STATUS] = EP_NONE;
16216 char c = *p++ - AAA;
16218 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
16219 if(*p >= '0' && *p <='9') p++;
16220 board[EP_STATUS] = c;
16225 if(sscanf(p, "%d", &i) == 1) {
16226 FENrulePlies = i; /* 50-move ply counter */
16227 /* (The move number is still ignored) */
16234 EditPositionPasteFEN(char *fen)
16237 Board initial_position;
16239 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
16240 DisplayError(_("Bad FEN position in clipboard"), 0);
16243 int savedBlackPlaysFirst = blackPlaysFirst;
16244 EditPositionEvent();
16245 blackPlaysFirst = savedBlackPlaysFirst;
16246 CopyBoard(boards[0], initial_position);
16247 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
16248 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
16249 DisplayBothClocks();
16250 DrawPosition(FALSE, boards[currentMove]);
16255 static char cseq[12] = "\\ ";
16257 Boolean set_cont_sequence(char *new_seq)
16262 // handle bad attempts to set the sequence
16264 return 0; // acceptable error - no debug
16266 len = strlen(new_seq);
16267 ret = (len > 0) && (len < sizeof(cseq));
16269 safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
16270 else if (appData.debugMode)
16271 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
16276 reformat a source message so words don't cross the width boundary. internal
16277 newlines are not removed. returns the wrapped size (no null character unless
16278 included in source message). If dest is NULL, only calculate the size required
16279 for the dest buffer. lp argument indicats line position upon entry, and it's
16280 passed back upon exit.
16282 int wrap(char *dest, char *src, int count, int width, int *lp)
16284 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16286 cseq_len = strlen(cseq);
16287 old_line = line = *lp;
16288 ansi = len = clen = 0;
16290 for (i=0; i < count; i++)
16292 if (src[i] == '\033')
16295 // if we hit the width, back up
16296 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16298 // store i & len in case the word is too long
16299 old_i = i, old_len = len;
16301 // find the end of the last word
16302 while (i && src[i] != ' ' && src[i] != '\n')
16308 // word too long? restore i & len before splitting it
16309 if ((old_i-i+clen) >= width)
16316 if (i && src[i-1] == ' ')
16319 if (src[i] != ' ' && src[i] != '\n')
16326 // now append the newline and continuation sequence
16331 strncpy(dest+len, cseq, cseq_len);
16339 dest[len] = src[i];
16343 if (src[i] == '\n')
16348 if (dest && appData.debugMode)
16350 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16351 count, width, line, len, *lp);
16352 show_bytes(debugFP, src, count);
16353 fprintf(debugFP, "\ndest: ");
16354 show_bytes(debugFP, dest, len);
16355 fprintf(debugFP, "\n");
16357 *lp = dest ? line : old_line;
16362 // [HGM] vari: routines for shelving variations
16365 PushInner(int firstMove, int lastMove)
16367 int i, j, nrMoves = lastMove - firstMove;
16369 // push current tail of game on stack
16370 savedResult[storedGames] = gameInfo.result;
16371 savedDetails[storedGames] = gameInfo.resultDetails;
16372 gameInfo.resultDetails = NULL;
16373 savedFirst[storedGames] = firstMove;
16374 savedLast [storedGames] = lastMove;
16375 savedFramePtr[storedGames] = framePtr;
16376 framePtr -= nrMoves; // reserve space for the boards
16377 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16378 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16379 for(j=0; j<MOVE_LEN; j++)
16380 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16381 for(j=0; j<2*MOVE_LEN; j++)
16382 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16383 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16384 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16385 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16386 pvInfoList[firstMove+i-1].depth = 0;
16387 commentList[framePtr+i] = commentList[firstMove+i];
16388 commentList[firstMove+i] = NULL;
16392 forwardMostMove = firstMove; // truncate game so we can start variation
16396 PushTail(int firstMove, int lastMove)
16398 if(appData.icsActive) { // only in local mode
16399 forwardMostMove = currentMove; // mimic old ICS behavior
16402 if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16404 PushInner(firstMove, lastMove);
16405 if(storedGames == 1) GreyRevert(FALSE);
16409 PopInner(Boolean annotate)
16412 char buf[8000], moveBuf[20];
16415 ToNrEvent(savedFirst[storedGames]); // sets currentMove
16416 nrMoves = savedLast[storedGames] - currentMove;
16419 if(!WhiteOnMove(currentMove))
16420 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16421 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16422 for(i=currentMove; i<forwardMostMove; i++) {
16424 snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16425 else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16426 strcat(buf, moveBuf);
16427 if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16428 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16432 for(i=1; i<=nrMoves; i++) { // copy last variation back
16433 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16434 for(j=0; j<MOVE_LEN; j++)
16435 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16436 for(j=0; j<2*MOVE_LEN; j++)
16437 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16438 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16439 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16440 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16441 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16442 commentList[currentMove+i] = commentList[framePtr+i];
16443 commentList[framePtr+i] = NULL;
16445 if(annotate) AppendComment(currentMove+1, buf, FALSE);
16446 framePtr = savedFramePtr[storedGames];
16447 gameInfo.result = savedResult[storedGames];
16448 if(gameInfo.resultDetails != NULL) {
16449 free(gameInfo.resultDetails);
16451 gameInfo.resultDetails = savedDetails[storedGames];
16452 forwardMostMove = currentMove + nrMoves;
16456 PopTail(Boolean annotate)
16458 if(appData.icsActive) return FALSE; // only in local mode
16459 if(!storedGames) return FALSE; // sanity
16460 CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16462 PopInner(annotate);
16464 if(storedGames == 0) GreyRevert(TRUE);
16470 { // remove all shelved variations
16472 for(i=0; i<storedGames; i++) {
16473 if(savedDetails[i])
16474 free(savedDetails[i]);
16475 savedDetails[i] = NULL;
16477 for(i=framePtr; i<MAX_MOVES; i++) {
16478 if(commentList[i]) free(commentList[i]);
16479 commentList[i] = NULL;
16481 framePtr = MAX_MOVES-1;
16486 LoadVariation(int index, char *text)
16487 { // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16488 char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16489 int level = 0, move;
16491 if(gameMode != EditGame && gameMode != AnalyzeMode) return;
16492 // first find outermost bracketing variation
16493 while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16494 if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16495 if(*p == '{') wait = '}'; else
16496 if(*p == '[') wait = ']'; else
16497 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16498 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16500 if(*p == wait) wait = NULLCHAR; // closing ]} found
16503 if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16504 if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16505 end[1] = NULLCHAR; // clip off comment beyond variation
16506 ToNrEvent(currentMove-1);
16507 PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
16508 // kludge: use ParsePV() to append variation to game
16509 move = currentMove;
16510 ParsePV(start, TRUE, TRUE);
16511 forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
16512 ClearPremoveHighlights();
16514 ToNrEvent(currentMove+1);