2 * backend.c -- Common back end for X and Windows NT versions of
4 * Copyright 1991 by Digital Equipment Corporation, Maynard,
7 * Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
8 * 2007, 2008, 2009, 2010, 2011 Free Software Foundation, Inc.
10 * Enhancements Copyright 2005 Alessandro Scotti
12 * The following terms apply to Digital Equipment Corporation's copyright
14 * ------------------------------------------------------------------------
17 * Permission to use, copy, modify, and distribute this software and its
18 * documentation for any purpose and without fee is hereby granted,
19 * provided that the above copyright notice appear in all copies and that
20 * both that copyright notice and this permission notice appear in
21 * supporting documentation, and that the name of Digital not be
22 * used in advertising or publicity pertaining to distribution of the
23 * software without specific, written prior permission.
25 * DIGITAL DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING
26 * ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL
27 * DIGITAL BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR
28 * ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS,
29 * WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION,
30 * ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS
32 * ------------------------------------------------------------------------
34 * The following terms apply to the enhanced version of XBoard
35 * distributed by the Free Software Foundation:
36 * ------------------------------------------------------------------------
38 * GNU XBoard is free software: you can redistribute it and/or modify
39 * it under the terms of the GNU General Public License as published by
40 * the Free Software Foundation, either version 3 of the License, or (at
41 * your option) any later version.
43 * GNU XBoard is distributed in the hope that it will be useful, but
44 * WITHOUT ANY WARRANTY; without even the implied warranty of
45 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
46 * General Public License for more details.
48 * You should have received a copy of the GNU General Public License
49 * along with this program. If not, see http://www.gnu.org/licenses/. *
51 *------------------------------------------------------------------------
52 ** See the file ChangeLog for a revision history. */
54 /* [AS] Also useful here for debugging */
58 #define DoSleep( n ) if( (n) != 0 ) Sleep( (n) );
60 int flock(int f, int code);
66 #define DoSleep( n ) if( (n) >= 0) sleep(n)
77 #include <sys/types.h>
86 #else /* not STDC_HEADERS */
89 # else /* not HAVE_STRING_H */
91 # endif /* not HAVE_STRING_H */
92 #endif /* not STDC_HEADERS */
95 # include <sys/fcntl.h>
96 #else /* not HAVE_SYS_FCNTL_H */
99 # endif /* HAVE_FCNTL_H */
100 #endif /* not HAVE_SYS_FCNTL_H */
102 #if TIME_WITH_SYS_TIME
103 # include <sys/time.h>
107 # include <sys/time.h>
113 #if defined(_amigados) && !defined(__GNUC__)
118 extern int gettimeofday(struct timeval *, struct timezone *);
126 #include "frontend.h"
133 #include "backendz.h"
137 # define _(s) gettext (s)
138 # define N_(s) gettext_noop (s)
139 # define T_(s) gettext(s)
152 /* A point in time */
154 long sec; /* Assuming this is >= 32 bits */
155 int ms; /* Assuming this is >= 16 bits */
158 int establish P((void));
159 void read_from_player P((InputSourceRef isr, VOIDSTAR closure,
160 char *buf, int count, int error));
161 void read_from_ics P((InputSourceRef isr, VOIDSTAR closure,
162 char *buf, int count, int error));
163 void ics_printf P((char *format, ...));
164 void SendToICS P((char *s));
165 void SendToICSDelayed P((char *s, long msdelay));
166 void SendMoveToICS P((ChessMove moveType, int fromX, int fromY, int toX, int toY, char promoChar));
167 void HandleMachineMove P((char *message, ChessProgramState *cps));
168 int AutoPlayOneMove P((void));
169 int LoadGameOneMove P((ChessMove readAhead));
170 int LoadGameFromFile P((char *filename, int n, char *title, int useList));
171 int LoadPositionFromFile P((char *filename, int n, char *title));
172 int SavePositionToFile P((char *filename));
173 void ApplyMove P((int fromX, int fromY, int toX, int toY, int promoChar,
175 void MakeMove P((int fromX, int fromY, int toX, int toY, int promoChar));
176 void ShowMove P((int fromX, int fromY, int toX, int toY));
177 int FinishMove P((ChessMove moveType, int fromX, int fromY, int toX, int toY,
178 /*char*/int promoChar));
179 void BackwardInner P((int target));
180 void ForwardInner P((int target));
181 int Adjudicate P((ChessProgramState *cps));
182 void GameEnds P((ChessMove result, char *resultDetails, int whosays));
183 void EditPositionDone P((Boolean fakeRights));
184 void PrintOpponents P((FILE *fp));
185 void PrintPosition P((FILE *fp, int move));
186 void StartChessProgram P((ChessProgramState *cps));
187 void SendToProgram P((char *message, ChessProgramState *cps));
188 void SendMoveToProgram P((int moveNum, ChessProgramState *cps));
189 void ReceiveFromProgram P((InputSourceRef isr, VOIDSTAR closure,
190 char *buf, int count, int error));
191 void SendTimeControl P((ChessProgramState *cps,
192 int mps, long tc, int inc, int sd, int st));
193 char *TimeControlTagValue P((void));
194 void Attention P((ChessProgramState *cps));
195 void FeedMovesToProgram P((ChessProgramState *cps, int upto));
196 int ResurrectChessProgram P((void));
197 void DisplayComment P((int moveNumber, char *text));
198 void DisplayMove P((int moveNumber));
200 void ParseGameHistory P((char *game));
201 void ParseBoard12 P((char *string));
202 void KeepAlive P((void));
203 void StartClocks P((void));
204 void SwitchClocks P((int nr));
205 void StopClocks P((void));
206 void ResetClocks P((void));
207 char *PGNDate P((void));
208 void SetGameInfo P((void));
209 Boolean ParseFEN P((Board board, int *blackPlaysFirst, char *fen));
210 int RegisterMove P((void));
211 void MakeRegisteredMove P((void));
212 void TruncateGame P((void));
213 int looking_at P((char *, int *, char *));
214 void CopyPlayerNameIntoFileName P((char **, char *));
215 char *SavePart P((char *));
216 int SaveGameOldStyle P((FILE *));
217 int SaveGamePGN P((FILE *));
218 void GetTimeMark P((TimeMark *));
219 long SubtractTimeMarks P((TimeMark *, TimeMark *));
220 int CheckFlags P((void));
221 long NextTickLength P((long));
222 void CheckTimeControl P((void));
223 void show_bytes P((FILE *, char *, int));
224 int string_to_rating P((char *str));
225 void ParseFeatures P((char* args, ChessProgramState *cps));
226 void InitBackEnd3 P((void));
227 void FeatureDone P((ChessProgramState* cps, int val));
228 void InitChessProgram P((ChessProgramState *cps, int setup));
229 void OutputKibitz(int window, char *text);
230 int PerpetualChase(int first, int last);
231 int EngineOutputIsUp();
232 void InitDrawingSizes(int x, int y);
233 void NextMatchGame P((void));
234 int NextTourneyGame P((int nr, int *swap));
235 int Pairing P((int nr, int nPlayers, int *w, int *b, int *sync));
238 extern void ConsoleCreate();
241 ChessProgramState *WhitePlayer();
242 void InsertIntoMemo P((int which, char *text)); // [HGM] kibitz: in engineo.c
243 int VerifyDisplayMode P(());
245 char *GetInfoFromComment( int, char * ); // [HGM] PV time: returns stripped comment
246 void InitEngineUCI( const char * iniDir, ChessProgramState * cps ); // [HGM] moved here from winboard.c
247 char *ProbeBook P((int moveNr, char *book)); // [HGM] book: returns a book move
248 char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
249 void ics_update_width P((int new_width));
250 extern char installDir[MSG_SIZ];
251 VariantClass startVariant; /* [HGM] nicks: initial variant */
254 extern int tinyLayout, smallLayout;
255 ChessProgramStats programStats;
256 char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
258 static int exiting = 0; /* [HGM] moved to top */
259 static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
260 int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
261 Board partnerBoard; /* [HGM] bughouse: for peeking at partner game */
262 int partnerHighlight[2];
263 Boolean partnerBoardValid = 0;
264 char partnerStatus[MSG_SIZ];
266 Boolean originalFlip;
267 Boolean twoBoards = 0;
268 char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
269 int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
270 VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
271 int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
272 Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
273 int opponentKibitzes;
274 int lastSavedGame; /* [HGM] save: ID of game */
275 char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
276 extern int chatCount;
278 char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
279 char lastMsg[MSG_SIZ];
280 ChessSquare pieceSweep = EmptySquare;
281 ChessSquare promoSweep = EmptySquare, defaultPromoChoice;
282 int promoDefaultAltered;
284 /* States for ics_getting_history */
286 #define H_REQUESTED 1
287 #define H_GOT_REQ_HEADER 2
288 #define H_GOT_UNREQ_HEADER 3
289 #define H_GETTING_MOVES 4
290 #define H_GOT_UNWANTED_HEADER 5
292 /* whosays values for GameEnds */
301 /* Maximum number of games in a cmail message */
302 #define CMAIL_MAX_GAMES 20
304 /* Different types of move when calling RegisterMove */
306 #define CMAIL_RESIGN 1
308 #define CMAIL_ACCEPT 3
310 /* Different types of result to remember for each game */
311 #define CMAIL_NOT_RESULT 0
312 #define CMAIL_OLD_RESULT 1
313 #define CMAIL_NEW_RESULT 2
315 /* Telnet protocol constants */
326 safeStrCpy( char *dst, const char *src, size_t count )
329 assert( dst != NULL );
330 assert( src != NULL );
333 for(i=0; i<count; i++) if((dst[i] = src[i]) == NULLCHAR) break;
334 if( i == count && dst[count-1] != NULLCHAR)
336 dst[ count-1 ] = '\0'; // make sure incomplete copy still null-terminated
337 if(appData.debugMode)
338 fprintf(debugFP, "safeStrCpy: copying %s into %s didn't work, not enough space %d\n",src,dst, (int)count);
344 /* Some compiler can't cast u64 to double
345 * This function do the job for us:
347 * We use the highest bit for cast, this only
348 * works if the highest bit is not
349 * in use (This should not happen)
351 * We used this for all compiler
354 u64ToDouble(u64 value)
357 u64 tmp = value & u64Const(0x7fffffffffffffff);
358 r = (double)(s64)tmp;
359 if (value & u64Const(0x8000000000000000))
360 r += 9.2233720368547758080e18; /* 2^63 */
364 /* Fake up flags for now, as we aren't keeping track of castling
365 availability yet. [HGM] Change of logic: the flag now only
366 indicates the type of castlings allowed by the rule of the game.
367 The actual rights themselves are maintained in the array
368 castlingRights, as part of the game history, and are not probed
374 int flags = F_ALL_CASTLE_OK;
375 if ((index % 2) == 0) flags |= F_WHITE_ON_MOVE;
376 switch (gameInfo.variant) {
378 flags &= ~F_ALL_CASTLE_OK;
379 case VariantGiveaway: // [HGM] moved this case label one down: seems Giveaway does have castling on ICC!
380 flags |= F_IGNORE_CHECK;
382 flags |= F_MANDATORY_CAPTURE; //[HGM] losers: sets flag so TestLegality rejects non-capts if capts exist
385 flags |= F_IGNORE_CHECK | F_ATOMIC_CAPTURE;
387 case VariantKriegspiel:
388 flags |= F_KRIEGSPIEL_CAPTURE;
390 case VariantCapaRandom:
391 case VariantFischeRandom:
392 flags |= F_FRC_TYPE_CASTLING; /* [HGM] enable this through flag */
393 case VariantNoCastle:
394 case VariantShatranj:
397 flags &= ~F_ALL_CASTLE_OK;
405 FILE *gameFileFP, *debugFP;
408 [AS] Note: sometimes, the sscanf() function is used to parse the input
409 into a fixed-size buffer. Because of this, we must be prepared to
410 receive strings as long as the size of the input buffer, which is currently
411 set to 4K for Windows and 8K for the rest.
412 So, we must either allocate sufficiently large buffers here, or
413 reduce the size of the input buffer in the input reading part.
416 char cmailMove[CMAIL_MAX_GAMES][MOVE_LEN], cmailMsg[MSG_SIZ];
417 char bookOutput[MSG_SIZ*10], thinkOutput[MSG_SIZ*10], lastHint[MSG_SIZ];
418 char thinkOutput1[MSG_SIZ*10];
420 ChessProgramState first, second;
422 /* premove variables */
425 int premoveFromX = 0;
426 int premoveFromY = 0;
427 int premovePromoChar = 0;
429 Boolean alarmSounded;
430 /* end premove variables */
432 char *ics_prefix = "$";
433 int ics_type = ICS_GENERIC;
435 int currentMove = 0, forwardMostMove = 0, backwardMostMove = 0;
436 int pauseExamForwardMostMove = 0;
437 int nCmailGames = 0, nCmailResults = 0, nCmailMovesRegistered = 0;
438 int cmailMoveRegistered[CMAIL_MAX_GAMES], cmailResult[CMAIL_MAX_GAMES];
439 int cmailMsgLoaded = FALSE, cmailMailedMove = FALSE;
440 int cmailOldMove = -1, firstMove = TRUE, flipView = FALSE;
441 int blackPlaysFirst = FALSE, startedFromSetupPosition = FALSE;
442 int searchTime = 0, pausing = FALSE, pauseExamInvalid = FALSE;
443 int whiteFlag = FALSE, blackFlag = FALSE;
444 int userOfferedDraw = FALSE;
445 int ics_user_moved = 0, ics_gamenum = -1, ics_getting_history = H_FALSE;
446 int matchMode = FALSE, hintRequested = FALSE, bookRequested = FALSE;
447 int cmailMoveType[CMAIL_MAX_GAMES];
448 long ics_clock_paused = 0;
449 ProcRef icsPR = NoProc, cmailPR = NoProc;
450 InputSourceRef telnetISR = NULL, fromUserISR = NULL, cmailISR = NULL;
451 GameMode gameMode = BeginningOfGame;
452 char moveList[MAX_MOVES][MOVE_LEN], parseList[MAX_MOVES][MOVE_LEN * 2];
453 char *commentList[MAX_MOVES], *cmailCommentList[CMAIL_MAX_GAMES];
454 ChessProgramStats_Move pvInfoList[MAX_MOVES]; /* [AS] Info about engine thinking */
455 int hiddenThinkOutputState = 0; /* [AS] */
456 int adjudicateLossThreshold = 0; /* [AS] Automatic adjudication */
457 int adjudicateLossPlies = 6;
458 char white_holding[64], black_holding[64];
459 TimeMark lastNodeCountTime;
460 long lastNodeCount=0;
461 int shiftKey; // [HGM] set by mouse handler
463 int have_sent_ICS_logon = 0;
465 int suddenDeath, whiteStartMove, blackStartMove; /* [HGM] for implementation of 'any per time' sessions, as in first part of byoyomi TC */
466 long whiteTimeRemaining, blackTimeRemaining, timeControl, timeIncrement, lastWhite, lastBlack;
467 long timeControl_2; /* [AS] Allow separate time controls */
468 char *fullTimeControlString = NULL, *nextSession, *whiteTC, *blackTC; /* [HGM] secondary TC: merge of MPS, TC and inc */
469 long timeRemaining[2][MAX_MOVES];
470 int matchGame = 0, nextGame = 0, roundNr = 0;
471 Boolean waitingForGame = FALSE;
472 TimeMark programStartTime, pauseStart;
473 char ics_handle[MSG_SIZ];
474 int have_set_title = 0;
476 /* animateTraining preserves the state of appData.animate
477 * when Training mode is activated. This allows the
478 * response to be animated when appData.animate == TRUE and
479 * appData.animateDragging == TRUE.
481 Boolean animateTraining;
487 Board boards[MAX_MOVES];
488 /* [HGM] Following 7 needed for accurate legality tests: */
489 signed char castlingRank[BOARD_FILES]; // and corresponding ranks
490 signed char initialRights[BOARD_FILES];
491 int nrCastlingRights; // For TwoKings, or to implement castling-unknown status
492 int initialRulePlies, FENrulePlies;
493 FILE *serverMoves = NULL; // next two for broadcasting (/serverMoves option)
496 int mute; // mute all sounds
498 // [HGM] vari: next 12 to save and restore variations
499 #define MAX_VARIATIONS 10
500 int framePtr = MAX_MOVES-1; // points to free stack entry
502 int savedFirst[MAX_VARIATIONS];
503 int savedLast[MAX_VARIATIONS];
504 int savedFramePtr[MAX_VARIATIONS];
505 char *savedDetails[MAX_VARIATIONS];
506 ChessMove savedResult[MAX_VARIATIONS];
508 void PushTail P((int firstMove, int lastMove));
509 Boolean PopTail P((Boolean annotate));
510 void PushInner P((int firstMove, int lastMove));
511 void PopInner P((Boolean annotate));
512 void CleanupTail P((void));
514 ChessSquare FIDEArray[2][BOARD_FILES] = {
515 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
516 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
517 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
518 BlackKing, BlackBishop, BlackKnight, BlackRook }
521 ChessSquare twoKingsArray[2][BOARD_FILES] = {
522 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
523 WhiteKing, WhiteKing, WhiteKnight, WhiteRook },
524 { BlackRook, BlackKnight, BlackBishop, BlackQueen,
525 BlackKing, BlackKing, BlackKnight, BlackRook }
528 ChessSquare KnightmateArray[2][BOARD_FILES] = {
529 { WhiteRook, WhiteMan, WhiteBishop, WhiteQueen,
530 WhiteUnicorn, WhiteBishop, WhiteMan, WhiteRook },
531 { BlackRook, BlackMan, BlackBishop, BlackQueen,
532 BlackUnicorn, BlackBishop, BlackMan, BlackRook }
535 ChessSquare SpartanArray[2][BOARD_FILES] = {
536 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
537 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
538 { BlackAlfil, BlackMarshall, BlackKing, BlackDragon,
539 BlackDragon, BlackKing, BlackAngel, BlackAlfil }
542 ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
543 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
544 WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
545 { BlackCardinal, BlackAlfil, BlackMarshall, BlackAngel,
546 BlackKing, BlackMarshall, BlackAlfil, BlackCardinal }
549 ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
550 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteKing,
551 WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
552 { BlackRook, BlackKnight, BlackAlfil, BlackKing,
553 BlackFerz, BlackAlfil, BlackKnight, BlackRook }
556 ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
557 { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
558 WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
559 { BlackRook, BlackKnight, BlackMan, BlackFerz,
560 BlackKing, BlackMan, BlackKnight, BlackRook }
564 #if (BOARD_FILES>=10)
565 ChessSquare ShogiArray[2][BOARD_FILES] = {
566 { WhiteQueen, WhiteKnight, WhiteFerz, WhiteWazir,
567 WhiteKing, WhiteWazir, WhiteFerz, WhiteKnight, WhiteQueen },
568 { BlackQueen, BlackKnight, BlackFerz, BlackWazir,
569 BlackKing, BlackWazir, BlackFerz, BlackKnight, BlackQueen }
572 ChessSquare XiangqiArray[2][BOARD_FILES] = {
573 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteFerz,
574 WhiteWazir, WhiteFerz, WhiteAlfil, WhiteKnight, WhiteRook },
575 { BlackRook, BlackKnight, BlackAlfil, BlackFerz,
576 BlackWazir, BlackFerz, BlackAlfil, BlackKnight, BlackRook }
579 ChessSquare CapablancaArray[2][BOARD_FILES] = {
580 { WhiteRook, WhiteKnight, WhiteAngel, WhiteBishop, WhiteQueen,
581 WhiteKing, WhiteBishop, WhiteMarshall, WhiteKnight, WhiteRook },
582 { BlackRook, BlackKnight, BlackAngel, BlackBishop, BlackQueen,
583 BlackKing, BlackBishop, BlackMarshall, BlackKnight, BlackRook }
586 ChessSquare GreatArray[2][BOARD_FILES] = {
587 { WhiteDragon, WhiteKnight, WhiteAlfil, WhiteGrasshopper, WhiteKing,
588 WhiteSilver, WhiteCardinal, WhiteAlfil, WhiteKnight, WhiteDragon },
589 { BlackDragon, BlackKnight, BlackAlfil, BlackGrasshopper, BlackKing,
590 BlackSilver, BlackCardinal, BlackAlfil, BlackKnight, BlackDragon },
593 ChessSquare JanusArray[2][BOARD_FILES] = {
594 { WhiteRook, WhiteAngel, WhiteKnight, WhiteBishop, WhiteKing,
595 WhiteQueen, WhiteBishop, WhiteKnight, WhiteAngel, WhiteRook },
596 { BlackRook, BlackAngel, BlackKnight, BlackBishop, BlackKing,
597 BlackQueen, BlackBishop, BlackKnight, BlackAngel, BlackRook }
601 ChessSquare GothicArray[2][BOARD_FILES] = {
602 { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen, WhiteMarshall,
603 WhiteKing, WhiteAngel, WhiteBishop, WhiteKnight, WhiteRook },
604 { BlackRook, BlackKnight, BlackBishop, BlackQueen, BlackMarshall,
605 BlackKing, BlackAngel, BlackBishop, BlackKnight, BlackRook }
608 #define GothicArray CapablancaArray
612 ChessSquare FalconArray[2][BOARD_FILES] = {
613 { WhiteRook, WhiteKnight, WhiteBishop, WhiteLance, WhiteQueen,
614 WhiteKing, WhiteLance, WhiteBishop, WhiteKnight, WhiteRook },
615 { BlackRook, BlackKnight, BlackBishop, BlackLance, BlackQueen,
616 BlackKing, BlackLance, BlackBishop, BlackKnight, BlackRook }
619 #define FalconArray CapablancaArray
622 #else // !(BOARD_FILES>=10)
623 #define XiangqiPosition FIDEArray
624 #define CapablancaArray FIDEArray
625 #define GothicArray FIDEArray
626 #define GreatArray FIDEArray
627 #endif // !(BOARD_FILES>=10)
629 #if (BOARD_FILES>=12)
630 ChessSquare CourierArray[2][BOARD_FILES] = {
631 { WhiteRook, WhiteKnight, WhiteAlfil, WhiteBishop, WhiteMan, WhiteKing,
632 WhiteFerz, WhiteWazir, WhiteBishop, WhiteAlfil, WhiteKnight, WhiteRook },
633 { BlackRook, BlackKnight, BlackAlfil, BlackBishop, BlackMan, BlackKing,
634 BlackFerz, BlackWazir, BlackBishop, BlackAlfil, BlackKnight, BlackRook }
636 #else // !(BOARD_FILES>=12)
637 #define CourierArray CapablancaArray
638 #endif // !(BOARD_FILES>=12)
641 Board initialPosition;
644 /* Convert str to a rating. Checks for special cases of "----",
646 "++++", etc. Also strips ()'s */
648 string_to_rating(str)
651 while(*str && !isdigit(*str)) ++str;
653 return 0; /* One of the special "no rating" cases */
661 /* Init programStats */
662 programStats.movelist[0] = 0;
663 programStats.depth = 0;
664 programStats.nr_moves = 0;
665 programStats.moves_left = 0;
666 programStats.nodes = 0;
667 programStats.time = -1; // [HGM] PGNtime: make invalid to recognize engine output
668 programStats.score = 0;
669 programStats.got_only_move = 0;
670 programStats.got_fail = 0;
671 programStats.line_is_book = 0;
676 { // [HGM] moved some code here from InitBackend1 that has to be done after both engines have contributed their settings
677 if (appData.firstPlaysBlack) {
678 first.twoMachinesColor = "black\n";
679 second.twoMachinesColor = "white\n";
681 first.twoMachinesColor = "white\n";
682 second.twoMachinesColor = "black\n";
685 first.other = &second;
686 second.other = &first;
689 if(appData.timeOddsMode) {
690 norm = appData.timeOdds[0];
691 if(norm > appData.timeOdds[1]) norm = appData.timeOdds[1];
693 first.timeOdds = appData.timeOdds[0]/norm;
694 second.timeOdds = appData.timeOdds[1]/norm;
697 if(programVersion) free(programVersion);
698 if (appData.noChessProgram) {
699 programVersion = (char*) malloc(5 + strlen(PACKAGE_STRING));
700 sprintf(programVersion, "%s", PACKAGE_STRING);
702 /* [HGM] tidy: use tidy name, in stead of full pathname (which was probably a bug due to / vs \ ) */
703 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
704 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
709 UnloadEngine(ChessProgramState *cps)
711 /* Kill off first chess program */
712 if (cps->isr != NULL)
713 RemoveInputSource(cps->isr);
716 if (cps->pr != NoProc) {
718 DoSleep( appData.delayBeforeQuit );
719 SendToProgram("quit\n", cps);
720 DoSleep( appData.delayAfterQuit );
721 DestroyChildProcess(cps->pr, cps->useSigterm);
724 if(appData.debugMode) fprintf(debugFP, "Unload %s\n", cps->which);
728 ClearOptions(ChessProgramState *cps)
731 cps->nrOptions = cps->comboCnt = 0;
732 for(i=0; i<MAX_OPTIONS; i++) {
733 cps->option[i].min = cps->option[i].max = cps->option[i].value = 0;
734 cps->option[i].textValue = 0;
738 char *engineNames[] = {
744 InitEngine(ChessProgramState *cps, int n)
745 { // [HGM] all engine initialiation put in a function that does one engine
749 cps->which = engineNames[n];
750 cps->maybeThinking = FALSE;
754 cps->sendDrawOffers = 1;
756 cps->program = appData.chessProgram[n];
757 cps->host = appData.host[n];
758 cps->dir = appData.directory[n];
759 cps->initString = appData.engInitString[n];
760 cps->computerString = appData.computerString[n];
761 cps->useSigint = TRUE;
762 cps->useSigterm = TRUE;
763 cps->reuse = appData.reuse[n];
764 cps->nps = appData.NPS[n]; // [HGM] nps: copy nodes per second
765 cps->useSetboard = FALSE;
767 cps->usePing = FALSE;
770 cps->usePlayother = FALSE;
771 cps->useColors = TRUE;
772 cps->useUsermove = FALSE;
773 cps->sendICS = FALSE;
774 cps->sendName = appData.icsActive;
775 cps->sdKludge = FALSE;
776 cps->stKludge = FALSE;
777 TidyProgramName(cps->program, cps->host, cps->tidy);
779 safeStrCpy(cps->variants, appData.variant, MSG_SIZ);
780 cps->analysisSupport = 2; /* detect */
781 cps->analyzing = FALSE;
782 cps->initDone = FALSE;
784 /* New features added by Tord: */
785 cps->useFEN960 = FALSE;
786 cps->useOOCastle = TRUE;
787 /* End of new features added by Tord. */
788 cps->fenOverride = appData.fenOverride[n];
790 /* [HGM] time odds: set factor for each machine */
791 cps->timeOdds = appData.timeOdds[n];
793 /* [HGM] secondary TC: how to handle sessions that do not fit in 'level'*/
794 cps->accumulateTC = appData.accumulateTC[n];
795 cps->maxNrOfSessions = 1;
799 cps->supportsNPS = UNKNOWN;
802 cps->optionSettings = appData.engOptions[n];
804 cps->scoreIsAbsolute = appData.scoreIsAbsolute[n]; /* [AS] */
805 cps->isUCI = appData.isUCI[n]; /* [AS] */
806 cps->hasOwnBookUCI = appData.hasOwnBookUCI[n]; /* [AS] */
808 if (appData.protocolVersion[n] > PROTOVER
809 || appData.protocolVersion[n] < 1)
814 len = snprintf(buf, MSG_SIZ, _("protocol version %d not supported"),
815 appData.protocolVersion[n]);
816 if( (len > MSG_SIZ) && appData.debugMode )
817 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
819 DisplayFatalError(buf, 0, 2);
823 cps->protocolVersion = appData.protocolVersion[n];
826 InitEngineUCI( installDir, cps ); // [HGM] moved here from winboard.c, to make available in xboard
829 ChessProgramState *savCps;
835 if(WaitForEngine(savCps, LoadEngine)) return;
836 CommonEngineInit(); // recalculate time odds
837 if(gameInfo.variant != StringToVariant(appData.variant)) {
838 // we changed variant when loading the engine; this forces us to reset
839 Reset(TRUE, savCps != &first);
840 EditGameEvent(); // for consistency with other path, as Reset changes mode
842 InitChessProgram(savCps, FALSE);
843 SendToProgram("force\n", savCps);
844 DisplayMessage("", "");
845 if (startedFromSetupPosition) SendBoard(savCps, backwardMostMove);
846 for (i = backwardMostMove; i < forwardMostMove; i++) SendMoveToProgram(i, savCps);
852 ReplaceEngine(ChessProgramState *cps, int n)
856 appData.noChessProgram = FALSE;
857 appData.clockMode = TRUE;
859 if(n) return; // only startup first engine immediately; second can wait
860 savCps = cps; // parameter to LoadEngine passed as globals, to allow scheduled calling :-(
864 extern char *engineName, *engineDir, *engineChoice, *engineLine, *nickName, *params;
865 extern Boolean isUCI, hasBook, storeVariant, v1, addToList;
868 Load(ChessProgramState *cps, int i)
870 char *p, *q, buf[MSG_SIZ], command[MSG_SIZ];
871 if(engineLine[0]) { // an engine was selected from the combo box
872 snprintf(buf, MSG_SIZ, "-fcp %s", engineLine);
873 SwapEngines(i); // kludge to parse -f* / -first* like it is -s* / -second*
874 ParseArgsFromString("-firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1");
875 ParseArgsFromString(buf);
877 ReplaceEngine(cps, i);
881 while(q = strchr(p, SLASH)) p = q+1;
882 if(*p== NULLCHAR) { DisplayError(_("You did not specify the engine executable"), 0); return; }
883 if(engineDir[0] != NULLCHAR)
884 appData.directory[i] = engineDir;
885 else if(p != engineName) { // derive directory from engine path, when not given
887 appData.directory[i] = strdup(engineName);
889 } else appData.directory[i] = ".";
891 snprintf(command, MSG_SIZ, "%s %s", p, params);
894 appData.chessProgram[i] = strdup(p);
895 appData.isUCI[i] = isUCI;
896 appData.protocolVersion[i] = v1 ? 1 : PROTOVER;
897 appData.hasOwnBookUCI[i] = hasBook;
900 q = firstChessProgramNames;
901 if(nickName[0]) snprintf(buf, MSG_SIZ, "\"%s\" -fcp ", nickName); else buf[0] = NULLCHAR;
902 snprintf(buf+strlen(buf), MSG_SIZ-strlen(buf), "\"%s\" -fd \"%s\"%s%s%s%s%s\n", p, appData.directory[i],
903 v1 ? " -firstProtocolVersion 1" : "",
904 hasBook ? "" : " -fNoOwnBookUCI",
905 isUCI ? " -fUCI" : "",
906 storeVariant ? " -variant " : "",
907 storeVariant ? VariantName(gameInfo.variant) : "");
908 firstChessProgramNames = malloc(len = strlen(q) + strlen(buf) + 1);
909 snprintf(firstChessProgramNames, len, "%s%s", q, buf);
912 ReplaceEngine(cps, i);
918 int matched, min, sec;
920 ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
921 startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
923 GetTimeMark(&programStartTime);
924 srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
925 pauseStart = programStartTime; pauseStart.sec -= 100; // [HGM] matchpause: fake a pause that has long since ended
928 programStats.ok_to_send = 1;
929 programStats.seen_stat = 0;
932 * Initialize game list
938 * Internet chess server status
940 if (appData.icsActive) {
941 appData.matchMode = FALSE;
942 appData.matchGames = 0;
944 appData.noChessProgram = !appData.zippyPlay;
946 appData.zippyPlay = FALSE;
947 appData.zippyTalk = FALSE;
948 appData.noChessProgram = TRUE;
950 if (*appData.icsHelper != NULLCHAR) {
951 appData.useTelnet = TRUE;
952 appData.telnetProgram = appData.icsHelper;
955 appData.zippyTalk = appData.zippyPlay = FALSE;
958 /* [AS] Initialize pv info list [HGM] and game state */
962 for( i=0; i<=framePtr; i++ ) {
963 pvInfoList[i].depth = -1;
964 boards[i][EP_STATUS] = EP_NONE;
965 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
970 * Parse timeControl resource
972 if (!ParseTimeControl(appData.timeControl, appData.timeIncrement,
973 appData.movesPerSession)) {
975 snprintf(buf, sizeof(buf), _("bad timeControl option %s"), appData.timeControl);
976 DisplayFatalError(buf, 0, 2);
980 * Parse searchTime resource
982 if (*appData.searchTime != NULLCHAR) {
983 matched = sscanf(appData.searchTime, "%d:%d", &min, &sec);
985 searchTime = min * 60;
986 } else if (matched == 2) {
987 searchTime = min * 60 + sec;
990 snprintf(buf, sizeof(buf), _("bad searchTime option %s"), appData.searchTime);
991 DisplayFatalError(buf, 0, 2);
995 /* [AS] Adjudication threshold */
996 adjudicateLossThreshold = appData.adjudicateLossThreshold;
998 InitEngine(&first, 0);
999 InitEngine(&second, 1);
1002 if (appData.icsActive) {
1003 appData.clockMode = TRUE; /* changes dynamically in ICS mode */
1004 } else if (appData.noChessProgram) { // [HGM] st: searchTime mode now also is clockMode
1005 appData.clockMode = FALSE;
1006 first.sendTime = second.sendTime = 0;
1010 /* Override some settings from environment variables, for backward
1011 compatibility. Unfortunately it's not feasible to have the env
1012 vars just set defaults, at least in xboard. Ugh.
1014 if (appData.icsActive && (appData.zippyPlay || appData.zippyTalk)) {
1019 if (!appData.icsActive) {
1023 /* Check for variants that are supported only in ICS mode,
1024 or not at all. Some that are accepted here nevertheless
1025 have bugs; see comments below.
1027 VariantClass variant = StringToVariant(appData.variant);
1029 case VariantBughouse: /* need four players and two boards */
1030 case VariantKriegspiel: /* need to hide pieces and move details */
1031 /* case VariantFischeRandom: (Fabien: moved below) */
1032 len = snprintf(buf,MSG_SIZ, _("Variant %s supported only in ICS mode"), appData.variant);
1033 if( (len > MSG_SIZ) && appData.debugMode )
1034 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1036 DisplayFatalError(buf, 0, 2);
1039 case VariantUnknown:
1040 case VariantLoadable:
1050 len = snprintf(buf, MSG_SIZ, _("Unknown variant name %s"), appData.variant);
1051 if( (len > MSG_SIZ) && appData.debugMode )
1052 fprintf(debugFP, "InitBackEnd1: buffer truncated.\n");
1054 DisplayFatalError(buf, 0, 2);
1057 case VariantXiangqi: /* [HGM] repetition rules not implemented */
1058 case VariantFairy: /* [HGM] TestLegality definitely off! */
1059 case VariantGothic: /* [HGM] should work */
1060 case VariantCapablanca: /* [HGM] should work */
1061 case VariantCourier: /* [HGM] initial forced moves not implemented */
1062 case VariantShogi: /* [HGM] could still mate with pawn drop */
1063 case VariantKnightmate: /* [HGM] should work */
1064 case VariantCylinder: /* [HGM] untested */
1065 case VariantFalcon: /* [HGM] untested */
1066 case VariantCrazyhouse: /* holdings not shown, ([HGM] fixed that!)
1067 offboard interposition not understood */
1068 case VariantNormal: /* definitely works! */
1069 case VariantWildCastle: /* pieces not automatically shuffled */
1070 case VariantNoCastle: /* pieces not automatically shuffled */
1071 case VariantFischeRandom: /* [HGM] works and shuffles pieces */
1072 case VariantLosers: /* should work except for win condition,
1073 and doesn't know captures are mandatory */
1074 case VariantSuicide: /* should work except for win condition,
1075 and doesn't know captures are mandatory */
1076 case VariantGiveaway: /* should work except for win condition,
1077 and doesn't know captures are mandatory */
1078 case VariantTwoKings: /* should work */
1079 case VariantAtomic: /* should work except for win condition */
1080 case Variant3Check: /* should work except for win condition */
1081 case VariantShatranj: /* should work except for all win conditions */
1082 case VariantMakruk: /* should work except for daw countdown */
1083 case VariantBerolina: /* might work if TestLegality is off */
1084 case VariantCapaRandom: /* should work */
1085 case VariantJanus: /* should work */
1086 case VariantSuper: /* experimental */
1087 case VariantGreat: /* experimental, requires legality testing to be off */
1088 case VariantSChess: /* S-Chess, should work */
1089 case VariantSpartan: /* should work */
1096 int NextIntegerFromString( char ** str, long * value )
1101 while( *s == ' ' || *s == '\t' ) {
1107 if( *s >= '0' && *s <= '9' ) {
1108 while( *s >= '0' && *s <= '9' ) {
1109 *value = *value * 10 + (*s - '0');
1121 int NextTimeControlFromString( char ** str, long * value )
1124 int result = NextIntegerFromString( str, &temp );
1127 *value = temp * 60; /* Minutes */
1128 if( **str == ':' ) {
1130 result = NextIntegerFromString( str, &temp );
1131 *value += temp; /* Seconds */
1138 int NextSessionFromString( char ** str, int *moves, long * tc, long *inc, int *incType)
1139 { /* [HGM] routine added to read '+moves/time' for secondary time control. */
1140 int result = -1, type = 0; long temp, temp2;
1142 if(**str != ':') return -1; // old params remain in force!
1144 if(**str == '*') type = *(*str)++, temp = 0; // sandclock TC
1145 if( NextIntegerFromString( str, &temp ) ) return -1;
1146 if(type) { *moves = 0; *tc = temp * 500; *inc = temp * 1000; *incType = '*'; return 0; }
1149 /* time only: incremental or sudden-death time control */
1150 if(**str == '+') { /* increment follows; read it */
1152 if(**str == '!') type = *(*str)++; // Bronstein TC
1153 if(result = NextIntegerFromString( str, &temp2)) return -1;
1154 *inc = temp2 * 1000;
1155 if(**str == '.') { // read fraction of increment
1156 char *start = ++(*str);
1157 if(result = NextIntegerFromString( str, &temp2)) return -1;
1159 while(start++ < *str) temp2 /= 10;
1163 *moves = 0; *tc = temp * 1000; *incType = type;
1167 (*str)++; /* classical time control */
1168 result = NextIntegerFromString( str, &temp2); // NOTE: already converted to seconds by ParseTimeControl()
1179 int GetTimeQuota(int movenr, int lastUsed, char *tcString)
1180 { /* [HGM] get time to add from the multi-session time-control string */
1181 int incType, moves=1; /* kludge to force reading of first session */
1182 long time, increment;
1185 if(!*s) return 0; // empty TC string means we ran out of the last sudden-death version
1186 if(appData.debugMode) fprintf(debugFP, "TC string = '%s'\n", tcString);
1188 if(moves) NextSessionFromString(&s, &moves, &time, &increment, &incType);
1189 nextSession = s; suddenDeath = moves == 0 && increment == 0;
1190 if(appData.debugMode) fprintf(debugFP, "mps=%d tc=%d inc=%d\n", moves, (int) time, (int) increment);
1191 if(movenr == -1) return time; /* last move before new session */
1192 if(incType == '*') increment = 0; else // for sandclock, time is added while not thinking
1193 if(incType == '!' && lastUsed < increment) increment = lastUsed;
1194 if(!moves) return increment; /* current session is incremental */
1195 if(movenr >= 0) movenr -= moves; /* we already finished this session */
1196 } while(movenr >= -1); /* try again for next session */
1198 return 0; // no new time quota on this move
1202 ParseTimeControl(tc, ti, mps)
1209 char buf[MSG_SIZ], buf2[MSG_SIZ], *mytc = tc;
1212 if(ti >= 0 && !strchr(tc, '+') && !strchr(tc, '/') ) mps = 0;
1213 if(!strchr(tc, '+') && !strchr(tc, '/') && sscanf(tc, "%d:%d", &min, &sec) >= 1)
1214 sprintf(mytc=buf2, "%d", 60*min+sec); // convert 'classical' min:sec tc string to seconds
1218 snprintf(buf, MSG_SIZ, ":%d/%s+%g", mps, mytc, ti);
1220 snprintf(buf, MSG_SIZ, ":%s+%g", mytc, ti);
1223 snprintf(buf, MSG_SIZ, ":%d/%s", mps, mytc);
1225 snprintf(buf, MSG_SIZ, ":%s", mytc);
1227 fullTimeControlString = StrSave(buf); // this should now be in PGN format
1229 if( NextTimeControlFromString( &tc, &tc1 ) != 0 ) {
1234 /* Parse second time control */
1237 if( NextTimeControlFromString( &tc, &tc2 ) != 0 ) {
1245 timeControl_2 = tc2 * 1000;
1255 timeControl = tc1 * 1000;
1258 timeIncrement = ti * 1000; /* convert to ms */
1259 movesPerSession = 0;
1262 movesPerSession = mps;
1270 if (appData.debugMode) {
1271 fprintf(debugFP, "%s\n", programVersion);
1274 set_cont_sequence(appData.wrapContSeq);
1275 if (appData.matchGames > 0) {
1276 appData.matchMode = TRUE;
1277 } else if (appData.matchMode) {
1278 appData.matchGames = 1;
1280 if(appData.matchMode && appData.sameColorGames > 0) /* [HGM] alternate: overrule matchGames */
1281 appData.matchGames = appData.sameColorGames;
1282 if(appData.rewindIndex > 1) { /* [HGM] autoinc: rewind implies auto-increment and overrules given index */
1283 if(appData.loadPositionIndex >= 0) appData.loadPositionIndex = -1;
1284 if(appData.loadGameIndex >= 0) appData.loadGameIndex = -1;
1287 if (appData.noChessProgram || first.protocolVersion == 1) {
1290 /* kludge: allow timeout for initial "feature" commands */
1292 DisplayMessage("", _("Starting chess program"));
1293 ScheduleDelayedEvent(InitBackEnd3, FEATURE_TIMEOUT);
1298 CalculateIndex(int index, int gameNr)
1299 { // [HGM] autoinc: absolute way to determine load index from game number (taking auto-inc and rewind into account)
1301 if(index > 0) return index; // fixed nmber
1302 if(index == 0) return 1;
1303 res = (index == -1 ? gameNr : (gameNr-1)/2 + 1); // autoinc
1304 if(appData.rewindIndex > 0) res = (res-1) % appData.rewindIndex + 1; // rewind
1309 LoadGameOrPosition(int gameNr)
1310 { // [HGM] taken out of MatchEvent and NextMatchGame (to combine it)
1311 if (*appData.loadGameFile != NULLCHAR) {
1312 if (!LoadGameFromFile(appData.loadGameFile,
1313 CalculateIndex(appData.loadGameIndex, gameNr),
1314 appData.loadGameFile, FALSE)) {
1315 DisplayFatalError(_("Bad game file"), 0, 1);
1318 } else if (*appData.loadPositionFile != NULLCHAR) {
1319 if (!LoadPositionFromFile(appData.loadPositionFile,
1320 CalculateIndex(appData.loadPositionIndex, gameNr),
1321 appData.loadPositionFile)) {
1322 DisplayFatalError(_("Bad position file"), 0, 1);
1330 ReserveGame(int gameNr, char resChar)
1332 FILE *tf = fopen(appData.tourneyFile, "r+");
1333 char *p, *q, c, buf[MSG_SIZ];
1334 if(tf == NULL) { nextGame = appData.matchGames + 1; return; } // kludge to terminate match
1335 safeStrCpy(buf, lastMsg, MSG_SIZ);
1336 DisplayMessage(_("Pick new game"), "");
1337 flock(fileno(tf), LOCK_EX); // lock the tourney file while we are messing with it
1338 ParseArgsFromFile(tf);
1339 p = q = appData.results;
1340 if(appData.debugMode) {
1341 char *r = appData.participants;
1342 fprintf(debugFP, "results = '%s'\n", p);
1343 while(*r) fprintf(debugFP, *r >= ' ' ? "%c" : "\\%03o", *r), r++;
1344 fprintf(debugFP, "\n");
1346 while(*q && *q != ' ') q++; // get first un-played game (could be beyond end!)
1348 q = malloc(strlen(p) + 2); // could be arbitrary long, but allow to extend by one!
1349 safeStrCpy(q, p, strlen(p) + 2);
1350 if(gameNr >= 0) q[gameNr] = resChar; // replace '*' with result
1351 if(appData.debugMode) fprintf(debugFP, "pick next game from '%s': %d\n", q, nextGame);
1352 if(nextGame <= appData.matchGames && resChar != ' ') { // already reserve next game, if tourney not yet done
1353 if(q[nextGame] == NULLCHAR) q[nextGame+1] = NULLCHAR; // append one char
1356 fseek(tf, -(strlen(p)+4), SEEK_END);
1358 if(c != '"') // depending on DOS or Unix line endings we can be one off
1359 fseek(tf, -(strlen(p)+2), SEEK_END);
1360 else fseek(tf, -(strlen(p)+3), SEEK_END);
1361 fprintf(tf, "%s\"\n", q); fclose(tf); // update, and flush by closing
1362 DisplayMessage(buf, "");
1363 free(p); appData.results = q;
1364 if(nextGame <= appData.matchGames && resChar != ' ' &&
1365 (gameNr < 0 || nextGame / appData.defaultMatchGames != gameNr / appData.defaultMatchGames)) {
1366 UnloadEngine(&first); // next game belongs to other pairing;
1367 UnloadEngine(&second); // already unload the engines, so TwoMachinesEvent will load new ones.
1372 MatchEvent(int mode)
1373 { // [HGM] moved out of InitBackend3, to make it callable when match starts through menu
1375 if(matchMode) { // already in match mode: switch it off
1377 appData.matchGames = appData.tourneyFile[0] ? nextGame: matchGame; // kludge to let match terminate after next game.
1378 ModeHighlight(); // kludgey way to remove checkmark...
1381 // if(gameMode != BeginningOfGame) {
1382 // DisplayError(_("You can only start a match from the initial position."), 0);
1386 appData.matchGames = appData.defaultMatchGames;
1387 /* Set up machine vs. machine match */
1389 NextTourneyGame(0, &dummy); // sets appData.matchGames if this is tourney, to make sure ReserveGame knows it
1390 if(appData.tourneyFile[0]) {
1392 if(nextGame > appData.matchGames) {
1394 snprintf(buf, MSG_SIZ, _("All games in tourney '%s' are already played or playing"), appData.tourneyFile);
1395 DisplayError(buf, 0);
1396 appData.tourneyFile[0] = 0;
1400 if (appData.noChessProgram) { // [HGM] in tourney engines are loaded automatically
1401 DisplayFatalError(_("Can't have a match with no chess programs"),
1406 matchGame = roundNr = 1;
1407 first.matchWins = second.matchWins = 0; // [HGM] match: needed in later matches
\r
1412 InitBackEnd3 P((void))
1414 GameMode initialMode;
1418 InitChessProgram(&first, startedFromSetupPosition);
1420 if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
1421 free(programVersion);
1422 programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
1423 sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
1426 if (appData.icsActive) {
1428 /* [DM] Make a console window if needed [HGM] merged ifs */
1434 if (*appData.icsCommPort != NULLCHAR)
1435 len = snprintf(buf, MSG_SIZ, _("Could not open comm port %s"),
1436 appData.icsCommPort);
1438 len = snprintf(buf, MSG_SIZ, _("Could not connect to host %s, port %s"),
1439 appData.icsHost, appData.icsPort);
1441 if( (len > MSG_SIZ) && appData.debugMode )
1442 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1444 DisplayFatalError(buf, err, 1);
1449 AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
1451 AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
1452 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
1453 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1454 } else if (appData.noChessProgram) {
1460 if (*appData.cmailGameName != NULLCHAR) {
1462 OpenLoopback(&cmailPR);
1464 AddInputSource(cmailPR, FALSE, CmailSigHandlerCallBack, &cmailISR);
1468 DisplayMessage("", "");
1469 if (StrCaseCmp(appData.initialMode, "") == 0) {
1470 initialMode = BeginningOfGame;
1471 if(!appData.icsActive && appData.noChessProgram) { // [HGM] could be fall-back
1472 gameMode = MachinePlaysBlack; // "Machine Black" might have been implicitly highlighted
1473 ModeHighlight(); // make sure XBoard knows it is highlighted, so it will un-highlight it
1474 gameMode = BeginningOfGame; // in case BeginningOfGame now means "Edit Position"
1477 } else if (StrCaseCmp(appData.initialMode, "TwoMachines") == 0) {
1478 initialMode = TwoMachinesPlay;
1479 } else if (StrCaseCmp(appData.initialMode, "AnalyzeFile") == 0) {
1480 initialMode = AnalyzeFile;
1481 } else if (StrCaseCmp(appData.initialMode, "Analysis") == 0) {
1482 initialMode = AnalyzeMode;
1483 } else if (StrCaseCmp(appData.initialMode, "MachineWhite") == 0) {
1484 initialMode = MachinePlaysWhite;
1485 } else if (StrCaseCmp(appData.initialMode, "MachineBlack") == 0) {
1486 initialMode = MachinePlaysBlack;
1487 } else if (StrCaseCmp(appData.initialMode, "EditGame") == 0) {
1488 initialMode = EditGame;
1489 } else if (StrCaseCmp(appData.initialMode, "EditPosition") == 0) {
1490 initialMode = EditPosition;
1491 } else if (StrCaseCmp(appData.initialMode, "Training") == 0) {
1492 initialMode = Training;
1494 len = snprintf(buf, MSG_SIZ, _("Unknown initialMode %s"), appData.initialMode);
1495 if( (len > MSG_SIZ) && appData.debugMode )
1496 fprintf(debugFP, "InitBackEnd3: buffer truncated.\n");
1498 DisplayFatalError(buf, 0, 2);
1502 if (appData.matchMode) {
1503 if(appData.tourneyFile[0]) { // start tourney from command line
1505 if(f = fopen(appData.tourneyFile, "r")) {
1506 ParseArgsFromFile(f); // make sure tourney parmeters re known
1508 } else appData.tourneyFile[0] = NULLCHAR; // for now ignore bad tourney file
1511 } else if (*appData.cmailGameName != NULLCHAR) {
1512 /* Set up cmail mode */
1513 ReloadCmailMsgEvent(TRUE);
1515 /* Set up other modes */
1516 if (initialMode == AnalyzeFile) {
1517 if (*appData.loadGameFile == NULLCHAR) {
1518 DisplayFatalError(_("AnalyzeFile mode requires a game file"), 0, 1);
1522 if (*appData.loadGameFile != NULLCHAR) {
1523 (void) LoadGameFromFile(appData.loadGameFile,
1524 appData.loadGameIndex,
1525 appData.loadGameFile, TRUE);
1526 } else if (*appData.loadPositionFile != NULLCHAR) {
1527 (void) LoadPositionFromFile(appData.loadPositionFile,
1528 appData.loadPositionIndex,
1529 appData.loadPositionFile);
1530 /* [HGM] try to make self-starting even after FEN load */
1531 /* to allow automatic setup of fairy variants with wtm */
1532 if(initialMode == BeginningOfGame && !blackPlaysFirst) {
1533 gameMode = BeginningOfGame;
1534 setboardSpoiledMachineBlack = 1;
1536 /* [HGM] loadPos: make that every new game uses the setup */
1537 /* from file as long as we do not switch variant */
1538 if(!blackPlaysFirst) {
1539 startedFromPositionFile = TRUE;
1540 CopyBoard(filePosition, boards[0]);
1543 if (initialMode == AnalyzeMode) {
1544 if (appData.noChessProgram) {
1545 DisplayFatalError(_("Analysis mode requires a chess engine"), 0, 2);
1548 if (appData.icsActive) {
1549 DisplayFatalError(_("Analysis mode does not work with ICS mode"),0,2);
1553 } else if (initialMode == AnalyzeFile) {
1554 appData.showThinking = TRUE; // [HGM] thinking: moved out of ShowThinkingEvent
1555 ShowThinkingEvent();
1557 AnalysisPeriodicEvent(1);
1558 } else if (initialMode == MachinePlaysWhite) {
1559 if (appData.noChessProgram) {
1560 DisplayFatalError(_("MachineWhite mode requires a chess engine"),
1564 if (appData.icsActive) {
1565 DisplayFatalError(_("MachineWhite mode does not work with ICS mode"),
1569 MachineWhiteEvent();
1570 } else if (initialMode == MachinePlaysBlack) {
1571 if (appData.noChessProgram) {
1572 DisplayFatalError(_("MachineBlack mode requires a chess engine"),
1576 if (appData.icsActive) {
1577 DisplayFatalError(_("MachineBlack mode does not work with ICS mode"),
1581 MachineBlackEvent();
1582 } else if (initialMode == TwoMachinesPlay) {
1583 if (appData.noChessProgram) {
1584 DisplayFatalError(_("TwoMachines mode requires a chess engine"),
1588 if (appData.icsActive) {
1589 DisplayFatalError(_("TwoMachines mode does not work with ICS mode"),
1594 } else if (initialMode == EditGame) {
1596 } else if (initialMode == EditPosition) {
1597 EditPositionEvent();
1598 } else if (initialMode == Training) {
1599 if (*appData.loadGameFile == NULLCHAR) {
1600 DisplayFatalError(_("Training mode requires a game file"), 0, 2);
1609 * Establish will establish a contact to a remote host.port.
1610 * Sets icsPR to a ProcRef for a process (or pseudo-process)
1611 * used to talk to the host.
1612 * Returns 0 if okay, error code if not.
1619 if (*appData.icsCommPort != NULLCHAR) {
1620 /* Talk to the host through a serial comm port */
1621 return OpenCommPort(appData.icsCommPort, &icsPR);
1623 } else if (*appData.gateway != NULLCHAR) {
1624 if (*appData.remoteShell == NULLCHAR) {
1625 /* Use the rcmd protocol to run telnet program on a gateway host */
1626 snprintf(buf, sizeof(buf), "%s %s %s",
1627 appData.telnetProgram, appData.icsHost, appData.icsPort);
1628 return OpenRcmd(appData.gateway, appData.remoteUser, buf, &icsPR);
1631 /* Use the rsh program to run telnet program on a gateway host */
1632 if (*appData.remoteUser == NULLCHAR) {
1633 snprintf(buf, sizeof(buf), "%s %s %s %s %s", appData.remoteShell,
1634 appData.gateway, appData.telnetProgram,
1635 appData.icsHost, appData.icsPort);
1637 snprintf(buf, sizeof(buf), "%s %s -l %s %s %s %s",
1638 appData.remoteShell, appData.gateway,
1639 appData.remoteUser, appData.telnetProgram,
1640 appData.icsHost, appData.icsPort);
1642 return StartChildProcess(buf, "", &icsPR);
1645 } else if (appData.useTelnet) {
1646 return OpenTelnet(appData.icsHost, appData.icsPort, &icsPR);
1649 /* TCP socket interface differs somewhat between
1650 Unix and NT; handle details in the front end.
1652 return OpenTCP(appData.icsHost, appData.icsPort, &icsPR);
1656 void EscapeExpand(char *p, char *q)
1657 { // [HGM] initstring: routine to shape up string arguments
1658 while(*p++ = *q++) if(p[-1] == '\\')
1660 case 'n': p[-1] = '\n'; break;
1661 case 'r': p[-1] = '\r'; break;
1662 case 't': p[-1] = '\t'; break;
1663 case '\\': p[-1] = '\\'; break;
1664 case 0: *p = 0; return;
1665 default: p[-1] = q[-1]; break;
1670 show_bytes(fp, buf, count)
1676 if (*buf < 040 || *(unsigned char *) buf > 0177) {
1677 fprintf(fp, "\\%03o", *buf & 0xff);
1686 /* Returns an errno value */
1688 OutputMaybeTelnet(pr, message, count, outError)
1694 char buf[8192], *p, *q, *buflim;
1695 int left, newcount, outcount;
1697 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet ||
1698 *appData.gateway != NULLCHAR) {
1699 if (appData.debugMode) {
1700 fprintf(debugFP, ">ICS: ");
1701 show_bytes(debugFP, message, count);
1702 fprintf(debugFP, "\n");
1704 return OutputToProcess(pr, message, count, outError);
1707 buflim = &buf[sizeof(buf)-1]; /* allow 1 byte for expanding last char */
1714 if (appData.debugMode) {
1715 fprintf(debugFP, ">ICS: ");
1716 show_bytes(debugFP, buf, newcount);
1717 fprintf(debugFP, "\n");
1719 outcount = OutputToProcess(pr, buf, newcount, outError);
1720 if (outcount < newcount) return -1; /* to be sure */
1727 } else if (((unsigned char) *p) == TN_IAC) {
1728 *q++ = (char) TN_IAC;
1735 if (appData.debugMode) {
1736 fprintf(debugFP, ">ICS: ");
1737 show_bytes(debugFP, buf, newcount);
1738 fprintf(debugFP, "\n");
1740 outcount = OutputToProcess(pr, buf, newcount, outError);
1741 if (outcount < newcount) return -1; /* to be sure */
1746 read_from_player(isr, closure, message, count, error)
1753 int outError, outCount;
1754 static int gotEof = 0;
1756 /* Pass data read from player on to ICS */
1759 outCount = OutputMaybeTelnet(icsPR, message, count, &outError);
1760 if (outCount < count) {
1761 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1763 } else if (count < 0) {
1764 RemoveInputSource(isr);
1765 DisplayFatalError(_("Error reading from keyboard"), error, 1);
1766 } else if (gotEof++ > 0) {
1767 RemoveInputSource(isr);
1768 DisplayFatalError(_("Got end of file from keyboard"), 0, 0);
1774 { // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
1775 if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
1776 connectionAlive = FALSE; // only sticks if no response to 'date' command.
1777 SendToICS("date\n");
1778 if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
1781 /* added routine for printf style output to ics */
1782 void ics_printf(char *format, ...)
1784 char buffer[MSG_SIZ];
1787 va_start(args, format);
1788 vsnprintf(buffer, sizeof(buffer), format, args);
1789 buffer[sizeof(buffer)-1] = '\0';
1798 int count, outCount, outError;
1800 if (icsPR == NULL) return;
1803 outCount = OutputMaybeTelnet(icsPR, s, count, &outError);
1804 if (outCount < count) {
1805 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1809 /* This is used for sending logon scripts to the ICS. Sending
1810 without a delay causes problems when using timestamp on ICC
1811 (at least on my machine). */
1813 SendToICSDelayed(s,msdelay)
1817 int count, outCount, outError;
1819 if (icsPR == NULL) return;
1822 if (appData.debugMode) {
1823 fprintf(debugFP, ">ICS: ");
1824 show_bytes(debugFP, s, count);
1825 fprintf(debugFP, "\n");
1827 outCount = OutputToProcessDelayed(icsPR, s, count, &outError,
1829 if (outCount < count) {
1830 DisplayFatalError(_("Error writing to ICS"), outError, 1);
1835 /* Remove all highlighting escape sequences in s
1836 Also deletes any suffix starting with '('
1839 StripHighlightAndTitle(s)
1842 static char retbuf[MSG_SIZ];
1845 while (*s != NULLCHAR) {
1846 while (*s == '\033') {
1847 while (*s != NULLCHAR && !isalpha(*s)) s++;
1848 if (*s != NULLCHAR) s++;
1850 while (*s != NULLCHAR && *s != '\033') {
1851 if (*s == '(' || *s == '[') {
1862 /* Remove all highlighting escape sequences in s */
1867 static char retbuf[MSG_SIZ];
1870 while (*s != NULLCHAR) {
1871 while (*s == '\033') {
1872 while (*s != NULLCHAR && !isalpha(*s)) s++;
1873 if (*s != NULLCHAR) s++;
1875 while (*s != NULLCHAR && *s != '\033') {
1883 char *variantNames[] = VARIANT_NAMES;
1888 return variantNames[v];
1892 /* Identify a variant from the strings the chess servers use or the
1893 PGN Variant tag names we use. */
1900 VariantClass v = VariantNormal;
1901 int i, found = FALSE;
1907 /* [HGM] skip over optional board-size prefixes */
1908 if( sscanf(e, "%dx%d_", &i, &i) == 2 ||
1909 sscanf(e, "%dx%d+%d_", &i, &i, &i) == 3 ) {
1910 while( *e++ != '_');
1913 if(StrCaseStr(e, "misc/")) { // [HGM] on FICS, misc/shogi is not shogi
1917 for (i=0; i<sizeof(variantNames)/sizeof(char*); i++) {
1918 if (StrCaseStr(e, variantNames[i])) {
1919 v = (VariantClass) i;
1926 if ((StrCaseStr(e, "fischer") && StrCaseStr(e, "random"))
1927 || StrCaseStr(e, "wild/fr")
1928 || StrCaseStr(e, "frc") || StrCaseStr(e, "960")) {
1929 v = VariantFischeRandom;
1930 } else if ((i = 4, p = StrCaseStr(e, "wild")) ||
1931 (i = 1, p = StrCaseStr(e, "w"))) {
1933 while (*p && (isspace(*p) || *p == '(' || *p == '/')) p++;
1940 case 0: /* FICS only, actually */
1942 /* Castling legal even if K starts on d-file */
1943 v = VariantWildCastle;
1948 /* Castling illegal even if K & R happen to start in
1949 normal positions. */
1950 v = VariantNoCastle;
1963 /* Castling legal iff K & R start in normal positions */
1969 /* Special wilds for position setup; unclear what to do here */
1970 v = VariantLoadable;
1973 /* Bizarre ICC game */
1974 v = VariantTwoKings;
1977 v = VariantKriegspiel;
1983 v = VariantFischeRandom;
1986 v = VariantCrazyhouse;
1989 v = VariantBughouse;
1995 /* Not quite the same as FICS suicide! */
1996 v = VariantGiveaway;
2002 v = VariantShatranj;
2005 /* Temporary names for future ICC types. The name *will* change in
2006 the next xboard/WinBoard release after ICC defines it. */
2044 v = VariantCapablanca;
2047 v = VariantKnightmate;
2053 v = VariantCylinder;
2059 v = VariantCapaRandom;
2062 v = VariantBerolina;
2074 /* Found "wild" or "w" in the string but no number;
2075 must assume it's normal chess. */
2079 len = snprintf(buf, MSG_SIZ, _("Unknown wild type %d"), wnum);
2080 if( (len > MSG_SIZ) && appData.debugMode )
2081 fprintf(debugFP, "StringToVariant: buffer truncated.\n");
2083 DisplayError(buf, 0);
2089 if (appData.debugMode) {
2090 fprintf(debugFP, _("recognized '%s' (%d) as variant %s\n"),
2091 e, wnum, VariantName(v));
2096 static int leftover_start = 0, leftover_len = 0;
2097 char star_match[STAR_MATCH_N][MSG_SIZ];
2099 /* Test whether pattern is present at &buf[*index]; if so, return TRUE,
2100 advance *index beyond it, and set leftover_start to the new value of
2101 *index; else return FALSE. If pattern contains the character '*', it
2102 matches any sequence of characters not containing '\r', '\n', or the
2103 character following the '*' (if any), and the matched sequence(s) are
2104 copied into star_match.
2107 looking_at(buf, index, pattern)
2112 char *bufp = &buf[*index], *patternp = pattern;
2114 char *matchp = star_match[0];
2117 if (*patternp == NULLCHAR) {
2118 *index = leftover_start = bufp - buf;
2122 if (*bufp == NULLCHAR) return FALSE;
2123 if (*patternp == '*') {
2124 if (*bufp == *(patternp + 1)) {
2126 matchp = star_match[++star_count];
2130 } else if (*bufp == '\n' || *bufp == '\r') {
2132 if (*patternp == NULLCHAR)
2137 *matchp++ = *bufp++;
2141 if (*patternp != *bufp) return FALSE;
2148 SendToPlayer(data, length)
2152 int error, outCount;
2153 outCount = OutputToProcess(NoProc, data, length, &error);
2154 if (outCount < length) {
2155 DisplayFatalError(_("Error writing to display"), error, 1);
2160 PackHolding(packed, holding)
2172 switch (runlength) {
2183 sprintf(q, "%d", runlength);
2195 /* Telnet protocol requests from the front end */
2197 TelnetRequest(ddww, option)
2198 unsigned char ddww, option;
2200 unsigned char msg[3];
2201 int outCount, outError;
2203 if (*appData.icsCommPort != NULLCHAR || appData.useTelnet) return;
2205 if (appData.debugMode) {
2206 char buf1[8], buf2[8], *ddwwStr, *optionStr;
2222 snprintf(buf1,sizeof(buf1)/sizeof(buf1[0]), "%d", ddww);
2231 snprintf(buf2,sizeof(buf2)/sizeof(buf2[0]), "%d", option);
2234 fprintf(debugFP, ">%s %s ", ddwwStr, optionStr);
2239 outCount = OutputToProcess(icsPR, (char *)msg, 3, &outError);
2241 DisplayFatalError(_("Error writing to ICS"), outError, 1);
2248 if (!appData.icsActive) return;
2249 TelnetRequest(TN_DO, TN_ECHO);
2255 if (!appData.icsActive) return;
2256 TelnetRequest(TN_DONT, TN_ECHO);
2260 CopyHoldings(Board board, char *holdings, ChessSquare lowestPiece)
2262 /* put the holdings sent to us by the server on the board holdings area */
2263 int i, j, holdingsColumn, holdingsStartRow, direction, countsColumn;
2267 if(gameInfo.holdingsWidth < 2) return;
2268 if(gameInfo.variant != VariantBughouse && board[HOLDINGS_SET])
2269 return; // prevent overwriting by pre-board holdings
2271 if( (int)lowestPiece >= BlackPawn ) {
2274 holdingsStartRow = BOARD_HEIGHT-1;
2277 holdingsColumn = BOARD_WIDTH-1;
2278 countsColumn = BOARD_WIDTH-2;
2279 holdingsStartRow = 0;
2283 for(i=0; i<BOARD_HEIGHT; i++) { /* clear holdings */
2284 board[i][holdingsColumn] = EmptySquare;
2285 board[i][countsColumn] = (ChessSquare) 0;
2287 while( (p=*holdings++) != NULLCHAR ) {
2288 piece = CharToPiece( ToUpper(p) );
2289 if(piece == EmptySquare) continue;
2290 /*j = (int) piece - (int) WhitePawn;*/
2291 j = PieceToNumber(piece);
2292 if(j >= gameInfo.holdingsSize) continue; /* ignore pieces that do not fit */
2293 if(j < 0) continue; /* should not happen */
2294 piece = (ChessSquare) ( (int)piece + (int)lowestPiece );
2295 board[holdingsStartRow+j*direction][holdingsColumn] = piece;
2296 board[holdingsStartRow+j*direction][countsColumn]++;
2302 VariantSwitch(Board board, VariantClass newVariant)
2304 int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
2305 static Board oldBoard;
2307 startedFromPositionFile = FALSE;
2308 if(gameInfo.variant == newVariant) return;
2310 /* [HGM] This routine is called each time an assignment is made to
2311 * gameInfo.variant during a game, to make sure the board sizes
2312 * are set to match the new variant. If that means adding or deleting
2313 * holdings, we shift the playing board accordingly
2314 * This kludge is needed because in ICS observe mode, we get boards
2315 * of an ongoing game without knowing the variant, and learn about the
2316 * latter only later. This can be because of the move list we requested,
2317 * in which case the game history is refilled from the beginning anyway,
2318 * but also when receiving holdings of a crazyhouse game. In the latter
2319 * case we want to add those holdings to the already received position.
2323 if (appData.debugMode) {
2324 fprintf(debugFP, "Switch board from %s to %s\n",
2325 VariantName(gameInfo.variant), VariantName(newVariant));
2326 setbuf(debugFP, NULL);
2328 shuffleOpenings = 0; /* [HGM] shuffle */
2329 gameInfo.holdingsSize = 5; /* [HGM] prepare holdings */
2333 newWidth = 9; newHeight = 9;
2334 gameInfo.holdingsSize = 7;
2335 case VariantBughouse:
2336 case VariantCrazyhouse:
2337 newHoldingsWidth = 2; break;
2341 newHoldingsWidth = 2;
2342 gameInfo.holdingsSize = 8;
2345 case VariantCapablanca:
2346 case VariantCapaRandom:
2349 newHoldingsWidth = gameInfo.holdingsSize = 0;
2352 if(newWidth != gameInfo.boardWidth ||
2353 newHeight != gameInfo.boardHeight ||
2354 newHoldingsWidth != gameInfo.holdingsWidth ) {
2356 /* shift position to new playing area, if needed */
2357 if(newHoldingsWidth > gameInfo.holdingsWidth) {
2358 for(i=0; i<BOARD_HEIGHT; i++)
2359 for(j=BOARD_RGHT-1; j>=BOARD_LEFT; j--)
2360 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2362 for(i=0; i<newHeight; i++) {
2363 board[i][0] = board[i][newWidth+2*newHoldingsWidth-1] = EmptySquare;
2364 board[i][1] = board[i][newWidth+2*newHoldingsWidth-2] = (ChessSquare) 0;
2366 } else if(newHoldingsWidth < gameInfo.holdingsWidth) {
2367 for(i=0; i<BOARD_HEIGHT; i++)
2368 for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
2369 board[i][j+newHoldingsWidth-gameInfo.holdingsWidth] =
2372 gameInfo.boardWidth = newWidth;
2373 gameInfo.boardHeight = newHeight;
2374 gameInfo.holdingsWidth = newHoldingsWidth;
2375 gameInfo.variant = newVariant;
2376 InitDrawingSizes(-2, 0);
2377 } else gameInfo.variant = newVariant;
2378 CopyBoard(oldBoard, board); // remember correctly formatted board
2379 InitPosition(FALSE); /* this sets up board[0], but also other stuff */
2380 DrawPosition(TRUE, currentMove ? boards[currentMove] : oldBoard);
2383 static int loggedOn = FALSE;
2385 /*-- Game start info cache: --*/
2387 char gs_kind[MSG_SIZ];
2388 static char player1Name[128] = "";
2389 static char player2Name[128] = "";
2390 static char cont_seq[] = "\n\\ ";
2391 static int player1Rating = -1;
2392 static int player2Rating = -1;
2393 /*----------------------------*/
2395 ColorClass curColor = ColorNormal;
2396 int suppressKibitz = 0;
2399 Boolean soughtPending = FALSE;
2400 Boolean seekGraphUp;
2401 #define MAX_SEEK_ADS 200
2403 char *seekAdList[MAX_SEEK_ADS];
2404 int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
2405 float tcList[MAX_SEEK_ADS];
2406 char colorList[MAX_SEEK_ADS];
2407 int nrOfSeekAds = 0;
2408 int minRating = 1010, maxRating = 2800;
2409 int hMargin = 10, vMargin = 20, h, w;
2410 extern int squareSize, lineGap;
2415 int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
2416 xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
2417 if(r < minRating+100 && r >=0 ) r = minRating+100;
2418 if(r > maxRating) r = maxRating;
2419 if(tc < 1.) tc = 1.;
2420 if(tc > 95.) tc = 95.;
2421 x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
2422 y = ((double)r - minRating)/(maxRating - minRating)
2423 * (h-vMargin-squareSize/8-1) + vMargin;
2424 if(ratingList[i] < 0) y = vMargin + squareSize/4;
2425 if(strstr(seekAdList[i], " u ")) color = 1;
2426 if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
2427 !strstr(seekAdList[i], "bullet") &&
2428 !strstr(seekAdList[i], "blitz") &&
2429 !strstr(seekAdList[i], "standard") ) color = 2;
2430 if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
2431 DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
2435 AddAd(char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
2437 char buf[MSG_SIZ], *ext = "";
2438 VariantClass v = StringToVariant(type);
2439 if(strstr(type, "wild")) {
2440 ext = type + 4; // append wild number
2441 if(v == VariantFischeRandom) type = "chess960"; else
2442 if(v == VariantLoadable) type = "setup"; else
2443 type = VariantName(v);
2445 snprintf(buf, MSG_SIZ, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
2446 if(nrOfSeekAds < MAX_SEEK_ADS-1) {
2447 if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
2448 ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
2449 sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
2450 tcList[nrOfSeekAds] = base + (2./3.)*inc;
2451 seekNrList[nrOfSeekAds] = nr;
2452 zList[nrOfSeekAds] = 0;
2453 seekAdList[nrOfSeekAds++] = StrSave(buf);
2454 if(plot) PlotSeekAd(nrOfSeekAds-1);
2461 int x = xList[i], y = yList[i], d=squareSize/4, k;
2462 DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
2463 if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
2464 // now replot every dot that overlapped
2465 for(k=0; k<nrOfSeekAds; k++) if(k != i) {
2466 int xx = xList[k], yy = yList[k];
2467 if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
2468 DrawSeekDot(xx, yy, colorList[k]);
2473 RemoveSeekAd(int nr)
2476 for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
2478 if(seekAdList[i]) free(seekAdList[i]);
2479 seekAdList[i] = seekAdList[--nrOfSeekAds];
2480 seekNrList[i] = seekNrList[nrOfSeekAds];
2481 ratingList[i] = ratingList[nrOfSeekAds];
2482 colorList[i] = colorList[nrOfSeekAds];
2483 tcList[i] = tcList[nrOfSeekAds];
2484 xList[i] = xList[nrOfSeekAds];
2485 yList[i] = yList[nrOfSeekAds];
2486 zList[i] = zList[nrOfSeekAds];
2487 seekAdList[nrOfSeekAds] = NULL;
2493 MatchSoughtLine(char *line)
2495 char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
2496 int nr, base, inc, u=0; char dummy;
2498 if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2499 sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
2501 (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
2502 sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
2503 // match: compact and save the line
2504 AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
2514 if(!seekGraphUp) return FALSE;
2515 h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
2516 w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
2518 DrawSeekBackground(0, 0, w, h);
2519 DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
2520 DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
2521 for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
2522 int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
2524 DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
2527 snprintf(buf, MSG_SIZ, "%d", i);
2528 DrawSeekText(buf, hMargin+squareSize/8+7, yy);
2531 DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
2532 for(i=1; i<100; i+=(i<10?1:5)) {
2533 int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
2534 DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
2535 if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
2537 snprintf(buf, MSG_SIZ, "%d", i);
2538 DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
2541 for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
2545 int SeekGraphClick(ClickType click, int x, int y, int moving)
2547 static int lastDown = 0, displayed = 0, lastSecond;
2548 if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
2549 if(click == Release || moving) return FALSE;
2551 soughtPending = TRUE;
2552 SendToICS(ics_prefix);
2553 SendToICS("sought\n"); // should this be "sought all"?
2554 } else { // issue challenge based on clicked ad
2555 int dist = 10000; int i, closest = 0, second = 0;
2556 for(i=0; i<nrOfSeekAds; i++) {
2557 int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
2558 if(d < dist) { dist = d; closest = i; }
2559 second += (d - zList[i] < 120); // count in-range ads
2560 if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
2564 second = (second > 1);
2565 if(displayed != closest || second != lastSecond) {
2566 DisplayMessage(second ? "!" : "", seekAdList[closest]);
2567 lastSecond = second; displayed = closest;
2569 if(click == Press) {
2570 if(moving == 2) zList[closest] = 100; // right-click; push to back on press
2573 } // on press 'hit', only show info
2574 if(moving == 2) return TRUE; // ignore right up-clicks on dot
2575 snprintf(buf, MSG_SIZ, "play %d\n", seekNrList[closest]);
2576 SendToICS(ics_prefix);
2578 return TRUE; // let incoming board of started game pop down the graph
2579 } else if(click == Release) { // release 'miss' is ignored
2580 zList[lastDown] = 100; // make future selection of the rejected ad more difficult
2581 if(moving == 2) { // right up-click
2582 nrOfSeekAds = 0; // refresh graph
2583 soughtPending = TRUE;
2584 SendToICS(ics_prefix);
2585 SendToICS("sought\n"); // should this be "sought all"?
2588 } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
2589 // press miss or release hit 'pop down' seek graph
2590 seekGraphUp = FALSE;
2591 DrawPosition(TRUE, NULL);
2597 read_from_ics(isr, closure, data, count, error)
2604 #define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
2605 #define STARTED_NONE 0
2606 #define STARTED_MOVES 1
2607 #define STARTED_BOARD 2
2608 #define STARTED_OBSERVE 3
2609 #define STARTED_HOLDINGS 4
2610 #define STARTED_CHATTER 5
2611 #define STARTED_COMMENT 6
2612 #define STARTED_MOVES_NOHIDE 7
2614 static int started = STARTED_NONE;
2615 static char parse[20000];
2616 static int parse_pos = 0;
2617 static char buf[BUF_SIZE + 1];
2618 static int firstTime = TRUE, intfSet = FALSE;
2619 static ColorClass prevColor = ColorNormal;
2620 static int savingComment = FALSE;
2621 static int cmatch = 0; // continuation sequence match
2628 int backup; /* [DM] For zippy color lines */
2630 char talker[MSG_SIZ]; // [HGM] chat
2633 connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
2635 if (appData.debugMode) {
2637 fprintf(debugFP, "<ICS: ");
2638 show_bytes(debugFP, data, count);
2639 fprintf(debugFP, "\n");
2643 if (appData.debugMode) { int f = forwardMostMove;
2644 fprintf(debugFP, "ics input %d, castling = %d %d %d %d %d %d\n", f,
2645 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
2646 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
2649 /* If last read ended with a partial line that we couldn't parse,
2650 prepend it to the new read and try again. */
2651 if (leftover_len > 0) {
2652 for (i=0; i<leftover_len; i++)
2653 buf[i] = buf[leftover_start + i];
2656 /* copy new characters into the buffer */
2657 bp = buf + leftover_len;
2658 buf_len=leftover_len;
2659 for (i=0; i<count; i++)
2662 if (data[i] == '\r')
2665 // join lines split by ICS?
2666 if (!appData.noJoin)
2669 Joining just consists of finding matches against the
2670 continuation sequence, and discarding that sequence
2671 if found instead of copying it. So, until a match
2672 fails, there's nothing to do since it might be the
2673 complete sequence, and thus, something we don't want
2676 if (data[i] == cont_seq[cmatch])
2679 if (cmatch == strlen(cont_seq))
2681 cmatch = 0; // complete match. just reset the counter
2684 it's possible for the ICS to not include the space
2685 at the end of the last word, making our [correct]
2686 join operation fuse two separate words. the server
2687 does this when the space occurs at the width setting.
2689 if (!buf_len || buf[buf_len-1] != ' ')
2700 match failed, so we have to copy what matched before
2701 falling through and copying this character. In reality,
2702 this will only ever be just the newline character, but
2703 it doesn't hurt to be precise.
2705 strncpy(bp, cont_seq, cmatch);
2717 buf[buf_len] = NULLCHAR;
2718 // next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
2723 while (i < buf_len) {
2724 /* Deal with part of the TELNET option negotiation
2725 protocol. We refuse to do anything beyond the
2726 defaults, except that we allow the WILL ECHO option,
2727 which ICS uses to turn off password echoing when we are
2728 directly connected to it. We reject this option
2729 if localLineEditing mode is on (always on in xboard)
2730 and we are talking to port 23, which might be a real
2731 telnet server that will try to keep WILL ECHO on permanently.
2733 if (buf_len - i >= 3 && (unsigned char) buf[i] == TN_IAC) {
2734 static int remoteEchoOption = FALSE; /* telnet ECHO option */
2735 unsigned char option;
2737 switch ((unsigned char) buf[++i]) {
2739 if (appData.debugMode)
2740 fprintf(debugFP, "\n<WILL ");
2741 switch (option = (unsigned char) buf[++i]) {
2743 if (appData.debugMode)
2744 fprintf(debugFP, "ECHO ");
2745 /* Reply only if this is a change, according
2746 to the protocol rules. */
2747 if (remoteEchoOption) break;
2748 if (appData.localLineEditing &&
2749 atoi(appData.icsPort) == TN_PORT) {
2750 TelnetRequest(TN_DONT, TN_ECHO);
2753 TelnetRequest(TN_DO, TN_ECHO);
2754 remoteEchoOption = TRUE;
2758 if (appData.debugMode)
2759 fprintf(debugFP, "%d ", option);
2760 /* Whatever this is, we don't want it. */
2761 TelnetRequest(TN_DONT, option);
2766 if (appData.debugMode)
2767 fprintf(debugFP, "\n<WONT ");
2768 switch (option = (unsigned char) buf[++i]) {
2770 if (appData.debugMode)
2771 fprintf(debugFP, "ECHO ");
2772 /* Reply only if this is a change, according
2773 to the protocol rules. */
2774 if (!remoteEchoOption) break;
2776 TelnetRequest(TN_DONT, TN_ECHO);
2777 remoteEchoOption = FALSE;
2780 if (appData.debugMode)
2781 fprintf(debugFP, "%d ", (unsigned char) option);
2782 /* Whatever this is, it must already be turned
2783 off, because we never agree to turn on
2784 anything non-default, so according to the
2785 protocol rules, we don't reply. */
2790 if (appData.debugMode)
2791 fprintf(debugFP, "\n<DO ");
2792 switch (option = (unsigned char) buf[++i]) {
2794 /* Whatever this is, we refuse to do it. */
2795 if (appData.debugMode)
2796 fprintf(debugFP, "%d ", option);
2797 TelnetRequest(TN_WONT, option);
2802 if (appData.debugMode)
2803 fprintf(debugFP, "\n<DONT ");
2804 switch (option = (unsigned char) buf[++i]) {
2806 if (appData.debugMode)
2807 fprintf(debugFP, "%d ", option);
2808 /* Whatever this is, we are already not doing
2809 it, because we never agree to do anything
2810 non-default, so according to the protocol
2811 rules, we don't reply. */
2816 if (appData.debugMode)
2817 fprintf(debugFP, "\n<IAC ");
2818 /* Doubled IAC; pass it through */
2822 if (appData.debugMode)
2823 fprintf(debugFP, "\n<%d ", (unsigned char) buf[i]);
2824 /* Drop all other telnet commands on the floor */
2827 if (oldi > next_out)
2828 SendToPlayer(&buf[next_out], oldi - next_out);
2834 /* OK, this at least will *usually* work */
2835 if (!loggedOn && looking_at(buf, &i, "ics%")) {
2839 if (loggedOn && !intfSet) {
2840 if (ics_type == ICS_ICC) {
2841 snprintf(str, MSG_SIZ,
2842 "/set-quietly interface %s\n/set-quietly style 12\n",
2844 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2845 strcat(str, "/set-2 51 1\n/set seek 1\n");
2846 } else if (ics_type == ICS_CHESSNET) {
2847 snprintf(str, MSG_SIZ, "/style 12\n");
2849 safeStrCpy(str, "alias $ @\n$set interface ", sizeof(str)/sizeof(str[0]));
2850 strcat(str, programVersion);
2851 strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
2852 if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
2853 strcat(str, "$iset seekremove 1\n$set seek 1\n");
2855 strcat(str, "$iset nohighlight 1\n");
2857 strcat(str, "$iset lock 1\n$style 12\n");
2860 NotifyFrontendLogin();
2864 if (started == STARTED_COMMENT) {
2865 /* Accumulate characters in comment */
2866 parse[parse_pos++] = buf[i];
2867 if (buf[i] == '\n') {
2868 parse[parse_pos] = NULLCHAR;
2869 if(chattingPartner>=0) {
2871 snprintf(mess, MSG_SIZ, "%s%s", talker, parse);
2872 OutputChatMessage(chattingPartner, mess);
2873 chattingPartner = -1;
2874 next_out = i+1; // [HGM] suppress printing in ICS window
2876 if(!suppressKibitz) // [HGM] kibitz
2877 AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
2878 else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
2879 int nrDigit = 0, nrAlph = 0, j;
2880 if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
2881 { parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
2882 parse[parse_pos] = NULLCHAR;
2883 // try to be smart: if it does not look like search info, it should go to
2884 // ICS interaction window after all, not to engine-output window.
2885 for(j=0; j<parse_pos; j++) { // count letters and digits
2886 nrDigit += (parse[j] >= '0' && parse[j] <= '9');
2887 nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
2888 nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
2890 if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
2891 int depth=0; float score;
2892 if(sscanf(parse, "!!! %f/%d", &score, &depth) == 2 && depth>0) {
2893 // [HGM] kibitz: save kibitzed opponent info for PGN and eval graph
2894 pvInfoList[forwardMostMove-1].depth = depth;
2895 pvInfoList[forwardMostMove-1].score = 100*score;
2897 OutputKibitz(suppressKibitz, parse);
2900 snprintf(tmp, MSG_SIZ, _("your opponent kibitzes: %s"), parse);
2901 SendToPlayer(tmp, strlen(tmp));
2903 next_out = i+1; // [HGM] suppress printing in ICS window
2905 started = STARTED_NONE;
2907 /* Don't match patterns against characters in comment */
2912 if (started == STARTED_CHATTER) {
2913 if (buf[i] != '\n') {
2914 /* Don't match patterns against characters in chatter */
2918 started = STARTED_NONE;
2919 if(suppressKibitz) next_out = i+1;
2922 /* Kludge to deal with rcmd protocol */
2923 if (firstTime && looking_at(buf, &i, "\001*")) {
2924 DisplayFatalError(&buf[1], 0, 1);
2930 if (!loggedOn && looking_at(buf, &i, "chessclub.com")) {
2933 if (appData.debugMode)
2934 fprintf(debugFP, "ics_type %d\n", ics_type);
2937 if (!loggedOn && looking_at(buf, &i, "freechess.org")) {
2938 ics_type = ICS_FICS;
2940 if (appData.debugMode)
2941 fprintf(debugFP, "ics_type %d\n", ics_type);
2944 if (!loggedOn && looking_at(buf, &i, "chess.net")) {
2945 ics_type = ICS_CHESSNET;
2947 if (appData.debugMode)
2948 fprintf(debugFP, "ics_type %d\n", ics_type);
2953 (looking_at(buf, &i, "\"*\" is *a registered name") ||
2954 looking_at(buf, &i, "Logging you in as \"*\"") ||
2955 looking_at(buf, &i, "will be \"*\""))) {
2956 safeStrCpy(ics_handle, star_match[0], sizeof(ics_handle)/sizeof(ics_handle[0]));
2960 if (loggedOn && !have_set_title && ics_handle[0] != NULLCHAR) {
2962 snprintf(buf, sizeof(buf), "%s@%s", ics_handle, appData.icsHost);
2963 DisplayIcsInteractionTitle(buf);
2964 have_set_title = TRUE;
2967 /* skip finger notes */
2968 if (started == STARTED_NONE &&
2969 ((buf[i] == ' ' && isdigit(buf[i+1])) ||
2970 (buf[i] == '1' && buf[i+1] == '0')) &&
2971 buf[i+2] == ':' && buf[i+3] == ' ') {
2972 started = STARTED_CHATTER;
2978 // [HGM] seekgraph: recognize sought lines and end-of-sought message
2979 if(appData.seekGraph) {
2980 if(soughtPending && MatchSoughtLine(buf+i)) {
2981 i = strstr(buf+i, "rated") - buf;
2982 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
2983 next_out = leftover_start = i;
2984 started = STARTED_CHATTER;
2985 suppressKibitz = TRUE;
2988 if((gameMode == IcsIdle || gameMode == BeginningOfGame)
2989 && looking_at(buf, &i, "* ads displayed")) {
2990 soughtPending = FALSE;
2995 if(appData.autoRefresh) {
2996 if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
2997 int s = (ics_type == ICS_ICC); // ICC format differs
2999 AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
3000 star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
3001 looking_at(buf, &i, "*% "); // eat prompt
3002 if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
3003 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3004 next_out = i; // suppress
3007 if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
3008 char *p = star_match[0];
3010 if(seekGraphUp) RemoveSeekAd(atoi(p));
3011 while(*p && *p++ != ' '); // next
3013 looking_at(buf, &i, "*% "); // eat prompt
3014 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3021 /* skip formula vars */
3022 if (started == STARTED_NONE &&
3023 buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
3024 started = STARTED_CHATTER;
3029 // [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
3030 if (appData.autoKibitz && started == STARTED_NONE &&
3031 !appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
3032 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
3033 if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
3034 (StrStr(star_match[0], gameInfo.white) == star_match[0] ||
3035 StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
3036 suppressKibitz = TRUE;
3037 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3039 if((StrStr(star_match[0], gameInfo.white) == star_match[0]
3040 && (gameMode == IcsPlayingWhite)) ||
3041 (StrStr(star_match[0], gameInfo.black) == star_match[0]
3042 && (gameMode == IcsPlayingBlack)) ) // opponent kibitz
3043 started = STARTED_CHATTER; // own kibitz we simply discard
3045 started = STARTED_COMMENT; // make sure it will be collected in parse[]
3046 parse_pos = 0; parse[0] = NULLCHAR;
3047 savingComment = TRUE;
3048 suppressKibitz = gameMode != IcsObserving ? 2 :
3049 (StrStr(star_match[0], gameInfo.white) == NULL) + 1;
3053 if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
3054 looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
3055 && atoi(star_match[0])) {
3056 // suppress the acknowledgements of our own autoKibitz
3058 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3059 if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
3060 SendToPlayer(star_match[0], strlen(star_match[0]));
3061 if(looking_at(buf, &i, "*% ")) // eat prompt
3062 suppressKibitz = FALSE;
3066 } // [HGM] kibitz: end of patch
3068 // [HGM] chat: intercept tells by users for which we have an open chat window
3070 if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
3071 looking_at(buf, &i, "* whispers:") ||
3072 looking_at(buf, &i, "* kibitzes:") ||
3073 looking_at(buf, &i, "* shouts:") ||
3074 looking_at(buf, &i, "* c-shouts:") ||
3075 looking_at(buf, &i, "--> * ") ||
3076 looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
3077 looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
3078 looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
3079 looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
3081 sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
3082 chattingPartner = -1;
3084 if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
3085 for(p=0; p<MAX_CHAT; p++) {
3086 if(chatPartner[p][0] >= '0' && chatPartner[p][0] <= '9' && channel == atoi(chatPartner[p])) {
3087 talker[0] = '['; strcat(talker, "] ");
3088 Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
3089 chattingPartner = p; break;
3092 if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
3093 for(p=0; p<MAX_CHAT; p++) {
3094 if(!strcmp("kibitzes", chatPartner[p])) {
3095 talker[0] = '['; strcat(talker, "] ");
3096 chattingPartner = p; break;
3099 if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
3100 for(p=0; p<MAX_CHAT; p++) {
3101 if(!strcmp("whispers", chatPartner[p])) {
3102 talker[0] = '['; strcat(talker, "] ");
3103 chattingPartner = p; break;
3106 if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
3107 if(buf[i-8] == '-' && buf[i-3] == 't')
3108 for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
3109 if(!strcmp("c-shouts", chatPartner[p])) {
3110 talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
3111 chattingPartner = p; break;
3114 if(chattingPartner < 0)
3115 for(p=0; p<MAX_CHAT; p++) {
3116 if(!strcmp("shouts", chatPartner[p])) {
3117 if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
3118 else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
3119 else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
3120 chattingPartner = p; break;
3124 if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
3125 for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
3126 talker[0] = 0; Colorize(ColorTell, FALSE);
3127 chattingPartner = p; break;
3129 if(chattingPartner<0) i = oldi; else {
3130 Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
3131 if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
3132 if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
3133 started = STARTED_COMMENT;
3134 parse_pos = 0; parse[0] = NULLCHAR;
3135 savingComment = 3 + chattingPartner; // counts as TRUE
3136 suppressKibitz = TRUE;
3139 } // [HGM] chat: end of patch
3142 if (appData.zippyTalk || appData.zippyPlay) {
3143 /* [DM] Backup address for color zippy lines */
3145 if (loggedOn == TRUE)
3146 if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
3147 (appData.zippyPlay && ZippyMatch(buf, &backup)));
3149 } // [DM] 'else { ' deleted
3151 /* Regular tells and says */
3152 (tkind = 1, looking_at(buf, &i, "* tells you: ")) ||
3153 looking_at(buf, &i, "* (your partner) tells you: ") ||
3154 looking_at(buf, &i, "* says: ") ||
3155 /* Don't color "message" or "messages" output */
3156 (tkind = 5, looking_at(buf, &i, "*. * (*:*): ")) ||
3157 looking_at(buf, &i, "*. * at *:*: ") ||
3158 looking_at(buf, &i, "--* (*:*): ") ||
3159 /* Message notifications (same color as tells) */
3160 looking_at(buf, &i, "* has left a message ") ||
3161 looking_at(buf, &i, "* just sent you a message:\n") ||
3162 /* Whispers and kibitzes */
3163 (tkind = 2, looking_at(buf, &i, "* whispers: ")) ||
3164 looking_at(buf, &i, "* kibitzes: ") ||
3166 (tkind = 3, looking_at(buf, &i, "*(*: "))) {
3168 if (tkind == 1 && strchr(star_match[0], ':')) {
3169 /* Avoid "tells you:" spoofs in channels */
3172 if (star_match[0][0] == NULLCHAR ||
3173 strchr(star_match[0], ' ') ||
3174 (tkind == 3 && strchr(star_match[1], ' '))) {
3175 /* Reject bogus matches */
3178 if (appData.colorize) {
3179 if (oldi > next_out) {
3180 SendToPlayer(&buf[next_out], oldi - next_out);
3185 Colorize(ColorTell, FALSE);
3186 curColor = ColorTell;
3189 Colorize(ColorKibitz, FALSE);
3190 curColor = ColorKibitz;
3193 p = strrchr(star_match[1], '(');
3200 Colorize(ColorChannel1, FALSE);
3201 curColor = ColorChannel1;
3203 Colorize(ColorChannel, FALSE);
3204 curColor = ColorChannel;
3208 curColor = ColorNormal;
3212 if (started == STARTED_NONE && appData.autoComment &&
3213 (gameMode == IcsObserving ||
3214 gameMode == IcsPlayingWhite ||
3215 gameMode == IcsPlayingBlack)) {
3216 parse_pos = i - oldi;
3217 memcpy(parse, &buf[oldi], parse_pos);
3218 parse[parse_pos] = NULLCHAR;
3219 started = STARTED_COMMENT;
3220 savingComment = TRUE;
3222 started = STARTED_CHATTER;
3223 savingComment = FALSE;
3230 if (looking_at(buf, &i, "* s-shouts: ") ||
3231 looking_at(buf, &i, "* c-shouts: ")) {
3232 if (appData.colorize) {
3233 if (oldi > next_out) {
3234 SendToPlayer(&buf[next_out], oldi - next_out);
3237 Colorize(ColorSShout, FALSE);
3238 curColor = ColorSShout;
3241 started = STARTED_CHATTER;
3245 if (looking_at(buf, &i, "--->")) {
3250 if (looking_at(buf, &i, "* shouts: ") ||
3251 looking_at(buf, &i, "--> ")) {
3252 if (appData.colorize) {
3253 if (oldi > next_out) {
3254 SendToPlayer(&buf[next_out], oldi - next_out);
3257 Colorize(ColorShout, FALSE);
3258 curColor = ColorShout;
3261 started = STARTED_CHATTER;
3265 if (looking_at( buf, &i, "Challenge:")) {
3266 if (appData.colorize) {
3267 if (oldi > next_out) {
3268 SendToPlayer(&buf[next_out], oldi - next_out);
3271 Colorize(ColorChallenge, FALSE);
3272 curColor = ColorChallenge;
3278 if (looking_at(buf, &i, "* offers you") ||
3279 looking_at(buf, &i, "* offers to be") ||
3280 looking_at(buf, &i, "* would like to") ||
3281 looking_at(buf, &i, "* requests to") ||
3282 looking_at(buf, &i, "Your opponent offers") ||
3283 looking_at(buf, &i, "Your opponent requests")) {
3285 if (appData.colorize) {
3286 if (oldi > next_out) {
3287 SendToPlayer(&buf[next_out], oldi - next_out);
3290 Colorize(ColorRequest, FALSE);
3291 curColor = ColorRequest;
3296 if (looking_at(buf, &i, "* (*) seeking")) {
3297 if (appData.colorize) {
3298 if (oldi > next_out) {
3299 SendToPlayer(&buf[next_out], oldi - next_out);
3302 Colorize(ColorSeek, FALSE);
3303 curColor = ColorSeek;
3308 if(i < backup) { i = backup; continue; } // [HGM] for if ZippyControl matches, but the colorie code doesn't
3310 if (looking_at(buf, &i, "\\ ")) {
3311 if (prevColor != ColorNormal) {
3312 if (oldi > next_out) {
3313 SendToPlayer(&buf[next_out], oldi - next_out);
3316 Colorize(prevColor, TRUE);
3317 curColor = prevColor;
3319 if (savingComment) {
3320 parse_pos = i - oldi;
3321 memcpy(parse, &buf[oldi], parse_pos);
3322 parse[parse_pos] = NULLCHAR;
3323 started = STARTED_COMMENT;
3324 if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
3325 chattingPartner = savingComment - 3; // kludge to remember the box
3327 started = STARTED_CHATTER;
3332 if (looking_at(buf, &i, "Black Strength :") ||
3333 looking_at(buf, &i, "<<< style 10 board >>>") ||
3334 looking_at(buf, &i, "<10>") ||
3335 looking_at(buf, &i, "#@#")) {
3336 /* Wrong board style */
3338 SendToICS(ics_prefix);
3339 SendToICS("set style 12\n");
3340 SendToICS(ics_prefix);
3341 SendToICS("refresh\n");
3345 if (!have_sent_ICS_logon && looking_at(buf, &i, "login:")) {
3347 have_sent_ICS_logon = 1;
3351 if (ics_getting_history != H_GETTING_MOVES /*smpos kludge*/ &&
3352 (looking_at(buf, &i, "\n<12> ") ||
3353 looking_at(buf, &i, "<12> "))) {
3355 if (oldi > next_out) {
3356 SendToPlayer(&buf[next_out], oldi - next_out);
3359 started = STARTED_BOARD;
3364 if ((started == STARTED_NONE && looking_at(buf, &i, "\n<b1> ")) ||
3365 looking_at(buf, &i, "<b1> ")) {
3366 if (oldi > next_out) {
3367 SendToPlayer(&buf[next_out], oldi - next_out);
3370 started = STARTED_HOLDINGS;
3375 if (looking_at(buf, &i, "* *vs. * *--- *")) {
3377 /* Header for a move list -- first line */
3379 switch (ics_getting_history) {
3383 case BeginningOfGame:
3384 /* User typed "moves" or "oldmoves" while we
3385 were idle. Pretend we asked for these
3386 moves and soak them up so user can step
3387 through them and/or save them.
3390 gameMode = IcsObserving;
3393 ics_getting_history = H_GOT_UNREQ_HEADER;
3395 case EditGame: /*?*/
3396 case EditPosition: /*?*/
3397 /* Should above feature work in these modes too? */
3398 /* For now it doesn't */
3399 ics_getting_history = H_GOT_UNWANTED_HEADER;
3402 ics_getting_history = H_GOT_UNWANTED_HEADER;
3407 /* Is this the right one? */
3408 if (gameInfo.white && gameInfo.black &&
3409 strcmp(gameInfo.white, star_match[0]) == 0 &&
3410 strcmp(gameInfo.black, star_match[2]) == 0) {
3412 ics_getting_history = H_GOT_REQ_HEADER;
3415 case H_GOT_REQ_HEADER:
3416 case H_GOT_UNREQ_HEADER:
3417 case H_GOT_UNWANTED_HEADER:
3418 case H_GETTING_MOVES:
3419 /* Should not happen */
3420 DisplayError(_("Error gathering move list: two headers"), 0);
3421 ics_getting_history = H_FALSE;
3425 /* Save player ratings into gameInfo if needed */
3426 if ((ics_getting_history == H_GOT_REQ_HEADER ||
3427 ics_getting_history == H_GOT_UNREQ_HEADER) &&
3428 (gameInfo.whiteRating == -1 ||
3429 gameInfo.blackRating == -1)) {
3431 gameInfo.whiteRating = string_to_rating(star_match[1]);
3432 gameInfo.blackRating = string_to_rating(star_match[3]);
3433 if (appData.debugMode)
3434 fprintf(debugFP, _("Ratings from header: W %d, B %d\n"),
3435 gameInfo.whiteRating, gameInfo.blackRating);
3440 if (looking_at(buf, &i,
3441 "* * match, initial time: * minute*, increment: * second")) {
3442 /* Header for a move list -- second line */
3443 /* Initial board will follow if this is a wild game */
3444 if (gameInfo.event != NULL) free(gameInfo.event);
3445 snprintf(str, MSG_SIZ, "ICS %s %s match", star_match[0], star_match[1]);
3446 gameInfo.event = StrSave(str);
3447 /* [HGM] we switched variant. Translate boards if needed. */
3448 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event));
3452 if (looking_at(buf, &i, "Move ")) {
3453 /* Beginning of a move list */
3454 switch (ics_getting_history) {
3456 /* Normally should not happen */
3457 /* Maybe user hit reset while we were parsing */
3460 /* Happens if we are ignoring a move list that is not
3461 * the one we just requested. Common if the user
3462 * tries to observe two games without turning off
3465 case H_GETTING_MOVES:
3466 /* Should not happen */
3467 DisplayError(_("Error gathering move list: nested"), 0);
3468 ics_getting_history = H_FALSE;
3470 case H_GOT_REQ_HEADER:
3471 ics_getting_history = H_GETTING_MOVES;
3472 started = STARTED_MOVES;
3474 if (oldi > next_out) {
3475 SendToPlayer(&buf[next_out], oldi - next_out);
3478 case H_GOT_UNREQ_HEADER:
3479 ics_getting_history = H_GETTING_MOVES;
3480 started = STARTED_MOVES_NOHIDE;
3483 case H_GOT_UNWANTED_HEADER:
3484 ics_getting_history = H_FALSE;
3490 if (looking_at(buf, &i, "% ") ||
3491 ((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
3492 && looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
3493 if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
3494 soughtPending = FALSE;
3498 if(suppressKibitz) next_out = i;
3499 savingComment = FALSE;
3503 case STARTED_MOVES_NOHIDE:
3504 memcpy(&parse[parse_pos], &buf[oldi], i - oldi);
3505 parse[parse_pos + i - oldi] = NULLCHAR;
3506 ParseGameHistory(parse);
3508 if (appData.zippyPlay && first.initDone) {
3509 FeedMovesToProgram(&first, forwardMostMove);
3510 if (gameMode == IcsPlayingWhite) {
3511 if (WhiteOnMove(forwardMostMove)) {
3512 if (first.sendTime) {
3513 if (first.useColors) {
3514 SendToProgram("black\n", &first);
3516 SendTimeRemaining(&first, TRUE);
3518 if (first.useColors) {
3519 SendToProgram("white\n", &first); // [HGM] book: made sending of "go\n" book dependent
3521 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: probe book for initial pos
3522 first.maybeThinking = TRUE;
3524 if (first.usePlayother) {
3525 if (first.sendTime) {
3526 SendTimeRemaining(&first, TRUE);
3528 SendToProgram("playother\n", &first);
3534 } else if (gameMode == IcsPlayingBlack) {
3535 if (!WhiteOnMove(forwardMostMove)) {
3536 if (first.sendTime) {
3537 if (first.useColors) {
3538 SendToProgram("white\n", &first);
3540 SendTimeRemaining(&first, FALSE);
3542 if (first.useColors) {
3543 SendToProgram("black\n", &first);
3545 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE);
3546 first.maybeThinking = TRUE;
3548 if (first.usePlayother) {
3549 if (first.sendTime) {
3550 SendTimeRemaining(&first, FALSE);
3552 SendToProgram("playother\n", &first);
3561 if (gameMode == IcsObserving && ics_gamenum == -1) {
3562 /* Moves came from oldmoves or moves command
3563 while we weren't doing anything else.
3565 currentMove = forwardMostMove;
3566 ClearHighlights();/*!!could figure this out*/
3567 flipView = appData.flipView;
3568 DrawPosition(TRUE, boards[currentMove]);
3569 DisplayBothClocks();
3570 snprintf(str, MSG_SIZ, "%s vs. %s",
3571 gameInfo.white, gameInfo.black);
3575 /* Moves were history of an active game */
3576 if (gameInfo.resultDetails != NULL) {
3577 free(gameInfo.resultDetails);
3578 gameInfo.resultDetails = NULL;
3581 HistorySet(parseList, backwardMostMove,
3582 forwardMostMove, currentMove-1);
3583 DisplayMove(currentMove - 1);
3584 if (started == STARTED_MOVES) next_out = i;
3585 started = STARTED_NONE;
3586 ics_getting_history = H_FALSE;
3589 case STARTED_OBSERVE:
3590 started = STARTED_NONE;
3591 SendToICS(ics_prefix);
3592 SendToICS("refresh\n");
3598 if(bookHit) { // [HGM] book: simulate book reply
3599 static char bookMove[MSG_SIZ]; // a bit generous?
3601 programStats.nodes = programStats.depth = programStats.time =
3602 programStats.score = programStats.got_only_move = 0;
3603 sprintf(programStats.movelist, "%s (xbook)", bookHit);
3605 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
3606 strcat(bookMove, bookHit);
3607 HandleMachineMove(bookMove, &first);
3612 if ((started == STARTED_MOVES || started == STARTED_BOARD ||
3613 started == STARTED_HOLDINGS ||
3614 started == STARTED_MOVES_NOHIDE) && i >= leftover_len) {
3615 /* Accumulate characters in move list or board */
3616 parse[parse_pos++] = buf[i];
3619 /* Start of game messages. Mostly we detect start of game
3620 when the first board image arrives. On some versions
3621 of the ICS, though, we need to do a "refresh" after starting
3622 to observe in order to get the current board right away. */
3623 if (looking_at(buf, &i, "Adding game * to observation list")) {
3624 started = STARTED_OBSERVE;
3628 /* Handle auto-observe */
3629 if (appData.autoObserve &&
3630 (gameMode == IcsIdle || gameMode == BeginningOfGame) &&
3631 looking_at(buf, &i, "Game notification: * (*) vs. * (*)")) {
3633 /* Choose the player that was highlighted, if any. */
3634 if (star_match[0][0] == '\033' ||
3635 star_match[1][0] != '\033') {
3636 player = star_match[0];
3638 player = star_match[2];
3640 snprintf(str, MSG_SIZ, "%sobserve %s\n",
3641 ics_prefix, StripHighlightAndTitle(player));
3644 /* Save ratings from notify string */
3645 safeStrCpy(player1Name, star_match[0], sizeof(player1Name)/sizeof(player1Name[0]));
3646 player1Rating = string_to_rating(star_match[1]);
3647 safeStrCpy(player2Name, star_match[2], sizeof(player2Name)/sizeof(player2Name[0]));
3648 player2Rating = string_to_rating(star_match[3]);
3650 if (appData.debugMode)
3652 "Ratings from 'Game notification:' %s %d, %s %d\n",
3653 player1Name, player1Rating,
3654 player2Name, player2Rating);
3659 /* Deal with automatic examine mode after a game,
3660 and with IcsObserving -> IcsExamining transition */
3661 if (looking_at(buf, &i, "Entering examine mode for game *") ||
3662 looking_at(buf, &i, "has made you an examiner of game *")) {
3664 int gamenum = atoi(star_match[0]);
3665 if ((gameMode == IcsIdle || gameMode == IcsObserving) &&
3666 gamenum == ics_gamenum) {
3667 /* We were already playing or observing this game;
3668 no need to refetch history */
3669 gameMode = IcsExamining;
3671 pauseExamForwardMostMove = forwardMostMove;
3672 } else if (currentMove < forwardMostMove) {
3673 ForwardInner(forwardMostMove);
3676 /* I don't think this case really can happen */
3677 SendToICS(ics_prefix);
3678 SendToICS("refresh\n");
3683 /* Error messages */
3684 // if (ics_user_moved) {
3685 if (1) { // [HGM] old way ignored error after move type in; ics_user_moved is not set then!
3686 if (looking_at(buf, &i, "Illegal move") ||
3687 looking_at(buf, &i, "Not a legal move") ||
3688 looking_at(buf, &i, "Your king is in check") ||
3689 looking_at(buf, &i, "It isn't your turn") ||
3690 looking_at(buf, &i, "It is not your move")) {
3692 if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
3693 currentMove = forwardMostMove-1;
3694 DisplayMove(currentMove - 1); /* before DMError */
3695 DrawPosition(FALSE, boards[currentMove]);
3696 SwitchClocks(forwardMostMove-1); // [HGM] race
3697 DisplayBothClocks();
3699 DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
3705 if (looking_at(buf, &i, "still have time") ||
3706 looking_at(buf, &i, "not out of time") ||
3707 looking_at(buf, &i, "either player is out of time") ||
3708 looking_at(buf, &i, "has timeseal; checking")) {
3709 /* We must have called his flag a little too soon */
3710 whiteFlag = blackFlag = FALSE;
3714 if (looking_at(buf, &i, "added * seconds to") ||
3715 looking_at(buf, &i, "seconds were added to")) {
3716 /* Update the clocks */
3717 SendToICS(ics_prefix);
3718 SendToICS("refresh\n");
3722 if (!ics_clock_paused && looking_at(buf, &i, "clock paused")) {
3723 ics_clock_paused = TRUE;
3728 if (ics_clock_paused && looking_at(buf, &i, "clock resumed")) {
3729 ics_clock_paused = FALSE;
3734 /* Grab player ratings from the Creating: message.
3735 Note we have to check for the special case when
3736 the ICS inserts things like [white] or [black]. */
3737 if (looking_at(buf, &i, "Creating: * (*)* * (*)") ||
3738 looking_at(buf, &i, "Creating: * (*) [*] * (*)")) {
3740 0 player 1 name (not necessarily white)
3742 2 empty, white, or black (IGNORED)
3743 3 player 2 name (not necessarily black)
3746 The names/ratings are sorted out when the game
3747 actually starts (below).
3749 safeStrCpy(player1Name, StripHighlightAndTitle(star_match[0]), sizeof(player1Name)/sizeof(player1Name[0]));
3750 player1Rating = string_to_rating(star_match[1]);
3751 safeStrCpy(player2Name, StripHighlightAndTitle(star_match[3]), sizeof(player2Name)/sizeof(player2Name[0]));
3752 player2Rating = string_to_rating(star_match[4]);
3754 if (appData.debugMode)
3756 "Ratings from 'Creating:' %s %d, %s %d\n",
3757 player1Name, player1Rating,
3758 player2Name, player2Rating);
3763 /* Improved generic start/end-of-game messages */
3764 if ((tkind=0, looking_at(buf, &i, "{Game * (* vs. *) *}*")) ||
3765 (tkind=1, looking_at(buf, &i, "{Game * (*(*) vs. *(*)) *}*"))){
3766 /* If tkind == 0: */
3767 /* star_match[0] is the game number */
3768 /* [1] is the white player's name */
3769 /* [2] is the black player's name */
3770 /* For end-of-game: */
3771 /* [3] is the reason for the game end */
3772 /* [4] is a PGN end game-token, preceded by " " */
3773 /* For start-of-game: */
3774 /* [3] begins with "Creating" or "Continuing" */
3775 /* [4] is " *" or empty (don't care). */
3776 int gamenum = atoi(star_match[0]);
3777 char *whitename, *blackname, *why, *endtoken;
3778 ChessMove endtype = EndOfFile;
3781 whitename = star_match[1];
3782 blackname = star_match[2];
3783 why = star_match[3];
3784 endtoken = star_match[4];
3786 whitename = star_match[1];
3787 blackname = star_match[3];
3788 why = star_match[5];
3789 endtoken = star_match[6];
3792 /* Game start messages */
3793 if (strncmp(why, "Creating ", 9) == 0 ||
3794 strncmp(why, "Continuing ", 11) == 0) {
3795 gs_gamenum = gamenum;
3796 safeStrCpy(gs_kind, strchr(why, ' ') + 1,sizeof(gs_kind)/sizeof(gs_kind[0]));
3797 VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
3799 if (appData.zippyPlay) {
3800 ZippyGameStart(whitename, blackname);
3803 partnerBoardValid = FALSE; // [HGM] bughouse
3807 /* Game end messages */
3808 if (gameMode == IcsIdle || gameMode == BeginningOfGame ||
3809 ics_gamenum != gamenum) {
3812 while (endtoken[0] == ' ') endtoken++;
3813 switch (endtoken[0]) {
3816 endtype = GameUnfinished;
3819 endtype = BlackWins;
3822 if (endtoken[1] == '/')
3823 endtype = GameIsDrawn;
3825 endtype = WhiteWins;
3828 GameEnds(endtype, why, GE_ICS);
3830 if (appData.zippyPlay && first.initDone) {
3831 ZippyGameEnd(endtype, why);
3832 if (first.pr == NULL) {
3833 /* Start the next process early so that we'll
3834 be ready for the next challenge */
3835 StartChessProgram(&first);
3837 /* Send "new" early, in case this command takes
3838 a long time to finish, so that we'll be ready
3839 for the next challenge. */
3840 gameInfo.variant = VariantNormal; // [HGM] variantswitch: suppress sending of 'variant'
3844 if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
3848 if (looking_at(buf, &i, "Removing game * from observation") ||
3849 looking_at(buf, &i, "no longer observing game *") ||
3850 looking_at(buf, &i, "Game * (*) has no examiners")) {
3851 if (gameMode == IcsObserving &&
3852 atoi(star_match[0]) == ics_gamenum)
3854 /* icsEngineAnalyze */
3855 if (appData.icsEngineAnalyze) {
3862 ics_user_moved = FALSE;
3867 if (looking_at(buf, &i, "no longer examining game *")) {
3868 if (gameMode == IcsExamining &&
3869 atoi(star_match[0]) == ics_gamenum)
3873 ics_user_moved = FALSE;
3878 /* Advance leftover_start past any newlines we find,
3879 so only partial lines can get reparsed */
3880 if (looking_at(buf, &i, "\n")) {
3881 prevColor = curColor;
3882 if (curColor != ColorNormal) {
3883 if (oldi > next_out) {
3884 SendToPlayer(&buf[next_out], oldi - next_out);
3887 Colorize(ColorNormal, FALSE);
3888 curColor = ColorNormal;
3890 if (started == STARTED_BOARD) {
3891 started = STARTED_NONE;
3892 parse[parse_pos] = NULLCHAR;
3893 ParseBoard12(parse);
3896 /* Send premove here */
3897 if (appData.premove) {
3899 if (currentMove == 0 &&
3900 gameMode == IcsPlayingWhite &&
3901 appData.premoveWhite) {
3902 snprintf(str, MSG_SIZ, "%s\n", appData.premoveWhiteText);
3903 if (appData.debugMode)
3904 fprintf(debugFP, "Sending premove:\n");
3906 } else if (currentMove == 1 &&
3907 gameMode == IcsPlayingBlack &&
3908 appData.premoveBlack) {
3909 snprintf(str, MSG_SIZ, "%s\n", appData.premoveBlackText);
3910 if (appData.debugMode)
3911 fprintf(debugFP, "Sending premove:\n");
3913 } else if (gotPremove) {
3915 ClearPremoveHighlights();
3916 if (appData.debugMode)
3917 fprintf(debugFP, "Sending premove:\n");
3918 UserMoveEvent(premoveFromX, premoveFromY,
3919 premoveToX, premoveToY,
3924 /* Usually suppress following prompt */
3925 if (!(forwardMostMove == 0 && gameMode == IcsExamining)) {
3926 while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
3927 if (looking_at(buf, &i, "*% ")) {
3928 savingComment = FALSE;
3933 } else if (started == STARTED_HOLDINGS) {
3935 char new_piece[MSG_SIZ];
3936 started = STARTED_NONE;
3937 parse[parse_pos] = NULLCHAR;
3938 if (appData.debugMode)
3939 fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
3940 parse, currentMove);
3941 if (sscanf(parse, " game %d", &gamenum) == 1) {
3942 if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
3943 if (gameInfo.variant == VariantNormal) {
3944 /* [HGM] We seem to switch variant during a game!
3945 * Presumably no holdings were displayed, so we have
3946 * to move the position two files to the right to
3947 * create room for them!
3949 VariantClass newVariant;
3950 switch(gameInfo.boardWidth) { // base guess on board width
3951 case 9: newVariant = VariantShogi; break;
3952 case 10: newVariant = VariantGreat; break;
3953 default: newVariant = VariantCrazyhouse; break;
3955 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
3956 /* Get a move list just to see the header, which
3957 will tell us whether this is really bug or zh */
3958 if (ics_getting_history == H_FALSE) {
3959 ics_getting_history = H_REQUESTED;
3960 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
3964 new_piece[0] = NULLCHAR;
3965 sscanf(parse, "game %d white [%s black [%s <- %s",
3966 &gamenum, white_holding, black_holding,
3968 white_holding[strlen(white_holding)-1] = NULLCHAR;
3969 black_holding[strlen(black_holding)-1] = NULLCHAR;
3970 /* [HGM] copy holdings to board holdings area */
3971 CopyHoldings(boards[forwardMostMove], white_holding, WhitePawn);
3972 CopyHoldings(boards[forwardMostMove], black_holding, BlackPawn);
3973 boards[forwardMostMove][HOLDINGS_SET] = 1; // flag holdings as set
3975 if (appData.zippyPlay && first.initDone) {
3976 ZippyHoldings(white_holding, black_holding,
3980 if (tinyLayout || smallLayout) {
3981 char wh[16], bh[16];
3982 PackHolding(wh, white_holding);
3983 PackHolding(bh, black_holding);
3984 snprintf(str, MSG_SIZ,"[%s-%s] %s-%s", wh, bh,
3985 gameInfo.white, gameInfo.black);
3987 snprintf(str, MSG_SIZ, "%s [%s] vs. %s [%s]",
3988 gameInfo.white, white_holding,
3989 gameInfo.black, black_holding);
3991 if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
3992 DrawPosition(FALSE, boards[currentMove]);
3994 } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
3995 sscanf(parse, "game %d white [%s black [%s <- %s",
3996 &gamenum, white_holding, black_holding,
3998 white_holding[strlen(white_holding)-1] = NULLCHAR;
3999 black_holding[strlen(black_holding)-1] = NULLCHAR;
4000 /* [HGM] copy holdings to partner-board holdings area */
4001 CopyHoldings(partnerBoard, white_holding, WhitePawn);
4002 CopyHoldings(partnerBoard, black_holding, BlackPawn);
4003 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
4004 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4005 if(twoBoards) { partnerUp = 0; flipView = !flipView; }
4008 /* Suppress following prompt */
4009 if (looking_at(buf, &i, "*% ")) {
4010 if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
4011 savingComment = FALSE;
4019 i++; /* skip unparsed character and loop back */
4022 if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
4023 // started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
4024 // SendToPlayer(&buf[next_out], i - next_out);
4025 started != STARTED_HOLDINGS && leftover_start > next_out) {
4026 SendToPlayer(&buf[next_out], leftover_start - next_out);
4030 leftover_len = buf_len - leftover_start;
4031 /* if buffer ends with something we couldn't parse,
4032 reparse it after appending the next read */
4034 } else if (count == 0) {
4035 RemoveInputSource(isr);
4036 DisplayFatalError(_("Connection closed by ICS"), 0, 0);
4038 DisplayFatalError(_("Error reading from ICS"), error, 1);
4043 /* Board style 12 looks like this:
4045 <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
4047 * The "<12> " is stripped before it gets to this routine. The two
4048 * trailing 0's (flip state and clock ticking) are later addition, and
4049 * some chess servers may not have them, or may have only the first.
4050 * Additional trailing fields may be added in the future.
4053 #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"
4055 #define RELATION_OBSERVING_PLAYED 0
4056 #define RELATION_OBSERVING_STATIC -2 /* examined, oldmoves, or smoves */
4057 #define RELATION_PLAYING_MYMOVE 1
4058 #define RELATION_PLAYING_NOTMYMOVE -1
4059 #define RELATION_EXAMINING 2
4060 #define RELATION_ISOLATED_BOARD -3
4061 #define RELATION_STARTING_POSITION -4 /* FICS only */
4064 ParseBoard12(string)
4067 GameMode newGameMode;
4068 int gamenum, newGame, newMove, relation, basetime, increment, ics_flip = 0, i;
4069 int j, k, n, moveNum, white_stren, black_stren, white_time, black_time, takeback;
4070 int double_push, castle_ws, castle_wl, castle_bs, castle_bl, irrev_count;
4071 char to_play, board_chars[200];
4072 char move_str[MSG_SIZ], str[MSG_SIZ], elapsed_time[MSG_SIZ];
4073 char black[32], white[32];
4075 int prevMove = currentMove;
4078 int fromX, fromY, toX, toY;
4080 int ranks=1, files=0; /* [HGM] ICS80: allow variable board size */
4081 char *bookHit = NULL; // [HGM] book
4082 Boolean weird = FALSE, reqFlag = FALSE;
4084 fromX = fromY = toX = toY = -1;
4088 if (appData.debugMode)
4089 fprintf(debugFP, _("Parsing board: %s\n"), string);
4091 move_str[0] = NULLCHAR;
4092 elapsed_time[0] = NULLCHAR;
4093 { /* [HGM] figure out how many ranks and files the board has, for ICS extension used by Capablanca server */
4095 while(i < 199 && (string[i] != ' ' || string[i+2] != ' ')) {
4096 if(string[i] == ' ') { ranks++; files = 0; }
4098 if(!strchr(" -pnbrqkPNBRQK" , string[i])) weird = TRUE; // test for fairies
4101 for(j = 0; j <i; j++) board_chars[j] = string[j];
4102 board_chars[i] = '\0';
4105 n = sscanf(string, PATTERN, &to_play, &double_push,
4106 &castle_ws, &castle_wl, &castle_bs, &castle_bl, &irrev_count,
4107 &gamenum, white, black, &relation, &basetime, &increment,
4108 &white_stren, &black_stren, &white_time, &black_time,
4109 &moveNum, str, elapsed_time, move_str, &ics_flip,
4113 snprintf(str, MSG_SIZ, _("Failed to parse board string:\n\"%s\""), string);
4114 DisplayError(str, 0);
4118 /* Convert the move number to internal form */
4119 moveNum = (moveNum - 1) * 2;
4120 if (to_play == 'B') moveNum++;
4121 if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
4122 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
4128 case RELATION_OBSERVING_PLAYED:
4129 case RELATION_OBSERVING_STATIC:
4130 if (gamenum == -1) {
4131 /* Old ICC buglet */
4132 relation = RELATION_OBSERVING_STATIC;
4134 newGameMode = IcsObserving;
4136 case RELATION_PLAYING_MYMOVE:
4137 case RELATION_PLAYING_NOTMYMOVE:
4139 ((relation == RELATION_PLAYING_MYMOVE) == (to_play == 'W')) ?
4140 IcsPlayingWhite : IcsPlayingBlack;
4142 case RELATION_EXAMINING:
4143 newGameMode = IcsExamining;
4145 case RELATION_ISOLATED_BOARD:
4147 /* Just display this board. If user was doing something else,
4148 we will forget about it until the next board comes. */
4149 newGameMode = IcsIdle;
4151 case RELATION_STARTING_POSITION:
4152 newGameMode = gameMode;
4156 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
4157 && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
4158 // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
4160 for (k = 0; k < ranks; k++) {
4161 for (j = 0; j < files; j++)
4162 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4163 if(gameInfo.holdingsWidth > 1) {
4164 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4165 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4168 CopyBoard(partnerBoard, board);
4169 if(toSqr = strchr(str, '/')) { // extract highlights from long move
4170 partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
4171 partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
4172 } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
4173 if(toSqr = strchr(str, '-')) {
4174 partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
4175 partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
4176 } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
4177 if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
4178 if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
4179 if(partnerUp) DrawPosition(FALSE, partnerBoard);
4180 if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
4181 snprintf(partnerStatus, MSG_SIZ,"W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
4182 (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
4183 DisplayMessage(partnerStatus, "");
4184 partnerBoardValid = TRUE;
4188 /* Modify behavior for initial board display on move listing
4191 switch (ics_getting_history) {
4195 case H_GOT_REQ_HEADER:
4196 case H_GOT_UNREQ_HEADER:
4197 /* This is the initial position of the current game */
4198 gamenum = ics_gamenum;
4199 moveNum = 0; /* old ICS bug workaround */
4200 if (to_play == 'B') {
4201 startedFromSetupPosition = TRUE;
4202 blackPlaysFirst = TRUE;
4204 if (forwardMostMove == 0) forwardMostMove = 1;
4205 if (backwardMostMove == 0) backwardMostMove = 1;
4206 if (currentMove == 0) currentMove = 1;
4208 newGameMode = gameMode;
4209 relation = RELATION_STARTING_POSITION; /* ICC needs this */
4211 case H_GOT_UNWANTED_HEADER:
4212 /* This is an initial board that we don't want */
4214 case H_GETTING_MOVES:
4215 /* Should not happen */
4216 DisplayError(_("Error gathering move list: extra board"), 0);
4217 ics_getting_history = H_FALSE;
4221 if (gameInfo.boardHeight != ranks || gameInfo.boardWidth != files ||
4222 weird && (int)gameInfo.variant < (int)VariantShogi) {
4223 /* [HGM] We seem to have switched variant unexpectedly
4224 * Try to guess new variant from board size
4226 VariantClass newVariant = VariantFairy; // if 8x8, but fairies present
4227 if(ranks == 8 && files == 10) newVariant = VariantCapablanca; else
4228 if(ranks == 10 && files == 9) newVariant = VariantXiangqi; else
4229 if(ranks == 8 && files == 12) newVariant = VariantCourier; else
4230 if(ranks == 9 && files == 9) newVariant = VariantShogi; else
4231 if(!weird) newVariant = VariantNormal;
4232 VariantSwitch(boards[currentMove], newVariant); /* temp guess */
4233 /* Get a move list just to see the header, which
4234 will tell us whether this is really bug or zh */
4235 if (ics_getting_history == H_FALSE) {
4236 ics_getting_history = H_REQUESTED; reqFlag = TRUE;
4237 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4242 /* Take action if this is the first board of a new game, or of a
4243 different game than is currently being displayed. */
4244 if (gamenum != ics_gamenum || newGameMode != gameMode ||
4245 relation == RELATION_ISOLATED_BOARD) {
4247 /* Forget the old game and get the history (if any) of the new one */
4248 if (gameMode != BeginningOfGame) {
4252 if (appData.autoRaiseBoard) BoardToTop();
4254 if (gamenum == -1) {
4255 newGameMode = IcsIdle;
4256 } else if ((moveNum > 0 || newGameMode == IcsObserving) && newGameMode != IcsIdle &&
4257 appData.getMoveList && !reqFlag) {
4258 /* Need to get game history */
4259 ics_getting_history = H_REQUESTED;
4260 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4264 /* Initially flip the board to have black on the bottom if playing
4265 black or if the ICS flip flag is set, but let the user change
4266 it with the Flip View button. */
4267 flipView = appData.autoFlipView ?
4268 (newGameMode == IcsPlayingBlack) || ics_flip :
4271 /* Done with values from previous mode; copy in new ones */
4272 gameMode = newGameMode;
4274 ics_gamenum = gamenum;
4275 if (gamenum == gs_gamenum) {
4276 int klen = strlen(gs_kind);
4277 if (gs_kind[klen - 1] == '.') gs_kind[klen - 1] = NULLCHAR;
4278 snprintf(str, MSG_SIZ, "ICS %s", gs_kind);
4279 gameInfo.event = StrSave(str);
4281 gameInfo.event = StrSave("ICS game");
4283 gameInfo.site = StrSave(appData.icsHost);
4284 gameInfo.date = PGNDate();
4285 gameInfo.round = StrSave("-");
4286 gameInfo.white = StrSave(white);
4287 gameInfo.black = StrSave(black);
4288 timeControl = basetime * 60 * 1000;
4290 timeIncrement = increment * 1000;
4291 movesPerSession = 0;
4292 gameInfo.timeControl = TimeControlTagValue();
4293 VariantSwitch(boards[currentMove], StringToVariant(gameInfo.event) );
4294 if (appData.debugMode) {
4295 fprintf(debugFP, "ParseBoard says variant = '%s'\n", gameInfo.event);
4296 fprintf(debugFP, "recognized as %s\n", VariantName(gameInfo.variant));
4297 setbuf(debugFP, NULL);
4300 gameInfo.outOfBook = NULL;
4302 /* Do we have the ratings? */
4303 if (strcmp(player1Name, white) == 0 &&
4304 strcmp(player2Name, black) == 0) {
4305 if (appData.debugMode)
4306 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4307 player1Rating, player2Rating);
4308 gameInfo.whiteRating = player1Rating;
4309 gameInfo.blackRating = player2Rating;
4310 } else if (strcmp(player2Name, white) == 0 &&
4311 strcmp(player1Name, black) == 0) {
4312 if (appData.debugMode)
4313 fprintf(debugFP, "Remembered ratings: W %d, B %d\n",
4314 player2Rating, player1Rating);
4315 gameInfo.whiteRating = player2Rating;
4316 gameInfo.blackRating = player1Rating;
4318 player1Name[0] = player2Name[0] = NULLCHAR;
4320 /* Silence shouts if requested */
4321 if (appData.quietPlay &&
4322 (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)) {
4323 SendToICS(ics_prefix);
4324 SendToICS("set shout 0\n");
4328 /* Deal with midgame name changes */
4330 if (!gameInfo.white || strcmp(gameInfo.white, white) != 0) {
4331 if (gameInfo.white) free(gameInfo.white);
4332 gameInfo.white = StrSave(white);
4334 if (!gameInfo.black || strcmp(gameInfo.black, black) != 0) {
4335 if (gameInfo.black) free(gameInfo.black);
4336 gameInfo.black = StrSave(black);
4340 /* Throw away game result if anything actually changes in examine mode */
4341 if (gameMode == IcsExamining && !newGame) {
4342 gameInfo.result = GameUnfinished;
4343 if (gameInfo.resultDetails != NULL) {
4344 free(gameInfo.resultDetails);
4345 gameInfo.resultDetails = NULL;
4349 /* In pausing && IcsExamining mode, we ignore boards coming
4350 in if they are in a different variation than we are. */
4351 if (pauseExamInvalid) return;
4352 if (pausing && gameMode == IcsExamining) {
4353 if (moveNum <= pauseExamForwardMostMove) {
4354 pauseExamInvalid = TRUE;
4355 forwardMostMove = pauseExamForwardMostMove;
4360 if (appData.debugMode) {
4361 fprintf(debugFP, "load %dx%d board\n", files, ranks);
4363 /* Parse the board */
4364 for (k = 0; k < ranks; k++) {
4365 for (j = 0; j < files; j++)
4366 board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
4367 if(gameInfo.holdingsWidth > 1) {
4368 board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
4369 board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
4372 CopyBoard(boards[moveNum], board);
4373 boards[moveNum][HOLDINGS_SET] = 0; // [HGM] indicate holdings not set
4375 startedFromSetupPosition =
4376 !CompareBoards(board, initialPosition);
4377 if(startedFromSetupPosition)
4378 initialRulePlies = irrev_count; /* [HGM] 50-move counter offset */
4381 /* [HGM] Set castling rights. Take the outermost Rooks,
4382 to make it also work for FRC opening positions. Note that board12
4383 is really defective for later FRC positions, as it has no way to
4384 indicate which Rook can castle if they are on the same side of King.
4385 For the initial position we grant rights to the outermost Rooks,
4386 and remember thos rights, and we then copy them on positions
4387 later in an FRC game. This means WB might not recognize castlings with
4388 Rooks that have moved back to their original position as illegal,
4389 but in ICS mode that is not its job anyway.
4391 if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
4392 { int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
4394 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4395 if(board[0][i] == WhiteRook) j = i;
4396 initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4397 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4398 if(board[0][i] == WhiteRook) j = i;
4399 initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4400 for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
4401 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4402 initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4403 for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
4404 if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
4405 initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
4407 if(gameInfo.variant == VariantKnightmate) { wKing = WhiteUnicorn; bKing = BlackUnicorn; }
4408 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4409 if(board[0][k] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = k;
4410 for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
4411 if(board[BOARD_HEIGHT-1][k] == bKing)
4412 initialRights[5] = boards[moveNum][CASTLING][5] = k;
4413 if(gameInfo.variant == VariantTwoKings) {
4414 // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
4415 if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
4416 if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
4419 r = boards[moveNum][CASTLING][0] = initialRights[0];
4420 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
4421 r = boards[moveNum][CASTLING][1] = initialRights[1];
4422 if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][1] = NoRights;
4423 r = boards[moveNum][CASTLING][3] = initialRights[3];
4424 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][3] = NoRights;
4425 r = boards[moveNum][CASTLING][4] = initialRights[4];
4426 if(board[BOARD_HEIGHT-1][r] != BlackRook) boards[moveNum][CASTLING][4] = NoRights;
4427 /* wildcastle kludge: always assume King has rights */
4428 r = boards[moveNum][CASTLING][2] = initialRights[2];
4429 r = boards[moveNum][CASTLING][5] = initialRights[5];
4431 /* [HGM] e.p. rights. Assume that ICS sends file number here? */
4432 boards[moveNum][EP_STATUS] = double_push == -1 ? EP_NONE : double_push + BOARD_LEFT;
4435 if (ics_getting_history == H_GOT_REQ_HEADER ||
4436 ics_getting_history == H_GOT_UNREQ_HEADER) {
4437 /* This was an initial position from a move list, not
4438 the current position */
4442 /* Update currentMove and known move number limits */
4443 newMove = newGame || moveNum > forwardMostMove;
4446 forwardMostMove = backwardMostMove = currentMove = moveNum;
4447 if (gameMode == IcsExamining && moveNum == 0) {
4448 /* Workaround for ICS limitation: we are not told the wild
4449 type when starting to examine a game. But if we ask for
4450 the move list, the move list header will tell us */
4451 ics_getting_history = H_REQUESTED;
4452 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4455 } else if (moveNum == forwardMostMove + 1 || moveNum == forwardMostMove
4456 || (moveNum < forwardMostMove && moveNum >= backwardMostMove)) {
4458 /* [DM] If we found takebacks during icsEngineAnalyze try send to engine */
4459 /* [HGM] applied this also to an engine that is silently watching */
4460 if (appData.zippyPlay && moveNum < forwardMostMove && first.initDone &&
4461 (gameMode == IcsObserving || gameMode == IcsExamining) &&
4462 gameInfo.variant == currentlyInitializedVariant) {
4463 takeback = forwardMostMove - moveNum;
4464 for (i = 0; i < takeback; i++) {
4465 if (appData.debugMode) fprintf(debugFP, "take back move\n");
4466 SendToProgram("undo\n", &first);
4471 forwardMostMove = moveNum;
4472 if (!pausing || currentMove > forwardMostMove)
4473 currentMove = forwardMostMove;
4475 /* New part of history that is not contiguous with old part */
4476 if (pausing && gameMode == IcsExamining) {
4477 pauseExamInvalid = TRUE;
4478 forwardMostMove = pauseExamForwardMostMove;
4481 if (gameMode == IcsExamining && moveNum > 0 && appData.getMoveList) {
4483 if(appData.zippyPlay && forwardMostMove > 0 && first.initDone) {
4484 // [HGM] when we will receive the move list we now request, it will be
4485 // fed to the engine from the first move on. So if the engine is not
4486 // in the initial position now, bring it there.
4487 InitChessProgram(&first, 0);
4490 ics_getting_history = H_REQUESTED;
4491 snprintf(str, MSG_SIZ, "%smoves %d\n", ics_prefix, gamenum);
4494 forwardMostMove = backwardMostMove = currentMove = moveNum;
4497 /* Update the clocks */
4498 if (strchr(elapsed_time, '.')) {
4500 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time;
4501 timeRemaining[1][moveNum] = blackTimeRemaining = black_time;
4503 /* Time is in seconds */
4504 timeRemaining[0][moveNum] = whiteTimeRemaining = white_time * 1000;
4505 timeRemaining[1][moveNum] = blackTimeRemaining = black_time * 1000;
4510 if (appData.zippyPlay && newGame &&
4511 gameMode != IcsObserving && gameMode != IcsIdle &&
4512 gameMode != IcsExamining)
4513 ZippyFirstBoard(moveNum, basetime, increment);
4516 /* Put the move on the move list, first converting
4517 to canonical algebraic form. */
4519 if (appData.debugMode) {
4520 if (appData.debugMode) { int f = forwardMostMove;
4521 fprintf(debugFP, "parseboard %d, castling = %d %d %d %d %d %d\n", f,
4522 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
4523 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
4525 fprintf(debugFP, "accepted move %s from ICS, parse it.\n", move_str);
4526 fprintf(debugFP, "moveNum = %d\n", moveNum);
4527 fprintf(debugFP, "board = %d-%d x %d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT);
4528 setbuf(debugFP, NULL);
4530 if (moveNum <= backwardMostMove) {
4531 /* We don't know what the board looked like before
4533 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4534 strcat(parseList[moveNum - 1], " ");
4535 strcat(parseList[moveNum - 1], elapsed_time);
4536 moveList[moveNum - 1][0] = NULLCHAR;
4537 } else if (strcmp(move_str, "none") == 0) {
4538 // [HGM] long SAN: swapped order; test for 'none' before parsing move
4539 /* Again, we don't know what the board looked like;
4540 this is really the start of the game. */
4541 parseList[moveNum - 1][0] = NULLCHAR;
4542 moveList[moveNum - 1][0] = NULLCHAR;
4543 backwardMostMove = moveNum;
4544 startedFromSetupPosition = TRUE;
4545 fromX = fromY = toX = toY = -1;
4547 // [HGM] long SAN: if legality-testing is off, disambiguation might not work or give wrong move.
4548 // So we parse the long-algebraic move string in stead of the SAN move
4549 int valid; char buf[MSG_SIZ], *prom;
4551 if(gameInfo.variant == VariantShogi && !strchr(move_str, '=') && !strchr(move_str, '@'))
4552 strcat(move_str, "="); // if ICS does not say 'promote' on non-drop, we defer.
4553 // str looks something like "Q/a1-a2"; kill the slash
4555 snprintf(buf, MSG_SIZ,"%c%s", str[0], str+2);
4556 else safeStrCpy(buf, str, sizeof(buf)/sizeof(buf[0])); // might be castling
4557 if((prom = strstr(move_str, "=")) && !strstr(buf, "="))
4558 strcat(buf, prom); // long move lacks promo specification!
4559 if(!appData.testLegality && move_str[1] != '@') { // drops never ambiguous (parser chokes on long form!)
4560 if(appData.debugMode)
4561 fprintf(debugFP, "replaced ICS move '%s' by '%s'\n", move_str, buf);
4562 safeStrCpy(move_str, buf, MSG_SIZ);
4564 valid = ParseOneMove(move_str, moveNum - 1, &moveType,
4565 &fromX, &fromY, &toX, &toY, &promoChar)
4566 || ParseOneMove(buf, moveNum - 1, &moveType,
4567 &fromX, &fromY, &toX, &toY, &promoChar);
4568 // end of long SAN patch
4570 (void) CoordsToAlgebraic(boards[moveNum - 1],
4571 PosFlags(moveNum - 1),
4572 fromY, fromX, toY, toX, promoChar,
4573 parseList[moveNum-1]);
4574 switch (MateTest(boards[moveNum], PosFlags(moveNum)) ) {
4580 if(gameInfo.variant != VariantShogi)
4581 strcat(parseList[moveNum - 1], "+");
4584 case MT_STAINMATE: // [HGM] xq: for notation stalemate that wins counts as checkmate
4585 strcat(parseList[moveNum - 1], "#");
4588 strcat(parseList[moveNum - 1], " ");
4589 strcat(parseList[moveNum - 1], elapsed_time);
4590 /* currentMoveString is set as a side-effect of ParseOneMove */
4591 if(gameInfo.variant == VariantShogi && currentMoveString[4]) currentMoveString[4] = '^';
4592 safeStrCpy(moveList[moveNum - 1], currentMoveString, sizeof(moveList[moveNum - 1])/sizeof(moveList[moveNum - 1][0]));
4593 strcat(moveList[moveNum - 1], "\n");
4595 if(gameInfo.holdingsWidth && !appData.disguise && gameInfo.variant != VariantSuper
4596 && gameInfo.variant != VariantGreat) // inherit info that ICS does not give from previous board
4597 for(k=0; k<ranks; k++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++) {
4598 ChessSquare old, new = boards[moveNum][k][j];
4599 if(fromY == DROP_RANK && k==toY && j==toX) continue; // dropped pieces always stand for themselves
4600 old = (k==toY && j==toX) ? boards[moveNum-1][fromY][fromX] : boards[moveNum-1][k][j]; // trace back mover
4601 if(old == new) continue;
4602 if(old == PROMOTED new) boards[moveNum][k][j] = old; // prevent promoted pieces to revert to primordial ones
4603 else if(new == WhiteWazir || new == BlackWazir) {
4604 if(old < WhiteCannon || old >= BlackPawn && old < BlackCannon)
4605 boards[moveNum][k][j] = PROMOTED old; // choose correct type of Gold in promotion
4606 else boards[moveNum][k][j] = old; // preserve type of Gold
4607 } else if((old == WhitePawn || old == BlackPawn) && new != EmptySquare) // Pawn promotions (but not e.p.capture!)
4608 boards[moveNum][k][j] = PROMOTED new; // use non-primordial representation of chosen piece
4611 /* Move from ICS was illegal!? Punt. */
4612 if (appData.debugMode) {
4613 fprintf(debugFP, "Illegal move from ICS '%s'\n", move_str);
4614 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
4616 safeStrCpy(parseList[moveNum - 1], move_str, sizeof(parseList[moveNum - 1])/sizeof(parseList[moveNum - 1][0]));
4617 strcat(parseList[moveNum - 1], " ");
4618 strcat(parseList[moveNum - 1], elapsed_time);
4619 moveList[moveNum - 1][0] = NULLCHAR;
4620 fromX = fromY = toX = toY = -1;
4623 if (appData.debugMode) {
4624 fprintf(debugFP, "Move parsed to '%s'\n", parseList[moveNum - 1]);
4625 setbuf(debugFP, NULL);
4629 /* Send move to chess program (BEFORE animating it). */
4630 if (appData.zippyPlay && !newGame && newMove &&
4631 (!appData.getMoveList || backwardMostMove == 0) && first.initDone) {
4633 if ((gameMode == IcsPlayingWhite && WhiteOnMove(moveNum)) ||
4634 (gameMode == IcsPlayingBlack && !WhiteOnMove(moveNum))) {
4635 if (moveList[moveNum - 1][0] == NULLCHAR) {
4636 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"),
4638 DisplayError(str, 0);
4640 if (first.sendTime) {
4641 SendTimeRemaining(&first, gameMode == IcsPlayingWhite);
4643 bookHit = SendMoveToBookUser(moveNum - 1, &first, FALSE); // [HGM] book
4644 if (firstMove && !bookHit) {
4646 if (first.useColors) {
4647 SendToProgram(gameMode == IcsPlayingWhite ?
4649 "black\ngo\n", &first);
4651 SendToProgram("go\n", &first);
4653 first.maybeThinking = TRUE;
4656 } else if (gameMode == IcsObserving || gameMode == IcsExamining) {
4657 if (moveList[moveNum - 1][0] == NULLCHAR) {
4658 snprintf(str, MSG_SIZ, _("Couldn't parse move \"%s\" from ICS"), move_str);
4659 DisplayError(str, 0);
4661 if(gameInfo.variant == currentlyInitializedVariant) // [HGM] refrain sending moves engine can't understand!
4662 SendMoveToProgram(moveNum - 1, &first);
4669 if (moveNum > 0 && !gotPremove && !appData.noGUI) {
4670 /* If move comes from a remote source, animate it. If it
4671 isn't remote, it will have already been animated. */
4672 if (!pausing && !ics_user_moved && prevMove == moveNum - 1) {
4673 AnimateMove(boards[moveNum - 1], fromX, fromY, toX, toY);
4675 if (!pausing && appData.highlightLastMove) {
4676 SetHighlights(fromX, fromY, toX, toY);
4680 /* Start the clocks */
4681 whiteFlag = blackFlag = FALSE;
4682 appData.clockMode = !(basetime == 0 && increment == 0);
4684 ics_clock_paused = TRUE;
4686 } else if (ticking == 1) {
4687 ics_clock_paused = FALSE;
4689 if (gameMode == IcsIdle ||
4690 relation == RELATION_OBSERVING_STATIC ||
4691 relation == RELATION_EXAMINING ||
4693 DisplayBothClocks();
4697 /* Display opponents and material strengths */
4698 if (gameInfo.variant != VariantBughouse &&
4699 gameInfo.variant != VariantCrazyhouse && !appData.noGUI) {
4700 if (tinyLayout || smallLayout) {
4701 if(gameInfo.variant == VariantNormal)
4702 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d}",
4703 gameInfo.white, white_stren, gameInfo.black, black_stren,
4704 basetime, increment);
4706 snprintf(str, MSG_SIZ, "%s(%d) %s(%d) {%d %d w%d}",
4707 gameInfo.white, white_stren, gameInfo.black, black_stren,
4708 basetime, increment, (int) gameInfo.variant);
4710 if(gameInfo.variant == VariantNormal)
4711 snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d}",
4712 gameInfo.white, white_stren, gameInfo.black, black_stren,
4713 basetime, increment);
4715 snprintf(str, MSG_SIZ, "%s (%d) vs. %s (%d) {%d %d %s}",
4716 gameInfo.white, white_stren, gameInfo.black, black_stren,
4717 basetime, increment, VariantName(gameInfo.variant));
4720 if (appData.debugMode) {
4721 fprintf(debugFP, "Display title '%s, gameInfo.variant = %d'\n", str, gameInfo.variant);
4726 /* Display the board */
4727 if (!pausing && !appData.noGUI) {
4729 if (appData.premove)
4731 ((gameMode == IcsPlayingWhite) && (WhiteOnMove(currentMove))) ||
4732 ((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
4733 ClearPremoveHighlights();
4735 j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
4736 if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
4737 DrawPosition(j, boards[currentMove]);
4739 DisplayMove(moveNum - 1);
4740 if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
4741 !((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
4742 (gameMode == IcsPlayingBlack) && (WhiteOnMove(moveNum)) ) ) {
4743 if(newMove) RingBell(); else PlayIcsUnfinishedSound();
4747 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
4749 if(bookHit) { // [HGM] book: simulate book reply
4750 static char bookMove[MSG_SIZ]; // a bit generous?
4752 programStats.nodes = programStats.depth = programStats.time =
4753 programStats.score = programStats.got_only_move = 0;
4754 sprintf(programStats.movelist, "%s (xbook)", bookHit);
4756 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
4757 strcat(bookMove, bookHit);
4758 HandleMachineMove(bookMove, &first);
4767 if (appData.icsActive && gameMode != IcsIdle && ics_gamenum > 0) {
4768 ics_getting_history = H_REQUESTED;
4769 snprintf(buf, MSG_SIZ, "%smoves %d\n", ics_prefix, ics_gamenum);
4775 AnalysisPeriodicEvent(force)
4778 if (((programStats.ok_to_send == 0 || programStats.line_is_book)
4779 && !force) || !appData.periodicUpdates)
4782 /* Send . command to Crafty to collect stats */
4783 SendToProgram(".\n", &first);
4785 /* Don't send another until we get a response (this makes
4786 us stop sending to old Crafty's which don't understand
4787 the "." command (sending illegal cmds resets node count & time,
4788 which looks bad)) */
4789 programStats.ok_to_send = 0;
4792 void ics_update_width(new_width)
4795 ics_printf("set width %d\n", new_width);
4799 SendMoveToProgram(moveNum, cps)
4801 ChessProgramState *cps;
4805 if (cps->useUsermove) {
4806 SendToProgram("usermove ", cps);
4810 if ((space = strchr(parseList[moveNum], ' ')) != NULL) {
4811 int len = space - parseList[moveNum];
4812 memcpy(buf, parseList[moveNum], len);
4814 buf[len] = NULLCHAR;
4816 snprintf(buf, MSG_SIZ,"%s\n", parseList[moveNum]);
4818 SendToProgram(buf, cps);
4820 if(cps->alphaRank) { /* [HGM] shogi: temporarily convert to shogi coordinates before sending */
4821 AlphaRank(moveList[moveNum], 4);
4822 SendToProgram(moveList[moveNum], cps);
4823 AlphaRank(moveList[moveNum], 4); // and back
4825 /* Added by Tord: Send castle moves in "O-O" in FRC games if required by
4826 * the engine. It would be nice to have a better way to identify castle
4828 if((gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom)
4829 && cps->useOOCastle) {
4830 int fromX = moveList[moveNum][0] - AAA;
4831 int fromY = moveList[moveNum][1] - ONE;
4832 int toX = moveList[moveNum][2] - AAA;
4833 int toY = moveList[moveNum][3] - ONE;
4834 if((boards[moveNum][fromY][fromX] == WhiteKing
4835 && boards[moveNum][toY][toX] == WhiteRook)
4836 || (boards[moveNum][fromY][fromX] == BlackKing
4837 && boards[moveNum][toY][toX] == BlackRook)) {
4838 if(toX > fromX) SendToProgram("O-O\n", cps);
4839 else SendToProgram("O-O-O\n", cps);
4841 else SendToProgram(moveList[moveNum], cps);
4843 else SendToProgram(moveList[moveNum], cps);
4844 /* End of additions by Tord */
4847 /* [HGM] setting up the opening has brought engine in force mode! */
4848 /* Send 'go' if we are in a mode where machine should play. */
4849 if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
4850 (gameMode == TwoMachinesPlay ||
4852 gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
4854 gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
4855 SendToProgram("go\n", cps);
4856 if (appData.debugMode) {
4857 fprintf(debugFP, "(extra)\n");
4860 setboardSpoiledMachineBlack = 0;
4864 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar)
4866 int fromX, fromY, toX, toY;
4869 char user_move[MSG_SIZ];
4873 snprintf(user_move, MSG_SIZ, _("say Internal error; bad moveType %d (%d,%d-%d,%d)"),
4874 (int)moveType, fromX, fromY, toX, toY);
4875 DisplayError(user_move + strlen("say "), 0);
4877 case WhiteKingSideCastle:
4878 case BlackKingSideCastle:
4879 case WhiteQueenSideCastleWild:
4880 case BlackQueenSideCastleWild:
4882 case WhiteHSideCastleFR:
4883 case BlackHSideCastleFR:
4885 snprintf(user_move, MSG_SIZ, "o-o\n");
4887 case WhiteQueenSideCastle:
4888 case BlackQueenSideCastle:
4889 case WhiteKingSideCastleWild:
4890 case BlackKingSideCastleWild:
4892 case WhiteASideCastleFR:
4893 case BlackASideCastleFR:
4895 snprintf(user_move, MSG_SIZ, "o-o-o\n");
4897 case WhiteNonPromotion:
4898 case BlackNonPromotion:
4899 sprintf(user_move, "%c%c%c%c==\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4901 case WhitePromotion:
4902 case BlackPromotion:
4903 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
4904 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4905 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4906 PieceToChar(WhiteFerz));
4907 else if(gameInfo.variant == VariantGreat)
4908 snprintf(user_move, MSG_SIZ,"%c%c%c%c=%c\n",
4909 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4910 PieceToChar(WhiteMan));
4912 snprintf(user_move, MSG_SIZ, "%c%c%c%c=%c\n",
4913 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
4919 snprintf(user_move, MSG_SIZ, "%c@%c%c\n",
4920 ToUpper(PieceToChar((ChessSquare) fromX)),
4921 AAA + toX, ONE + toY);
4923 case IllegalMove: /* could be a variant we don't quite understand */
4924 if(fromY == DROP_RANK) goto drop; // We need 'IllegalDrop' move type?
4926 case WhiteCapturesEnPassant:
4927 case BlackCapturesEnPassant:
4928 snprintf(user_move, MSG_SIZ,"%c%c%c%c\n",
4929 AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
4932 SendToICS(user_move);
4933 if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
4934 ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
4939 { // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
4940 int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
4941 static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
4942 if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
4943 DisplayError("You cannot do this while you are playing or observing", 0);
4946 if(gameMode != IcsExamining) { // is this ever not the case?
4947 char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
4949 if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
4950 snprintf(command,MSG_SIZ, "match %s", ics_handle);
4951 } else { // on FICS we must first go to general examine mode
4952 safeStrCpy(command, "examine\nbsetup", sizeof(command)/sizeof(command[0])); // and specify variant within it with bsetups
4954 if(gameInfo.variant != VariantNormal) {
4955 // try figure out wild number, as xboard names are not always valid on ICS
4956 for(i=1; i<=36; i++) {
4957 snprintf(buf, MSG_SIZ, "wild/%d", i);
4958 if(StringToVariant(buf) == gameInfo.variant) break;
4960 if(i<=36 && ics_type == ICS_ICC) snprintf(buf, MSG_SIZ,"%s w%d\n", command, i);
4961 else if(i == 22) snprintf(buf,MSG_SIZ, "%s fr\n", command);
4962 else snprintf(buf, MSG_SIZ,"%s %s\n", command, VariantName(gameInfo.variant));
4963 } else snprintf(buf, MSG_SIZ,"%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
4964 SendToICS(ics_prefix);
4966 if(startedFromSetupPosition || backwardMostMove != 0) {
4967 fen = PositionToFEN(backwardMostMove, NULL);
4968 if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
4969 snprintf(buf, MSG_SIZ,"loadfen %s\n", fen);
4971 } else { // FICS: everything has to set by separate bsetup commands
4972 p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
4973 snprintf(buf, MSG_SIZ,"bsetup fen %s\n", fen);
4975 if(!WhiteOnMove(backwardMostMove)) {
4976 SendToICS("bsetup tomove black\n");
4978 i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
4979 snprintf(buf, MSG_SIZ,"bsetup wcastle %s\n", castlingStrings[i]);
4981 i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
4982 snprintf(buf, MSG_SIZ, "bsetup bcastle %s\n", castlingStrings[i]);
4984 i = boards[backwardMostMove][EP_STATUS];
4985 if(i >= 0) { // set e.p.
4986 snprintf(buf, MSG_SIZ,"bsetup eppos %c\n", i+AAA);
4992 if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
4993 SendToICS("bsetup done\n"); // switch to normal examining.
4995 for(i = backwardMostMove; i<last; i++) {
4997 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"%s\n", parseList[i]);
5000 SendToICS(ics_prefix);
5001 SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
5005 CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
5010 if (rf == DROP_RANK) {
5011 sprintf(move, "%c@%c%c\n",
5012 ToUpper(PieceToChar((ChessSquare) ff)), AAA + ft, ONE + rt);
5014 if (promoChar == 'x' || promoChar == NULLCHAR) {
5015 sprintf(move, "%c%c%c%c\n",
5016 AAA + ff, ONE + rf, AAA + ft, ONE + rt);
5018 sprintf(move, "%c%c%c%c%c\n",
5019 AAA + ff, ONE + rf, AAA + ft, ONE + rt, promoChar);
5025 ProcessICSInitScript(f)
5030 while (fgets(buf, MSG_SIZ, f)) {
5031 SendToICSDelayed(buf,(long)appData.msLoginDelay);
5038 static int lastX, lastY, selectFlag, dragging;
5043 ChessSquare king = WhiteKing, pawn = WhitePawn, last = promoSweep;
5044 if(gameInfo.variant == VariantKnightmate) king = WhiteUnicorn;
5045 if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway) king = EmptySquare;
5046 if(promoSweep >= BlackPawn) king = WHITE_TO_BLACK king, pawn = WHITE_TO_BLACK pawn;
5047 if(gameInfo.variant == VariantSpartan && pawn == BlackPawn) pawn = BlackLance, king = EmptySquare;
5048 if(fromY != BOARD_HEIGHT-2 && fromY != 1) pawn = EmptySquare;
5051 if(promoSweep == EmptySquare) promoSweep = BlackPawn; // wrap
5052 else if((int)promoSweep == -1) promoSweep = WhiteKing;
5053 else if(promoSweep == BlackPawn && step < 0) promoSweep = WhitePawn;
5054 else if(promoSweep == WhiteKing && step > 0) promoSweep = BlackKing;
5056 } while(PieceToChar(promoSweep) == '.' || PieceToChar(promoSweep) == '~' || promoSweep == pawn ||
5057 appData.testLegality && (promoSweep == king ||
5058 gameInfo.variant == VariantShogi && promoSweep != PROMOTED last && last != PROMOTED promoSweep && last != promoSweep));
5059 ChangeDragPiece(promoSweep);
5062 int PromoScroll(int x, int y)
5066 if(promoSweep == EmptySquare || !appData.sweepSelect) return FALSE;
5067 if(abs(x - lastX) < 15 && abs(y - lastY) < 15) return FALSE;
5068 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5069 if(!step) return FALSE;
5070 lastX = x; lastY = y;
5071 if((promoSweep < BlackPawn) == flipView) step = -step;
5072 if(step > 0) selectFlag = 1;
5073 if(!selectFlag) Sweep(step);
5080 ChessSquare piece = boards[currentMove][toY][toX];
5083 if(pieceSweep == EmptySquare) pieceSweep = WhitePawn; // wrap
5084 if((int)pieceSweep == -1) pieceSweep = BlackKing;
5085 if(!step) step = -1;
5086 } while(PieceToChar(pieceSweep) == '.');
5087 boards[currentMove][toY][toX] = pieceSweep;
5088 DrawPosition(FALSE, boards[currentMove]);
5089 boards[currentMove][toY][toX] = piece;
5091 /* [HGM] Shogi move preprocessor: swap digits for letters, vice versa */
5093 AlphaRank(char *move, int n)
5095 // char *p = move, c; int x, y;
5097 if (appData.debugMode) {
5098 fprintf(debugFP, "alphaRank(%s,%d)\n", move, n);
5102 move[2]>='0' && move[2]<='9' &&
5103 move[3]>='a' && move[3]<='x' ) {
5105 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5106 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5108 if(move[0]>='0' && move[0]<='9' &&
5109 move[1]>='a' && move[1]<='x' &&
5110 move[2]>='0' && move[2]<='9' &&
5111 move[3]>='a' && move[3]<='x' ) {
5112 /* input move, Shogi -> normal */
5113 move[0] = BOARD_RGHT -1 - (move[0]-'1') + AAA;
5114 move[1] = BOARD_HEIGHT-1 - (move[1]-'a') + ONE;
5115 move[2] = BOARD_RGHT -1 - (move[2]-'1') + AAA;
5116 move[3] = BOARD_HEIGHT-1 - (move[3]-'a') + ONE;
5119 move[3]>='0' && move[3]<='9' &&
5120 move[2]>='a' && move[2]<='x' ) {
5122 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5123 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5126 move[0]>='a' && move[0]<='x' &&
5127 move[3]>='0' && move[3]<='9' &&
5128 move[2]>='a' && move[2]<='x' ) {
5129 /* output move, normal -> Shogi */
5130 move[0] = BOARD_RGHT - 1 - (move[0]-AAA) + '1';
5131 move[1] = BOARD_HEIGHT-1 - (move[1]-ONE) + 'a';
5132 move[2] = BOARD_RGHT - 1 - (move[2]-AAA) + '1';
5133 move[3] = BOARD_HEIGHT-1 - (move[3]-ONE) + 'a';
5134 if(move[4] == PieceToChar(BlackQueen)) move[4] = '+';
5136 if (appData.debugMode) {
5137 fprintf(debugFP, " out = '%s'\n", move);
5141 char yy_textstr[8000];
5143 /* Parser for moves from gnuchess, ICS, or user typein box */
5145 ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
5148 ChessMove *moveType;
5149 int *fromX, *fromY, *toX, *toY;
5152 *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
5154 switch (*moveType) {
5155 case WhitePromotion:
5156 case BlackPromotion:
5157 case WhiteNonPromotion:
5158 case BlackNonPromotion:
5160 case WhiteCapturesEnPassant:
5161 case BlackCapturesEnPassant:
5162 case WhiteKingSideCastle:
5163 case WhiteQueenSideCastle:
5164 case BlackKingSideCastle:
5165 case BlackQueenSideCastle:
5166 case WhiteKingSideCastleWild:
5167 case WhiteQueenSideCastleWild:
5168 case BlackKingSideCastleWild:
5169 case BlackQueenSideCastleWild:
5170 /* Code added by Tord: */
5171 case WhiteHSideCastleFR:
5172 case WhiteASideCastleFR:
5173 case BlackHSideCastleFR:
5174 case BlackASideCastleFR:
5175 /* End of code added by Tord */
5176 case IllegalMove: /* bug or odd chess variant */
5177 *fromX = currentMoveString[0] - AAA;
5178 *fromY = currentMoveString[1] - ONE;
5179 *toX = currentMoveString[2] - AAA;
5180 *toY = currentMoveString[3] - ONE;
5181 *promoChar = currentMoveString[4];
5182 if (*fromX < BOARD_LEFT || *fromX >= BOARD_RGHT || *fromY < 0 || *fromY >= BOARD_HEIGHT ||
5183 *toX < BOARD_LEFT || *toX >= BOARD_RGHT || *toY < 0 || *toY >= BOARD_HEIGHT) {
5184 if (appData.debugMode) {
5185 fprintf(debugFP, "Off-board move (%d,%d)-(%d,%d)%c, type = %d\n", *fromX, *fromY, *toX, *toY, *promoChar, *moveType);
5187 *fromX = *fromY = *toX = *toY = 0;
5190 if (appData.testLegality) {
5191 return (*moveType != IllegalMove);
5193 return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
5194 WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
5199 *fromX = *moveType == WhiteDrop ?
5200 (int) CharToPiece(ToUpper(currentMoveString[0])) :
5201 (int) CharToPiece(ToLower(currentMoveString[0]));
5203 *toX = currentMoveString[2] - AAA;
5204 *toY = currentMoveString[3] - ONE;
5205 *promoChar = NULLCHAR;
5209 case ImpossibleMove:
5219 if (appData.debugMode) {
5220 fprintf(debugFP, "Impossible move %s, type = %d\n", currentMoveString, *moveType);
5223 *fromX = *fromY = *toX = *toY = 0;
5224 *promoChar = NULLCHAR;
5229 Boolean pushed = FALSE;
5232 ParsePV(char *pv, Boolean storeComments)
5233 { // Parse a string of PV moves, and append to current game, behind forwardMostMove
5234 int fromX, fromY, toX, toY; char promoChar;
5239 if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
5240 PushInner(currentMove, forwardMostMove); // [HGM] engine might not be thinking on forwardMost position!
5243 endPV = forwardMostMove;
5245 while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
5246 if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
5247 valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
5248 if(appData.debugMode){
5249 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);
5251 if(!valid && nr == 0 &&
5252 ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
5253 nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
5254 // Hande case where played move is different from leading PV move
5255 CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
5256 CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
5257 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
5258 if(!CompareBoards(boards[endPV], boards[endPV+2])) {
5259 endPV += 2; // if position different, keep this
5260 moveList[endPV-1][0] = fromX + AAA;
5261 moveList[endPV-1][1] = fromY + ONE;
5262 moveList[endPV-1][2] = toX + AAA;
5263 moveList[endPV-1][3] = toY + ONE;
5264 parseList[endPV-1][0] = NULLCHAR;
5265 safeStrCpy(moveList[endPV-2], "_0_0", sizeof(moveList[endPV-2])/sizeof(moveList[endPV-2][0])); // suppress premove highlight on takeback move
5268 pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
5269 if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
5270 if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
5271 if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
5272 valid++; // allow comments in PV
5276 if(endPV+1 > framePtr) break; // no space, truncate
5279 CopyBoard(boards[endPV], boards[endPV-1]);
5280 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
5281 moveList[endPV-1][0] = fromX + AAA;
5282 moveList[endPV-1][1] = fromY + ONE;
5283 moveList[endPV-1][2] = toX + AAA;
5284 moveList[endPV-1][3] = toY + ONE;
5285 moveList[endPV-1][4] = promoChar;
5286 moveList[endPV-1][5] = NULLCHAR;
5287 strncat(moveList[endPV-1], "\n", MOVE_LEN);
5289 CoordsToAlgebraic(boards[endPV - 1],
5290 PosFlags(endPV - 1),
5291 fromY, fromX, toY, toX, promoChar,
5292 parseList[endPV - 1]);
5294 parseList[endPV-1][0] = NULLCHAR;
5296 currentMove = endPV;
5297 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5298 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5299 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5300 DrawPosition(TRUE, boards[currentMove]);
5304 LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
5309 if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
5310 lastX = x; lastY = y;
5311 while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
5313 while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
5314 if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
5316 do{ while(buf[index] && buf[index] != '\n') index++;
5317 } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
5319 ParsePV(buf+startPV, FALSE);
5320 *start = startPV; *end = index-1;
5325 LoadPV(int x, int y)
5326 { // called on right mouse click to load PV
5327 int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
5328 lastX = x; lastY = y;
5329 ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
5336 if(endPV < 0) return;
5338 currentMove = forwardMostMove;
5339 if(pushed) { PopInner(0); pushed = FALSE; } // restore shelved game contnuation
5340 ClearPremoveHighlights();
5341 DrawPosition(TRUE, boards[currentMove]);
5345 MovePV(int x, int y, int h)
5346 { // step through PV based on mouse coordinates (called on mouse move)
5347 int margin = h>>3, step = 0;
5349 // we must somehow check if right button is still down (might be released off board!)
5350 if(endPV < 0 && pieceSweep == EmptySquare) return; // needed in XBoard because lastX/Y is shared :-(
5351 if(abs(x - lastX) < 7 && abs(y - lastY) < 7) return;
5352 if( y > lastY + 2 ) step = -1; else if(y < lastY - 2) step = 1;
5354 lastX = x; lastY = y;
5356 if(pieceSweep != EmptySquare) { NextPiece(step); return; }
5357 if(endPV < 0) return;
5358 if(y < margin) step = 1; else
5359 if(y > h - margin) step = -1;
5360 if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
5361 currentMove += step;
5362 if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
5363 SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
5364 moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
5365 DrawPosition(FALSE, boards[currentMove]);
5369 // [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
5370 // All positions will have equal probability, but the current method will not provide a unique
5371 // numbering scheme for arrays that contain 3 or more pieces of the same kind.
5377 int piecesLeft[(int)BlackPawn];
5378 int seed, nrOfShuffles;
5380 void GetPositionNumber()
5381 { // sets global variable seed
5384 seed = appData.defaultFrcPosition;
5385 if(seed < 0) { // randomize based on time for negative FRC position numbers
5386 for(i=0; i<50; i++) seed += random();
5387 seed = random() ^ random() >> 8 ^ random() << 8;
5388 if(seed<0) seed = -seed;
5392 int put(Board board, int pieceType, int rank, int n, int shade)
5393 // put the piece on the (n-1)-th empty squares of the given shade
5397 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
5398 if( (((i-BOARD_LEFT)&1)+1) & shade && board[rank][i] == EmptySquare && n-- == 0) {
5399 board[rank][i] = (ChessSquare) pieceType;
5400 squaresLeft[((i-BOARD_LEFT)&1) + 1]--;
5402 piecesLeft[pieceType]--;
5410 void AddOnePiece(Board board, int pieceType, int rank, int shade)
5411 // calculate where the next piece goes, (any empty square), and put it there
5415 i = seed % squaresLeft[shade];
5416 nrOfShuffles *= squaresLeft[shade];
5417 seed /= squaresLeft[shade];
5418 put(board, pieceType, rank, i, shade);
5421 void AddTwoPieces(Board board, int pieceType, int rank)
5422 // calculate where the next 2 identical pieces go, (any empty square), and put it there
5424 int i, n=squaresLeft[ANY], j=n-1, k;
5426 k = n*(n-1)/2; // nr of possibilities, not counting permutations
5427 i = seed % k; // pick one
5430 while(i >= j) i -= j--;
5431 j = n - 1 - j; i += j;
5432 put(board, pieceType, rank, j, ANY);
5433 put(board, pieceType, rank, i, ANY);
5436 void SetUpShuffle(Board board, int number)
5440 GetPositionNumber(); nrOfShuffles = 1;
5442 squaresLeft[DARK] = (BOARD_RGHT - BOARD_LEFT + 1)/2;
5443 squaresLeft[ANY] = BOARD_RGHT - BOARD_LEFT;
5444 squaresLeft[LITE] = squaresLeft[ANY] - squaresLeft[DARK];
5446 for(p = 0; p<=(int)WhiteKing; p++) piecesLeft[p] = 0;
5448 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // count pieces and clear board
5449 p = (int) board[0][i];
5450 if(p < (int) BlackPawn) piecesLeft[p] ++;
5451 board[0][i] = EmptySquare;
5454 if(PosFlags(0) & F_ALL_CASTLE_OK) {
5455 // shuffles restricted to allow normal castling put KRR first
5456 if(piecesLeft[(int)WhiteKing]) // King goes rightish of middle
5457 put(board, WhiteKing, 0, (gameInfo.boardWidth+1)/2, ANY);
5458 else if(piecesLeft[(int)WhiteUnicorn]) // in Knightmate Unicorn castles
5459 put(board, WhiteUnicorn, 0, (gameInfo.boardWidth+1)/2, ANY);
5460 if(piecesLeft[(int)WhiteRook]) // First supply a Rook for K-side castling
5461 put(board, WhiteRook, 0, gameInfo.boardWidth-2, ANY);
5462 if(piecesLeft[(int)WhiteRook]) // Then supply a Rook for Q-side castling
5463 put(board, WhiteRook, 0, 0, ANY);
5464 // in variants with super-numerary Kings and Rooks, we leave these for the shuffle
5467 if(((BOARD_RGHT-BOARD_LEFT) & 1) == 0)
5468 // only for even boards make effort to put pairs of colorbound pieces on opposite colors
5469 for(p = (int) WhiteKing; p > (int) WhitePawn; p--) {
5470 if(p != (int) WhiteBishop && p != (int) WhiteFerz && p != (int) WhiteAlfil) continue;
5471 while(piecesLeft[p] >= 2) {
5472 AddOnePiece(board, p, 0, LITE);
5473 AddOnePiece(board, p, 0, DARK);
5475 // Odd color-bound pieces are shuffled with the rest (to not run out of paired squares)
5478 for(p = (int) WhiteKing - 2; p > (int) WhitePawn; p--) {
5479 // Remaining pieces (non-colorbound, or odd color bound) can be put anywhere
5480 // but we leave King and Rooks for last, to possibly obey FRC restriction
5481 if(p == (int)WhiteRook) continue;
5482 while(piecesLeft[p] >= 2) AddTwoPieces(board, p, 0); // add in pairs, for not counting permutations
5483 if(piecesLeft[p]) AddOnePiece(board, p, 0, ANY); // add the odd piece
5486 // now everything is placed, except perhaps King (Unicorn) and Rooks
5488 if(PosFlags(0) & F_FRC_TYPE_CASTLING) {
5489 // Last King gets castling rights
5490 while(piecesLeft[(int)WhiteUnicorn]) {
5491 i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5492 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5495 while(piecesLeft[(int)WhiteKing]) {
5496 i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
5497 initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
5502 while(piecesLeft[(int)WhiteKing]) AddOnePiece(board, WhiteKing, 0, ANY);
5503 while(piecesLeft[(int)WhiteUnicorn]) AddOnePiece(board, WhiteUnicorn, 0, ANY);
5506 // Only Rooks can be left; simply place them all
5507 while(piecesLeft[(int)WhiteRook]) {
5508 i = put(board, WhiteRook, 0, 0, ANY);
5509 if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
5512 initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
5514 initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
5517 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
5518 board[BOARD_HEIGHT-1][i] = (int) board[0][i] < BlackPawn ? WHITE_TO_BLACK board[0][i] : EmptySquare;
5521 if(number >= 0) appData.defaultFrcPosition %= nrOfShuffles; // normalize
5524 int SetCharTable( char *table, const char * map )
5525 /* [HGM] moved here from winboard.c because of its general usefulness */
5526 /* Basically a safe strcpy that uses the last character as King */
5528 int result = FALSE; int NrPieces;
5530 if( map != NULL && (NrPieces=strlen(map)) <= (int) EmptySquare
5531 && NrPieces >= 12 && !(NrPieces&1)) {
5532 int i; /* [HGM] Accept even length from 12 to 34 */
5534 for( i=0; i<(int) EmptySquare; i++ ) table[i] = '.';
5535 for( i=0; i<NrPieces/2-1; i++ ) {
5537 table[i + (int)BlackPawn - (int) WhitePawn] = map[i+NrPieces/2];
5539 table[(int) WhiteKing] = map[NrPieces/2-1];
5540 table[(int) BlackKing] = map[NrPieces-1];
5548 void Prelude(Board board)
5549 { // [HGM] superchess: random selection of exo-pieces
5550 int i, j, k; ChessSquare p;
5551 static ChessSquare exoPieces[4] = { WhiteAngel, WhiteMarshall, WhiteSilver, WhiteLance };
5553 GetPositionNumber(); // use FRC position number
5555 if(appData.pieceToCharTable != NULL) { // select pieces to participate from given char table
5556 SetCharTable(pieceToChar, appData.pieceToCharTable);
5557 for(i=(int)WhiteQueen+1, j=0; i<(int)WhiteKing && j<4; i++)
5558 if(PieceToChar((ChessSquare)i) != '.') exoPieces[j++] = (ChessSquare) i;
5561 j = seed%4; seed /= 4;
5562 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5563 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5564 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5565 j = seed%3 + (seed%3 >= j); seed /= 3;
5566 p = board[0][BOARD_LEFT+j]; board[0][BOARD_LEFT+j] = EmptySquare; k = PieceToNumber(p);
5567 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5568 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5569 j = seed%3; seed /= 3;
5570 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5571 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5572 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5573 j = seed%2 + (seed%2 >= j); seed /= 2;
5574 p = board[0][BOARD_LEFT+j+5]; board[0][BOARD_LEFT+j+5] = EmptySquare; k = PieceToNumber(p);
5575 board[k][BOARD_WIDTH-1] = p; board[k][BOARD_WIDTH-2]++;
5576 board[BOARD_HEIGHT-1-k][0] = WHITE_TO_BLACK p; board[BOARD_HEIGHT-1-k][1]++;
5577 j = seed%4; seed /= 4; put(board, exoPieces[3], 0, j, ANY);
5578 j = seed%3; seed /= 3; put(board, exoPieces[2], 0, j, ANY);
5579 j = seed%2; seed /= 2; put(board, exoPieces[1], 0, j, ANY);
5580 put(board, exoPieces[0], 0, 0, ANY);
5581 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) board[BOARD_HEIGHT-1][i] = WHITE_TO_BLACK board[0][i];
5585 InitPosition(redraw)
5588 ChessSquare (* pieces)[BOARD_FILES];
5589 int i, j, pawnRow, overrule,
5590 oldx = gameInfo.boardWidth,
5591 oldy = gameInfo.boardHeight,
5592 oldh = gameInfo.holdingsWidth;
5595 if(appData.icsActive) shuffleOpenings = FALSE; // [HGM] shuffle: in ICS mode, only shuffle on ICS request
5597 /* [AS] Initialize pv info list [HGM] and game status */
5599 for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
5600 pvInfoList[i].depth = 0;
5601 boards[i][EP_STATUS] = EP_NONE;
5602 for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
5605 initialRulePlies = 0; /* 50-move counter start */
5607 castlingRank[0] = castlingRank[1] = castlingRank[2] = 0;
5608 castlingRank[3] = castlingRank[4] = castlingRank[5] = BOARD_HEIGHT-1;
5612 /* [HGM] logic here is completely changed. In stead of full positions */
5613 /* the initialized data only consist of the two backranks. The switch */
5614 /* selects which one we will use, which is than copied to the Board */
5615 /* initialPosition, which for the rest is initialized by Pawns and */
5616 /* empty squares. This initial position is then copied to boards[0], */
5617 /* possibly after shuffling, so that it remains available. */
5619 gameInfo.holdingsWidth = 0; /* default board sizes */
5620 gameInfo.boardWidth = 8;
5621 gameInfo.boardHeight = 8;
5622 gameInfo.holdingsSize = 0;
5623 nrCastlingRights = -1; /* [HGM] Kludge to indicate default should be used */
5624 for(i=0; i<BOARD_FILES-2; i++)
5625 initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
5626 initialPosition[EP_STATUS] = EP_NONE;
5627 SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
5628 if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
5629 SetCharTable(pieceNickName, appData.pieceNickNames);
5630 else SetCharTable(pieceNickName, "............");
5633 switch (gameInfo.variant) {
5634 case VariantFischeRandom:
5635 shuffleOpenings = TRUE;
5638 case VariantShatranj:
5639 pieces = ShatranjArray;
5640 nrCastlingRights = 0;
5641 SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
5644 pieces = makrukArray;
5645 nrCastlingRights = 0;
5646 startedFromSetupPosition = TRUE;
5647 SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
5649 case VariantTwoKings:
5650 pieces = twoKingsArray;
5652 case VariantCapaRandom:
5653 shuffleOpenings = TRUE;
5654 case VariantCapablanca:
5655 pieces = CapablancaArray;
5656 gameInfo.boardWidth = 10;
5657 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5660 pieces = GothicArray;
5661 gameInfo.boardWidth = 10;
5662 SetCharTable(pieceToChar, "PNBRQ..ACKpnbrq..ack");
5665 SetCharTable(pieceToChar, "PNBRQ..HEKpnbrq..hek");
5666 gameInfo.holdingsSize = 7;
5669 pieces = JanusArray;
5670 gameInfo.boardWidth = 10;
5671 SetCharTable(pieceToChar, "PNBRQ..JKpnbrq..jk");
5672 nrCastlingRights = 6;
5673 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5674 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5675 initialPosition[CASTLING][2] = initialRights[2] =(BOARD_WIDTH-1)>>1;
5676 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5677 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5678 initialPosition[CASTLING][5] = initialRights[5] =(BOARD_WIDTH-1)>>1;
5681 pieces = FalconArray;
5682 gameInfo.boardWidth = 10;
5683 SetCharTable(pieceToChar, "PNBRQ.............FKpnbrq.............fk");
5685 case VariantXiangqi:
5686 pieces = XiangqiArray;
5687 gameInfo.boardWidth = 9;
5688 gameInfo.boardHeight = 10;
5689 nrCastlingRights = 0;
5690 SetCharTable(pieceToChar, "PH.R.AE..K.C.ph.r.ae..k.c.");
5693 pieces = ShogiArray;
5694 gameInfo.boardWidth = 9;
5695 gameInfo.boardHeight = 9;
5696 gameInfo.holdingsSize = 7;
5697 nrCastlingRights = 0;
5698 SetCharTable(pieceToChar, "PNBRLS...G.++++++Kpnbrls...g.++++++k");
5700 case VariantCourier:
5701 pieces = CourierArray;
5702 gameInfo.boardWidth = 12;
5703 nrCastlingRights = 0;
5704 SetCharTable(pieceToChar, "PNBR.FE..WMKpnbr.fe..wmk");
5706 case VariantKnightmate:
5707 pieces = KnightmateArray;
5708 SetCharTable(pieceToChar, "P.BRQ.....M.........K.p.brq.....m.........k.");
5710 case VariantSpartan:
5711 pieces = SpartanArray;
5712 SetCharTable(pieceToChar, "PNBRQ................K......lwg.....c...h..k");
5715 pieces = fairyArray;
5716 SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
5719 pieces = GreatArray;
5720 gameInfo.boardWidth = 10;
5721 SetCharTable(pieceToChar, "PN....E...S..HWGMKpn....e...s..hwgmk");
5722 gameInfo.holdingsSize = 8;
5726 SetCharTable(pieceToChar, "PNBRQ..SE.......V.AKpnbrq..se.......v.ak");
5727 gameInfo.holdingsSize = 8;
5728 startedFromSetupPosition = TRUE;
5730 case VariantCrazyhouse:
5731 case VariantBughouse:
5733 SetCharTable(pieceToChar, "PNBRQ.......~~~~Kpnbrq.......~~~~k");
5734 gameInfo.holdingsSize = 5;
5736 case VariantWildCastle:
5738 /* !!?shuffle with kings guaranteed to be on d or e file */
5739 shuffleOpenings = 1;
5741 case VariantNoCastle:
5743 nrCastlingRights = 0;
5744 /* !!?unconstrained back-rank shuffle */
5745 shuffleOpenings = 1;
5750 if(appData.NrFiles >= 0) {
5751 if(gameInfo.boardWidth != appData.NrFiles) overrule++;
5752 gameInfo.boardWidth = appData.NrFiles;
5754 if(appData.NrRanks >= 0) {
5755 gameInfo.boardHeight = appData.NrRanks;
5757 if(appData.holdingsSize >= 0) {
5758 i = appData.holdingsSize;
5759 if(i > gameInfo.boardHeight) i = gameInfo.boardHeight;
5760 gameInfo.holdingsSize = i;
5762 if(gameInfo.holdingsSize) gameInfo.holdingsWidth = 2;
5763 if(BOARD_HEIGHT > BOARD_RANKS || BOARD_WIDTH > BOARD_FILES)
5764 DisplayFatalError(_("Recompile to support this BOARD_RANKS or BOARD_FILES!"), 0, 2);
5766 pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
5767 if(pawnRow < 1) pawnRow = 1;
5768 if(gameInfo.variant == VariantMakruk) pawnRow = 2;
5770 /* User pieceToChar list overrules defaults */
5771 if(appData.pieceToCharTable != NULL)
5772 SetCharTable(pieceToChar, appData.pieceToCharTable);
5774 for( j=0; j<BOARD_WIDTH; j++ ) { ChessSquare s = EmptySquare;
5776 if(j==BOARD_LEFT-1 || j==BOARD_RGHT)
5777 s = (ChessSquare) 0; /* account holding counts in guard band */
5778 for( i=0; i<BOARD_HEIGHT; i++ )
5779 initialPosition[i][j] = s;
5781 if(j < BOARD_LEFT || j >= BOARD_RGHT || overrule) continue;
5782 initialPosition[0][j] = pieces[0][j-gameInfo.holdingsWidth];
5783 initialPosition[pawnRow][j] = WhitePawn;
5784 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = gameInfo.variant == VariantSpartan ? BlackLance : BlackPawn;
5785 if(gameInfo.variant == VariantXiangqi) {
5787 initialPosition[pawnRow][j] =
5788 initialPosition[BOARD_HEIGHT-pawnRow-1][j] = EmptySquare;
5789 if(j==BOARD_LEFT+1 || j>=BOARD_RGHT-2) {
5790 initialPosition[2][j] = WhiteCannon;
5791 initialPosition[BOARD_HEIGHT-3][j] = BlackCannon;
5795 initialPosition[BOARD_HEIGHT-1][j] = pieces[1][j-gameInfo.holdingsWidth];
5797 if( (gameInfo.variant == VariantShogi) && !overrule ) {
5800 initialPosition[1][j] = WhiteBishop;
5801 initialPosition[BOARD_HEIGHT-2][j] = BlackRook;
5803 initialPosition[1][j] = WhiteRook;
5804 initialPosition[BOARD_HEIGHT-2][j] = BlackBishop;
5807 if( nrCastlingRights == -1) {
5808 /* [HGM] Build normal castling rights (must be done after board sizing!) */
5809 /* This sets default castling rights from none to normal corners */
5810 /* Variants with other castling rights must set them themselves above */
5811 nrCastlingRights = 6;
5813 initialPosition[CASTLING][0] = initialRights[0] = BOARD_RGHT-1;
5814 initialPosition[CASTLING][1] = initialRights[1] = BOARD_LEFT;
5815 initialPosition[CASTLING][2] = initialRights[2] = BOARD_WIDTH>>1;
5816 initialPosition[CASTLING][3] = initialRights[3] = BOARD_RGHT-1;
5817 initialPosition[CASTLING][4] = initialRights[4] = BOARD_LEFT;
5818 initialPosition[CASTLING][5] = initialRights[5] = BOARD_WIDTH>>1;
5821 if(gameInfo.variant == VariantSuper) Prelude(initialPosition);
5822 if(gameInfo.variant == VariantGreat) { // promotion commoners
5823 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-1] = WhiteMan;
5824 initialPosition[PieceToNumber(WhiteMan)][BOARD_WIDTH-2] = 9;
5825 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][0] = BlackMan;
5826 initialPosition[BOARD_HEIGHT-1-PieceToNumber(WhiteMan)][1] = 9;
5828 if( gameInfo.variant == VariantSChess ) {
5829 initialPosition[1][0] = BlackMarshall;
5830 initialPosition[2][0] = BlackAngel;
5831 initialPosition[6][BOARD_WIDTH-1] = WhiteMarshall;
5832 initialPosition[5][BOARD_WIDTH-1] = WhiteAngel;
5833 initialPosition[1][1] = initialPosition[2][1] =
5834 initialPosition[6][BOARD_WIDTH-2] = initialPosition[5][BOARD_WIDTH-2] = 1;
5836 if (appData.debugMode) {
5837 fprintf(debugFP, "shuffleOpenings = %d\n", shuffleOpenings);
5839 if(shuffleOpenings) {
5840 SetUpShuffle(initialPosition, appData.defaultFrcPosition);
5841 startedFromSetupPosition = TRUE;
5843 if(startedFromPositionFile) {
5844 /* [HGM] loadPos: use PositionFile for every new game */
5845 CopyBoard(initialPosition, filePosition);
5846 for(i=0; i<nrCastlingRights; i++)
5847 initialRights[i] = filePosition[CASTLING][i];
5848 startedFromSetupPosition = TRUE;
5851 CopyBoard(boards[0], initialPosition);
5853 if(oldx != gameInfo.boardWidth ||
5854 oldy != gameInfo.boardHeight ||
5855 oldv != gameInfo.variant ||
5856 oldh != gameInfo.holdingsWidth
5858 InitDrawingSizes(-2 ,0);
5860 oldv = gameInfo.variant;
5862 DrawPosition(TRUE, boards[currentMove]);
5866 SendBoard(cps, moveNum)
5867 ChessProgramState *cps;
5870 char message[MSG_SIZ];
5872 if (cps->useSetboard) {
5873 char* fen = PositionToFEN(moveNum, cps->fenOverride);
5874 snprintf(message, MSG_SIZ,"setboard %s\n", fen);
5875 SendToProgram(message, cps);
5881 /* Kludge to set black to move, avoiding the troublesome and now
5882 * deprecated "black" command.
5884 if (!WhiteOnMove(moveNum)) // [HGM] but better a deprecated command than an illegal move...
5885 SendToProgram(boards[0][1][BOARD_LEFT] == WhitePawn ? "a2a3\n" : "black\n", cps);
5887 SendToProgram("edit\n", cps);
5888 SendToProgram("#\n", cps);
5889 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5890 bp = &boards[moveNum][i][BOARD_LEFT];
5891 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5892 if ((int) *bp < (int) BlackPawn) {
5893 snprintf(message, MSG_SIZ, "%c%c%c\n", PieceToChar(*bp),
5895 if(message[0] == '+' || message[0] == '~') {
5896 snprintf(message, MSG_SIZ,"%c%c%c+\n",
5897 PieceToChar((ChessSquare)(DEMOTED *bp)),
5900 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5901 message[1] = BOARD_RGHT - 1 - j + '1';
5902 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5904 SendToProgram(message, cps);
5909 SendToProgram("c\n", cps);
5910 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
5911 bp = &boards[moveNum][i][BOARD_LEFT];
5912 for (j = BOARD_LEFT; j < BOARD_RGHT; j++, bp++) {
5913 if (((int) *bp != (int) EmptySquare)
5914 && ((int) *bp >= (int) BlackPawn)) {
5915 snprintf(message,MSG_SIZ, "%c%c%c\n", ToUpper(PieceToChar(*bp)),
5917 if(message[0] == '+' || message[0] == '~') {
5918 snprintf(message, MSG_SIZ,"%c%c%c+\n",
5919 PieceToChar((ChessSquare)(DEMOTED *bp)),
5922 if(cps->alphaRank) { /* [HGM] shogi: translate coords */
5923 message[1] = BOARD_RGHT - 1 - j + '1';
5924 message[2] = BOARD_HEIGHT - 1 - i + 'a';
5926 SendToProgram(message, cps);
5931 SendToProgram(".\n", cps);
5933 setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
5937 DefaultPromoChoice(int white)
5940 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
5941 result = WhiteFerz; // no choice
5942 else if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantGiveaway)
5943 result= WhiteKing; // in Suicide Q is the last thing we want
5944 else if(gameInfo.variant == VariantSpartan)
5945 result = white ? WhiteQueen : WhiteAngel;
5946 else result = WhiteQueen;
5947 if(!white) result = WHITE_TO_BLACK result;
5951 static int autoQueen; // [HGM] oneclick
5954 HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
5956 /* [HGM] rewritten IsPromotion to only flag promotions that offer a choice */
5957 /* [HGM] add Shogi promotions */
5958 int promotionZoneSize=1, highestPromotingPiece = (int)WhitePawn;
5963 if(fromX < BOARD_LEFT || fromX >= BOARD_RGHT) return FALSE; // drop
5964 if(toX < BOARD_LEFT || toX >= BOARD_RGHT) return FALSE; // move into holdings
5966 if(gameMode == EditPosition || gameInfo.variant == VariantXiangqi || // no promotions
5967 !(fromX >=0 && fromY >= 0 && toX >= 0 && toY >= 0) ) // invalid move
5970 piece = boards[currentMove][fromY][fromX];
5971 if(gameInfo.variant == VariantShogi) {
5972 promotionZoneSize = BOARD_HEIGHT/3;
5973 highestPromotingPiece = (int)WhiteFerz;
5974 } else if(gameInfo.variant == VariantMakruk) {
5975 promotionZoneSize = 3;
5978 // Treat Lance as Pawn when it is not representing Amazon
5979 if(gameInfo.variant != VariantSuper) {
5980 if(piece == WhiteLance) piece = WhitePawn; else
5981 if(piece == BlackLance) piece = BlackPawn;
5984 // next weed out all moves that do not touch the promotion zone at all
5985 if((int)piece >= BlackPawn) {
5986 if(toY >= promotionZoneSize && fromY >= promotionZoneSize)
5988 highestPromotingPiece = WHITE_TO_BLACK highestPromotingPiece;
5990 if( toY < BOARD_HEIGHT - promotionZoneSize &&
5991 fromY < BOARD_HEIGHT - promotionZoneSize) return FALSE;
5994 if( (int)piece > highestPromotingPiece ) return FALSE; // non-promoting piece
5996 // weed out mandatory Shogi promotions
5997 if(gameInfo.variant == VariantShogi) {
5998 if(piece >= BlackPawn) {
5999 if(toY == 0 && piece == BlackPawn ||
6000 toY == 0 && piece == BlackQueen ||
6001 toY <= 1 && piece == BlackKnight) {
6006 if(toY == BOARD_HEIGHT-1 && piece == WhitePawn ||
6007 toY == BOARD_HEIGHT-1 && piece == WhiteQueen ||
6008 toY >= BOARD_HEIGHT-2 && piece == WhiteKnight) {
6015 // weed out obviously illegal Pawn moves
6016 if(appData.testLegality && (piece == WhitePawn || piece == BlackPawn) ) {
6017 if(toX > fromX+1 || toX < fromX-1) return FALSE; // wide
6018 if(piece == WhitePawn && toY != fromY+1) return FALSE; // deep
6019 if(piece == BlackPawn && toY != fromY-1) return FALSE; // deep
6020 if(fromX != toX && gameInfo.variant == VariantShogi) return FALSE;
6021 // note we are not allowed to test for valid (non-)capture, due to premove
6024 // we either have a choice what to promote to, or (in Shogi) whether to promote
6025 if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
6026 *promoChoice = PieceToChar(BlackFerz); // no choice
6029 // no sense asking what we must promote to if it is going to explode...
6030 if(gameInfo.variant == VariantAtomic && boards[currentMove][toY][toX] != EmptySquare) {
6031 *promoChoice = PieceToChar(BlackQueen); // Queen as good as any
6034 // give caller the default choice even if we will not make it
6035 *promoChoice = ToLower(PieceToChar(defaultPromoChoice));
6036 if(gameInfo.variant == VariantShogi) *promoChoice = '+';
6037 if(appData.sweepSelect && gameInfo.variant != VariantGreat
6038 && gameInfo.variant != VariantShogi
6039 && gameInfo.variant != VariantSuper) return FALSE;
6040 if(autoQueen) return FALSE; // predetermined
6042 // suppress promotion popup on illegal moves that are not premoves
6043 premove = gameMode == IcsPlayingWhite && !WhiteOnMove(currentMove) ||
6044 gameMode == IcsPlayingBlack && WhiteOnMove(currentMove);
6045 if(appData.testLegality && !premove) {
6046 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6047 fromY, fromX, toY, toX, gameInfo.variant == VariantShogi ? '+' : NULLCHAR);
6048 if(moveType != WhitePromotion && moveType != BlackPromotion)
6056 InPalace(row, column)
6058 { /* [HGM] for Xiangqi */
6059 if( (row < 3 || row > BOARD_HEIGHT-4) &&
6060 column < (BOARD_WIDTH + 4)/2 &&
6061 column > (BOARD_WIDTH - 5)/2 ) return TRUE;
6066 PieceForSquare (x, y)
6070 if (x < 0 || x >= BOARD_WIDTH || y < 0 || y >= BOARD_HEIGHT)
6073 return boards[currentMove][y][x];
6077 OKToStartUserMove(x, y)
6080 ChessSquare from_piece;
6083 if (matchMode) return FALSE;
6084 if (gameMode == EditPosition) return TRUE;
6086 if (x >= 0 && y >= 0)
6087 from_piece = boards[currentMove][y][x];
6089 from_piece = EmptySquare;
6091 if (from_piece == EmptySquare) return FALSE;
6093 white_piece = (int)from_piece >= (int)WhitePawn &&
6094 (int)from_piece < (int)BlackPawn; /* [HGM] can be > King! */
6097 case PlayFromGameFile:
6099 case TwoMachinesPlay:
6107 case MachinePlaysWhite:
6108 case IcsPlayingBlack:
6109 if (appData.zippyPlay) return FALSE;
6111 DisplayMoveError(_("You are playing Black"));
6116 case MachinePlaysBlack:
6117 case IcsPlayingWhite:
6118 if (appData.zippyPlay) return FALSE;
6120 DisplayMoveError(_("You are playing White"));
6126 if (!white_piece && WhiteOnMove(currentMove)) {
6127 DisplayMoveError(_("It is White's turn"));
6130 if (white_piece && !WhiteOnMove(currentMove)) {
6131 DisplayMoveError(_("It is Black's turn"));
6134 if (cmailMsgLoaded && (currentMove < cmailOldMove)) {
6135 /* Editing correspondence game history */
6136 /* Could disallow this or prompt for confirmation */
6141 case BeginningOfGame:
6142 if (appData.icsActive) return FALSE;
6143 if (!appData.noChessProgram) {
6145 DisplayMoveError(_("You are playing White"));
6152 if (!white_piece && WhiteOnMove(currentMove)) {
6153 DisplayMoveError(_("It is White's turn"));
6156 if (white_piece && !WhiteOnMove(currentMove)) {
6157 DisplayMoveError(_("It is Black's turn"));
6166 if (currentMove != forwardMostMove && gameMode != AnalyzeMode
6167 && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
6168 && gameMode != AnalyzeFile && gameMode != Training) {
6169 DisplayMoveError(_("Displayed position is not current"));
6176 OnlyMove(int *x, int *y, Boolean captures) {
6177 DisambiguateClosure cl;
6178 if (appData.zippyPlay) return FALSE;
6180 case MachinePlaysBlack:
6181 case IcsPlayingWhite:
6182 case BeginningOfGame:
6183 if(!WhiteOnMove(currentMove)) return FALSE;
6185 case MachinePlaysWhite:
6186 case IcsPlayingBlack:
6187 if(WhiteOnMove(currentMove)) return FALSE;
6194 cl.pieceIn = EmptySquare;
6199 cl.promoCharIn = NULLCHAR;
6200 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6201 if( cl.kind == NormalMove ||
6202 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6203 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6204 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6211 if(cl.kind != ImpossibleMove) return FALSE;
6212 cl.pieceIn = EmptySquare;
6217 cl.promoCharIn = NULLCHAR;
6218 Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
6219 if( cl.kind == NormalMove ||
6220 cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
6221 cl.kind == WhitePromotion || cl.kind == BlackPromotion ||
6222 cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
6227 autoQueen = TRUE; // act as if autoQueen on when we click to-square
6233 FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
6234 int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
6235 int lastLoadGameUseList = FALSE;
6236 char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
6237 ChessMove lastLoadGameStart = EndOfFile;
6240 UserMoveEvent(fromX, fromY, toX, toY, promoChar)
6241 int fromX, fromY, toX, toY;
6245 ChessSquare pdown, pup;
6247 /* Check if the user is playing in turn. This is complicated because we
6248 let the user "pick up" a piece before it is his turn. So the piece he
6249 tried to pick up may have been captured by the time he puts it down!
6250 Therefore we use the color the user is supposed to be playing in this
6251 test, not the color of the piece that is currently on the starting
6252 square---except in EditGame mode, where the user is playing both
6253 sides; fortunately there the capture race can't happen. (It can
6254 now happen in IcsExamining mode, but that's just too bad. The user
6255 will get a somewhat confusing message in that case.)
6259 case PlayFromGameFile:
6261 case TwoMachinesPlay:
6265 /* We switched into a game mode where moves are not accepted,
6266 perhaps while the mouse button was down. */
6269 case MachinePlaysWhite:
6270 /* User is moving for Black */
6271 if (WhiteOnMove(currentMove)) {
6272 DisplayMoveError(_("It is White's turn"));
6277 case MachinePlaysBlack:
6278 /* User is moving for White */
6279 if (!WhiteOnMove(currentMove)) {
6280 DisplayMoveError(_("It is Black's turn"));
6287 case BeginningOfGame:
6290 if(fromY == DROP_RANK) break; // [HGM] drop moves (entered through move type-in) are automatically assigned to side-to-move
6291 if ((int) boards[currentMove][fromY][fromX] >= (int) BlackPawn &&
6292 (int) boards[currentMove][fromY][fromX] < (int) EmptySquare) {
6293 /* User is moving for Black */
6294 if (WhiteOnMove(currentMove)) {
6295 DisplayMoveError(_("It is White's turn"));
6299 /* User is moving for White */
6300 if (!WhiteOnMove(currentMove)) {
6301 DisplayMoveError(_("It is Black's turn"));
6307 case IcsPlayingBlack:
6308 /* User is moving for Black */
6309 if (WhiteOnMove(currentMove)) {
6310 if (!appData.premove) {
6311 DisplayMoveError(_("It is White's turn"));
6312 } else if (toX >= 0 && toY >= 0) {
6315 premoveFromX = fromX;
6316 premoveFromY = fromY;
6317 premovePromoChar = promoChar;
6319 if (appData.debugMode)
6320 fprintf(debugFP, "Got premove: fromX %d,"
6321 "fromY %d, toX %d, toY %d\n",
6322 fromX, fromY, toX, toY);
6328 case IcsPlayingWhite:
6329 /* User is moving for White */
6330 if (!WhiteOnMove(currentMove)) {
6331 if (!appData.premove) {
6332 DisplayMoveError(_("It is Black's turn"));
6333 } else if (toX >= 0 && toY >= 0) {
6336 premoveFromX = fromX;
6337 premoveFromY = fromY;
6338 premovePromoChar = promoChar;
6340 if (appData.debugMode)
6341 fprintf(debugFP, "Got premove: fromX %d,"
6342 "fromY %d, toX %d, toY %d\n",
6343 fromX, fromY, toX, toY);
6353 /* EditPosition, empty square, or different color piece;
6354 click-click move is possible */
6355 if (toX == -2 || toY == -2) {
6356 boards[0][fromY][fromX] = EmptySquare;
6357 DrawPosition(FALSE, boards[currentMove]);
6359 } else if (toX >= 0 && toY >= 0) {
6360 boards[0][toY][toX] = boards[0][fromY][fromX];
6361 if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
6362 if(boards[0][fromY][0] != EmptySquare) {
6363 if(boards[0][fromY][1]) boards[0][fromY][1]--;
6364 if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
6367 if(fromX == BOARD_RGHT+1) {
6368 if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
6369 if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
6370 if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
6373 boards[0][fromY][fromX] = EmptySquare;
6374 DrawPosition(FALSE, boards[currentMove]);
6380 if(toX < 0 || toY < 0) return;
6381 pdown = boards[currentMove][fromY][fromX];
6382 pup = boards[currentMove][toY][toX];
6384 /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
6385 if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ) {
6386 if( pup != EmptySquare ) return;
6387 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
6388 if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
6389 moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
6390 // holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
6391 if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
6392 fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
6393 while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
6397 /* [HGM] always test for legality, to get promotion info */
6398 moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
6399 fromY, fromX, toY, toX, promoChar);
6400 /* [HGM] but possibly ignore an IllegalMove result */
6401 if (appData.testLegality) {
6402 if (moveType == IllegalMove || moveType == ImpossibleMove) {
6403 DisplayMoveError(_("Illegal move"));
6408 FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
6411 /* Common tail of UserMoveEvent and DropMenuEvent */
6413 FinishMove(moveType, fromX, fromY, toX, toY, promoChar)
6415 int fromX, fromY, toX, toY;
6416 /*char*/int promoChar;
6420 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
6421 // [HGM] superchess: suppress promotions to non-available piece
6422 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
6423 if(WhiteOnMove(currentMove)) {
6424 if(!boards[currentMove][k][BOARD_WIDTH-2]) return 0;
6426 if(!boards[currentMove][BOARD_HEIGHT-1-k][1]) return 0;
6430 /* [HGM] <popupFix> kludge to avoid having to know the exact promotion
6431 move type in caller when we know the move is a legal promotion */
6432 if(moveType == NormalMove && promoChar)
6433 moveType = WhiteOnMove(currentMove) ? WhitePromotion : BlackPromotion;
6435 /* [HGM] <popupFix> The following if has been moved here from
6436 UserMoveEvent(). Because it seemed to belong here (why not allow
6437 piece drops in training games?), and because it can only be
6438 performed after it is known to what we promote. */
6439 if (gameMode == Training) {
6440 /* compare the move played on the board to the next move in the
6441 * game. If they match, display the move and the opponent's response.
6442 * If they don't match, display an error message.
6446 CopyBoard(testBoard, boards[currentMove]);
6447 ApplyMove(fromX, fromY, toX, toY, promoChar, testBoard);
6449 if (CompareBoards(testBoard, boards[currentMove+1])) {
6450 ForwardInner(currentMove+1);
6452 /* Autoplay the opponent's response.
6453 * if appData.animate was TRUE when Training mode was entered,
6454 * the response will be animated.
6456 saveAnimate = appData.animate;
6457 appData.animate = animateTraining;
6458 ForwardInner(currentMove+1);
6459 appData.animate = saveAnimate;
6461 /* check for the end of the game */
6462 if (currentMove >= forwardMostMove) {
6463 gameMode = PlayFromGameFile;
6465 SetTrainingModeOff();
6466 DisplayInformation(_("End of game"));
6469 DisplayError(_("Incorrect move"), 0);
6474 /* Ok, now we know that the move is good, so we can kill
6475 the previous line in Analysis Mode */
6476 if ((gameMode == AnalyzeMode || gameMode == EditGame)
6477 && currentMove < forwardMostMove) {
6478 if(appData.variations && shiftKey) PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
6479 else forwardMostMove = currentMove;
6482 /* If we need the chess program but it's dead, restart it */
6483 ResurrectChessProgram();
6485 /* A user move restarts a paused game*/
6489 thinkOutput[0] = NULLCHAR;
6491 MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
6493 if(Adjudicate(NULL)) { // [HGM] adjudicate: take care of automatic game end
6494 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6498 if (gameMode == BeginningOfGame) {
6499 if (appData.noChessProgram) {
6500 gameMode = EditGame;
6504 gameMode = MachinePlaysBlack;
6507 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
6509 if (first.sendName) {
6510 snprintf(buf, MSG_SIZ,"name %s\n", gameInfo.white);
6511 SendToProgram(buf, &first);
6518 /* Relay move to ICS or chess engine */
6519 if (appData.icsActive) {
6520 if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
6521 gameMode == IcsExamining) {
6522 if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
6523 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
6525 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6527 // also send plain move, in case ICS does not understand atomic claims
6528 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
6532 if (first.sendTime && (gameMode == BeginningOfGame ||
6533 gameMode == MachinePlaysWhite ||
6534 gameMode == MachinePlaysBlack)) {
6535 SendTimeRemaining(&first, gameMode != MachinePlaysBlack);
6537 if (gameMode != EditGame && gameMode != PlayFromGameFile) {
6538 // [HGM] book: if program might be playing, let it use book
6539 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, FALSE);
6540 first.maybeThinking = TRUE;
6541 } else SendMoveToProgram(forwardMostMove-1, &first);
6542 if (currentMove == cmailOldMove + 1) {
6543 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
6547 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
6551 if(appData.testLegality)
6552 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
6558 if (WhiteOnMove(currentMove)) {
6559 GameEnds(BlackWins, "Black mates", GE_PLAYER);
6561 GameEnds(WhiteWins, "White mates", GE_PLAYER);
6565 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
6570 case MachinePlaysBlack:
6571 case MachinePlaysWhite:
6572 /* disable certain menu options while machine is thinking */
6573 SetMachineThinkingEnables();
6580 userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
6581 promoDefaultAltered = FALSE; // [HGM] fall back on default choice
6583 if(bookHit) { // [HGM] book: simulate book reply
6584 static char bookMove[MSG_SIZ]; // a bit generous?
6586 programStats.nodes = programStats.depth = programStats.time =
6587 programStats.score = programStats.got_only_move = 0;
6588 sprintf(programStats.movelist, "%s (xbook)", bookHit);
6590 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
6591 strcat(bookMove, bookHit);
6592 HandleMachineMove(bookMove, &first);
6598 Mark(board, flags, kind, rf, ff, rt, ft, closure)
6605 typedef char Markers[BOARD_RANKS][BOARD_FILES];
6606 Markers *m = (Markers *) closure;
6607 if(rf == fromY && ff == fromX)
6608 (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
6609 || kind == WhiteCapturesEnPassant
6610 || kind == BlackCapturesEnPassant);
6611 else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
6615 MarkTargetSquares(int clear)
6618 if(!appData.markers || !appData.highlightDragging ||
6619 !appData.testLegality || gameMode == EditPosition) return;
6621 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
6624 GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
6625 if(PosFlags(0) & F_MANDATORY_CAPTURE) {
6626 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
6628 for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
6631 DrawPosition(TRUE, NULL);
6635 Explode(Board board, int fromX, int fromY, int toX, int toY)
6637 if(gameInfo.variant == VariantAtomic &&
6638 (board[toY][toX] != EmptySquare || // capture?
6639 toX != fromX && (board[fromY][fromX] == WhitePawn || // e.p. ?
6640 board[fromY][fromX] == BlackPawn )
6642 AnimateAtomicCapture(board, fromX, fromY, toX, toY);
6648 ChessSquare gatingPiece = EmptySquare; // exported to front-end, for dragging
6650 int CanPromote(ChessSquare piece, int y)
6652 if(gameMode == EditPosition) return FALSE; // no promotions when editing position
6653 // some variants have fixed promotion piece, no promotion at all, or another selection mechanism
6654 if(gameInfo.variant == VariantShogi || gameInfo.variant == VariantXiangqi ||
6655 gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat ||
6656 gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier ||
6657 gameInfo.variant == VariantMakruk) return FALSE;
6658 return (piece == BlackPawn && y == 1 ||
6659 piece == WhitePawn && y == BOARD_HEIGHT-2 ||
6660 piece == BlackLance && y == 1 ||
6661 piece == WhiteLance && y == BOARD_HEIGHT-2 );
6664 void LeftClick(ClickType clickType, int xPix, int yPix)
6667 Boolean saveAnimate;
6668 static int second = 0, promotionChoice = 0, clearFlag = 0;
6669 char promoChoice = NULLCHAR;
6672 if(appData.seekGraph && appData.icsActive && loggedOn &&
6673 (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
6674 SeekGraphClick(clickType, xPix, yPix, 0);
6678 if (clickType == Press) ErrorPopDown();
6679 MarkTargetSquares(1);
6681 x = EventToSquare(xPix, BOARD_WIDTH);
6682 y = EventToSquare(yPix, BOARD_HEIGHT);
6683 if (!flipView && y >= 0) {
6684 y = BOARD_HEIGHT - 1 - y;
6686 if (flipView && x >= 0) {
6687 x = BOARD_WIDTH - 1 - x;
6690 if(promoSweep != EmptySquare) { // up-click during sweep-select of promo-piece
6691 defaultPromoChoice = promoSweep;
6692 promoSweep = EmptySquare; // terminate sweep
6693 promoDefaultAltered = TRUE;
6694 if(!selectFlag) x = fromX, y = fromY; // and fake up-click on same square if we were still selecting
6697 if(promotionChoice) { // we are waiting for a click to indicate promotion piece
6698 if(clickType == Release) return; // ignore upclick of click-click destination
6699 promotionChoice = FALSE; // only one chance: if click not OK it is interpreted as cancel
6700 if(appData.debugMode) fprintf(debugFP, "promotion click, x=%d, y=%d\n", x, y);
6701 if(gameInfo.holdingsWidth &&
6702 (WhiteOnMove(currentMove)
6703 ? x == BOARD_WIDTH-1 && y < gameInfo.holdingsSize && y > 0
6704 : x == 0 && y >= BOARD_HEIGHT - gameInfo.holdingsSize && y < BOARD_HEIGHT-1) ) {
6705 // click in right holdings, for determining promotion piece
6706 ChessSquare p = boards[currentMove][y][x];
6707 if(appData.debugMode) fprintf(debugFP, "square contains %d\n", (int)p);
6708 if(p != EmptySquare) {
6709 FinishMove(NormalMove, fromX, fromY, toX, toY, ToLower(PieceToChar(p)));
6714 DrawPosition(FALSE, boards[currentMove]);
6718 /* [HGM] holdings: next 5 lines: ignore all clicks between board and holdings */
6719 if(clickType == Press
6720 && ( x == BOARD_LEFT-1 || x == BOARD_RGHT
6721 || x == BOARD_LEFT-2 && y < BOARD_HEIGHT-gameInfo.holdingsSize
6722 || x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
6725 if(clickType == Press && fromX == x && fromY == y && promoDefaultAltered)
6726 fromX = fromY = -1; // second click on piece after altering default promo piece treated as first click
6728 if(!promoDefaultAltered) { // determine default promotion piece, based on the side the user is moving for
6729 int side = (gameMode == IcsPlayingWhite || gameMode == MachinePlaysBlack ||
6730 gameMode != MachinePlaysWhite && gameMode != IcsPlayingBlack && WhiteOnMove(currentMove));
6731 defaultPromoChoice = DefaultPromoChoice(side);
6734 autoQueen = appData.alwaysPromoteToQueen;
6738 gatingPiece = EmptySquare;
6739 if (clickType != Press) {
6740 if(dragging) { // [HGM] from-square must have been reset due to game end since last press
6741 DragPieceEnd(xPix, yPix); dragging = 0;
6742 DrawPosition(FALSE, NULL);
6746 fromX = x; fromY = y;
6747 if(!appData.oneClick || !OnlyMove(&x, &y, FALSE) ||
6748 // even if only move, we treat as normal when this would trigger a promotion popup, to allow sweep selection
6749 appData.sweepSelect && CanPromote(boards[currentMove][fromY][fromX], fromY) && originalY != y) {
6751 if (OKToStartUserMove(fromX, fromY)) {
6753 MarkTargetSquares(0);
6754 DragPieceBegin(xPix, yPix); dragging = 1;
6755 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][fromY][fromX], fromY)) {
6756 promoSweep = defaultPromoChoice;
6757 selectFlag = 0; lastX = xPix; lastY = yPix;
6758 Sweep(0); // Pawn that is going to promote: preview promotion piece
6759 DisplayMessage("", _("Pull pawn backwards to under-promote"));
6761 if (appData.highlightDragging) {
6762 SetHighlights(fromX, fromY, -1, -1);
6764 } else fromX = fromY = -1;
6770 if (clickType == Press && gameMode != EditPosition) {
6775 // ignore off-board to clicks
6776 if(y < 0 || x < 0) return;
6778 /* Check if clicking again on the same color piece */
6779 fromP = boards[currentMove][fromY][fromX];
6780 toP = boards[currentMove][y][x];
6781 frc = gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom || gameInfo.variant == VariantSChess;
6782 if ((WhitePawn <= fromP && fromP <= WhiteKing &&
6783 WhitePawn <= toP && toP <= WhiteKing &&
6784 !(fromP == WhiteKing && toP == WhiteRook && frc) &&
6785 !(fromP == WhiteRook && toP == WhiteKing && frc)) ||
6786 (BlackPawn <= fromP && fromP <= BlackKing &&
6787 BlackPawn <= toP && toP <= BlackKing &&
6788 !(fromP == BlackRook && toP == BlackKing && frc) && // allow also RxK as FRC castling
6789 !(fromP == BlackKing && toP == BlackRook && frc))) {
6790 /* Clicked again on same color piece -- changed his mind */
6791 second = (x == fromX && y == fromY);
6792 promoDefaultAltered = FALSE;
6793 if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
6794 if (appData.highlightDragging) {
6795 SetHighlights(x, y, -1, -1);
6799 if (OKToStartUserMove(x, y)) {
6800 if(gameInfo.variant == VariantSChess && // S-Chess: back-rank piece selected after holdings means gating
6801 (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) &&
6802 y == (toP < BlackPawn ? 0 : BOARD_HEIGHT-1))
6803 gatingPiece = boards[currentMove][fromY][fromX];
6804 else gatingPiece = EmptySquare;
6806 fromY = y; dragging = 1;
6807 MarkTargetSquares(0);
6808 DragPieceBegin(xPix, yPix);
6809 if(appData.sweepSelect && CanPromote(piece = boards[currentMove][y][x], y)) {
6810 promoSweep = defaultPromoChoice;
6811 selectFlag = 0; lastX = xPix; lastY = yPix;
6812 Sweep(0); // Pawn that is going to promote: preview promotion piece
6816 if(x == fromX && y == fromY) return; // if OnlyMove altered (x,y) we go on
6819 // ignore clicks on holdings
6820 if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
6823 if (clickType == Release && x == fromX && y == fromY) {
6824 DragPieceEnd(xPix, yPix); dragging = 0;
6826 // a deferred attempt to click-click move an empty square on top of a piece
6827 boards[currentMove][y][x] = EmptySquare;
6829 DrawPosition(FALSE, boards[currentMove]);
6830 fromX = fromY = -1; clearFlag = 0;
6833 if (appData.animateDragging) {
6834 /* Undo animation damage if any */
6835 DrawPosition(FALSE, NULL);
6838 /* Second up/down in same square; just abort move */
6841 gatingPiece = EmptySquare;
6844 ClearPremoveHighlights();
6846 /* First upclick in same square; start click-click mode */
6847 SetHighlights(x, y, -1, -1);
6854 /* we now have a different from- and (possibly off-board) to-square */
6855 /* Completed move */
6858 saveAnimate = appData.animate;
6859 if (clickType == Press) {
6860 if(gameMode == EditPosition && boards[currentMove][fromY][fromX] == EmptySquare) {
6861 // must be Edit Position mode with empty-square selected
6862 fromX = x; fromY = y; DragPieceBegin(xPix, yPix); dragging = 1; // consider this a new attempt to drag
6863 if(x >= BOARD_LEFT && x < BOARD_RGHT) clearFlag = 1; // and defer click-click move of empty-square to up-click
6866 /* Finish clickclick move */
6867 if (appData.animate || appData.highlightLastMove) {
6868 SetHighlights(fromX, fromY, toX, toY);
6873 /* Finish drag move */
6874 if (appData.highlightLastMove) {
6875 SetHighlights(fromX, fromY, toX, toY);
6879 DragPieceEnd(xPix, yPix); dragging = 0;
6880 /* Don't animate move and drag both */
6881 appData.animate = FALSE;
6884 // moves into holding are invalid for now (except in EditPosition, adapting to-square)
6885 if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
6886 ChessSquare piece = boards[currentMove][fromY][fromX];
6887 if(gameMode == EditPosition && piece != EmptySquare &&
6888 fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
6891 if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
6892 n = PieceToNumber(piece - (int)BlackPawn);
6893 if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
6894 boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
6895 boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
6897 if(x == BOARD_RGHT+1 && piece < BlackPawn) {
6898 n = PieceToNumber(piece);
6899 if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
6900 boards[currentMove][n][BOARD_WIDTH-1] = piece;
6901 boards[currentMove][n][BOARD_WIDTH-2]++;
6903 boards[currentMove][fromY][fromX] = EmptySquare;
6907 DrawPosition(TRUE, boards[currentMove]);
6911 // off-board moves should not be highlighted
6912 if(x < 0 || y < 0) ClearHighlights();
6914 if(gatingPiece != EmptySquare) promoChoice = ToLower(PieceToChar(gatingPiece));
6916 if (HasPromotionChoice(fromX, fromY, toX, toY, &promoChoice)) {
6917 SetHighlights(fromX, fromY, toX, toY);
6918 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
6919 // [HGM] super: promotion to captured piece selected from holdings
6920 ChessSquare p = boards[currentMove][fromY][fromX], q = boards[currentMove][toY][toX];
6921 promotionChoice = TRUE;
6922 // kludge follows to temporarily execute move on display, without promoting yet
6923 boards[currentMove][fromY][fromX] = EmptySquare; // move Pawn to 8th rank
6924 boards[currentMove][toY][toX] = p;
6925 DrawPosition(FALSE, boards[currentMove]);
6926 boards[currentMove][fromY][fromX] = p; // take back, but display stays
6927 boards[currentMove][toY][toX] = q;
6928 DisplayMessage("Click in holdings to choose piece", "");
6933 int oldMove = currentMove;
6934 UserMoveEvent(fromX, fromY, toX, toY, promoChoice);
6935 if (!appData.highlightLastMove || gotPremove) ClearHighlights();
6936 if (gotPremove) SetPremoveHighlights(fromX, fromY, toX, toY);
6937 if(saveAnimate && !appData.animate && currentMove != oldMove && // drag-move was performed
6938 Explode(boards[currentMove-1], fromX, fromY, toX, toY))
6939 DrawPosition(TRUE, boards[currentMove]);
6942 appData.animate = saveAnimate;
6943 if (appData.animate || appData.animateDragging) {
6944 /* Undo animation damage if needed */
6945 DrawPosition(FALSE, NULL);
6949 int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
6950 { // front-end-free part taken out of PieceMenuPopup
6951 int whichMenu; int xSqr, ySqr;
6953 if(seekGraphUp) { // [HGM] seekgraph
6954 if(action == Press) SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
6955 if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
6959 if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
6960 && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
6961 if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
6962 if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
6963 if(action == Press) {
6964 originalFlip = flipView;
6965 flipView = !flipView; // temporarily flip board to see game from partners perspective
6966 DrawPosition(TRUE, partnerBoard);
6967 DisplayMessage(partnerStatus, "");
6969 } else if(action == Release) {
6970 flipView = originalFlip;
6971 DrawPosition(TRUE, boards[currentMove]);
6977 xSqr = EventToSquare(x, BOARD_WIDTH);
6978 ySqr = EventToSquare(y, BOARD_HEIGHT);
6979 if (action == Release) {
6980 if(pieceSweep != EmptySquare) {
6981 EditPositionMenuEvent(pieceSweep, toX, toY);
6982 pieceSweep = EmptySquare;
6983 } else UnLoadPV(); // [HGM] pv
6985 if (action != Press) return -2; // return code to be ignored
6988 if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;
\r
6990 if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;
\r
6991 if (xSqr < 0 || ySqr < 0) return -1;
6992 if(appData.pieceMenu) { whichMenu = 0; break; } // edit-position menu
6993 pieceSweep = shiftKey ? BlackPawn : WhitePawn; // [HGM] sweep: prepare selecting piece by mouse sweep
6994 toX = xSqr; toY = ySqr; lastX = x, lastY = y;
6995 if(flipView) toX = BOARD_WIDTH - 1 - toX; else toY = BOARD_HEIGHT - 1 - toY;
6999 if(!appData.icsEngineAnalyze) return -1;
7000 case IcsPlayingWhite:
7001 case IcsPlayingBlack:
7002 if(!appData.zippyPlay) goto noZip;
7005 case MachinePlaysWhite:
7006 case MachinePlaysBlack:
7007 case TwoMachinesPlay: // [HGM] pv: use for showing PV
7008 if (!appData.dropMenu) {
7010 return 2; // flag front-end to grab mouse events
7012 if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
7013 gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
7016 if (xSqr < 0 || ySqr < 0) return -1;
7017 if (!appData.dropMenu || appData.testLegality &&
7018 gameInfo.variant != VariantBughouse &&
7019 gameInfo.variant != VariantCrazyhouse) return -1;
7020 whichMenu = 1; // drop menu
7026 if (((*fromX = xSqr) < 0) ||
7027 ((*fromY = ySqr) < 0)) {
7028 *fromX = *fromY = -1;
7032 *fromX = BOARD_WIDTH - 1 - *fromX;
7034 *fromY = BOARD_HEIGHT - 1 - *fromY;
7039 void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
7041 // char * hint = lastHint;
7042 FrontEndProgramStats stats;
7044 stats.which = cps == &first ? 0 : 1;
7045 stats.depth = cpstats->depth;
7046 stats.nodes = cpstats->nodes;
7047 stats.score = cpstats->score;
7048 stats.time = cpstats->time;
7049 stats.pv = cpstats->movelist;
7050 stats.hint = lastHint;
7051 stats.an_move_index = 0;
7052 stats.an_move_count = 0;
7054 if( gameMode == AnalyzeMode || gameMode == AnalyzeFile ) {
7055 stats.hint = cpstats->move_name;
7056 stats.an_move_index = cpstats->nr_moves - cpstats->moves_left;
7057 stats.an_move_count = cpstats->nr_moves;
7060 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
7062 SetProgramStats( &stats );
7065 #define MAXPLAYERS 500
7068 TourneyStandings(int display)
7070 int i, w, b, color, wScore, bScore, dummy, nr=0, nPlayers=0;
7071 int score[MAXPLAYERS], ranking[MAXPLAYERS], points[MAXPLAYERS], games[MAXPLAYERS];
7072 char result, *p, *names[MAXPLAYERS];
7074 names[0] = p = strdup(appData.participants);
7075 while(p = strchr(p, '\n')) *p++ = NULLCHAR, names[++nPlayers] = p; // count participants
7077 for(i=0; i<nPlayers; i++) score[i] = games[i] = 0;
7079 while(result = appData.results[nr]) {
7080 color = Pairing(nr, nPlayers, &w, &b, &dummy);
7081 if(!(color ^ matchGame & 1)) { dummy = w; w = b; b = dummy; }
7082 wScore = bScore = 0;
7084 case '+': wScore = 2; break;
7085 case '-': bScore = 2; break;
7086 case '=': wScore = bScore = 1; break;
7088 case '*': return strdup("busy"); // tourney not finished
7096 if(appData.tourneyType > 0) nPlayers = appData.tourneyType; // in gauntlet, list only gauntlet engine(s)
7097 for(w=0; w<nPlayers; w++) {
7099 for(i=0; i<nPlayers; i++) if(score[i] > bScore) bScore = score[i], b = i;
7100 ranking[w] = b; points[w] = bScore; score[b] = -2;
7102 p = malloc(nPlayers*34+1);
7103 for(w=0; w<nPlayers && w<display; w++)
7104 sprintf(p+34*w, "%2d. %5.1f/%-3d %-19.19s\n", w+1, points[w]/2., games[ranking[w]], names[ranking[w]]);
7110 Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
7111 { // count all piece types
7113 *nB = *nW = *wStale = *bStale = *bishopColor = 0;
7114 for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
7115 for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
7118 if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
7119 if(p == BlackPawn && r == 0) (*bStale)++; // count last-Rank Pawns (XQ) separately
7120 if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
7121 if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
7122 p == BlackBishop || p == BlackFerz || p == BlackAlfil )
7123 *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
7128 SufficientDefence(int pCnt[], int side, int nMine, int nHis)
7130 int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
7131 int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
7133 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
7134 if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
7135 if(myPawns == 2 && nMine == 3) // KPP
7136 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
7137 if(myPawns == 1 && nMine == 2) // KP
7138 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7139 if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
7140 return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
7141 if(myPawns) return FALSE;
7142 if(pCnt[WhiteRook+side])
7143 return pCnt[BlackRook-side] ||
7144 pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
7145 pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
7146 pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
7147 if(pCnt[WhiteCannon+side]) {
7148 if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
7149 return majorDefense || pCnt[BlackAlfil-side] >= 2;
7151 if(pCnt[WhiteKnight+side])
7152 return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
7157 MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
7159 VariantClass v = gameInfo.variant;
7161 if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
7162 if(v == VariantShatranj) return TRUE; // always winnable through baring
7163 if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
7164 if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
7166 if(v == VariantXiangqi) {
7167 int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
7169 nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
7170 if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
7171 if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
7172 if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
7173 // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
7174 if(stale) // we have at least one last-rank P plus perhaps C
7175 return majors // KPKX
7176 || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
7178 return pCnt[WhiteFerz+side] // KCAK
7179 || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
7180 || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
7181 // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
7183 } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
7184 int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
7186 if(nMine == 1) return FALSE; // bare King
7187 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
7188 nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
7189 if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
7190 // by now we have King + 1 piece (or multiple Bishops on the same color)
7191 if(pCnt[WhiteKnight+side])
7192 return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
7193 pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
7194 || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
7196 return (pCnt[BlackKnight-side]); // KBKN, KFKN
7197 if(pCnt[WhiteAlfil+side])
7198 return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
7199 if(pCnt[WhiteWazir+side])
7200 return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
7207 Adjudicate(ChessProgramState *cps)
7208 { // [HGM] some adjudications useful with buggy engines
7209 // [HGM] adjudicate: made into separate routine, which now can be called after every move
7210 // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
7211 // Actually ending the game is now based on the additional internal condition canAdjudicate.
7212 // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
7213 int k, count = 0; static int bare = 1;
7214 ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
7215 Boolean canAdjudicate = !appData.icsActive;
7217 // most tests only when we understand the game, i.e. legality-checking on
7218 if( appData.testLegality )
7219 { /* [HGM] Some more adjudications for obstinate engines */
7220 int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
7221 static int moveCount = 6;
7223 char *reason = NULL;
7225 /* Count what is on board. */
7226 Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
7228 /* Some material-based adjudications that have to be made before stalemate test */
7229 if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
7230 // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
7231 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
7232 if(canAdjudicate && appData.checkMates) {
7234 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7235 GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
7236 "Xboard adjudication: King destroyed", GE_XBOARD );
7241 /* Bare King in Shatranj (loses) or Losers (wins) */
7242 if( nrW == 1 || nrB == 1) {
7243 if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
7244 boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
7245 if(canAdjudicate && appData.checkMates) {
7247 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
7248 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7249 "Xboard adjudication: Bare king", GE_XBOARD );
7253 if( gameInfo.variant == VariantShatranj && --bare < 0)
7255 boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
7256 if(canAdjudicate && appData.checkMates) {
7257 /* but only adjudicate if adjudication enabled */
7259 SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
7260 GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
7261 "Xboard adjudication: Bare king", GE_XBOARD );
7268 // don't wait for engine to announce game end if we can judge ourselves
7269 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
7271 if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
7272 int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
7273 for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
7274 if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
7277 reason = "Xboard adjudication: 3rd check";
7278 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
7288 reason = "Xboard adjudication: Stalemate";
7289 if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
7290 boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
7291 if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
7292 boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
7293 else if(gameInfo.variant == VariantSuicide) // in suicide it depends
7294 boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
7295 ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
7296 EP_CHECKMATE : EP_WINS);
7297 else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
7298 boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
7302 reason = "Xboard adjudication: Checkmate";
7303 boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
7307 switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
7309 result = GameIsDrawn; break;
7311 result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
7313 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
7317 if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
7319 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7320 GameEnds( result, reason, GE_XBOARD );
7324 /* Next absolutely insufficient mating material. */
7325 if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
7326 !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
7327 { /* includes KBK, KNK, KK of KBKB with like Bishops */
7329 /* always flag draws, for judging claims */
7330 boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
7332 if(canAdjudicate && appData.materialDraws) {
7333 /* but only adjudicate them if adjudication enabled */
7334 if(engineOpponent) {
7335 SendToProgram("force\n", engineOpponent); // suppress reply
7336 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
7338 GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
7343 /* Then some trivial draws (only adjudicate, cannot be claimed) */
7344 if(gameInfo.variant == VariantXiangqi ?
7345 SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
7347 ( nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
7348 || nr[WhiteQueen] && nr[BlackQueen]==1 /* KQKQ */
7349 || nr[WhiteKnight]==2 || nr[BlackKnight]==2 /* KNNK */
7350 || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
7352 if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
7353 { /* if the first 3 moves do not show a tactical win, declare draw */
7354 if(engineOpponent) {
7355 SendToProgram("force\n", engineOpponent); // suppress reply
7356 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7358 GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
7361 } else moveCount = 6;
7363 if (appData.debugMode) { int i;
7364 fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
7365 forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
7366 appData.drawRepeats);
7367 for( i=forwardMostMove; i>=backwardMostMove; i-- )
7368 fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
7372 // Repetition draws and 50-move rule can be applied independently of legality testing
7374 /* Check for rep-draws */
7376 for(k = forwardMostMove-2;
7377 k>=backwardMostMove && k>=forwardMostMove-100 &&
7378 (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
7379 (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
7382 if(CompareBoards(boards[k], boards[forwardMostMove])) {
7383 /* compare castling rights */
7384 if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
7385 (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
7386 rights++; /* King lost rights, while rook still had them */
7387 if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
7388 if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
7389 boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
7390 rights++; /* but at least one rook lost them */
7392 if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
7393 (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
7395 if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
7396 if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
7397 boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
7400 if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
7401 && appData.drawRepeats > 1) {
7402 /* adjudicate after user-specified nr of repeats */
7403 int result = GameIsDrawn;
7404 char *details = "XBoard adjudication: repetition draw";
7405 if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
7406 // [HGM] xiangqi: check for forbidden perpetuals
7407 int m, ourPerpetual = 1, hisPerpetual = 1;
7408 for(m=forwardMostMove; m>k; m-=2) {
7409 if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
7410 ourPerpetual = 0; // the current mover did not always check
7411 if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
7412 hisPerpetual = 0; // the opponent did not always check
7414 if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
7415 ourPerpetual, hisPerpetual);
7416 if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
7417 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7418 details = "Xboard adjudication: perpetual checking";
7420 if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
7421 break; // (or we would have caught him before). Abort repetition-checking loop.
7423 // Now check for perpetual chases
7424 if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
7425 hisPerpetual = PerpetualChase(k, forwardMostMove);
7426 ourPerpetual = PerpetualChase(k+1, forwardMostMove);
7427 if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
7428 result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
7429 details = "Xboard adjudication: perpetual chasing";
7431 if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
7432 break; // Abort repetition-checking loop.
7434 // if neither of us is checking or chasing all the time, or both are, it is draw
7436 if(engineOpponent) {
7437 SendToProgram("force\n", engineOpponent); // suppress reply
7438 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7440 GameEnds( result, details, GE_XBOARD );
7443 if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
7444 boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
7448 /* Now we test for 50-move draws. Determine ply count */
7449 count = forwardMostMove;
7450 /* look for last irreversble move */
7451 while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
7453 /* if we hit starting position, add initial plies */
7454 if( count == backwardMostMove )
7455 count -= initialRulePlies;
7456 count = forwardMostMove - count;
7457 if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
7458 // adjust reversible move counter for checks in Xiangqi
7459 int i = forwardMostMove - count, inCheck = 0, lastCheck;
7460 if(i < backwardMostMove) i = backwardMostMove;
7461 while(i <= forwardMostMove) {
7462 lastCheck = inCheck; // check evasion does not count
7463 inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
7464 if(inCheck || lastCheck) count--; // check does not count
7469 boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
7470 /* this is used to judge if draw claims are legal */
7471 if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
7472 if(engineOpponent) {
7473 SendToProgram("force\n", engineOpponent); // suppress reply
7474 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7476 GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
7480 /* if draw offer is pending, treat it as a draw claim
7481 * when draw condition present, to allow engines a way to
7482 * claim draws before making their move to avoid a race
7483 * condition occurring after their move
7485 if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
7487 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
7488 p = "Draw claim: 50-move rule";
7489 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
7490 p = "Draw claim: 3-fold repetition";
7491 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
7492 p = "Draw claim: insufficient mating material";
7493 if( p != NULL && canAdjudicate) {
7494 if(engineOpponent) {
7495 SendToProgram("force\n", engineOpponent); // suppress reply
7496 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7498 GameEnds( GameIsDrawn, p, GE_XBOARD );
7503 if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
7504 if(engineOpponent) {
7505 SendToProgram("force\n", engineOpponent); // suppress reply
7506 SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
7508 GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
7514 char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
7515 { // [HGM] book: this routine intercepts moves to simulate book replies
7516 char *bookHit = NULL;
7518 //first determine if the incoming move brings opponent into his book
7519 if(appData.usePolyglotBook && (cps == &first ? !appData.firstHasOwnBookUCI : !appData.secondHasOwnBookUCI))
7520 bookHit = ProbeBook(moveNr+1, appData.polyglotBook); // returns move
7521 if(appData.debugMode) fprintf(debugFP, "book hit = %s\n", bookHit ? bookHit : "(NULL)");
7522 if(bookHit != NULL && !cps->bookSuspend) {
7523 // make sure opponent is not going to reply after receiving move to book position
7524 SendToProgram("force\n", cps);
7525 cps->bookSuspend = TRUE; // flag indicating it has to be restarted
7527 if(!initial) SendMoveToProgram(moveNr, cps); // with hit on initial position there is no move
7528 // now arrange restart after book miss
7530 // after a book hit we never send 'go', and the code after the call to this routine
7531 // has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
7533 snprintf(buf, MSG_SIZ, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
7534 SendToProgram(buf, cps);
7535 if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
7536 } else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
7537 SendToProgram("go\n", cps);
7538 cps->bookSuspend = FALSE; // after a 'go' we are never suspended
7539 } else { // 'go' might be sent based on 'firstMove' after this routine returns
7540 if(cps->bookSuspend && !firstMove) // 'go' needed, and it will not be done after we return
7541 SendToProgram("go\n", cps);
7542 cps->bookSuspend = FALSE; // anyhow, we will not be suspended after a miss
7544 return bookHit; // notify caller of hit, so it can take action to send move to opponent
7548 ChessProgramState *savedState;
7549 void DeferredBookMove(void)
7551 if(savedState->lastPing != savedState->lastPong)
7552 ScheduleDelayedEvent(DeferredBookMove, 10);
7554 HandleMachineMove(savedMessage, savedState);
7558 HandleMachineMove(message, cps)
7560 ChessProgramState *cps;
7562 char machineMove[MSG_SIZ], buf1[MSG_SIZ*10], buf2[MSG_SIZ];
7563 char realname[MSG_SIZ];
7564 int fromX, fromY, toX, toY;
7573 FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
7575 * Kludge to ignore BEL characters
7577 while (*message == '\007') message++;
7580 * [HGM] engine debug message: ignore lines starting with '#' character
7582 if(cps->debug && *message == '#') return;
7585 * Look for book output
7587 if (cps == &first && bookRequested) {
7588 if (message[0] == '\t' || message[0] == ' ') {
7589 /* Part of the book output is here; append it */
7590 strcat(bookOutput, message);
7591 strcat(bookOutput, " \n");
7593 } else if (bookOutput[0] != NULLCHAR) {
7594 /* All of book output has arrived; display it */
7595 char *p = bookOutput;
7596 while (*p != NULLCHAR) {
7597 if (*p == '\t') *p = ' ';
7600 DisplayInformation(bookOutput);
7601 bookRequested = FALSE;
7602 /* Fall through to parse the current output */
7607 * Look for machine move.
7609 if ((sscanf(message, "%s %s %s", buf1, buf2, machineMove) == 3 && strcmp(buf2, "...") == 0) ||
7610 (sscanf(message, "%s %s", buf1, machineMove) == 2 && strcmp(buf1, "move") == 0))
7612 /* This method is only useful on engines that support ping */
7613 if (cps->lastPing != cps->lastPong) {
7614 if (gameMode == BeginningOfGame) {
7615 /* Extra move from before last new; ignore */
7616 if (appData.debugMode) {
7617 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7620 if (appData.debugMode) {
7621 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7622 cps->which, gameMode);
7625 SendToProgram("undo\n", cps);
7631 case BeginningOfGame:
7632 /* Extra move from before last reset; ignore */
7633 if (appData.debugMode) {
7634 fprintf(debugFP, "Ignoring extra move from %s\n", cps->which);
7641 /* Extra move after we tried to stop. The mode test is
7642 not a reliable way of detecting this problem, but it's
7643 the best we can do on engines that don't support ping.
7645 if (appData.debugMode) {
7646 fprintf(debugFP, "Undoing extra move from %s, gameMode %d\n",
7647 cps->which, gameMode);
7649 SendToProgram("undo\n", cps);
7652 case MachinePlaysWhite:
7653 case IcsPlayingWhite:
7654 machineWhite = TRUE;
7657 case MachinePlaysBlack:
7658 case IcsPlayingBlack:
7659 machineWhite = FALSE;
7662 case TwoMachinesPlay:
7663 machineWhite = (cps->twoMachinesColor[0] == 'w');
7666 if (WhiteOnMove(forwardMostMove) != machineWhite) {
7667 if (appData.debugMode) {
7669 "Ignoring move out of turn by %s, gameMode %d"
7670 ", forwardMost %d\n",
7671 cps->which, gameMode, forwardMostMove);
7676 if (appData.debugMode) { int f = forwardMostMove;
7677 fprintf(debugFP, "machine move %d, castling = %d %d %d %d %d %d\n", f,
7678 boards[f][CASTLING][0],boards[f][CASTLING][1],boards[f][CASTLING][2],
7679 boards[f][CASTLING][3],boards[f][CASTLING][4],boards[f][CASTLING][5]);
7681 if(cps->alphaRank) AlphaRank(machineMove, 4);
7682 if (!ParseOneMove(machineMove, forwardMostMove, &moveType,
7683 &fromX, &fromY, &toX, &toY, &promoChar)) {
7684 /* Machine move could not be parsed; ignore it. */
7685 snprintf(buf1, MSG_SIZ*10, _("Illegal move \"%s\" from %s machine"),
7686 machineMove, _(cps->which));
7687 DisplayError(buf1, 0);
7688 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to invalid move: %s (%c%c%c%c) res=%d",
7689 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, moveType);
7690 if (gameMode == TwoMachinesPlay) {
7691 GameEnds(machineWhite ? BlackWins : WhiteWins,
7697 /* [HGM] Apparently legal, but so far only tested with EP_UNKOWN */
7698 /* So we have to redo legality test with true e.p. status here, */
7699 /* to make sure an illegal e.p. capture does not slip through, */
7700 /* to cause a forfeit on a justified illegal-move complaint */
7701 /* of the opponent. */
7702 if( gameMode==TwoMachinesPlay && appData.testLegality ) {
7704 moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
7705 fromY, fromX, toY, toX, promoChar);
7706 if (appData.debugMode) {
7708 for(i=0; i< nrCastlingRights; i++) fprintf(debugFP, "(%d,%d) ",
7709 boards[forwardMostMove][CASTLING][i], castlingRank[i]);
7710 fprintf(debugFP, "castling rights\n");
7712 if(moveType == IllegalMove) {
7713 snprintf(buf1, MSG_SIZ*10, "Xboard: Forfeit due to illegal move: %s (%c%c%c%c)%c",
7714 machineMove, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, 0);
7715 GameEnds(machineWhite ? BlackWins : WhiteWins,
7718 } else if(gameInfo.variant != VariantFischeRandom && gameInfo.variant != VariantCapaRandom)
7719 /* [HGM] Kludge to handle engines that send FRC-style castling
7720 when they shouldn't (like TSCP-Gothic) */
7722 case WhiteASideCastleFR:
7723 case BlackASideCastleFR:
7725 currentMoveString[2]++;
7727 case WhiteHSideCastleFR:
7728 case BlackHSideCastleFR:
7730 currentMoveString[2]--;
7732 default: ; // nothing to do, but suppresses warning of pedantic compilers
7735 hintRequested = FALSE;
7736 lastHint[0] = NULLCHAR;
7737 bookRequested = FALSE;
7738 /* Program may be pondering now */
7739 cps->maybeThinking = TRUE;
7740 if (cps->sendTime == 2) cps->sendTime = 1;
7741 if (cps->offeredDraw) cps->offeredDraw--;
7743 /* [AS] Save move info*/
7744 pvInfoList[ forwardMostMove ].score = programStats.score;
7745 pvInfoList[ forwardMostMove ].depth = programStats.depth;
7746 pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
7748 MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
7750 /* [AS] Adjudicate game if needed (note: remember that forwardMostMove now points past the last move) */
7751 if( gameMode == TwoMachinesPlay && adjudicateLossThreshold != 0 && forwardMostMove >= adjudicateLossPlies ) {
7754 while( count < adjudicateLossPlies ) {
7755 int score = pvInfoList[ forwardMostMove - count - 1 ].score;
7758 score = -score; /* Flip score for winning side */
7761 if( score > adjudicateLossThreshold ) {
7768 if( count >= adjudicateLossPlies ) {
7769 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7771 GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
7772 "Xboard adjudication",
7779 if(Adjudicate(cps)) {
7780 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7781 return; // [HGM] adjudicate: for all automatic game ends
7785 if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
7787 if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
7788 SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
7790 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7792 SendMoveToICS(moveType, fromX, fromY, toX, toY, promoChar);
7794 if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
7795 char buf[3*MSG_SIZ];
7797 snprintf(buf, 3*MSG_SIZ, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
7798 programStats.score / 100.,
7800 programStats.time / 100.,
7801 (unsigned int)programStats.nodes,
7802 (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
7803 programStats.movelist);
7805 if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
7810 /* [AS] Clear stats for next move */
7811 ClearProgramStats();
7812 thinkOutput[0] = NULLCHAR;
7813 hiddenThinkOutputState = 0;
7816 if (gameMode == TwoMachinesPlay) {
7817 /* [HGM] relaying draw offers moved to after reception of move */
7818 /* and interpreting offer as claim if it brings draw condition */
7819 if (cps->offeredDraw == 1 && cps->other->sendDrawOffers) {
7820 SendToProgram("draw\n", cps->other);
7822 if (cps->other->sendTime) {
7823 SendTimeRemaining(cps->other,
7824 cps->other->twoMachinesColor[0] == 'w');
7826 bookHit = SendMoveToBookUser(forwardMostMove-1, cps->other, FALSE);
7827 if (firstMove && !bookHit) {
7829 if (cps->other->useColors) {
7830 SendToProgram(cps->other->twoMachinesColor, cps->other);
7832 SendToProgram("go\n", cps->other);
7834 cps->other->maybeThinking = TRUE;
7837 ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
7839 if (!pausing && appData.ringBellAfterMoves) {
7844 * Reenable menu items that were disabled while
7845 * machine was thinking
7847 if (gameMode != TwoMachinesPlay)
7848 SetUserThinkingEnables();
7850 // [HGM] book: after book hit opponent has received move and is now in force mode
7851 // force the book reply into it, and then fake that it outputted this move by jumping
7852 // back to the beginning of HandleMachineMove, with cps toggled and message set to this move
7854 static char bookMove[MSG_SIZ]; // a bit generous?
7856 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
7857 strcat(bookMove, bookHit);
7860 programStats.nodes = programStats.depth = programStats.time =
7861 programStats.score = programStats.got_only_move = 0;
7862 sprintf(programStats.movelist, "%s (xbook)", bookHit);
7864 if(cps->lastPing != cps->lastPong) {
7865 savedMessage = message; // args for deferred call
7867 ScheduleDelayedEvent(DeferredBookMove, 10);
7876 /* Set special modes for chess engines. Later something general
7877 * could be added here; for now there is just one kludge feature,
7878 * needed because Crafty 15.10 and earlier don't ignore SIGINT
7879 * when "xboard" is given as an interactive command.
7881 if (strncmp(message, "kibitz Hello from Crafty", 24) == 0) {
7882 cps->useSigint = FALSE;
7883 cps->useSigterm = FALSE;
7885 if (strncmp(message, "feature ", 8) == 0) { // [HGM] moved forward to pre-empt non-compliant commands
7886 ParseFeatures(message+8, cps);
7887 return; // [HGM] This return was missing, causing option features to be recognized as non-compliant commands!
7890 if (!appData.testLegality && !strncmp(message, "setup ", 6)) { // [HGM] allow first engine to define opening position
7891 int dummy, s=6; char buf[MSG_SIZ];
7892 if(appData.icsActive || forwardMostMove != 0 || cps != &first || startedFromSetupPosition) return;
7893 if(sscanf(message, "setup (%s", buf) == 1) s = 8 + strlen(buf), buf[s-9] = NULLCHAR, SetCharTable(pieceToChar, buf);
7894 ParseFEN(boards[0], &dummy, message+s);
7895 DrawPosition(TRUE, boards[0]);
7896 startedFromSetupPosition = TRUE;
7899 /* [HGM] Allow engine to set up a position. Don't ask me why one would
7900 * want this, I was asked to put it in, and obliged.
7902 if (!strncmp(message, "setboard ", 9)) {
7903 Board initial_position;
7905 GameEnds(GameUnfinished, "Engine aborts game", GE_XBOARD);
7907 if (!ParseFEN(initial_position, &blackPlaysFirst, message + 9)) {
7908 DisplayError(_("Bad FEN received from engine"), 0);
7912 CopyBoard(boards[0], initial_position);
7913 initialRulePlies = FENrulePlies;
7914 if(blackPlaysFirst) gameMode = MachinePlaysWhite;
7915 else gameMode = MachinePlaysBlack;
7916 DrawPosition(FALSE, boards[currentMove]);
7922 * Look for communication commands
7924 if (!strncmp(message, "telluser ", 9)) {
7925 if(message[9] == '\\' && message[10] == '\\')
7926 EscapeExpand(message+9, message+11); // [HGM] esc: allow escape sequences in popup box
7927 DisplayNote(message + 9);
7930 if (!strncmp(message, "tellusererror ", 14)) {
7932 if(message[14] == '\\' && message[15] == '\\')
7933 EscapeExpand(message+14, message+16); // [HGM] esc: allow escape sequences in popup box
7934 DisplayError(message + 14, 0);
7937 if (!strncmp(message, "tellopponent ", 13)) {
7938 if (appData.icsActive) {
7940 snprintf(buf1, sizeof(buf1), "%ssay %s\n", ics_prefix, message + 13);
7944 DisplayNote(message + 13);
7948 if (!strncmp(message, "tellothers ", 11)) {
7949 if (appData.icsActive) {
7951 snprintf(buf1, sizeof(buf1), "%swhisper %s\n", ics_prefix, message + 11);
7957 if (!strncmp(message, "tellall ", 8)) {
7958 if (appData.icsActive) {
7960 snprintf(buf1, sizeof(buf1), "%skibitz %s\n", ics_prefix, message + 8);
7964 DisplayNote(message + 8);
7968 if (strncmp(message, "warning", 7) == 0) {
7969 /* Undocumented feature, use tellusererror in new code */
7970 DisplayError(message, 0);
7973 if (sscanf(message, "askuser %s %[^\n]", buf1, buf2) == 2) {
7974 safeStrCpy(realname, cps->tidy, sizeof(realname)/sizeof(realname[0]));
7975 strcat(realname, " query");
7976 AskQuestion(realname, buf2, buf1, cps->pr);
7979 /* Commands from the engine directly to ICS. We don't allow these to be
7980 * sent until we are logged on. Crafty kibitzes have been known to
7981 * interfere with the login process.
7984 if (!strncmp(message, "tellics ", 8)) {
7985 SendToICS(message + 8);
7989 if (!strncmp(message, "tellicsnoalias ", 15)) {
7990 SendToICS(ics_prefix);
7991 SendToICS(message + 15);
7995 /* The following are for backward compatibility only */
7996 if (!strncmp(message,"whisper",7) || !strncmp(message,"kibitz",6) ||
7997 !strncmp(message,"draw",4) || !strncmp(message,"tell",3)) {
7998 SendToICS(ics_prefix);
8004 if (sscanf(message, "pong %d", &cps->lastPong) == 1) {
8008 * If the move is illegal, cancel it and redraw the board.
8009 * Also deal with other error cases. Matching is rather loose
8010 * here to accommodate engines written before the spec.
8012 if (strncmp(message + 1, "llegal move", 11) == 0 ||
8013 strncmp(message, "Error", 5) == 0) {
8014 if (StrStr(message, "name") ||
8015 StrStr(message, "rating") || StrStr(message, "?") ||
8016 StrStr(message, "result") || StrStr(message, "board") ||
8017 StrStr(message, "bk") || StrStr(message, "computer") ||
8018 StrStr(message, "variant") || StrStr(message, "hint") ||
8019 StrStr(message, "random") || StrStr(message, "depth") ||
8020 StrStr(message, "accepted")) {
8023 if (StrStr(message, "protover")) {
8024 /* Program is responding to input, so it's apparently done
8025 initializing, and this error message indicates it is
8026 protocol version 1. So we don't need to wait any longer
8027 for it to initialize and send feature commands. */
8028 FeatureDone(cps, 1);
8029 cps->protocolVersion = 1;
8032 cps->maybeThinking = FALSE;
8034 if (StrStr(message, "draw")) {
8035 /* Program doesn't have "draw" command */
8036 cps->sendDrawOffers = 0;
8039 if (cps->sendTime != 1 &&
8040 (StrStr(message, "time") || StrStr(message, "otim"))) {
8041 /* Program apparently doesn't have "time" or "otim" command */
8045 if (StrStr(message, "analyze")) {
8046 cps->analysisSupport = FALSE;
8047 cps->analyzing = FALSE;
8049 snprintf(buf2,MSG_SIZ, _("%s does not support analysis"), cps->tidy);
8050 DisplayError(buf2, 0);
8053 if (StrStr(message, "(no matching move)st")) {
8054 /* Special kludge for GNU Chess 4 only */
8055 cps->stKludge = TRUE;
8056 SendTimeControl(cps, movesPerSession, timeControl,
8057 timeIncrement, appData.searchDepth,
8061 if (StrStr(message, "(no matching move)sd")) {
8062 /* Special kludge for GNU Chess 4 only */
8063 cps->sdKludge = TRUE;
8064 SendTimeControl(cps, movesPerSession, timeControl,
8065 timeIncrement, appData.searchDepth,
8069 if (!StrStr(message, "llegal")) {
8072 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8073 gameMode == IcsIdle) return;
8074 if (forwardMostMove <= backwardMostMove) return;
8075 if (pausing) PauseEvent();
8076 if(appData.forceIllegal) {
8077 // [HGM] illegal: machine refused move; force position after move into it
8078 SendToProgram("force\n", cps);
8079 if(!cps->useSetboard) { // hideous kludge on kludge, because SendBoard sucks.
8080 // we have a real problem now, as SendBoard will use the a2a3 kludge
8081 // when black is to move, while there might be nothing on a2 or black
8082 // might already have the move. So send the board as if white has the move.
8083 // But first we must change the stm of the engine, as it refused the last move
8084 SendBoard(cps, 0); // always kludgeless, as white is to move on boards[0]
8085 if(WhiteOnMove(forwardMostMove)) {
8086 SendToProgram("a7a6\n", cps); // for the engine black still had the move
8087 SendBoard(cps, forwardMostMove); // kludgeless board
8089 SendToProgram("a2a3\n", cps); // for the engine white still had the move
8090 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
8091 SendBoard(cps, forwardMostMove+1); // kludgeless board
8093 } else SendBoard(cps, forwardMostMove); // FEN case, also sets stm properly
8094 if(gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
8095 gameMode == TwoMachinesPlay)
8096 SendToProgram("go\n", cps);
8099 if (gameMode == PlayFromGameFile) {
8100 /* Stop reading this game file */
8101 gameMode = EditGame;
8104 /* [HGM] illegal-move claim should forfeit game when Xboard */
8105 /* only passes fully legal moves */
8106 if( appData.testLegality && gameMode == TwoMachinesPlay ) {
8107 GameEnds( cps->twoMachinesColor[0] == 'w' ? BlackWins : WhiteWins,
8108 "False illegal-move claim", GE_XBOARD );
8109 return; // do not take back move we tested as valid
8111 currentMove = forwardMostMove-1;
8112 DisplayMove(currentMove-1); /* before DisplayMoveError */
8113 SwitchClocks(forwardMostMove-1); // [HGM] race
8114 DisplayBothClocks();
8115 snprintf(buf1, 10*MSG_SIZ, _("Illegal move \"%s\" (rejected by %s chess program)"),
8116 parseList[currentMove], _(cps->which));
8117 DisplayMoveError(buf1);
8118 DrawPosition(FALSE, boards[currentMove]);
8121 if (strncmp(message, "time", 4) == 0 && StrStr(message, "Illegal")) {
8122 /* Program has a broken "time" command that
8123 outputs a string not ending in newline.
8129 * If chess program startup fails, exit with an error message.
8130 * Attempts to recover here are futile.
8132 if ((StrStr(message, "unknown host") != NULL)
8133 || (StrStr(message, "No remote directory") != NULL)
8134 || (StrStr(message, "not found") != NULL)
8135 || (StrStr(message, "No such file") != NULL)
8136 || (StrStr(message, "can't alloc") != NULL)
8137 || (StrStr(message, "Permission denied") != NULL)) {
8139 cps->maybeThinking = FALSE;
8140 snprintf(buf1, sizeof(buf1), _("Failed to start %s chess program %s on %s: %s\n"),
8141 _(cps->which), cps->program, cps->host, message);
8142 RemoveInputSource(cps->isr);
8143 if(appData.icsActive) DisplayFatalError(buf1, 0, 1); else {
8144 if(cps == &first) appData.noChessProgram = TRUE;
8145 DisplayError(buf1, 0);
8151 * Look for hint output
8153 if (sscanf(message, "Hint: %s", buf1) == 1) {
8154 if (cps == &first && hintRequested) {
8155 hintRequested = FALSE;
8156 if (ParseOneMove(buf1, forwardMostMove, &moveType,
8157 &fromX, &fromY, &toX, &toY, &promoChar)) {
8158 (void) CoordsToAlgebraic(boards[forwardMostMove],
8159 PosFlags(forwardMostMove),
8160 fromY, fromX, toY, toX, promoChar, buf1);
8161 snprintf(buf2, sizeof(buf2), _("Hint: %s"), buf1);
8162 DisplayInformation(buf2);
8164 /* Hint move could not be parsed!? */
8165 snprintf(buf2, sizeof(buf2),
8166 _("Illegal hint move \"%s\"\nfrom %s chess program"),
8167 buf1, _(cps->which));
8168 DisplayError(buf2, 0);
8171 safeStrCpy(lastHint, buf1, sizeof(lastHint)/sizeof(lastHint[0]));
8177 * Ignore other messages if game is not in progress
8179 if (gameMode == BeginningOfGame || gameMode == EndOfGame ||
8180 gameMode == IcsIdle || cps->lastPing != cps->lastPong) return;
8183 * look for win, lose, draw, or draw offer
8185 if (strncmp(message, "1-0", 3) == 0) {
8186 char *p, *q, *r = "";
8187 p = strchr(message, '{');
8195 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first)); /* [HGM] pass claimer indication for claim test */
8197 } else if (strncmp(message, "0-1", 3) == 0) {
8198 char *p, *q, *r = "";
8199 p = strchr(message, '{');
8207 /* Kludge for Arasan 4.1 bug */
8208 if (strcmp(r, "Black resigns") == 0) {
8209 GameEnds(WhiteWins, r, GE_ENGINE1 + (cps != &first));
8212 GameEnds(BlackWins, r, GE_ENGINE1 + (cps != &first));
8214 } else if (strncmp(message, "1/2", 3) == 0) {
8215 char *p, *q, *r = "";
8216 p = strchr(message, '{');
8225 GameEnds(GameIsDrawn, r, GE_ENGINE1 + (cps != &first));
8228 } else if (strncmp(message, "White resign", 12) == 0) {
8229 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8231 } else if (strncmp(message, "Black resign", 12) == 0) {
8232 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8234 } else if (strncmp(message, "White matches", 13) == 0 ||
8235 strncmp(message, "Black matches", 13) == 0 ) {
8236 /* [HGM] ignore GNUShogi noises */
8238 } else if (strncmp(message, "White", 5) == 0 &&
8239 message[5] != '(' &&
8240 StrStr(message, "Black") == NULL) {
8241 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8243 } else if (strncmp(message, "Black", 5) == 0 &&
8244 message[5] != '(') {
8245 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8247 } else if (strcmp(message, "resign") == 0 ||
8248 strcmp(message, "computer resigns") == 0) {
8250 case MachinePlaysBlack:
8251 case IcsPlayingBlack:
8252 GameEnds(WhiteWins, "Black resigns", GE_ENGINE);
8254 case MachinePlaysWhite:
8255 case IcsPlayingWhite:
8256 GameEnds(BlackWins, "White resigns", GE_ENGINE);
8258 case TwoMachinesPlay:
8259 if (cps->twoMachinesColor[0] == 'w')
8260 GameEnds(BlackWins, "White resigns", GE_ENGINE1 + (cps != &first));
8262 GameEnds(WhiteWins, "Black resigns", GE_ENGINE1 + (cps != &first));
8269 } else if (strncmp(message, "opponent mates", 14) == 0) {
8271 case MachinePlaysBlack:
8272 case IcsPlayingBlack:
8273 GameEnds(WhiteWins, "White mates", GE_ENGINE);
8275 case MachinePlaysWhite:
8276 case IcsPlayingWhite:
8277 GameEnds(BlackWins, "Black mates", GE_ENGINE);
8279 case TwoMachinesPlay:
8280 if (cps->twoMachinesColor[0] == 'w')
8281 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8283 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8290 } else if (strncmp(message, "computer mates", 14) == 0) {
8292 case MachinePlaysBlack:
8293 case IcsPlayingBlack:
8294 GameEnds(BlackWins, "Black mates", GE_ENGINE1);
8296 case MachinePlaysWhite:
8297 case IcsPlayingWhite:
8298 GameEnds(WhiteWins, "White mates", GE_ENGINE);
8300 case TwoMachinesPlay:
8301 if (cps->twoMachinesColor[0] == 'w')
8302 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8304 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8311 } else if (strncmp(message, "checkmate", 9) == 0) {
8312 if (WhiteOnMove(forwardMostMove)) {
8313 GameEnds(BlackWins, "Black mates", GE_ENGINE1 + (cps != &first));
8315 GameEnds(WhiteWins, "White mates", GE_ENGINE1 + (cps != &first));
8318 } else if (strstr(message, "Draw") != NULL ||
8319 strstr(message, "game is a draw") != NULL) {
8320 GameEnds(GameIsDrawn, "Draw", GE_ENGINE1 + (cps != &first));
8322 } else if (strstr(message, "offer") != NULL &&
8323 strstr(message, "draw") != NULL) {
8325 if (appData.zippyPlay && first.initDone) {
8326 /* Relay offer to ICS */
8327 SendToICS(ics_prefix);
8328 SendToICS("draw\n");
8331 cps->offeredDraw = 2; /* valid until this engine moves twice */
8332 if (gameMode == TwoMachinesPlay) {
8333 if (cps->other->offeredDraw) {
8334 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8335 /* [HGM] in two-machine mode we delay relaying draw offer */
8336 /* until after we also have move, to see if it is really claim */
8338 } else if (gameMode == MachinePlaysWhite ||
8339 gameMode == MachinePlaysBlack) {
8340 if (userOfferedDraw) {
8341 DisplayInformation(_("Machine accepts your draw offer"));
8342 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
8344 DisplayInformation(_("Machine offers a draw\nSelect Action / Draw to agree"));
8351 * Look for thinking output
8353 if ( appData.showThinking // [HGM] thinking: test all options that cause this output
8354 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
8356 int plylev, mvleft, mvtot, curscore, time;
8357 char mvname[MOVE_LEN];
8361 int prefixHint = FALSE;
8362 mvname[0] = NULLCHAR;
8365 case MachinePlaysBlack:
8366 case IcsPlayingBlack:
8367 if (WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8369 case MachinePlaysWhite:
8370 case IcsPlayingWhite:
8371 if (!WhiteOnMove(forwardMostMove)) prefixHint = TRUE;
8376 case IcsObserving: /* [DM] icsEngineAnalyze */
8377 if (!appData.icsEngineAnalyze) ignore = TRUE;
8379 case TwoMachinesPlay:
8380 if ((cps->twoMachinesColor[0] == 'w') != WhiteOnMove(forwardMostMove)) {
8390 ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
8392 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8393 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
8395 if (plyext != ' ' && plyext != '\t') {
8399 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8400 if( cps->scoreIsAbsolute &&
8401 ( gameMode == MachinePlaysBlack ||
8402 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
8403 gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
8404 (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
8405 !WhiteOnMove(currentMove)
8408 curscore = -curscore;
8412 tempStats.depth = plylev;
8413 tempStats.nodes = nodes;
8414 tempStats.time = time;
8415 tempStats.score = curscore;
8416 tempStats.got_only_move = 0;
8418 if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
8421 if(cps->nps == 0) ticklen = 10*time; // use engine reported time
8422 else ticklen = (1000. * u64ToDouble(nodes)) / cps->nps; // convert node count to time
8423 if(WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysWhite ||
8424 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'w'))
8425 whiteTimeRemaining = timeRemaining[0][forwardMostMove] - ticklen;
8426 if(!WhiteOnMove(forwardMostMove) && (gameMode == MachinePlaysBlack ||
8427 gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b'))
8428 blackTimeRemaining = timeRemaining[1][forwardMostMove] - ticklen;
8431 /* Buffer overflow protection */
8432 if (buf1[0] != NULLCHAR) {
8433 if (strlen(buf1) >= sizeof(tempStats.movelist)
8434 && appData.debugMode) {
8436 "PV is too long; using the first %u bytes.\n",
8437 (unsigned) sizeof(tempStats.movelist) - 1);
8440 safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist)/sizeof(tempStats.movelist[0]) );
8442 sprintf(tempStats.movelist, " no PV\n");
8445 if (tempStats.seen_stat) {
8446 tempStats.ok_to_send = 1;
8449 if (strchr(tempStats.movelist, '(') != NULL) {
8450 tempStats.line_is_book = 1;
8451 tempStats.nr_moves = 0;
8452 tempStats.moves_left = 0;
8454 tempStats.line_is_book = 0;
8457 if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
8458 programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
8460 SendProgramStatsToFrontend( cps, &tempStats );
8463 [AS] Protect the thinkOutput buffer from overflow... this
8464 is only useful if buf1 hasn't overflowed first!
8466 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "[%d]%c%+.2f %s%s",
8468 (gameMode == TwoMachinesPlay ?
8469 ToUpper(cps->twoMachinesColor[0]) : ' '),
8470 ((double) curscore) / 100.0,
8471 prefixHint ? lastHint : "",
8472 prefixHint ? " " : "" );
8474 if( buf1[0] != NULLCHAR ) {
8475 unsigned max_len = sizeof(thinkOutput) - strlen(thinkOutput) - 1;
8477 if( strlen(buf1) > max_len ) {
8478 if( appData.debugMode) {
8479 fprintf(debugFP,"PV is too long for thinkOutput, truncating.\n");
8481 buf1[max_len+1] = '\0';
8484 strcat( thinkOutput, buf1 );
8487 if (currentMove == forwardMostMove || gameMode == AnalyzeMode
8488 || gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8489 DisplayMove(currentMove - 1);
8493 } else if ((p=StrStr(message, "(only move)")) != NULL) {
8494 /* crafty (9.25+) says "(only move) <move>"
8495 * if there is only 1 legal move
8497 sscanf(p, "(only move) %s", buf1);
8498 snprintf(thinkOutput, sizeof(thinkOutput)/sizeof(thinkOutput[0]), "%s (only move)", buf1);
8499 sprintf(programStats.movelist, "%s (only move)", buf1);
8500 programStats.depth = 1;
8501 programStats.nr_moves = 1;
8502 programStats.moves_left = 1;
8503 programStats.nodes = 1;
8504 programStats.time = 1;
8505 programStats.got_only_move = 1;
8507 /* Not really, but we also use this member to
8508 mean "line isn't going to change" (Crafty
8509 isn't searching, so stats won't change) */
8510 programStats.line_is_book = 1;
8512 SendProgramStatsToFrontend( cps, &programStats );
8514 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8515 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8516 DisplayMove(currentMove - 1);
8519 } else if (sscanf(message,"stat01: %d " u64Display " %d %d %d %s",
8520 &time, &nodes, &plylev, &mvleft,
8521 &mvtot, mvname) >= 5) {
8522 /* The stat01: line is from Crafty (9.29+) in response
8523 to the "." command */
8524 programStats.seen_stat = 1;
8525 cps->maybeThinking = TRUE;
8527 if (programStats.got_only_move || !appData.periodicUpdates)
8530 programStats.depth = plylev;
8531 programStats.time = time;
8532 programStats.nodes = nodes;
8533 programStats.moves_left = mvleft;
8534 programStats.nr_moves = mvtot;
8535 safeStrCpy(programStats.move_name, mvname, sizeof(programStats.move_name)/sizeof(programStats.move_name[0]));
8536 programStats.ok_to_send = 1;
8537 programStats.movelist[0] = '\0';
8539 SendProgramStatsToFrontend( cps, &programStats );
8543 } else if (strncmp(message,"++",2) == 0) {
8544 /* Crafty 9.29+ outputs this */
8545 programStats.got_fail = 2;
8548 } else if (strncmp(message,"--",2) == 0) {
8549 /* Crafty 9.29+ outputs this */
8550 programStats.got_fail = 1;
8553 } else if (thinkOutput[0] != NULLCHAR &&
8554 strncmp(message, " ", 4) == 0) {
8555 unsigned message_len;
8558 while (*p && *p == ' ') p++;
8560 message_len = strlen( p );
8562 /* [AS] Avoid buffer overflow */
8563 if( sizeof(thinkOutput) - strlen(thinkOutput) - 1 > message_len ) {
8564 strcat(thinkOutput, " ");
8565 strcat(thinkOutput, p);
8568 if( sizeof(programStats.movelist) - strlen(programStats.movelist) - 1 > message_len ) {
8569 strcat(programStats.movelist, " ");
8570 strcat(programStats.movelist, p);
8573 if (currentMove == forwardMostMove || gameMode==AnalyzeMode ||
8574 gameMode == AnalyzeFile || appData.icsEngineAnalyze) {
8575 DisplayMove(currentMove - 1);
8583 if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
8584 &plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5)
8586 ChessProgramStats cpstats;
8588 if (plyext != ' ' && plyext != '\t') {
8592 /* [AS] Negate score if machine is playing black and reporting absolute scores */
8593 if( cps->scoreIsAbsolute && ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) ) {
8594 curscore = -curscore;
8597 cpstats.depth = plylev;
8598 cpstats.nodes = nodes;
8599 cpstats.time = time;
8600 cpstats.score = curscore;
8601 cpstats.got_only_move = 0;
8602 cpstats.movelist[0] = '\0';
8604 if (buf1[0] != NULLCHAR) {
8605 safeStrCpy( cpstats.movelist, buf1, sizeof(cpstats.movelist)/sizeof(cpstats.movelist[0]) );
8608 cpstats.ok_to_send = 0;
8609 cpstats.line_is_book = 0;
8610 cpstats.nr_moves = 0;
8611 cpstats.moves_left = 0;
8613 SendProgramStatsToFrontend( cps, &cpstats );
8620 /* Parse a game score from the character string "game", and
8621 record it as the history of the current game. The game
8622 score is NOT assumed to start from the standard position.
8623 The display is not updated in any way.
8626 ParseGameHistory(game)
8630 int fromX, fromY, toX, toY, boardIndex;
8635 if (appData.debugMode)
8636 fprintf(debugFP, "Parsing game history: %s\n", game);
8638 if (gameInfo.event == NULL) gameInfo.event = StrSave("ICS game");
8639 gameInfo.site = StrSave(appData.icsHost);
8640 gameInfo.date = PGNDate();
8641 gameInfo.round = StrSave("-");
8643 /* Parse out names of players */
8644 while (*game == ' ') game++;
8646 while (*game != ' ') *p++ = *game++;
8648 gameInfo.white = StrSave(buf);
8649 while (*game == ' ') game++;
8651 while (*game != ' ' && *game != '\n') *p++ = *game++;
8653 gameInfo.black = StrSave(buf);
8656 boardIndex = blackPlaysFirst ? 1 : 0;
8659 yyboardindex = boardIndex;
8660 moveType = (ChessMove) Myylex();
8662 case IllegalMove: /* maybe suicide chess, etc. */
8663 if (appData.debugMode) {
8664 fprintf(debugFP, "Illegal move from ICS: '%s'\n", yy_text);
8665 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8666 setbuf(debugFP, NULL);
8668 case WhitePromotion:
8669 case BlackPromotion:
8670 case WhiteNonPromotion:
8671 case BlackNonPromotion:
8673 case WhiteCapturesEnPassant:
8674 case BlackCapturesEnPassant:
8675 case WhiteKingSideCastle:
8676 case WhiteQueenSideCastle:
8677 case BlackKingSideCastle:
8678 case BlackQueenSideCastle:
8679 case WhiteKingSideCastleWild:
8680 case WhiteQueenSideCastleWild:
8681 case BlackKingSideCastleWild:
8682 case BlackQueenSideCastleWild:
8684 case WhiteHSideCastleFR:
8685 case WhiteASideCastleFR:
8686 case BlackHSideCastleFR:
8687 case BlackASideCastleFR:
8689 fromX = currentMoveString[0] - AAA;
8690 fromY = currentMoveString[1] - ONE;
8691 toX = currentMoveString[2] - AAA;
8692 toY = currentMoveString[3] - ONE;
8693 promoChar = currentMoveString[4];
8697 fromX = moveType == WhiteDrop ?
8698 (int) CharToPiece(ToUpper(currentMoveString[0])) :
8699 (int) CharToPiece(ToLower(currentMoveString[0]));
8701 toX = currentMoveString[2] - AAA;
8702 toY = currentMoveString[3] - ONE;
8703 promoChar = NULLCHAR;
8707 snprintf(buf, MSG_SIZ, _("Ambiguous move in ICS output: \"%s\""), yy_text);
8708 if (appData.debugMode) {
8709 fprintf(debugFP, "Ambiguous move from ICS: '%s'\n", yy_text);
8710 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8711 setbuf(debugFP, NULL);
8713 DisplayError(buf, 0);
8715 case ImpossibleMove:
8717 snprintf(buf, MSG_SIZ, _("Illegal move in ICS output: \"%s\""), yy_text);
8718 if (appData.debugMode) {
8719 fprintf(debugFP, "Impossible move from ICS: '%s'\n", yy_text);
8720 fprintf(debugFP, "board L=%d, R=%d, H=%d, holdings=%d\n", BOARD_LEFT, BOARD_RGHT, BOARD_HEIGHT, gameInfo.holdingsWidth);
8721 setbuf(debugFP, NULL);
8723 DisplayError(buf, 0);
8726 if (boardIndex < backwardMostMove) {
8727 /* Oops, gap. How did that happen? */
8728 DisplayError(_("Gap in move list"), 0);
8731 backwardMostMove = blackPlaysFirst ? 1 : 0;
8732 if (boardIndex > forwardMostMove) {
8733 forwardMostMove = boardIndex;
8737 if (boardIndex > (blackPlaysFirst ? 1 : 0)) {
8738 strcat(parseList[boardIndex-1], " ");
8739 strcat(parseList[boardIndex-1], yy_text);
8751 case GameUnfinished:
8752 if (gameMode == IcsExamining) {
8753 if (boardIndex < backwardMostMove) {
8754 /* Oops, gap. How did that happen? */
8757 backwardMostMove = blackPlaysFirst ? 1 : 0;
8760 gameInfo.result = moveType;
8761 p = strchr(yy_text, '{');
8762 if (p == NULL) p = strchr(yy_text, '(');
8765 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
8767 q = strchr(p, *p == '{' ? '}' : ')');
8768 if (q != NULL) *q = NULLCHAR;
8771 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
8772 gameInfo.resultDetails = StrSave(p);
8775 if (boardIndex >= forwardMostMove &&
8776 !(gameMode == IcsObserving && ics_gamenum == -1)) {
8777 backwardMostMove = blackPlaysFirst ? 1 : 0;
8780 (void) CoordsToAlgebraic(boards[boardIndex], PosFlags(boardIndex),
8781 fromY, fromX, toY, toX, promoChar,
8782 parseList[boardIndex]);
8783 CopyBoard(boards[boardIndex + 1], boards[boardIndex]);
8784 /* currentMoveString is set as a side-effect of yylex */
8785 safeStrCpy(moveList[boardIndex], currentMoveString, sizeof(moveList[boardIndex])/sizeof(moveList[boardIndex][0]));
8786 strcat(moveList[boardIndex], "\n");
8788 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[boardIndex]);
8789 switch (MateTest(boards[boardIndex], PosFlags(boardIndex)) ) {
8795 if(gameInfo.variant != VariantShogi)
8796 strcat(parseList[boardIndex - 1], "+");
8800 strcat(parseList[boardIndex - 1], "#");
8807 /* Apply a move to the given board */
8809 ApplyMove(fromX, fromY, toX, toY, promoChar, board)
8810 int fromX, fromY, toX, toY;
8814 ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
8815 int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
8817 /* [HGM] compute & store e.p. status and castling rights for new position */
8818 /* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
8820 if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
8821 oldEP = (signed char)board[EP_STATUS];
8822 board[EP_STATUS] = EP_NONE;
8824 if( board[toY][toX] != EmptySquare )
8825 board[EP_STATUS] = EP_CAPTURE;
8827 if (fromY == DROP_RANK) {
8829 piece = board[toY][toX] = (ChessSquare) fromX;
8833 if( board[fromY][fromX] == WhiteLance || board[fromY][fromX] == BlackLance ) {
8834 if( gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi )
8835 board[EP_STATUS] = EP_PAWN_MOVE; // Lance is Pawn-like in most variants
8837 if( board[fromY][fromX] == WhitePawn ) {
8838 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8839 board[EP_STATUS] = EP_PAWN_MOVE;
8841 if(toX>BOARD_LEFT && board[toY][toX-1] == BlackPawn &&
8842 gameInfo.variant != VariantBerolina || toX < fromX)
8843 board[EP_STATUS] = toX | berolina;
8844 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == BlackPawn &&
8845 gameInfo.variant != VariantBerolina || toX > fromX)
8846 board[EP_STATUS] = toX;
8849 if( board[fromY][fromX] == BlackPawn ) {
8850 if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
8851 board[EP_STATUS] = EP_PAWN_MOVE;
8852 if( toY-fromY== -2) {
8853 if(toX>BOARD_LEFT && board[toY][toX-1] == WhitePawn &&
8854 gameInfo.variant != VariantBerolina || toX < fromX)
8855 board[EP_STATUS] = toX | berolina;
8856 if(toX<BOARD_RGHT-1 && board[toY][toX+1] == WhitePawn &&
8857 gameInfo.variant != VariantBerolina || toX > fromX)
8858 board[EP_STATUS] = toX;
8862 for(i=0; i<nrCastlingRights; i++) {
8863 if(board[CASTLING][i] == fromX && castlingRank[i] == fromY ||
8864 board[CASTLING][i] == toX && castlingRank[i] == toY
8865 ) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
8868 if (fromX == toX && fromY == toY) return;
8870 piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
8871 king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
8872 if(gameInfo.variant == VariantKnightmate)
8873 king += (int) WhiteUnicorn - (int) WhiteKing;
8875 /* Code added by Tord: */
8876 /* FRC castling assumed when king captures friendly rook. [HGM] or RxK for S-Chess */
8877 if (board[fromY][fromX] == WhiteKing && board[toY][toX] == WhiteRook ||
8878 board[fromY][fromX] == WhiteRook && board[toY][toX] == WhiteKing) {
8879 board[fromY][fromX] = EmptySquare;
8880 board[toY][toX] = EmptySquare;
8881 if((toX > fromX) != (piece == WhiteRook)) {
8882 board[0][BOARD_RGHT-2] = WhiteKing; board[0][BOARD_RGHT-3] = WhiteRook;
8884 board[0][BOARD_LEFT+2] = WhiteKing; board[0][BOARD_LEFT+3] = WhiteRook;
8886 } else if (board[fromY][fromX] == BlackKing && board[toY][toX] == BlackRook ||
8887 board[fromY][fromX] == BlackRook && board[toY][toX] == BlackKing) {
8888 board[fromY][fromX] = EmptySquare;
8889 board[toY][toX] = EmptySquare;
8890 if((toX > fromX) != (piece == BlackRook)) {
8891 board[BOARD_HEIGHT-1][BOARD_RGHT-2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_RGHT-3] = BlackRook;
8893 board[BOARD_HEIGHT-1][BOARD_LEFT+2] = BlackKing; board[BOARD_HEIGHT-1][BOARD_LEFT+3] = BlackRook;
8895 /* End of code added by Tord */
8897 } else if (board[fromY][fromX] == king
8898 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8899 && toY == fromY && toX > fromX+1) {
8900 board[fromY][fromX] = EmptySquare;
8901 board[toY][toX] = king;
8902 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8903 board[fromY][BOARD_RGHT-1] = EmptySquare;
8904 } else if (board[fromY][fromX] == king
8905 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8906 && toY == fromY && toX < fromX-1) {
8907 board[fromY][fromX] = EmptySquare;
8908 board[toY][toX] = king;
8909 board[toY][toX+1] = board[fromY][BOARD_LEFT];
8910 board[fromY][BOARD_LEFT] = EmptySquare;
8911 } else if ((board[fromY][fromX] == WhitePawn && gameInfo.variant != VariantXiangqi ||
8912 board[fromY][fromX] == WhiteLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8913 && toY >= BOARD_HEIGHT-promoRank
8915 /* white pawn promotion */
8916 board[toY][toX] = CharToPiece(ToUpper(promoChar));
8917 if (board[toY][toX] == EmptySquare) {
8918 board[toY][toX] = WhiteQueen;
8920 if(gameInfo.variant==VariantBughouse ||
8921 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8922 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8923 board[fromY][fromX] = EmptySquare;
8924 } else if ((fromY == BOARD_HEIGHT-4)
8926 && gameInfo.variant != VariantXiangqi
8927 && gameInfo.variant != VariantBerolina
8928 && (board[fromY][fromX] == WhitePawn)
8929 && (board[toY][toX] == EmptySquare)) {
8930 board[fromY][fromX] = EmptySquare;
8931 board[toY][toX] = WhitePawn;
8932 captured = board[toY - 1][toX];
8933 board[toY - 1][toX] = EmptySquare;
8934 } else if ((fromY == BOARD_HEIGHT-4)
8936 && gameInfo.variant == VariantBerolina
8937 && (board[fromY][fromX] == WhitePawn)
8938 && (board[toY][toX] == EmptySquare)) {
8939 board[fromY][fromX] = EmptySquare;
8940 board[toY][toX] = WhitePawn;
8941 if(oldEP & EP_BEROLIN_A) {
8942 captured = board[fromY][fromX-1];
8943 board[fromY][fromX-1] = EmptySquare;
8944 }else{ captured = board[fromY][fromX+1];
8945 board[fromY][fromX+1] = EmptySquare;
8947 } else if (board[fromY][fromX] == king
8948 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8949 && toY == fromY && toX > fromX+1) {
8950 board[fromY][fromX] = EmptySquare;
8951 board[toY][toX] = king;
8952 board[toY][toX-1] = board[fromY][BOARD_RGHT-1];
8953 board[fromY][BOARD_RGHT-1] = EmptySquare;
8954 } else if (board[fromY][fromX] == king
8955 && fromX != BOARD_LEFT && fromX != BOARD_RGHT-1 // [HGM] cylinder */
8956 && toY == fromY && toX < fromX-1) {
8957 board[fromY][fromX] = EmptySquare;
8958 board[toY][toX] = king;
8959 board[toY][toX+1] = board[fromY][BOARD_LEFT];
8960 board[fromY][BOARD_LEFT] = EmptySquare;
8961 } else if (fromY == 7 && fromX == 3
8962 && board[fromY][fromX] == BlackKing
8963 && toY == 7 && toX == 5) {
8964 board[fromY][fromX] = EmptySquare;
8965 board[toY][toX] = BlackKing;
8966 board[fromY][7] = EmptySquare;
8967 board[toY][4] = BlackRook;
8968 } else if (fromY == 7 && fromX == 3
8969 && board[fromY][fromX] == BlackKing
8970 && toY == 7 && toX == 1) {
8971 board[fromY][fromX] = EmptySquare;
8972 board[toY][toX] = BlackKing;
8973 board[fromY][0] = EmptySquare;
8974 board[toY][2] = BlackRook;
8975 } else if ((board[fromY][fromX] == BlackPawn && gameInfo.variant != VariantXiangqi ||
8976 board[fromY][fromX] == BlackLance && gameInfo.variant != VariantSuper && gameInfo.variant != VariantShogi)
8979 /* black pawn promotion */
8980 board[toY][toX] = CharToPiece(ToLower(promoChar));
8981 if (board[toY][toX] == EmptySquare) {
8982 board[toY][toX] = BlackQueen;
8984 if(gameInfo.variant==VariantBughouse ||
8985 gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
8986 board[toY][toX] = (ChessSquare) (PROMOTED board[toY][toX]);
8987 board[fromY][fromX] = EmptySquare;
8988 } else if ((fromY == 3)
8990 && gameInfo.variant != VariantXiangqi
8991 && gameInfo.variant != VariantBerolina
8992 && (board[fromY][fromX] == BlackPawn)
8993 && (board[toY][toX] == EmptySquare)) {
8994 board[fromY][fromX] = EmptySquare;
8995 board[toY][toX] = BlackPawn;
8996 captured = board[toY + 1][toX];
8997 board[toY + 1][toX] = EmptySquare;
8998 } else if ((fromY == 3)
9000 && gameInfo.variant == VariantBerolina
9001 && (board[fromY][fromX] == BlackPawn)
9002 && (board[toY][toX] == EmptySquare)) {
9003 board[fromY][fromX] = EmptySquare;
9004 board[toY][toX] = BlackPawn;
9005 if(oldEP & EP_BEROLIN_A) {
9006 captured = board[fromY][fromX-1];
9007 board[fromY][fromX-1] = EmptySquare;
9008 }else{ captured = board[fromY][fromX+1];
9009 board[fromY][fromX+1] = EmptySquare;
9012 board[toY][toX] = board[fromY][fromX];
9013 board[fromY][fromX] = EmptySquare;
9017 if (gameInfo.holdingsWidth != 0) {
9019 /* !!A lot more code needs to be written to support holdings */
9020 /* [HGM] OK, so I have written it. Holdings are stored in the */
9021 /* penultimate board files, so they are automaticlly stored */
9022 /* in the game history. */
9023 if (fromY == DROP_RANK || gameInfo.variant == VariantSChess
9024 && promoChar && piece != WhitePawn && piece != BlackPawn) {
9025 /* Delete from holdings, by decreasing count */
9026 /* and erasing image if necessary */
9027 p = fromY == DROP_RANK ? (int) fromX : CharToPiece(piece > BlackPawn ? ToLower(promoChar) : ToUpper(promoChar));
9028 if(p < (int) BlackPawn) { /* white drop */
9029 p -= (int)WhitePawn;
9030 p = PieceToNumber((ChessSquare)p);
9031 if(p >= gameInfo.holdingsSize) p = 0;
9032 if(--board[p][BOARD_WIDTH-2] <= 0)
9033 board[p][BOARD_WIDTH-1] = EmptySquare;
9034 if((int)board[p][BOARD_WIDTH-2] < 0)
9035 board[p][BOARD_WIDTH-2] = 0;
9036 } else { /* black drop */
9037 p -= (int)BlackPawn;
9038 p = PieceToNumber((ChessSquare)p);
9039 if(p >= gameInfo.holdingsSize) p = 0;
9040 if(--board[BOARD_HEIGHT-1-p][1] <= 0)
9041 board[BOARD_HEIGHT-1-p][0] = EmptySquare;
9042 if((int)board[BOARD_HEIGHT-1-p][1] < 0)
9043 board[BOARD_HEIGHT-1-p][1] = 0;
9046 if (captured != EmptySquare && gameInfo.holdingsSize > 0
9047 && gameInfo.variant != VariantBughouse && gameInfo.variant != VariantSChess ) {
9048 /* [HGM] holdings: Add to holdings, if holdings exist */
9049 if(gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
9050 // [HGM] superchess: suppress flipping color of captured pieces by reverse pre-flip
9051 captured = (int) captured >= (int) BlackPawn ? BLACK_TO_WHITE captured : WHITE_TO_BLACK captured;
9054 if (p >= (int) BlackPawn) {
9055 p -= (int)BlackPawn;
9056 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9057 /* in Shogi restore piece to its original first */
9058 captured = (ChessSquare) (DEMOTED captured);
9061 p = PieceToNumber((ChessSquare)p);
9062 if(p >= gameInfo.holdingsSize) { p = 0; captured = BlackPawn; }
9063 board[p][BOARD_WIDTH-2]++;
9064 board[p][BOARD_WIDTH-1] = BLACK_TO_WHITE captured;
9066 p -= (int)WhitePawn;
9067 if(gameInfo.variant == VariantShogi && DEMOTED p >= 0) {
9068 captured = (ChessSquare) (DEMOTED captured);
9071 p = PieceToNumber((ChessSquare)p);
9072 if(p >= gameInfo.holdingsSize) { p = 0; captured = WhitePawn; }
9073 board[BOARD_HEIGHT-1-p][1]++;
9074 board[BOARD_HEIGHT-1-p][0] = WHITE_TO_BLACK captured;
9077 } else if (gameInfo.variant == VariantAtomic) {
9078 if (captured != EmptySquare) {
9080 for (y = toY-1; y <= toY+1; y++) {
9081 for (x = toX-1; x <= toX+1; x++) {
9082 if (y >= 0 && y < BOARD_HEIGHT && x >= BOARD_LEFT && x < BOARD_RGHT &&
9083 board[y][x] != WhitePawn && board[y][x] != BlackPawn) {
9084 board[y][x] = EmptySquare;
9088 board[toY][toX] = EmptySquare;
9091 if(gameInfo.variant == VariantSChess && promoChar != NULLCHAR && promoChar != '=' && piece != WhitePawn && piece != BlackPawn) {
9092 board[fromY][fromX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar)); // S-Chess gating
9094 if(promoChar == '+') {
9095 /* [HGM] Shogi-style promotions, to piece implied by original (Might overwrite orinary Pawn promotion) */
9096 board[toY][toX] = (ChessSquare) (PROMOTED piece);
9097 } else if(!appData.testLegality && promoChar != NULLCHAR && promoChar != '=') { // without legality testing, unconditionally believe promoChar
9098 board[toY][toX] = CharToPiece(piece < BlackPawn ? ToUpper(promoChar) : ToLower(promoChar));
9100 if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9101 && promoChar != NULLCHAR && gameInfo.holdingsSize) {
9102 // [HGM] superchess: take promotion piece out of holdings
9103 int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
9104 if((int)piece < (int)BlackPawn) { // determine stm from piece color
9105 if(!--board[k][BOARD_WIDTH-2])
9106 board[k][BOARD_WIDTH-1] = EmptySquare;
9108 if(!--board[BOARD_HEIGHT-1-k][1])
9109 board[BOARD_HEIGHT-1-k][0] = EmptySquare;
9115 /* Updates forwardMostMove */
9117 MakeMove(fromX, fromY, toX, toY, promoChar)
9118 int fromX, fromY, toX, toY;
9121 // forwardMostMove++; // [HGM] bare: moved downstream
9123 if(serverMoves != NULL) { /* [HGM] write moves on file for broadcasting (should be separate routine, really) */
9124 int timeLeft; static int lastLoadFlag=0; int king, piece;
9125 piece = boards[forwardMostMove][fromY][fromX];
9126 king = piece < (int) BlackPawn ? WhiteKing : BlackKing;
9127 if(gameInfo.variant == VariantKnightmate)
9128 king += (int) WhiteUnicorn - (int) WhiteKing;
9129 if(forwardMostMove == 0) {
9131 fprintf(serverMoves, "%s;", second.tidy);
9132 fprintf(serverMoves, "%s;", first.tidy);
9133 if(!blackPlaysFirst)
9134 fprintf(serverMoves, "%s;", second.tidy);
9135 } else fprintf(serverMoves, loadFlag|lastLoadFlag ? ":" : ";");
9136 lastLoadFlag = loadFlag;
9138 fprintf(serverMoves, "%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+toY);
9139 // print castling suffix
9140 if( toY == fromY && piece == king ) {
9142 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_RGHT-1, ONE+fromY, AAA+toX-1,ONE+toY);
9144 fprintf(serverMoves, ":%c%c:%c%c", AAA+BOARD_LEFT, ONE+fromY, AAA+toX+1,ONE+toY);
9147 if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
9148 boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
9149 boards[forwardMostMove][toY][toX] == EmptySquare
9150 && fromX != toX && fromY != toY)
9151 fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
9153 if(promoChar != NULLCHAR)
9154 fprintf(serverMoves, ":%c:%c%c", promoChar, AAA+toX, ONE+toY);
9156 fprintf(serverMoves, "/%d/%d",
9157 pvInfoList[forwardMostMove].depth, pvInfoList[forwardMostMove].score);
9158 if(forwardMostMove+1 & 1) timeLeft = whiteTimeRemaining/1000;
9159 else timeLeft = blackTimeRemaining/1000;
9160 fprintf(serverMoves, "/%d", timeLeft);
9162 fflush(serverMoves);
9165 if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
9166 DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
9170 UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
9171 if (commentList[forwardMostMove+1] != NULL) {
9172 free(commentList[forwardMostMove+1]);
9173 commentList[forwardMostMove+1] = NULL;
9175 CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
9176 ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
9177 // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
9178 SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
9179 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
9180 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
9181 gameInfo.result = GameUnfinished;
9182 if (gameInfo.resultDetails != NULL) {
9183 free(gameInfo.resultDetails);
9184 gameInfo.resultDetails = NULL;
9186 CoordsToComputerAlgebraic(fromY, fromX, toY, toX, promoChar,
9187 moveList[forwardMostMove - 1]);
9188 (void) CoordsToAlgebraic(boards[forwardMostMove - 1],
9189 PosFlags(forwardMostMove - 1),
9190 fromY, fromX, toY, toX, promoChar,
9191 parseList[forwardMostMove - 1]);
9192 switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
9198 if(gameInfo.variant != VariantShogi)
9199 strcat(parseList[forwardMostMove - 1], "+");
9203 strcat(parseList[forwardMostMove - 1], "#");
9206 if (appData.debugMode) {
9207 fprintf(debugFP, "move: %s, parse: %s (%c)\n", moveList[forwardMostMove-1], parseList[forwardMostMove-1], moveList[forwardMostMove-1][4]);
9212 /* Updates currentMove if not pausing */
9214 ShowMove(fromX, fromY, toX, toY)
9216 int instant = (gameMode == PlayFromGameFile) ?
9217 (matchMode || (appData.timeDelay == 0 && !pausing)) : pausing;
9218 if(appData.noGUI) return;
9219 if (!pausing || gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
9221 if (forwardMostMove == currentMove + 1) {
9222 AnimateMove(boards[forwardMostMove - 1],
9223 fromX, fromY, toX, toY);
9225 if (appData.highlightLastMove) {
9226 SetHighlights(fromX, fromY, toX, toY);
9229 currentMove = forwardMostMove;
9232 if (instant) return;
9234 DisplayMove(currentMove - 1);
9235 DrawPosition(FALSE, boards[currentMove]);
9236 DisplayBothClocks();
9237 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
9240 void SendEgtPath(ChessProgramState *cps)
9241 { /* [HGM] EGT: match formats given in feature with those given by user, and send info for each match */
9242 char buf[MSG_SIZ], name[MSG_SIZ], *p;
9244 if((p = cps->egtFormats) == NULL || appData.egtFormats == NULL) return;
9247 char c, *q = name+1, *r, *s;
9249 name[0] = ','; // extract next format name from feature and copy with prefixed ','
9250 while(*p && *p != ',') *q++ = *p++;
9252 if( appData.defaultPathEGTB && appData.defaultPathEGTB[0] &&
9253 strcmp(name, ",nalimov:") == 0 ) {
9254 // take nalimov path from the menu-changeable option first, if it is defined
9255 snprintf(buf, MSG_SIZ, "egtpath nalimov %s\n", appData.defaultPathEGTB);
9256 SendToProgram(buf,cps); // send egtbpath command for nalimov
9258 if( (s = StrStr(appData.egtFormats, name+1)) == appData.egtFormats ||
9259 (s = StrStr(appData.egtFormats, name)) != NULL) {
9260 // format name occurs amongst user-supplied formats, at beginning or immediately after comma
9261 s = r = StrStr(s, ":") + 1; // beginning of path info
9262 while(*r && *r != ',') r++; // path info is everything upto next ';' or end of string
9263 c = *r; *r = 0; // temporarily null-terminate path info
9264 *--q = 0; // strip of trailig ':' from name
9265 snprintf(buf, MSG_SIZ, "egtpath %s %s\n", name+1, s);
9267 SendToProgram(buf,cps); // send egtbpath command for this format
9269 if(*p == ',') p++; // read away comma to position for next format name
9274 InitChessProgram(cps, setup)
9275 ChessProgramState *cps;
9276 int setup; /* [HGM] needed to setup FRC opening position */
9278 char buf[MSG_SIZ], b[MSG_SIZ]; int overruled;
9279 if (appData.noChessProgram) return;
9280 hintRequested = FALSE;
9281 bookRequested = FALSE;
9283 /* [HGM] some new WB protocol commands to configure engine are sent now, if engine supports them */
9284 /* moved to before sending initstring in 4.3.15, so Polyglot can delay UCI 'isready' to recepton of 'new' */
9285 if(cps->memSize) { /* [HGM] memory */
9286 snprintf(buf, MSG_SIZ, "memory %d\n", appData.defaultHashSize + appData.defaultCacheSizeEGTB);
9287 SendToProgram(buf, cps);
9289 SendEgtPath(cps); /* [HGM] EGT */
9290 if(cps->maxCores) { /* [HGM] SMP: (protocol specified must be last settings command before new!) */
9291 snprintf(buf, MSG_SIZ, "cores %d\n", appData.smpCores);
9292 SendToProgram(buf, cps);
9295 SendToProgram(cps->initString, cps);
9296 if (gameInfo.variant != VariantNormal &&
9297 gameInfo.variant != VariantLoadable
9298 /* [HGM] also send variant if board size non-standard */
9299 || gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0
9301 char *v = VariantName(gameInfo.variant);
9302 if (cps->protocolVersion != 1 && StrStr(cps->variants, v) == NULL) {
9303 /* [HGM] in protocol 1 we have to assume all variants valid */
9304 snprintf(buf, MSG_SIZ, _("Variant %s not supported by %s"), v, cps->tidy);
9305 DisplayFatalError(buf, 0, 1);
9309 /* [HGM] make prefix for non-standard board size. Awkward testing... */
9310 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9311 if( gameInfo.variant == VariantXiangqi )
9312 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 10 || gameInfo.holdingsSize != 0;
9313 if( gameInfo.variant == VariantShogi )
9314 overruled = gameInfo.boardWidth != 9 || gameInfo.boardHeight != 9 || gameInfo.holdingsSize != 7;
9315 if( gameInfo.variant == VariantBughouse || gameInfo.variant == VariantCrazyhouse )
9316 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 5;
9317 if( gameInfo.variant == VariantCapablanca || gameInfo.variant == VariantCapaRandom ||
9318 gameInfo.variant == VariantGothic || gameInfo.variant == VariantFalcon || gameInfo.variant == VariantJanus )
9319 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9320 if( gameInfo.variant == VariantCourier )
9321 overruled = gameInfo.boardWidth != 12 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 0;
9322 if( gameInfo.variant == VariantSuper )
9323 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9324 if( gameInfo.variant == VariantGreat )
9325 overruled = gameInfo.boardWidth != 10 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 8;
9326 if( gameInfo.variant == VariantSChess )
9327 overruled = gameInfo.boardWidth != 8 || gameInfo.boardHeight != 8 || gameInfo.holdingsSize != 7;
9330 snprintf(b, MSG_SIZ, "%dx%d+%d_%s", gameInfo.boardWidth, gameInfo.boardHeight,
9331 gameInfo.holdingsSize, VariantName(gameInfo.variant)); // cook up sized variant name
9332 /* [HGM] varsize: try first if this defiant size variant is specifically known */
9333 if(StrStr(cps->variants, b) == NULL) {
9334 // specific sized variant not known, check if general sizing allowed
9335 if (cps->protocolVersion != 1) { // for protocol 1 we cannot check and hope for the best
9336 if(StrStr(cps->variants, "boardsize") == NULL) {
9337 snprintf(buf, MSG_SIZ, "Board size %dx%d+%d not supported by %s",
9338 gameInfo.boardWidth, gameInfo.boardHeight, gameInfo.holdingsSize, cps->tidy);
9339 DisplayFatalError(buf, 0, 1);
9342 /* [HGM] here we really should compare with the maximum supported board size */
9345 } else snprintf(b, MSG_SIZ,"%s", VariantName(gameInfo.variant));
9346 snprintf(buf, MSG_SIZ, "variant %s\n", b);
9347 SendToProgram(buf, cps);
9349 currentlyInitializedVariant = gameInfo.variant;
9351 /* [HGM] send opening position in FRC to first engine */
9353 SendToProgram("force\n", cps);
9355 /* engine is now in force mode! Set flag to wake it up after first move. */
9356 setboardSpoiledMachineBlack = 1;
9360 snprintf(buf, sizeof(buf), "ics %s\n", appData.icsActive ? appData.icsHost : "-");
9361 SendToProgram(buf, cps);
9363 cps->maybeThinking = FALSE;
9364 cps->offeredDraw = 0;
9365 if (!appData.icsActive) {
9366 SendTimeControl(cps, movesPerSession, timeControl,
9367 timeIncrement, appData.searchDepth,
9370 if (appData.showThinking
9371 // [HGM] thinking: four options require thinking output to be sent
9372 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp()
9374 SendToProgram("post\n", cps);
9376 SendToProgram("hard\n", cps);
9377 if (!appData.ponderNextMove) {
9378 /* Warning: "easy" is a toggle in GNU Chess, so don't send
9379 it without being sure what state we are in first. "hard"
9380 is not a toggle, so that one is OK.
9382 SendToProgram("easy\n", cps);
9385 snprintf(buf, MSG_SIZ, "ping %d\n", ++cps->lastPing);
9386 SendToProgram(buf, cps);
9388 cps->initDone = TRUE;
9393 StartChessProgram(cps)
9394 ChessProgramState *cps;
9399 if (appData.noChessProgram) return;
9400 cps->initDone = FALSE;
9402 if (strcmp(cps->host, "localhost") == 0) {
9403 err = StartChildProcess(cps->program, cps->dir, &cps->pr);
9404 } else if (*appData.remoteShell == NULLCHAR) {
9405 err = OpenRcmd(cps->host, appData.remoteUser, cps->program, &cps->pr);
9407 if (*appData.remoteUser == NULLCHAR) {
9408 snprintf(buf, sizeof(buf), "%s %s %s", appData.remoteShell, cps->host,
9411 snprintf(buf, sizeof(buf), "%s %s -l %s %s", appData.remoteShell,
9412 cps->host, appData.remoteUser, cps->program);
9414 err = StartChildProcess(buf, "", &cps->pr);
9418 snprintf(buf, MSG_SIZ, _("Startup failure on '%s'"), cps->program);
9419 DisplayFatalError(buf, err, 1);
9425 cps->isr = AddInputSource(cps->pr, TRUE, ReceiveFromProgram, cps);
9426 if (cps->protocolVersion > 1) {
9427 snprintf(buf, MSG_SIZ, "xboard\nprotover %d\n", cps->protocolVersion);
9428 cps->nrOptions = 0; // [HGM] options: clear all engine-specific options
9429 cps->comboCnt = 0; // and values of combo boxes
9430 SendToProgram(buf, cps);
9432 SendToProgram("xboard\n", cps);
9437 TwoMachinesEventIfReady P((void))
9439 static int curMess = 0;
9440 if (first.lastPing != first.lastPong) {
9441 if(curMess != 1) DisplayMessage("", _("Waiting for first chess program")); curMess = 1;
9442 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9445 if (second.lastPing != second.lastPong) {
9446 if(curMess != 2) DisplayMessage("", _("Waiting for second chess program")); curMess = 2;
9447 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10); // [HGM] fast: lowered from 1000
9450 DisplayMessage("", ""); curMess = 0;
9456 CreateTourney(char *name)
9459 if(name[0] == NULLCHAR) return 0;
9460 f = fopen(appData.tourneyFile, "r");
9461 if(f) { // file exists
9462 ParseArgsFromFile(f); // parse it
9464 f = fopen(appData.tourneyFile, "w");
9465 if(f == NULL) { DisplayError("Could not write on tourney file", 0); return 0; } else {
9466 // create a file with tournament description
9467 fprintf(f, "-participants {%s}\n", appData.participants);
9468 fprintf(f, "-tourneyType %d\n", appData.tourneyType);
9469 fprintf(f, "-tourneyCycles %d\n", appData.tourneyCycles);
9470 fprintf(f, "-defaultMatchGames %d\n", appData.defaultMatchGames);
9471 fprintf(f, "-syncAfterRound %s\n", appData.roundSync ? "true" : "false");
9472 fprintf(f, "-syncAfterCycle %s\n", appData.cycleSync ? "true" : "false");
9473 fprintf(f, "-saveGameFile \"%s\"\n", appData.saveGameFile);
9474 fprintf(f, "-loadGameFile \"%s\"\n", appData.loadGameFile);
9475 fprintf(f, "-loadGameIndex %d\n", appData.loadGameIndex);
9476 fprintf(f, "-loadPositionFile \"%s\"\n", appData.loadPositionFile);
9477 fprintf(f, "-loadPositionIndex %d\n", appData.loadPositionIndex);
9478 fprintf(f, "-rewindIndex %d\n", appData.rewindIndex);
9479 fprintf(f, "-results \"\"\n");
9483 appData.noChessProgram = FALSE;
9484 appData.clockMode = TRUE;
9489 #define MAXENGINES 1000
9490 char *command[MAXENGINES], *mnemonic[MAXENGINES];
9492 void NamesToList(char *names, char **engineList, char **engineMnemonic)
9494 char buf[MSG_SIZ], *p, *q;
9498 while(*p && *p != '\n') *q++ = *p++;
9500 if(engineList[i]) free(engineList[i]);
9501 engineList[i] = strdup(buf);
9503 TidyProgramName(engineList[i], "localhost", buf);
9504 if(engineMnemonic[i]) free(engineMnemonic[i]);
9505 if((q = strstr(engineList[i]+2, "variant")) && q[-2]== ' ' && (q[-1]=='/' || q[-1]=='-') && (q[7]==' ' || q[7]=='=')) {
9507 sscanf(q + 8, "%s", buf + strlen(buf));
9510 engineMnemonic[i] = strdup(buf);
9512 if(i > MAXENGINES - 2) break;
9514 engineList[i] = NULL;
9517 // following implemented as macro to avoid type limitations
9518 #define SWAP(item, temp) temp = appData.item[0]; appData.item[0] = appData.item[n]; appData.item[n] = temp;
9520 void SwapEngines(int n)
9521 { // swap settings for first engine and other engine (so far only some selected options)
9526 SWAP(chessProgram, p)
9528 SWAP(hasOwnBookUCI, h)
9529 SWAP(protocolVersion, h)
9531 SWAP(scoreIsAbsolute, h)
9537 SetPlayer(int player)
9538 { // [HGM] find the engine line of the partcipant given by number, and parse its options.
9540 char buf[MSG_SIZ], *engineName, *p = appData.participants;
9541 static char resetOptions[] = "-reuse -firstIsUCI false -firstHasOwnBookUCI true -firstTimeOdds 1 -firstOptions \"\" "
9542 "-firstNeedsNoncompliantFEN false -firstNPS -1";
9543 for(i=0; i<player; i++) p = strchr(p, '\n') + 1;
9544 engineName = strdup(p); if(p = strchr(engineName, '\n')) *p = NULLCHAR;
9545 for(i=1; command[i]; i++) if(!strcmp(mnemonic[i], engineName)) break;
9547 snprintf(buf, MSG_SIZ, "-fcp %s", command[i]);
9548 ParseArgsFromString(resetOptions);
9549 ParseArgsFromString(buf);
9555 Pairing(int nr, int nPlayers, int *whitePlayer, int *blackPlayer, int *syncInterval)
9556 { // determine players from game number
9557 int curCycle, curRound, curPairing, gamesPerCycle, gamesPerRound, roundsPerCycle, pairingsPerRound;
9559 if(appData.tourneyType == 0) {
9560 roundsPerCycle = (nPlayers - 1) | 1;
9561 pairingsPerRound = nPlayers / 2;
9562 } else if(appData.tourneyType > 0) {
9563 roundsPerCycle = nPlayers - appData.tourneyType;
9564 pairingsPerRound = appData.tourneyType;
9566 gamesPerRound = pairingsPerRound * appData.defaultMatchGames;
9567 gamesPerCycle = gamesPerRound * roundsPerCycle;
9568 appData.matchGames = gamesPerCycle * appData.tourneyCycles - 1; // fake like all games are one big match
9569 curCycle = nr / gamesPerCycle; nr %= gamesPerCycle;
9570 curRound = nr / gamesPerRound; nr %= gamesPerRound;
9571 curPairing = nr / appData.defaultMatchGames; nr %= appData.defaultMatchGames;
9572 matchGame = nr + curCycle * appData.defaultMatchGames + 1; // fake game nr that loads correct game or position from file
9573 roundNr = (curCycle * roundsPerCycle + curRound) * appData.defaultMatchGames + nr + 1;
9575 if(appData.cycleSync) *syncInterval = gamesPerCycle;
9576 if(appData.roundSync) *syncInterval = gamesPerRound;
9578 if(appData.debugMode) fprintf(debugFP, "cycle=%d, round=%d, pairing=%d curGame=%d\n", curCycle, curRound, curPairing, matchGame);
9580 if(appData.tourneyType == 0) {
9581 if(curPairing == (nPlayers-1)/2 ) {
9582 *whitePlayer = curRound;
9583 *blackPlayer = nPlayers - 1; // this is the 'bye' when nPlayer is odd
9585 *whitePlayer = curRound - pairingsPerRound + curPairing;
9586 if(*whitePlayer < 0) *whitePlayer += nPlayers-1+(nPlayers&1);
9587 *blackPlayer = curRound + pairingsPerRound - curPairing;
9588 if(*blackPlayer >= nPlayers-1+(nPlayers&1)) *blackPlayer -= nPlayers-1+(nPlayers&1);
9590 } else if(appData.tourneyType > 0) {
9591 *whitePlayer = curPairing;
9592 *blackPlayer = curRound + appData.tourneyType;
9595 // take care of white/black alternation per round.
9596 // For cycles and games this is already taken care of by default, derived from matchGame!
9597 return curRound & 1;
9601 NextTourneyGame(int nr, int *swapColors)
9602 { // !!!major kludge!!! fiddle appData settings to get everything in order for next tourney game
9604 int whitePlayer, blackPlayer, firstBusy=1000000000, syncInterval = 0, nPlayers=0;
9606 if(appData.tourneyFile[0] == NULLCHAR) return 1; // no tourney, always allow next game
9607 tf = fopen(appData.tourneyFile, "r");
9608 if(tf == NULL) { DisplayFatalError(_("Bad tournament file"), 0, 1); return 0; }
9609 ParseArgsFromFile(tf); fclose(tf);
9611 p = appData.participants;
9612 while(p = strchr(p, '\n')) p++, nPlayers++; // count participants
9613 *swapColors = Pairing(nr, nPlayers, &whitePlayer, &blackPlayer, &syncInterval);
9616 p = q = appData.results;
9617 while(*q) if(*q++ == '*' || q[-1] == ' ') { firstBusy = q - p - 1; break; }
9618 if(firstBusy/syncInterval < (nextGame/syncInterval)) {
9619 DisplayMessage(_("Waiting for other game(s)"),"");
9620 waitingForGame = TRUE;
9621 ScheduleDelayedEvent(NextMatchGame, 1000); // wait for all games of previous round to finish
9624 waitingForGame = FALSE;
9627 if(first.pr != NoProc) return 1; // engines already loaded
9629 // redefine engines, engine dir, etc.
9630 NamesToList(firstChessProgramNames, command, mnemonic); // get mnemonics of installed engines
9631 SetPlayer(whitePlayer); // find white player amongst it, and parse its engine line
9633 SetPlayer(blackPlayer); // find black player amongst it, and parse its engine line
9634 SwapEngines(1); // and make that valid for second engine by swapping
9635 InitEngine(&first, 0); // initialize ChessProgramStates based on new settings.
9636 InitEngine(&second, 1);
9637 CommonEngineInit(); // after this TwoMachinesEvent will create correct engine processes
9643 { // performs game initialization that does not invoke engines, and then tries to start the game
9644 int firstWhite, swapColors = 0;
9645 if(!NextTourneyGame(nextGame, &swapColors)) return; // this sets matchGame, -fcp / -scp and other options for next game, if needed
9646 firstWhite = appData.firstPlaysBlack ^ (matchGame & 1 | appData.sameColorGames > 1); // non-incremental default
9647 firstWhite ^= swapColors; // reverses if NextTourneyGame says we are in an odd round
9648 first.twoMachinesColor = firstWhite ? "white\n" : "black\n"; // perform actual color assignement
9649 second.twoMachinesColor = firstWhite ? "black\n" : "white\n";
9650 appData.noChessProgram = (first.pr == NoProc); // kludge to prevent Reset from starting up chess program
9651 Reset(FALSE, first.pr != NoProc);
9652 appData.noChessProgram = FALSE;
9653 if(!LoadGameOrPosition(matchGame)) return; // setup game; abort when bad game/pos file
9657 void UserAdjudicationEvent( int result )
9659 ChessMove gameResult = GameIsDrawn;
9662 gameResult = WhiteWins;
9664 else if( result < 0 ) {
9665 gameResult = BlackWins;
9668 if( gameMode == TwoMachinesPlay ) {
9669 GameEnds( gameResult, "User adjudication", GE_XBOARD );
9674 // [HGM] save: calculate checksum of game to make games easily identifiable
9675 int StringCheckSum(char *s)
9678 if(s==NULL) return 0;
9679 while(*s) i = i*259 + *s++;
9686 for(i=backwardMostMove; i<forwardMostMove; i++) {
9687 sum += pvInfoList[i].depth;
9688 sum += StringCheckSum(parseList[i]);
9689 sum += StringCheckSum(commentList[i]);
9692 if(i>1 && sum==0) sum++; // make sure never zero for non-empty game
9693 return sum + StringCheckSum(commentList[i]);
9694 } // end of save patch
9697 GameEnds(result, resultDetails, whosays)
9699 char *resultDetails;
9702 GameMode nextGameMode;
9704 char buf[MSG_SIZ], popupRequested = 0, *ranking = NULL;
9706 if(endingGame) return; /* [HGM] crash: forbid recursion */
9708 if(twoBoards) { // [HGM] dual: switch back to one board
9709 twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
9710 DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
9712 if (appData.debugMode) {
9713 fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
9714 result, resultDetails ? resultDetails : "(null)", whosays);
9717 fromX = fromY = -1; // [HGM] abort any move the user is entering.
9719 if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
9720 /* If we are playing on ICS, the server decides when the
9721 game is over, but the engine can offer to draw, claim
9725 if (appData.zippyPlay && first.initDone) {
9726 if (result == GameIsDrawn) {
9727 /* In case draw still needs to be claimed */
9728 SendToICS(ics_prefix);
9729 SendToICS("draw\n");
9730 } else if (StrCaseStr(resultDetails, "resign")) {
9731 SendToICS(ics_prefix);
9732 SendToICS("resign\n");
9736 endingGame = 0; /* [HGM] crash */
9740 /* If we're loading the game from a file, stop */
9741 if (whosays == GE_FILE) {
9742 (void) StopLoadGameTimer();
9746 /* Cancel draw offers */
9747 first.offeredDraw = second.offeredDraw = 0;
9749 /* If this is an ICS game, only ICS can really say it's done;
9750 if not, anyone can. */
9751 isIcsGame = (gameMode == IcsPlayingWhite ||
9752 gameMode == IcsPlayingBlack ||
9753 gameMode == IcsObserving ||
9754 gameMode == IcsExamining);
9756 if (!isIcsGame || whosays == GE_ICS) {
9757 /* OK -- not an ICS game, or ICS said it was done */
9759 if (!isIcsGame && !appData.noChessProgram)
9760 SetUserThinkingEnables();
9762 /* [HGM] if a machine claims the game end we verify this claim */
9763 if(gameMode == TwoMachinesPlay && appData.testClaims) {
9764 if(appData.testLegality && whosays >= GE_ENGINE1 ) {
9766 ChessMove trueResult = (ChessMove) -1;
9768 claimer = whosays == GE_ENGINE1 ? /* color of claimer */
9769 first.twoMachinesColor[0] :
9770 second.twoMachinesColor[0] ;
9772 // [HGM] losers: because the logic is becoming a bit hairy, determine true result first
9773 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
9774 /* [HGM] verify: engine mate claims accepted if they were flagged */
9775 trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
9777 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
9778 /* [HGM] verify: engine mate claims accepted if they were flagged */
9779 trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
9781 if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
9782 trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
9785 // now verify win claims, but not in drop games, as we don't understand those yet
9786 if( (gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper
9787 || gameInfo.variant == VariantGreat) &&
9788 (result == WhiteWins && claimer == 'w' ||
9789 result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
9790 if (appData.debugMode) {
9791 fprintf(debugFP, "result=%d sp=%d move=%d\n",
9792 result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
9794 if(result != trueResult) {
9795 snprintf(buf, MSG_SIZ, "False win claim: '%s'", resultDetails);
9796 result = claimer == 'w' ? BlackWins : WhiteWins;
9797 resultDetails = buf;
9800 if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
9801 && (forwardMostMove <= backwardMostMove ||
9802 (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
9803 (claimer=='b')==(forwardMostMove&1))
9805 /* [HGM] verify: draws that were not flagged are false claims */
9806 snprintf(buf, MSG_SIZ, "False draw claim: '%s'", resultDetails);
9807 result = claimer == 'w' ? BlackWins : WhiteWins;
9808 resultDetails = buf;
9810 /* (Claiming a loss is accepted no questions asked!) */
9812 /* [HGM] bare: don't allow bare King to win */
9813 if((gameInfo.holdingsWidth == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat)
9814 && gameInfo.variant != VariantLosers && gameInfo.variant != VariantGiveaway
9815 && gameInfo.variant != VariantSuicide // [HGM] losers: except in losers, of course...
9816 && result != GameIsDrawn)
9817 { int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
9818 for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
9819 int p = (signed char)boards[forwardMostMove][i][j] - color;
9820 if(p >= 0 && p <= (int)WhiteKing) k++;
9822 if (appData.debugMode) {
9823 fprintf(debugFP, "GE(%d, %s, %d) bare king k=%d color=%d\n",
9824 result, resultDetails ? resultDetails : "(null)", whosays, k, color);
9827 result = GameIsDrawn;
9828 snprintf(buf, MSG_SIZ, "%s but bare king", resultDetails);
9829 resultDetails = buf;
9835 if(serverMoves != NULL && !loadFlag) { char c = '=';
9836 if(result==WhiteWins) c = '+';
9837 if(result==BlackWins) c = '-';
9838 if(resultDetails != NULL)
9839 fprintf(serverMoves, ";%c;%s\n", c, resultDetails);
9841 if (resultDetails != NULL) {
9842 gameInfo.result = result;
9843 gameInfo.resultDetails = StrSave(resultDetails);
9845 /* display last move only if game was not loaded from file */
9846 if ((whosays != GE_FILE) && (currentMove == forwardMostMove))
9847 DisplayMove(currentMove - 1);
9849 if (forwardMostMove != 0) {
9850 if (gameMode != PlayFromGameFile && gameMode != EditGame
9851 && lastSavedGame != GameCheckSum() // [HGM] save: suppress duplicates
9853 if (*appData.saveGameFile != NULLCHAR) {
9854 SaveGameToFile(appData.saveGameFile, TRUE);
9855 } else if (appData.autoSaveGames) {
9858 if (*appData.savePositionFile != NULLCHAR) {
9859 SavePositionToFile(appData.savePositionFile);
9864 /* Tell program how game ended in case it is learning */
9865 /* [HGM] Moved this to after saving the PGN, just in case */
9866 /* engine died and we got here through time loss. In that */
9867 /* case we will get a fatal error writing the pipe, which */
9868 /* would otherwise lose us the PGN. */
9869 /* [HGM] crash: not needed anymore, but doesn't hurt; */
9870 /* output during GameEnds should never be fatal anymore */
9871 if (gameMode == MachinePlaysWhite ||
9872 gameMode == MachinePlaysBlack ||
9873 gameMode == TwoMachinesPlay ||
9874 gameMode == IcsPlayingWhite ||
9875 gameMode == IcsPlayingBlack ||
9876 gameMode == BeginningOfGame) {
9878 snprintf(buf, MSG_SIZ, "result %s {%s}\n", PGNResult(result),
9880 if (first.pr != NoProc) {
9881 SendToProgram(buf, &first);
9883 if (second.pr != NoProc &&
9884 gameMode == TwoMachinesPlay) {
9885 SendToProgram(buf, &second);
9890 if (appData.icsActive) {
9891 if (appData.quietPlay &&
9892 (gameMode == IcsPlayingWhite ||
9893 gameMode == IcsPlayingBlack)) {
9894 SendToICS(ics_prefix);
9895 SendToICS("set shout 1\n");
9897 nextGameMode = IcsIdle;
9898 ics_user_moved = FALSE;
9899 /* clean up premove. It's ugly when the game has ended and the
9900 * premove highlights are still on the board.
9904 ClearPremoveHighlights();
9905 DrawPosition(FALSE, boards[currentMove]);
9907 if (whosays == GE_ICS) {
9910 if (gameMode == IcsPlayingWhite)
9912 else if(gameMode == IcsPlayingBlack)
9916 if (gameMode == IcsPlayingBlack)
9918 else if(gameMode == IcsPlayingWhite)
9925 PlayIcsUnfinishedSound();
9928 } else if (gameMode == EditGame ||
9929 gameMode == PlayFromGameFile ||
9930 gameMode == AnalyzeMode ||
9931 gameMode == AnalyzeFile) {
9932 nextGameMode = gameMode;
9934 nextGameMode = EndOfGame;
9939 nextGameMode = gameMode;
9942 if (appData.noChessProgram) {
9943 gameMode = nextGameMode;
9945 endingGame = 0; /* [HGM] crash */
9950 /* Put first chess program into idle state */
9951 if (first.pr != NoProc &&
9952 (gameMode == MachinePlaysWhite ||
9953 gameMode == MachinePlaysBlack ||
9954 gameMode == TwoMachinesPlay ||
9955 gameMode == IcsPlayingWhite ||
9956 gameMode == IcsPlayingBlack ||
9957 gameMode == BeginningOfGame)) {
9958 SendToProgram("force\n", &first);
9959 if (first.usePing) {
9961 snprintf(buf, MSG_SIZ, "ping %d\n", ++first.lastPing);
9962 SendToProgram(buf, &first);
9965 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9966 /* Kill off first chess program */
9967 if (first.isr != NULL)
9968 RemoveInputSource(first.isr);
9971 if (first.pr != NoProc) {
9973 DoSleep( appData.delayBeforeQuit );
9974 SendToProgram("quit\n", &first);
9975 DoSleep( appData.delayAfterQuit );
9976 DestroyChildProcess(first.pr, first.useSigterm);
9981 /* Put second chess program into idle state */
9982 if (second.pr != NoProc &&
9983 gameMode == TwoMachinesPlay) {
9984 SendToProgram("force\n", &second);
9985 if (second.usePing) {
9987 snprintf(buf, MSG_SIZ, "ping %d\n", ++second.lastPing);
9988 SendToProgram(buf, &second);
9991 } else if (result != GameUnfinished || nextGameMode == IcsIdle) {
9992 /* Kill off second chess program */
9993 if (second.isr != NULL)
9994 RemoveInputSource(second.isr);
9997 if (second.pr != NoProc) {
9998 DoSleep( appData.delayBeforeQuit );
9999 SendToProgram("quit\n", &second);
10000 DoSleep( appData.delayAfterQuit );
10001 DestroyChildProcess(second.pr, second.useSigterm);
10003 second.pr = NoProc;
10006 if (matchMode && (gameMode == TwoMachinesPlay || waitingForGame && exiting)) {
10007 char resChar = '=';
10011 if (first.twoMachinesColor[0] == 'w') {
10014 second.matchWins++;
10019 if (first.twoMachinesColor[0] == 'b') {
10022 second.matchWins++;
10025 case GameUnfinished:
10031 if(waitingForGame) resChar = ' '; // quit while waiting for round sync: unreserve already reserved game
10032 if(appData.tourneyFile[0] && !abortMatch){ // [HGM] we are in a tourney; update tourney file with game result
10033 ReserveGame(nextGame, resChar); // sets nextGame
10034 if(nextGame > appData.matchGames) appData.tourneyFile[0] = 0, ranking = TourneyStandings(3); // tourney is done
10035 } else roundNr = nextGame = matchGame + 1; // normal match, just increment; round equals matchGame
10037 if (nextGame <= appData.matchGames && !abortMatch) {
10038 gameMode = nextGameMode;
10039 matchGame = nextGame; // this will be overruled in tourney mode!
10040 GetTimeMark(&pauseStart); // [HGM] matchpause: stipulate a pause
10041 ScheduleDelayedEvent(NextMatchGame, 10); // but start game immediately (as it will wait out the pause itself)
10042 endingGame = 0; /* [HGM] crash */
10045 gameMode = nextGameMode;
10046 snprintf(buf, MSG_SIZ, _("Match %s vs. %s: final score %d-%d-%d"),
10047 first.tidy, second.tidy,
10048 first.matchWins, second.matchWins,
10049 appData.matchGames - (first.matchWins + second.matchWins));
10050 popupRequested++; // [HGM] crash: postpone to after resetting endingGame
10051 if (appData.firstPlaysBlack) { // [HGM] match: back to original for next match
10052 first.twoMachinesColor = "black\n";
10053 second.twoMachinesColor = "white\n";
10055 first.twoMachinesColor = "white\n";
10056 second.twoMachinesColor = "black\n";
10060 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
10061 !(nextGameMode == AnalyzeMode || nextGameMode == AnalyzeFile))
10063 gameMode = nextGameMode;
10065 endingGame = 0; /* [HGM] crash */
10066 if(popupRequested) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
10067 if(matchMode == TRUE) { // match through command line: exit with or without popup
10069 if(strcmp(ranking, "busy")) DisplayFatalError(ranking, 0, 0);
10071 } else DisplayFatalError(buf, 0, 0);
10072 } else { // match through menu; just stop, with or without popup
10073 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10075 if(strcmp(ranking, "busy")) DisplayNote(ranking);
10076 } else DisplayNote(buf);
10078 if(ranking) free(ranking);
10082 /* Assumes program was just initialized (initString sent).
10083 Leaves program in force mode. */
10085 FeedMovesToProgram(cps, upto)
10086 ChessProgramState *cps;
10091 if (appData.debugMode)
10092 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10093 startedFromSetupPosition ? "position and " : "",
10094 backwardMostMove, upto, cps->which);
10095 if(currentlyInitializedVariant != gameInfo.variant) {
10097 // [HGM] variantswitch: make engine aware of new variant
10098 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10099 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10100 snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10101 SendToProgram(buf, cps);
10102 currentlyInitializedVariant = gameInfo.variant;
10104 SendToProgram("force\n", cps);
10105 if (startedFromSetupPosition) {
10106 SendBoard(cps, backwardMostMove);
10107 if (appData.debugMode) {
10108 fprintf(debugFP, "feedMoves\n");
10111 for (i = backwardMostMove; i < upto; i++) {
10112 SendMoveToProgram(i, cps);
10118 ResurrectChessProgram()
10120 /* The chess program may have exited.
10121 If so, restart it and feed it all the moves made so far. */
10122 static int doInit = 0;
10124 if (appData.noChessProgram) return 1;
10126 if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10127 if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10128 if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10129 doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10131 if (first.pr != NoProc) return 1;
10132 StartChessProgram(&first);
10134 InitChessProgram(&first, FALSE);
10135 FeedMovesToProgram(&first, currentMove);
10137 if (!first.sendTime) {
10138 /* can't tell gnuchess what its clock should read,
10139 so we bow to its notion. */
10141 timeRemaining[0][currentMove] = whiteTimeRemaining;
10142 timeRemaining[1][currentMove] = blackTimeRemaining;
10145 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10146 appData.icsEngineAnalyze) && first.analysisSupport) {
10147 SendToProgram("analyze\n", &first);
10148 first.analyzing = TRUE;
10154 * Button procedures
10157 Reset(redraw, init)
10162 if (appData.debugMode) {
10163 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10164 redraw, init, gameMode);
10166 CleanupTail(); // [HGM] vari: delete any stored variations
10167 pausing = pauseExamInvalid = FALSE;
10168 startedFromSetupPosition = blackPlaysFirst = FALSE;
10170 whiteFlag = blackFlag = FALSE;
10171 userOfferedDraw = FALSE;
10172 hintRequested = bookRequested = FALSE;
10173 first.maybeThinking = FALSE;
10174 second.maybeThinking = FALSE;
10175 first.bookSuspend = FALSE; // [HGM] book
10176 second.bookSuspend = FALSE;
10177 thinkOutput[0] = NULLCHAR;
10178 lastHint[0] = NULLCHAR;
10179 ClearGameInfo(&gameInfo);
10180 gameInfo.variant = StringToVariant(appData.variant);
10181 ics_user_moved = ics_clock_paused = FALSE;
10182 ics_getting_history = H_FALSE;
10184 white_holding[0] = black_holding[0] = NULLCHAR;
10185 ClearProgramStats();
10186 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10190 flipView = appData.flipView;
10191 ClearPremoveHighlights();
10192 gotPremove = FALSE;
10193 alarmSounded = FALSE;
10195 GameEnds(EndOfFile, NULL, GE_PLAYER);
10196 if(appData.serverMovesName != NULL) {
10197 /* [HGM] prepare to make moves file for broadcasting */
10198 clock_t t = clock();
10199 if(serverMoves != NULL) fclose(serverMoves);
10200 serverMoves = fopen(appData.serverMovesName, "r");
10201 if(serverMoves != NULL) {
10202 fclose(serverMoves);
10203 /* delay 15 sec before overwriting, so all clients can see end */
10204 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10206 serverMoves = fopen(appData.serverMovesName, "w");
10210 gameMode = BeginningOfGame;
10212 if(appData.icsActive) gameInfo.variant = VariantNormal;
10213 currentMove = forwardMostMove = backwardMostMove = 0;
10214 InitPosition(redraw);
10215 for (i = 0; i < MAX_MOVES; i++) {
10216 if (commentList[i] != NULL) {
10217 free(commentList[i]);
10218 commentList[i] = NULL;
10222 timeRemaining[0][0] = whiteTimeRemaining;
10223 timeRemaining[1][0] = blackTimeRemaining;
10225 if (first.pr == NULL) {
10226 StartChessProgram(&first);
10229 InitChessProgram(&first, startedFromSetupPosition);
10232 DisplayMessage("", "");
10233 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10234 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10241 if (!AutoPlayOneMove())
10243 if (matchMode || appData.timeDelay == 0)
10245 if (appData.timeDelay < 0)
10247 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10256 int fromX, fromY, toX, toY;
10258 if (appData.debugMode) {
10259 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10262 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10265 if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10266 pvInfoList[currentMove].depth = programStats.depth;
10267 pvInfoList[currentMove].score = programStats.score;
10268 pvInfoList[currentMove].time = 0;
10269 if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10272 if (currentMove >= forwardMostMove) {
10273 if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10274 gameMode = EditGame;
10277 /* [AS] Clear current move marker at the end of a game */
10278 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10283 toX = moveList[currentMove][2] - AAA;
10284 toY = moveList[currentMove][3] - ONE;
10286 if (moveList[currentMove][1] == '@') {
10287 if (appData.highlightLastMove) {
10288 SetHighlights(-1, -1, toX, toY);
10291 fromX = moveList[currentMove][0] - AAA;
10292 fromY = moveList[currentMove][1] - ONE;
10294 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10296 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10298 if (appData.highlightLastMove) {
10299 SetHighlights(fromX, fromY, toX, toY);
10302 DisplayMove(currentMove);
10303 SendMoveToProgram(currentMove++, &first);
10304 DisplayBothClocks();
10305 DrawPosition(FALSE, boards[currentMove]);
10306 // [HGM] PV info: always display, routine tests if empty
10307 DisplayComment(currentMove - 1, commentList[currentMove]);
10313 LoadGameOneMove(readAhead)
10314 ChessMove readAhead;
10316 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10317 char promoChar = NULLCHAR;
10318 ChessMove moveType;
10319 char move[MSG_SIZ];
10322 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10323 gameMode != AnalyzeMode && gameMode != Training) {
10328 yyboardindex = forwardMostMove;
10329 if (readAhead != EndOfFile) {
10330 moveType = readAhead;
10332 if (gameFileFP == NULL)
10334 moveType = (ChessMove) Myylex();
10338 switch (moveType) {
10340 if (appData.debugMode)
10341 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10344 /* append the comment but don't display it */
10345 AppendComment(currentMove, p, FALSE);
10348 case WhiteCapturesEnPassant:
10349 case BlackCapturesEnPassant:
10350 case WhitePromotion:
10351 case BlackPromotion:
10352 case WhiteNonPromotion:
10353 case BlackNonPromotion:
10355 case WhiteKingSideCastle:
10356 case WhiteQueenSideCastle:
10357 case BlackKingSideCastle:
10358 case BlackQueenSideCastle:
10359 case WhiteKingSideCastleWild:
10360 case WhiteQueenSideCastleWild:
10361 case BlackKingSideCastleWild:
10362 case BlackQueenSideCastleWild:
10364 case WhiteHSideCastleFR:
10365 case WhiteASideCastleFR:
10366 case BlackHSideCastleFR:
10367 case BlackASideCastleFR:
10369 if (appData.debugMode)
10370 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10371 fromX = currentMoveString[0] - AAA;
10372 fromY = currentMoveString[1] - ONE;
10373 toX = currentMoveString[2] - AAA;
10374 toY = currentMoveString[3] - ONE;
10375 promoChar = currentMoveString[4];
10380 if (appData.debugMode)
10381 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10382 fromX = moveType == WhiteDrop ?
10383 (int) CharToPiece(ToUpper(currentMoveString[0])) :
10384 (int) CharToPiece(ToLower(currentMoveString[0]));
10386 toX = currentMoveString[2] - AAA;
10387 toY = currentMoveString[3] - ONE;
10393 case GameUnfinished:
10394 if (appData.debugMode)
10395 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10396 p = strchr(yy_text, '{');
10397 if (p == NULL) p = strchr(yy_text, '(');
10400 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10402 q = strchr(p, *p == '{' ? '}' : ')');
10403 if (q != NULL) *q = NULLCHAR;
10406 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10407 GameEnds(moveType, p, GE_FILE);
10409 if (cmailMsgLoaded) {
10411 flipView = WhiteOnMove(currentMove);
10412 if (moveType == GameUnfinished) flipView = !flipView;
10413 if (appData.debugMode)
10414 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10419 if (appData.debugMode)
10420 fprintf(debugFP, "Parser hit end of file\n");
10421 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10427 if (WhiteOnMove(currentMove)) {
10428 GameEnds(BlackWins, "Black mates", GE_FILE);
10430 GameEnds(WhiteWins, "White mates", GE_FILE);
10434 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10440 case MoveNumberOne:
10441 if (lastLoadGameStart == GNUChessGame) {
10442 /* GNUChessGames have numbers, but they aren't move numbers */
10443 if (appData.debugMode)
10444 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10445 yy_text, (int) moveType);
10446 return LoadGameOneMove(EndOfFile); /* tail recursion */
10448 /* else fall thru */
10453 /* Reached start of next game in file */
10454 if (appData.debugMode)
10455 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10456 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10462 if (WhiteOnMove(currentMove)) {
10463 GameEnds(BlackWins, "Black mates", GE_FILE);
10465 GameEnds(WhiteWins, "White mates", GE_FILE);
10469 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10475 case PositionDiagram: /* should not happen; ignore */
10476 case ElapsedTime: /* ignore */
10477 case NAG: /* ignore */
10478 if (appData.debugMode)
10479 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10480 yy_text, (int) moveType);
10481 return LoadGameOneMove(EndOfFile); /* tail recursion */
10484 if (appData.testLegality) {
10485 if (appData.debugMode)
10486 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10487 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10488 (forwardMostMove / 2) + 1,
10489 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10490 DisplayError(move, 0);
10493 if (appData.debugMode)
10494 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10495 yy_text, currentMoveString);
10496 fromX = currentMoveString[0] - AAA;
10497 fromY = currentMoveString[1] - ONE;
10498 toX = currentMoveString[2] - AAA;
10499 toY = currentMoveString[3] - ONE;
10500 promoChar = currentMoveString[4];
10504 case AmbiguousMove:
10505 if (appData.debugMode)
10506 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10507 snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10508 (forwardMostMove / 2) + 1,
10509 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10510 DisplayError(move, 0);
10515 case ImpossibleMove:
10516 if (appData.debugMode)
10517 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10518 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10519 (forwardMostMove / 2) + 1,
10520 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10521 DisplayError(move, 0);
10527 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10528 DrawPosition(FALSE, boards[currentMove]);
10529 DisplayBothClocks();
10530 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10531 DisplayComment(currentMove - 1, commentList[currentMove]);
10533 (void) StopLoadGameTimer();
10535 cmailOldMove = forwardMostMove;
10538 /* currentMoveString is set as a side-effect of yylex */
10540 thinkOutput[0] = NULLCHAR;
10541 MakeMove(fromX, fromY, toX, toY, promoChar);
10542 currentMove = forwardMostMove;
10547 /* Load the nth game from the given file */
10549 LoadGameFromFile(filename, n, title, useList)
10553 /*Boolean*/ int useList;
10558 if (strcmp(filename, "-") == 0) {
10562 f = fopen(filename, "rb");
10564 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10565 DisplayError(buf, errno);
10569 if (fseek(f, 0, 0) == -1) {
10570 /* f is not seekable; probably a pipe */
10573 if (useList && n == 0) {
10574 int error = GameListBuild(f);
10576 DisplayError(_("Cannot build game list"), error);
10577 } else if (!ListEmpty(&gameList) &&
10578 ((ListGame *) gameList.tailPred)->number > 1) {
10579 GameListPopUp(f, title);
10586 return LoadGame(f, n, title, FALSE);
10591 MakeRegisteredMove()
10593 int fromX, fromY, toX, toY;
10595 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10596 switch (cmailMoveType[lastLoadGameNumber - 1]) {
10599 if (appData.debugMode)
10600 fprintf(debugFP, "Restoring %s for game %d\n",
10601 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10603 thinkOutput[0] = NULLCHAR;
10604 safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10605 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10606 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10607 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10608 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10609 promoChar = cmailMove[lastLoadGameNumber - 1][4];
10610 MakeMove(fromX, fromY, toX, toY, promoChar);
10611 ShowMove(fromX, fromY, toX, toY);
10613 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10620 if (WhiteOnMove(currentMove)) {
10621 GameEnds(BlackWins, "Black mates", GE_PLAYER);
10623 GameEnds(WhiteWins, "White mates", GE_PLAYER);
10628 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10635 if (WhiteOnMove(currentMove)) {
10636 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10638 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10643 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10654 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10656 CmailLoadGame(f, gameNumber, title, useList)
10664 if (gameNumber > nCmailGames) {
10665 DisplayError(_("No more games in this message"), 0);
10668 if (f == lastLoadGameFP) {
10669 int offset = gameNumber - lastLoadGameNumber;
10671 cmailMsg[0] = NULLCHAR;
10672 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10673 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10674 nCmailMovesRegistered--;
10676 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10677 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10678 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10681 if (! RegisterMove()) return FALSE;
10685 retVal = LoadGame(f, gameNumber, title, useList);
10687 /* Make move registered during previous look at this game, if any */
10688 MakeRegisteredMove();
10690 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10691 commentList[currentMove]
10692 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10693 DisplayComment(currentMove - 1, commentList[currentMove]);
10699 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10704 int gameNumber = lastLoadGameNumber + offset;
10705 if (lastLoadGameFP == NULL) {
10706 DisplayError(_("No game has been loaded yet"), 0);
10709 if (gameNumber <= 0) {
10710 DisplayError(_("Can't back up any further"), 0);
10713 if (cmailMsgLoaded) {
10714 return CmailLoadGame(lastLoadGameFP, gameNumber,
10715 lastLoadGameTitle, lastLoadGameUseList);
10717 return LoadGame(lastLoadGameFP, gameNumber,
10718 lastLoadGameTitle, lastLoadGameUseList);
10724 /* Load the nth game from open file f */
10726 LoadGame(f, gameNumber, title, useList)
10734 int gn = gameNumber;
10735 ListGame *lg = NULL;
10736 int numPGNTags = 0;
10738 GameMode oldGameMode;
10739 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10741 if (appData.debugMode)
10742 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10744 if (gameMode == Training )
10745 SetTrainingModeOff();
10747 oldGameMode = gameMode;
10748 if (gameMode != BeginningOfGame) {
10749 Reset(FALSE, TRUE);
10753 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10754 fclose(lastLoadGameFP);
10758 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10761 fseek(f, lg->offset, 0);
10762 GameListHighlight(gameNumber);
10766 DisplayError(_("Game number out of range"), 0);
10771 if (fseek(f, 0, 0) == -1) {
10772 if (f == lastLoadGameFP ?
10773 gameNumber == lastLoadGameNumber + 1 :
10777 DisplayError(_("Can't seek on game file"), 0);
10782 lastLoadGameFP = f;
10783 lastLoadGameNumber = gameNumber;
10784 safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10785 lastLoadGameUseList = useList;
10789 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10790 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10791 lg->gameInfo.black);
10793 } else if (*title != NULLCHAR) {
10794 if (gameNumber > 1) {
10795 snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10798 DisplayTitle(title);
10802 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10803 gameMode = PlayFromGameFile;
10807 currentMove = forwardMostMove = backwardMostMove = 0;
10808 CopyBoard(boards[0], initialPosition);
10812 * Skip the first gn-1 games in the file.
10813 * Also skip over anything that precedes an identifiable
10814 * start of game marker, to avoid being confused by
10815 * garbage at the start of the file. Currently
10816 * recognized start of game markers are the move number "1",
10817 * the pattern "gnuchess .* game", the pattern
10818 * "^[#;%] [^ ]* game file", and a PGN tag block.
10819 * A game that starts with one of the latter two patterns
10820 * will also have a move number 1, possibly
10821 * following a position diagram.
10822 * 5-4-02: Let's try being more lenient and allowing a game to
10823 * start with an unnumbered move. Does that break anything?
10825 cm = lastLoadGameStart = EndOfFile;
10827 yyboardindex = forwardMostMove;
10828 cm = (ChessMove) Myylex();
10831 if (cmailMsgLoaded) {
10832 nCmailGames = CMAIL_MAX_GAMES - gn;
10835 DisplayError(_("Game not found in file"), 0);
10842 lastLoadGameStart = cm;
10845 case MoveNumberOne:
10846 switch (lastLoadGameStart) {
10851 case MoveNumberOne:
10853 gn--; /* count this game */
10854 lastLoadGameStart = cm;
10863 switch (lastLoadGameStart) {
10866 case MoveNumberOne:
10868 gn--; /* count this game */
10869 lastLoadGameStart = cm;
10872 lastLoadGameStart = cm; /* game counted already */
10880 yyboardindex = forwardMostMove;
10881 cm = (ChessMove) Myylex();
10882 } while (cm == PGNTag || cm == Comment);
10889 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10890 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
10891 != CMAIL_OLD_RESULT) {
10893 cmailResult[ CMAIL_MAX_GAMES
10894 - gn - 1] = CMAIL_OLD_RESULT;
10900 /* Only a NormalMove can be at the start of a game
10901 * without a position diagram. */
10902 if (lastLoadGameStart == EndOfFile ) {
10904 lastLoadGameStart = MoveNumberOne;
10913 if (appData.debugMode)
10914 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10916 if (cm == XBoardGame) {
10917 /* Skip any header junk before position diagram and/or move 1 */
10919 yyboardindex = forwardMostMove;
10920 cm = (ChessMove) Myylex();
10922 if (cm == EndOfFile ||
10923 cm == GNUChessGame || cm == XBoardGame) {
10924 /* Empty game; pretend end-of-file and handle later */
10929 if (cm == MoveNumberOne || cm == PositionDiagram ||
10930 cm == PGNTag || cm == Comment)
10933 } else if (cm == GNUChessGame) {
10934 if (gameInfo.event != NULL) {
10935 free(gameInfo.event);
10937 gameInfo.event = StrSave(yy_text);
10940 startedFromSetupPosition = FALSE;
10941 while (cm == PGNTag) {
10942 if (appData.debugMode)
10943 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10944 err = ParsePGNTag(yy_text, &gameInfo);
10945 if (!err) numPGNTags++;
10947 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10948 if(gameInfo.variant != oldVariant) {
10949 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10950 ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10951 InitPosition(TRUE);
10952 oldVariant = gameInfo.variant;
10953 if (appData.debugMode)
10954 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10958 if (gameInfo.fen != NULL) {
10959 Board initial_position;
10960 startedFromSetupPosition = TRUE;
10961 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10963 DisplayError(_("Bad FEN position in file"), 0);
10966 CopyBoard(boards[0], initial_position);
10967 if (blackPlaysFirst) {
10968 currentMove = forwardMostMove = backwardMostMove = 1;
10969 CopyBoard(boards[1], initial_position);
10970 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10971 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10972 timeRemaining[0][1] = whiteTimeRemaining;
10973 timeRemaining[1][1] = blackTimeRemaining;
10974 if (commentList[0] != NULL) {
10975 commentList[1] = commentList[0];
10976 commentList[0] = NULL;
10979 currentMove = forwardMostMove = backwardMostMove = 0;
10981 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10983 initialRulePlies = FENrulePlies;
10984 for( i=0; i< nrCastlingRights; i++ )
10985 initialRights[i] = initial_position[CASTLING][i];
10987 yyboardindex = forwardMostMove;
10988 free(gameInfo.fen);
10989 gameInfo.fen = NULL;
10992 yyboardindex = forwardMostMove;
10993 cm = (ChessMove) Myylex();
10995 /* Handle comments interspersed among the tags */
10996 while (cm == Comment) {
10998 if (appData.debugMode)
10999 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11001 AppendComment(currentMove, p, FALSE);
11002 yyboardindex = forwardMostMove;
11003 cm = (ChessMove) Myylex();
11007 /* don't rely on existence of Event tag since if game was
11008 * pasted from clipboard the Event tag may not exist
11010 if (numPGNTags > 0){
11012 if (gameInfo.variant == VariantNormal) {
11013 VariantClass v = StringToVariant(gameInfo.event);
11014 // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11015 if(v < VariantShogi) gameInfo.variant = v;
11018 if( appData.autoDisplayTags ) {
11019 tags = PGNTags(&gameInfo);
11020 TagsPopUp(tags, CmailMsg());
11025 /* Make something up, but don't display it now */
11030 if (cm == PositionDiagram) {
11033 Board initial_position;
11035 if (appData.debugMode)
11036 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11038 if (!startedFromSetupPosition) {
11040 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11041 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11052 initial_position[i][j++] = CharToPiece(*p);
11055 while (*p == ' ' || *p == '\t' ||
11056 *p == '\n' || *p == '\r') p++;
11058 if (strncmp(p, "black", strlen("black"))==0)
11059 blackPlaysFirst = TRUE;
11061 blackPlaysFirst = FALSE;
11062 startedFromSetupPosition = TRUE;
11064 CopyBoard(boards[0], initial_position);
11065 if (blackPlaysFirst) {
11066 currentMove = forwardMostMove = backwardMostMove = 1;
11067 CopyBoard(boards[1], initial_position);
11068 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11069 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11070 timeRemaining[0][1] = whiteTimeRemaining;
11071 timeRemaining[1][1] = blackTimeRemaining;
11072 if (commentList[0] != NULL) {
11073 commentList[1] = commentList[0];
11074 commentList[0] = NULL;
11077 currentMove = forwardMostMove = backwardMostMove = 0;
11080 yyboardindex = forwardMostMove;
11081 cm = (ChessMove) Myylex();
11084 if (first.pr == NoProc) {
11085 StartChessProgram(&first);
11087 InitChessProgram(&first, FALSE);
11088 SendToProgram("force\n", &first);
11089 if (startedFromSetupPosition) {
11090 SendBoard(&first, forwardMostMove);
11091 if (appData.debugMode) {
11092 fprintf(debugFP, "Load Game\n");
11094 DisplayBothClocks();
11097 /* [HGM] server: flag to write setup moves in broadcast file as one */
11098 loadFlag = appData.suppressLoadMoves;
11100 while (cm == Comment) {
11102 if (appData.debugMode)
11103 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11105 AppendComment(currentMove, p, FALSE);
11106 yyboardindex = forwardMostMove;
11107 cm = (ChessMove) Myylex();
11110 if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11111 cm == WhiteWins || cm == BlackWins ||
11112 cm == GameIsDrawn || cm == GameUnfinished) {
11113 DisplayMessage("", _("No moves in game"));
11114 if (cmailMsgLoaded) {
11115 if (appData.debugMode)
11116 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11120 DrawPosition(FALSE, boards[currentMove]);
11121 DisplayBothClocks();
11122 gameMode = EditGame;
11129 // [HGM] PV info: routine tests if comment empty
11130 if (!matchMode && (pausing || appData.timeDelay != 0)) {
11131 DisplayComment(currentMove - 1, commentList[currentMove]);
11133 if (!matchMode && appData.timeDelay != 0)
11134 DrawPosition(FALSE, boards[currentMove]);
11136 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11137 programStats.ok_to_send = 1;
11140 /* if the first token after the PGN tags is a move
11141 * and not move number 1, retrieve it from the parser
11143 if (cm != MoveNumberOne)
11144 LoadGameOneMove(cm);
11146 /* load the remaining moves from the file */
11147 while (LoadGameOneMove(EndOfFile)) {
11148 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11149 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11152 /* rewind to the start of the game */
11153 currentMove = backwardMostMove;
11155 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11157 if (oldGameMode == AnalyzeFile ||
11158 oldGameMode == AnalyzeMode) {
11159 AnalyzeFileEvent();
11162 if (matchMode || appData.timeDelay == 0) {
11164 gameMode = EditGame;
11166 } else if (appData.timeDelay > 0) {
11167 AutoPlayGameLoop();
11170 if (appData.debugMode)
11171 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11173 loadFlag = 0; /* [HGM] true game starts */
11177 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11179 ReloadPosition(offset)
11182 int positionNumber = lastLoadPositionNumber + offset;
11183 if (lastLoadPositionFP == NULL) {
11184 DisplayError(_("No position has been loaded yet"), 0);
11187 if (positionNumber <= 0) {
11188 DisplayError(_("Can't back up any further"), 0);
11191 return LoadPosition(lastLoadPositionFP, positionNumber,
11192 lastLoadPositionTitle);
11195 /* Load the nth position from the given file */
11197 LoadPositionFromFile(filename, n, title)
11205 if (strcmp(filename, "-") == 0) {
11206 return LoadPosition(stdin, n, "stdin");
11208 f = fopen(filename, "rb");
11210 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11211 DisplayError(buf, errno);
11214 return LoadPosition(f, n, title);
11219 /* Load the nth position from the given open file, and close it */
11221 LoadPosition(f, positionNumber, title)
11223 int positionNumber;
11226 char *p, line[MSG_SIZ];
11227 Board initial_position;
11228 int i, j, fenMode, pn;
11230 if (gameMode == Training )
11231 SetTrainingModeOff();
11233 if (gameMode != BeginningOfGame) {
11234 Reset(FALSE, TRUE);
11236 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
11237 fclose(lastLoadPositionFP);
11239 if (positionNumber == 0) positionNumber = 1;
11240 lastLoadPositionFP = f;
11241 lastLoadPositionNumber = positionNumber;
11242 safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
11243 if (first.pr == NoProc) {
11244 StartChessProgram(&first);
11245 InitChessProgram(&first, FALSE);
11247 pn = positionNumber;
11248 if (positionNumber < 0) {
11249 /* Negative position number means to seek to that byte offset */
11250 if (fseek(f, -positionNumber, 0) == -1) {
11251 DisplayError(_("Can't seek on position file"), 0);
11256 if (fseek(f, 0, 0) == -1) {
11257 if (f == lastLoadPositionFP ?
11258 positionNumber == lastLoadPositionNumber + 1 :
11259 positionNumber == 1) {
11262 DisplayError(_("Can't seek on position file"), 0);
11267 /* See if this file is FEN or old-style xboard */
11268 if (fgets(line, MSG_SIZ, f) == NULL) {
11269 DisplayError(_("Position not found in file"), 0);
11272 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
11273 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
11276 if (fenMode || line[0] == '#') pn--;
11278 /* skip positions before number pn */
11279 if (fgets(line, MSG_SIZ, f) == NULL) {
11281 DisplayError(_("Position not found in file"), 0);
11284 if (fenMode || line[0] == '#') pn--;
11289 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
11290 DisplayError(_("Bad FEN position in file"), 0);
11294 (void) fgets(line, MSG_SIZ, f);
11295 (void) fgets(line, MSG_SIZ, f);
11297 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11298 (void) fgets(line, MSG_SIZ, f);
11299 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
11302 initial_position[i][j++] = CharToPiece(*p);
11306 blackPlaysFirst = FALSE;
11308 (void) fgets(line, MSG_SIZ, f);
11309 if (strncmp(line, "black", strlen("black"))==0)
11310 blackPlaysFirst = TRUE;
11313 startedFromSetupPosition = TRUE;
11315 SendToProgram("force\n", &first);
11316 CopyBoard(boards[0], initial_position);
11317 if (blackPlaysFirst) {
11318 currentMove = forwardMostMove = backwardMostMove = 1;
11319 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11320 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11321 CopyBoard(boards[1], initial_position);
11322 DisplayMessage("", _("Black to play"));
11324 currentMove = forwardMostMove = backwardMostMove = 0;
11325 DisplayMessage("", _("White to play"));
11327 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
11328 SendBoard(&first, forwardMostMove);
11329 if (appData.debugMode) {
11331 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
11332 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
11333 fprintf(debugFP, "Load Position\n");
11336 if (positionNumber > 1) {
11337 snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
11338 DisplayTitle(line);
11340 DisplayTitle(title);
11342 gameMode = EditGame;
11345 timeRemaining[0][1] = whiteTimeRemaining;
11346 timeRemaining[1][1] = blackTimeRemaining;
11347 DrawPosition(FALSE, boards[currentMove]);
11354 CopyPlayerNameIntoFileName(dest, src)
11357 while (*src != NULLCHAR && *src != ',') {
11362 *(*dest)++ = *src++;
11367 char *DefaultFileName(ext)
11370 static char def[MSG_SIZ];
11373 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
11375 CopyPlayerNameIntoFileName(&p, gameInfo.white);
11377 CopyPlayerNameIntoFileName(&p, gameInfo.black);
11379 safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
11386 /* Save the current game to the given file */
11388 SaveGameToFile(filename, append)
11396 if (strcmp(filename, "-") == 0) {
11397 return SaveGame(stdout, 0, NULL);
11399 f = fopen(filename, append ? "a" : "w");
11401 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11402 DisplayError(buf, errno);
11405 safeStrCpy(buf, lastMsg, MSG_SIZ);
11406 DisplayMessage(_("Waiting for access to save file"), "");
11407 flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
11408 DisplayMessage(_("Saving game"), "");
11409 if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError("Bad Seek", errno); // better safe than sorry...
11410 result = SaveGame(f, 0, NULL);
11411 DisplayMessage(buf, "");
11421 static char buf[MSG_SIZ];
11424 p = strchr(str, ' ');
11425 if (p == NULL) return str;
11426 strncpy(buf, str, p - str);
11427 buf[p - str] = NULLCHAR;
11431 #define PGN_MAX_LINE 75
11433 #define PGN_SIDE_WHITE 0
11434 #define PGN_SIDE_BLACK 1
11437 static int FindFirstMoveOutOfBook( int side )
11441 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
11442 int index = backwardMostMove;
11443 int has_book_hit = 0;
11445 if( (index % 2) != side ) {
11449 while( index < forwardMostMove ) {
11450 /* Check to see if engine is in book */
11451 int depth = pvInfoList[index].depth;
11452 int score = pvInfoList[index].score;
11458 else if( score == 0 && depth == 63 ) {
11459 in_book = 1; /* Zappa */
11461 else if( score == 2 && depth == 99 ) {
11462 in_book = 1; /* Abrok */
11465 has_book_hit += in_book;
11481 void GetOutOfBookInfo( char * buf )
11485 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11487 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
11488 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
11492 if( oob[0] >= 0 || oob[1] >= 0 ) {
11493 for( i=0; i<2; i++ ) {
11497 if( i > 0 && oob[0] >= 0 ) {
11498 strcat( buf, " " );
11501 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
11502 sprintf( buf+strlen(buf), "%s%.2f",
11503 pvInfoList[idx].score >= 0 ? "+" : "",
11504 pvInfoList[idx].score / 100.0 );
11510 /* Save game in PGN style and close the file */
11515 int i, offset, linelen, newblock;
11519 int movelen, numlen, blank;
11520 char move_buffer[100]; /* [AS] Buffer for move+PV info */
11522 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11524 tm = time((time_t *) NULL);
11526 PrintPGNTags(f, &gameInfo);
11528 if (backwardMostMove > 0 || startedFromSetupPosition) {
11529 char *fen = PositionToFEN(backwardMostMove, NULL);
11530 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
11531 fprintf(f, "\n{--------------\n");
11532 PrintPosition(f, backwardMostMove);
11533 fprintf(f, "--------------}\n");
11537 /* [AS] Out of book annotation */
11538 if( appData.saveOutOfBookInfo ) {
11541 GetOutOfBookInfo( buf );
11543 if( buf[0] != '\0' ) {
11544 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
11551 i = backwardMostMove;
11555 while (i < forwardMostMove) {
11556 /* Print comments preceding this move */
11557 if (commentList[i] != NULL) {
11558 if (linelen > 0) fprintf(f, "\n");
11559 fprintf(f, "%s", commentList[i]);
11564 /* Format move number */
11566 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
11569 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
11571 numtext[0] = NULLCHAR;
11573 numlen = strlen(numtext);
11576 /* Print move number */
11577 blank = linelen > 0 && numlen > 0;
11578 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
11587 fprintf(f, "%s", numtext);
11591 safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
11592 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
11595 blank = linelen > 0 && movelen > 0;
11596 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11605 fprintf(f, "%s", move_buffer);
11606 linelen += movelen;
11608 /* [AS] Add PV info if present */
11609 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
11610 /* [HGM] add time */
11611 char buf[MSG_SIZ]; int seconds;
11613 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
11619 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
11622 seconds = (seconds + 4)/10; // round to full seconds
11624 snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
11626 snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
11629 snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
11630 pvInfoList[i].score >= 0 ? "+" : "",
11631 pvInfoList[i].score / 100.0,
11632 pvInfoList[i].depth,
11635 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11637 /* Print score/depth */
11638 blank = linelen > 0 && movelen > 0;
11639 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11648 fprintf(f, "%s", move_buffer);
11649 linelen += movelen;
11655 /* Start a new line */
11656 if (linelen > 0) fprintf(f, "\n");
11658 /* Print comments after last move */
11659 if (commentList[i] != NULL) {
11660 fprintf(f, "%s\n", commentList[i]);
11664 if (gameInfo.resultDetails != NULL &&
11665 gameInfo.resultDetails[0] != NULLCHAR) {
11666 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11667 PGNResult(gameInfo.result));
11669 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11673 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11677 /* Save game in old style and close the file */
11679 SaveGameOldStyle(f)
11685 tm = time((time_t *) NULL);
11687 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11690 if (backwardMostMove > 0 || startedFromSetupPosition) {
11691 fprintf(f, "\n[--------------\n");
11692 PrintPosition(f, backwardMostMove);
11693 fprintf(f, "--------------]\n");
11698 i = backwardMostMove;
11699 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11701 while (i < forwardMostMove) {
11702 if (commentList[i] != NULL) {
11703 fprintf(f, "[%s]\n", commentList[i]);
11706 if ((i % 2) == 1) {
11707 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
11710 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
11712 if (commentList[i] != NULL) {
11716 if (i >= forwardMostMove) {
11720 fprintf(f, "%s\n", parseList[i]);
11725 if (commentList[i] != NULL) {
11726 fprintf(f, "[%s]\n", commentList[i]);
11729 /* This isn't really the old style, but it's close enough */
11730 if (gameInfo.resultDetails != NULL &&
11731 gameInfo.resultDetails[0] != NULLCHAR) {
11732 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11733 gameInfo.resultDetails);
11735 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11742 /* Save the current game to open file f and close the file */
11744 SaveGame(f, dummy, dummy2)
11749 if (gameMode == EditPosition) EditPositionDone(TRUE);
11750 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11751 if (appData.oldSaveStyle)
11752 return SaveGameOldStyle(f);
11754 return SaveGamePGN(f);
11757 /* Save the current position to the given file */
11759 SavePositionToFile(filename)
11765 if (strcmp(filename, "-") == 0) {
11766 return SavePosition(stdout, 0, NULL);
11768 f = fopen(filename, "a");
11770 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11771 DisplayError(buf, errno);
11774 safeStrCpy(buf, lastMsg, MSG_SIZ);
11775 DisplayMessage(_("Waiting for access to save file"), "");
11776 flock(fileno(f), LOCK_EX); // [HGM] lock
11777 DisplayMessage(_("Saving position"), "");
11778 lseek(fileno(f), 0, SEEK_END); // better safe than sorry...
11779 SavePosition(f, 0, NULL);
11780 DisplayMessage(buf, "");
11786 /* Save the current position to the given open file and close the file */
11788 SavePosition(f, dummy, dummy2)
11796 if (gameMode == EditPosition) EditPositionDone(TRUE);
11797 if (appData.oldSaveStyle) {
11798 tm = time((time_t *) NULL);
11800 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11802 fprintf(f, "[--------------\n");
11803 PrintPosition(f, currentMove);
11804 fprintf(f, "--------------]\n");
11806 fen = PositionToFEN(currentMove, NULL);
11807 fprintf(f, "%s\n", fen);
11815 ReloadCmailMsgEvent(unregister)
11819 static char *inFilename = NULL;
11820 static char *outFilename;
11822 struct stat inbuf, outbuf;
11825 /* Any registered moves are unregistered if unregister is set, */
11826 /* i.e. invoked by the signal handler */
11828 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11829 cmailMoveRegistered[i] = FALSE;
11830 if (cmailCommentList[i] != NULL) {
11831 free(cmailCommentList[i]);
11832 cmailCommentList[i] = NULL;
11835 nCmailMovesRegistered = 0;
11838 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11839 cmailResult[i] = CMAIL_NOT_RESULT;
11843 if (inFilename == NULL) {
11844 /* Because the filenames are static they only get malloced once */
11845 /* and they never get freed */
11846 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11847 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11849 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11850 sprintf(outFilename, "%s.out", appData.cmailGameName);
11853 status = stat(outFilename, &outbuf);
11855 cmailMailedMove = FALSE;
11857 status = stat(inFilename, &inbuf);
11858 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11861 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11862 counts the games, notes how each one terminated, etc.
11864 It would be nice to remove this kludge and instead gather all
11865 the information while building the game list. (And to keep it
11866 in the game list nodes instead of having a bunch of fixed-size
11867 parallel arrays.) Note this will require getting each game's
11868 termination from the PGN tags, as the game list builder does
11869 not process the game moves. --mann
11871 cmailMsgLoaded = TRUE;
11872 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11874 /* Load first game in the file or popup game menu */
11875 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11877 #endif /* !WIN32 */
11885 char string[MSG_SIZ];
11887 if ( cmailMailedMove
11888 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11889 return TRUE; /* Allow free viewing */
11892 /* Unregister move to ensure that we don't leave RegisterMove */
11893 /* with the move registered when the conditions for registering no */
11895 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11896 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11897 nCmailMovesRegistered --;
11899 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11901 free(cmailCommentList[lastLoadGameNumber - 1]);
11902 cmailCommentList[lastLoadGameNumber - 1] = NULL;
11906 if (cmailOldMove == -1) {
11907 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11911 if (currentMove > cmailOldMove + 1) {
11912 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11916 if (currentMove < cmailOldMove) {
11917 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11921 if (forwardMostMove > currentMove) {
11922 /* Silently truncate extra moves */
11926 if ( (currentMove == cmailOldMove + 1)
11927 || ( (currentMove == cmailOldMove)
11928 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11929 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11930 if (gameInfo.result != GameUnfinished) {
11931 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11934 if (commentList[currentMove] != NULL) {
11935 cmailCommentList[lastLoadGameNumber - 1]
11936 = StrSave(commentList[currentMove]);
11938 safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
11940 if (appData.debugMode)
11941 fprintf(debugFP, "Saving %s for game %d\n",
11942 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11944 snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11946 f = fopen(string, "w");
11947 if (appData.oldSaveStyle) {
11948 SaveGameOldStyle(f); /* also closes the file */
11950 snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
11951 f = fopen(string, "w");
11952 SavePosition(f, 0, NULL); /* also closes the file */
11954 fprintf(f, "{--------------\n");
11955 PrintPosition(f, currentMove);
11956 fprintf(f, "--------------}\n\n");
11958 SaveGame(f, 0, NULL); /* also closes the file*/
11961 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11962 nCmailMovesRegistered ++;
11963 } else if (nCmailGames == 1) {
11964 DisplayError(_("You have not made a move yet"), 0);
11975 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11976 FILE *commandOutput;
11977 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11978 int nBytes = 0; /* Suppress warnings on uninitialized variables */
11984 if (! cmailMsgLoaded) {
11985 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11989 if (nCmailGames == nCmailResults) {
11990 DisplayError(_("No unfinished games"), 0);
11994 #if CMAIL_PROHIBIT_REMAIL
11995 if (cmailMailedMove) {
11996 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);
11997 DisplayError(msg, 0);
12002 if (! (cmailMailedMove || RegisterMove())) return;
12004 if ( cmailMailedMove
12005 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
12006 snprintf(string, MSG_SIZ, partCommandString,
12007 appData.debugMode ? " -v" : "", appData.cmailGameName);
12008 commandOutput = popen(string, "r");
12010 if (commandOutput == NULL) {
12011 DisplayError(_("Failed to invoke cmail"), 0);
12013 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12014 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12016 if (nBuffers > 1) {
12017 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12018 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12019 nBytes = MSG_SIZ - 1;
12021 (void) memcpy(msg, buffer, nBytes);
12023 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12025 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12026 cmailMailedMove = TRUE; /* Prevent >1 moves */
12029 for (i = 0; i < nCmailGames; i ++) {
12030 if (cmailResult[i] == CMAIL_NOT_RESULT) {
12035 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12037 snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12039 appData.cmailGameName,
12041 LoadGameFromFile(buffer, 1, buffer, FALSE);
12042 cmailMsgLoaded = FALSE;
12046 DisplayInformation(msg);
12047 pclose(commandOutput);
12050 if ((*cmailMsg) != '\0') {
12051 DisplayInformation(cmailMsg);
12056 #endif /* !WIN32 */
12065 int prependComma = 0;
12067 char string[MSG_SIZ]; /* Space for game-list */
12070 if (!cmailMsgLoaded) return "";
12072 if (cmailMailedMove) {
12073 snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12075 /* Create a list of games left */
12076 snprintf(string, MSG_SIZ, "[");
12077 for (i = 0; i < nCmailGames; i ++) {
12078 if (! ( cmailMoveRegistered[i]
12079 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12080 if (prependComma) {
12081 snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12083 snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12087 strcat(string, number);
12090 strcat(string, "]");
12092 if (nCmailMovesRegistered + nCmailResults == 0) {
12093 switch (nCmailGames) {
12095 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12099 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12103 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12108 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12110 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12115 if (nCmailResults == nCmailGames) {
12116 snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12118 snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12123 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12135 if (gameMode == Training)
12136 SetTrainingModeOff();
12139 cmailMsgLoaded = FALSE;
12140 if (appData.icsActive) {
12141 SendToICS(ics_prefix);
12142 SendToICS("refresh\n");
12152 /* Give up on clean exit */
12156 /* Keep trying for clean exit */
12160 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12162 if (telnetISR != NULL) {
12163 RemoveInputSource(telnetISR);
12165 if (icsPR != NoProc) {
12166 DestroyChildProcess(icsPR, TRUE);
12169 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12170 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12172 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12173 /* make sure this other one finishes before killing it! */
12174 if(endingGame) { int count = 0;
12175 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12176 while(endingGame && count++ < 10) DoSleep(1);
12177 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12180 /* Kill off chess programs */
12181 if (first.pr != NoProc) {
12184 DoSleep( appData.delayBeforeQuit );
12185 SendToProgram("quit\n", &first);
12186 DoSleep( appData.delayAfterQuit );
12187 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12189 if (second.pr != NoProc) {
12190 DoSleep( appData.delayBeforeQuit );
12191 SendToProgram("quit\n", &second);
12192 DoSleep( appData.delayAfterQuit );
12193 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12195 if (first.isr != NULL) {
12196 RemoveInputSource(first.isr);
12198 if (second.isr != NULL) {
12199 RemoveInputSource(second.isr);
12202 ShutDownFrontEnd();
12209 if (appData.debugMode)
12210 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
12214 if (gameMode == MachinePlaysWhite ||
12215 gameMode == MachinePlaysBlack) {
12218 DisplayBothClocks();
12220 if (gameMode == PlayFromGameFile) {
12221 if (appData.timeDelay >= 0)
12222 AutoPlayGameLoop();
12223 } else if (gameMode == IcsExamining && pauseExamInvalid) {
12224 Reset(FALSE, TRUE);
12225 SendToICS(ics_prefix);
12226 SendToICS("refresh\n");
12227 } else if (currentMove < forwardMostMove) {
12228 ForwardInner(forwardMostMove);
12230 pauseExamInvalid = FALSE;
12232 switch (gameMode) {
12236 pauseExamForwardMostMove = forwardMostMove;
12237 pauseExamInvalid = FALSE;
12240 case IcsPlayingWhite:
12241 case IcsPlayingBlack:
12245 case PlayFromGameFile:
12246 (void) StopLoadGameTimer();
12250 case BeginningOfGame:
12251 if (appData.icsActive) return;
12252 /* else fall through */
12253 case MachinePlaysWhite:
12254 case MachinePlaysBlack:
12255 case TwoMachinesPlay:
12256 if (forwardMostMove == 0)
12257 return; /* don't pause if no one has moved */
12258 if ((gameMode == MachinePlaysWhite &&
12259 !WhiteOnMove(forwardMostMove)) ||
12260 (gameMode == MachinePlaysBlack &&
12261 WhiteOnMove(forwardMostMove))) {
12274 char title[MSG_SIZ];
12276 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
12277 safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
12279 snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
12280 WhiteOnMove(currentMove - 1) ? " " : ".. ",
12281 parseList[currentMove - 1]);
12284 EditCommentPopUp(currentMove, title, commentList[currentMove]);
12291 char *tags = PGNTags(&gameInfo);
12292 EditTagsPopUp(tags, NULL);
12299 if (appData.noChessProgram || gameMode == AnalyzeMode)
12302 if (gameMode != AnalyzeFile) {
12303 if (!appData.icsEngineAnalyze) {
12305 if (gameMode != EditGame) return;
12307 ResurrectChessProgram();
12308 SendToProgram("analyze\n", &first);
12309 first.analyzing = TRUE;
12310 /*first.maybeThinking = TRUE;*/
12311 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12312 EngineOutputPopUp();
12314 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
12319 StartAnalysisClock();
12320 GetTimeMark(&lastNodeCountTime);
12327 if (appData.noChessProgram || gameMode == AnalyzeFile)
12330 if (gameMode != AnalyzeMode) {
12332 if (gameMode != EditGame) return;
12333 ResurrectChessProgram();
12334 SendToProgram("analyze\n", &first);
12335 first.analyzing = TRUE;
12336 /*first.maybeThinking = TRUE;*/
12337 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12338 EngineOutputPopUp();
12340 gameMode = AnalyzeFile;
12345 StartAnalysisClock();
12346 GetTimeMark(&lastNodeCountTime);
12351 MachineWhiteEvent()
12354 char *bookHit = NULL;
12356 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
12360 if (gameMode == PlayFromGameFile ||
12361 gameMode == TwoMachinesPlay ||
12362 gameMode == Training ||
12363 gameMode == AnalyzeMode ||
12364 gameMode == EndOfGame)
12367 if (gameMode == EditPosition)
12368 EditPositionDone(TRUE);
12370 if (!WhiteOnMove(currentMove)) {
12371 DisplayError(_("It is not White's turn"), 0);
12375 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12378 if (gameMode == EditGame || gameMode == AnalyzeMode ||
12379 gameMode == AnalyzeFile)
12382 ResurrectChessProgram(); /* in case it isn't running */
12383 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
12384 gameMode = MachinePlaysWhite;
12387 gameMode = MachinePlaysWhite;
12391 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12393 if (first.sendName) {
12394 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
12395 SendToProgram(buf, &first);
12397 if (first.sendTime) {
12398 if (first.useColors) {
12399 SendToProgram("black\n", &first); /*gnu kludge*/
12401 SendTimeRemaining(&first, TRUE);
12403 if (first.useColors) {
12404 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
12406 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12407 SetMachineThinkingEnables();
12408 first.maybeThinking = TRUE;
12412 if (appData.autoFlipView && !flipView) {
12413 flipView = !flipView;
12414 DrawPosition(FALSE, NULL);
12415 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
12418 if(bookHit) { // [HGM] book: simulate book reply
12419 static char bookMove[MSG_SIZ]; // a bit generous?
12421 programStats.nodes = programStats.depth = programStats.time =
12422 programStats.score = programStats.got_only_move = 0;
12423 sprintf(programStats.movelist, "%s (xbook)", bookHit);
12425 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12426 strcat(bookMove, bookHit);
12427 HandleMachineMove(bookMove, &first);
12432 MachineBlackEvent()
12435 char *bookHit = NULL;
12437 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
12441 if (gameMode == PlayFromGameFile ||
12442 gameMode == TwoMachinesPlay ||
12443 gameMode == Training ||
12444 gameMode == AnalyzeMode ||
12445 gameMode == EndOfGame)
12448 if (gameMode == EditPosition)
12449 EditPositionDone(TRUE);
12451 if (WhiteOnMove(currentMove)) {
12452 DisplayError(_("It is not Black's turn"), 0);
12456 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12459 if (gameMode == EditGame || gameMode == AnalyzeMode ||
12460 gameMode == AnalyzeFile)
12463 ResurrectChessProgram(); /* in case it isn't running */
12464 gameMode = MachinePlaysBlack;
12468 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12470 if (first.sendName) {
12471 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
12472 SendToProgram(buf, &first);
12474 if (first.sendTime) {
12475 if (first.useColors) {
12476 SendToProgram("white\n", &first); /*gnu kludge*/
12478 SendTimeRemaining(&first, FALSE);
12480 if (first.useColors) {
12481 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
12483 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12484 SetMachineThinkingEnables();
12485 first.maybeThinking = TRUE;
12488 if (appData.autoFlipView && flipView) {
12489 flipView = !flipView;
12490 DrawPosition(FALSE, NULL);
12491 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
12493 if(bookHit) { // [HGM] book: simulate book reply
12494 static char bookMove[MSG_SIZ]; // a bit generous?
12496 programStats.nodes = programStats.depth = programStats.time =
12497 programStats.score = programStats.got_only_move = 0;
12498 sprintf(programStats.movelist, "%s (xbook)", bookHit);
12500 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12501 strcat(bookMove, bookHit);
12502 HandleMachineMove(bookMove, &first);
12508 DisplayTwoMachinesTitle()
12511 if (appData.matchGames > 0) {
12512 if (first.twoMachinesColor[0] == 'w') {
12513 snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12514 gameInfo.white, gameInfo.black,
12515 first.matchWins, second.matchWins,
12516 matchGame - 1 - (first.matchWins + second.matchWins));
12518 snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12519 gameInfo.white, gameInfo.black,
12520 second.matchWins, first.matchWins,
12521 matchGame - 1 - (first.matchWins + second.matchWins));
12524 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12530 SettingsMenuIfReady()
12532 if (second.lastPing != second.lastPong) {
12533 DisplayMessage("", _("Waiting for second chess program"));
12534 ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
12538 DisplayMessage("", "");
12539 SettingsPopUp(&second);
12543 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
12546 if (cps->pr == NULL) {
12547 StartChessProgram(cps);
12548 if (cps->protocolVersion == 1) {
12551 /* kludge: allow timeout for initial "feature" command */
12553 snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
12554 DisplayMessage("", buf);
12555 ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
12563 TwoMachinesEvent P((void))
12567 ChessProgramState *onmove;
12568 char *bookHit = NULL;
12569 static int stalling = 0;
12573 if (appData.noChessProgram) return;
12575 switch (gameMode) {
12576 case TwoMachinesPlay:
12578 case MachinePlaysWhite:
12579 case MachinePlaysBlack:
12580 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12581 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12585 case BeginningOfGame:
12586 case PlayFromGameFile:
12589 if (gameMode != EditGame) return;
12592 EditPositionDone(TRUE);
12603 // forwardMostMove = currentMove;
12604 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
12606 if(!ResurrectChessProgram()) return; /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
12608 if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
12609 if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
12610 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12614 InitChessProgram(&second, FALSE); // unbalances ping of second engine
12615 SendToProgram("force\n", &second);
12617 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12620 GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
12621 if(appData.matchPause>10000 || appData.matchPause<10)
12622 appData.matchPause = 10000; /* [HGM] make pause adjustable */
12623 wait = SubtractTimeMarks(&now, &pauseStart);
12624 if(wait < appData.matchPause) {
12625 ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
12629 DisplayMessage("", "");
12630 if (startedFromSetupPosition) {
12631 SendBoard(&second, backwardMostMove);
12632 if (appData.debugMode) {
12633 fprintf(debugFP, "Two Machines\n");
12636 for (i = backwardMostMove; i < forwardMostMove; i++) {
12637 SendMoveToProgram(i, &second);
12640 gameMode = TwoMachinesPlay;
12644 DisplayTwoMachinesTitle();
12646 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
12651 if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
12652 SendToProgram(first.computerString, &first);
12653 if (first.sendName) {
12654 snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
12655 SendToProgram(buf, &first);
12657 SendToProgram(second.computerString, &second);
12658 if (second.sendName) {
12659 snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12660 SendToProgram(buf, &second);
12664 if (!first.sendTime || !second.sendTime) {
12665 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12666 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12668 if (onmove->sendTime) {
12669 if (onmove->useColors) {
12670 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12672 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12674 if (onmove->useColors) {
12675 SendToProgram(onmove->twoMachinesColor, onmove);
12677 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12678 // SendToProgram("go\n", onmove);
12679 onmove->maybeThinking = TRUE;
12680 SetMachineThinkingEnables();
12684 if(bookHit) { // [HGM] book: simulate book reply
12685 static char bookMove[MSG_SIZ]; // a bit generous?
12687 programStats.nodes = programStats.depth = programStats.time =
12688 programStats.score = programStats.got_only_move = 0;
12689 sprintf(programStats.movelist, "%s (xbook)", bookHit);
12691 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12692 strcat(bookMove, bookHit);
12693 savedMessage = bookMove; // args for deferred call
12694 savedState = onmove;
12695 ScheduleDelayedEvent(DeferredBookMove, 1);
12702 if (gameMode == Training) {
12703 SetTrainingModeOff();
12704 gameMode = PlayFromGameFile;
12705 DisplayMessage("", _("Training mode off"));
12707 gameMode = Training;
12708 animateTraining = appData.animate;
12710 /* make sure we are not already at the end of the game */
12711 if (currentMove < forwardMostMove) {
12712 SetTrainingModeOn();
12713 DisplayMessage("", _("Training mode on"));
12715 gameMode = PlayFromGameFile;
12716 DisplayError(_("Already at end of game"), 0);
12725 if (!appData.icsActive) return;
12726 switch (gameMode) {
12727 case IcsPlayingWhite:
12728 case IcsPlayingBlack:
12731 case BeginningOfGame:
12739 EditPositionDone(TRUE);
12752 gameMode = IcsIdle;
12763 switch (gameMode) {
12765 SetTrainingModeOff();
12767 case MachinePlaysWhite:
12768 case MachinePlaysBlack:
12769 case BeginningOfGame:
12770 SendToProgram("force\n", &first);
12771 SetUserThinkingEnables();
12773 case PlayFromGameFile:
12774 (void) StopLoadGameTimer();
12775 if (gameFileFP != NULL) {
12780 EditPositionDone(TRUE);
12785 SendToProgram("force\n", &first);
12787 case TwoMachinesPlay:
12788 GameEnds(EndOfFile, NULL, GE_PLAYER);
12789 ResurrectChessProgram();
12790 SetUserThinkingEnables();
12793 ResurrectChessProgram();
12795 case IcsPlayingBlack:
12796 case IcsPlayingWhite:
12797 DisplayError(_("Warning: You are still playing a game"), 0);
12800 DisplayError(_("Warning: You are still observing a game"), 0);
12803 DisplayError(_("Warning: You are still examining a game"), 0);
12814 first.offeredDraw = second.offeredDraw = 0;
12816 if (gameMode == PlayFromGameFile) {
12817 whiteTimeRemaining = timeRemaining[0][currentMove];
12818 blackTimeRemaining = timeRemaining[1][currentMove];
12822 if (gameMode == MachinePlaysWhite ||
12823 gameMode == MachinePlaysBlack ||
12824 gameMode == TwoMachinesPlay ||
12825 gameMode == EndOfGame) {
12826 i = forwardMostMove;
12827 while (i > currentMove) {
12828 SendToProgram("undo\n", &first);
12831 whiteTimeRemaining = timeRemaining[0][currentMove];
12832 blackTimeRemaining = timeRemaining[1][currentMove];
12833 DisplayBothClocks();
12834 if (whiteFlag || blackFlag) {
12835 whiteFlag = blackFlag = 0;
12840 gameMode = EditGame;
12847 EditPositionEvent()
12849 if (gameMode == EditPosition) {
12855 if (gameMode != EditGame) return;
12857 gameMode = EditPosition;
12860 if (currentMove > 0)
12861 CopyBoard(boards[0], boards[currentMove]);
12863 blackPlaysFirst = !WhiteOnMove(currentMove);
12865 currentMove = forwardMostMove = backwardMostMove = 0;
12866 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12873 /* [DM] icsEngineAnalyze - possible call from other functions */
12874 if (appData.icsEngineAnalyze) {
12875 appData.icsEngineAnalyze = FALSE;
12877 DisplayMessage("",_("Close ICS engine analyze..."));
12879 if (first.analysisSupport && first.analyzing) {
12880 SendToProgram("exit\n", &first);
12881 first.analyzing = FALSE;
12883 thinkOutput[0] = NULLCHAR;
12887 EditPositionDone(Boolean fakeRights)
12889 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12891 startedFromSetupPosition = TRUE;
12892 InitChessProgram(&first, FALSE);
12893 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12894 boards[0][EP_STATUS] = EP_NONE;
12895 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12896 if(boards[0][0][BOARD_WIDTH>>1] == king) {
12897 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12898 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12899 } else boards[0][CASTLING][2] = NoRights;
12900 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12901 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12902 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12903 } else boards[0][CASTLING][5] = NoRights;
12905 SendToProgram("force\n", &first);
12906 if (blackPlaysFirst) {
12907 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12908 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12909 currentMove = forwardMostMove = backwardMostMove = 1;
12910 CopyBoard(boards[1], boards[0]);
12912 currentMove = forwardMostMove = backwardMostMove = 0;
12914 SendBoard(&first, forwardMostMove);
12915 if (appData.debugMode) {
12916 fprintf(debugFP, "EditPosDone\n");
12919 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12920 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12921 gameMode = EditGame;
12923 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12924 ClearHighlights(); /* [AS] */
12927 /* Pause for `ms' milliseconds */
12928 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12938 } while (SubtractTimeMarks(&m2, &m1) < ms);
12941 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12943 SendMultiLineToICS(buf)
12946 char temp[MSG_SIZ+1], *p;
12953 strncpy(temp, buf, len);
12958 if (*p == '\n' || *p == '\r')
12963 strcat(temp, "\n");
12965 SendToPlayer(temp, strlen(temp));
12969 SetWhiteToPlayEvent()
12971 if (gameMode == EditPosition) {
12972 blackPlaysFirst = FALSE;
12973 DisplayBothClocks(); /* works because currentMove is 0 */
12974 } else if (gameMode == IcsExamining) {
12975 SendToICS(ics_prefix);
12976 SendToICS("tomove white\n");
12981 SetBlackToPlayEvent()
12983 if (gameMode == EditPosition) {
12984 blackPlaysFirst = TRUE;
12985 currentMove = 1; /* kludge */
12986 DisplayBothClocks();
12988 } else if (gameMode == IcsExamining) {
12989 SendToICS(ics_prefix);
12990 SendToICS("tomove black\n");
12995 EditPositionMenuEvent(selection, x, y)
12996 ChessSquare selection;
13000 ChessSquare piece = boards[0][y][x];
13002 if (gameMode != EditPosition && gameMode != IcsExamining) return;
13004 switch (selection) {
13006 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13007 SendToICS(ics_prefix);
13008 SendToICS("bsetup clear\n");
13009 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13010 SendToICS(ics_prefix);
13011 SendToICS("clearboard\n");
13013 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13014 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13015 for (y = 0; y < BOARD_HEIGHT; y++) {
13016 if (gameMode == IcsExamining) {
13017 if (boards[currentMove][y][x] != EmptySquare) {
13018 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13023 boards[0][y][x] = p;
13028 if (gameMode == EditPosition) {
13029 DrawPosition(FALSE, boards[0]);
13034 SetWhiteToPlayEvent();
13038 SetBlackToPlayEvent();
13042 if (gameMode == IcsExamining) {
13043 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13044 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13047 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13048 if(x == BOARD_LEFT-2) {
13049 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13050 boards[0][y][1] = 0;
13052 if(x == BOARD_RGHT+1) {
13053 if(y >= gameInfo.holdingsSize) break;
13054 boards[0][y][BOARD_WIDTH-2] = 0;
13057 boards[0][y][x] = EmptySquare;
13058 DrawPosition(FALSE, boards[0]);
13063 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13064 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
13065 selection = (ChessSquare) (PROMOTED piece);
13066 } else if(piece == EmptySquare) selection = WhiteSilver;
13067 else selection = (ChessSquare)((int)piece - 1);
13071 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13072 piece > (int)BlackMan && piece <= (int)BlackKing ) {
13073 selection = (ChessSquare) (DEMOTED piece);
13074 } else if(piece == EmptySquare) selection = BlackSilver;
13075 else selection = (ChessSquare)((int)piece + 1);
13080 if(gameInfo.variant == VariantShatranj ||
13081 gameInfo.variant == VariantXiangqi ||
13082 gameInfo.variant == VariantCourier ||
13083 gameInfo.variant == VariantMakruk )
13084 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13089 if(gameInfo.variant == VariantXiangqi)
13090 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13091 if(gameInfo.variant == VariantKnightmate)
13092 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13095 if (gameMode == IcsExamining) {
13096 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13097 snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13098 PieceToChar(selection), AAA + x, ONE + y);
13101 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13103 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13104 n = PieceToNumber(selection - BlackPawn);
13105 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13106 boards[0][BOARD_HEIGHT-1-n][0] = selection;
13107 boards[0][BOARD_HEIGHT-1-n][1]++;
13109 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13110 n = PieceToNumber(selection);
13111 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13112 boards[0][n][BOARD_WIDTH-1] = selection;
13113 boards[0][n][BOARD_WIDTH-2]++;
13116 boards[0][y][x] = selection;
13117 DrawPosition(TRUE, boards[0]);
13125 DropMenuEvent(selection, x, y)
13126 ChessSquare selection;
13129 ChessMove moveType;
13131 switch (gameMode) {
13132 case IcsPlayingWhite:
13133 case MachinePlaysBlack:
13134 if (!WhiteOnMove(currentMove)) {
13135 DisplayMoveError(_("It is Black's turn"));
13138 moveType = WhiteDrop;
13140 case IcsPlayingBlack:
13141 case MachinePlaysWhite:
13142 if (WhiteOnMove(currentMove)) {
13143 DisplayMoveError(_("It is White's turn"));
13146 moveType = BlackDrop;
13149 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13155 if (moveType == BlackDrop && selection < BlackPawn) {
13156 selection = (ChessSquare) ((int) selection
13157 + (int) BlackPawn - (int) WhitePawn);
13159 if (boards[currentMove][y][x] != EmptySquare) {
13160 DisplayMoveError(_("That square is occupied"));
13164 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13170 /* Accept a pending offer of any kind from opponent */
13172 if (appData.icsActive) {
13173 SendToICS(ics_prefix);
13174 SendToICS("accept\n");
13175 } else if (cmailMsgLoaded) {
13176 if (currentMove == cmailOldMove &&
13177 commentList[cmailOldMove] != NULL &&
13178 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13179 "Black offers a draw" : "White offers a draw")) {
13181 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13182 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13184 DisplayError(_("There is no pending offer on this move"), 0);
13185 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13188 /* Not used for offers from chess program */
13195 /* Decline a pending offer of any kind from opponent */
13197 if (appData.icsActive) {
13198 SendToICS(ics_prefix);
13199 SendToICS("decline\n");
13200 } else if (cmailMsgLoaded) {
13201 if (currentMove == cmailOldMove &&
13202 commentList[cmailOldMove] != NULL &&
13203 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13204 "Black offers a draw" : "White offers a draw")) {
13206 AppendComment(cmailOldMove, "Draw declined", TRUE);
13207 DisplayComment(cmailOldMove - 1, "Draw declined");
13210 DisplayError(_("There is no pending offer on this move"), 0);
13213 /* Not used for offers from chess program */
13220 /* Issue ICS rematch command */
13221 if (appData.icsActive) {
13222 SendToICS(ics_prefix);
13223 SendToICS("rematch\n");
13230 /* Call your opponent's flag (claim a win on time) */
13231 if (appData.icsActive) {
13232 SendToICS(ics_prefix);
13233 SendToICS("flag\n");
13235 switch (gameMode) {
13238 case MachinePlaysWhite:
13241 GameEnds(GameIsDrawn, "Both players ran out of time",
13244 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
13246 DisplayError(_("Your opponent is not out of time"), 0);
13249 case MachinePlaysBlack:
13252 GameEnds(GameIsDrawn, "Both players ran out of time",
13255 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
13257 DisplayError(_("Your opponent is not out of time"), 0);
13265 ClockClick(int which)
13266 { // [HGM] code moved to back-end from winboard.c
13267 if(which) { // black clock
13268 if (gameMode == EditPosition || gameMode == IcsExamining) {
13269 if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13270 SetBlackToPlayEvent();
13271 } else if (gameMode == EditGame || shiftKey) {
13272 AdjustClock(which, -1);
13273 } else if (gameMode == IcsPlayingWhite ||
13274 gameMode == MachinePlaysBlack) {
13277 } else { // white clock
13278 if (gameMode == EditPosition || gameMode == IcsExamining) {
13279 if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13280 SetWhiteToPlayEvent();
13281 } else if (gameMode == EditGame || shiftKey) {
13282 AdjustClock(which, -1);
13283 } else if (gameMode == IcsPlayingBlack ||
13284 gameMode == MachinePlaysWhite) {
13293 /* Offer draw or accept pending draw offer from opponent */
13295 if (appData.icsActive) {
13296 /* Note: tournament rules require draw offers to be
13297 made after you make your move but before you punch
13298 your clock. Currently ICS doesn't let you do that;
13299 instead, you immediately punch your clock after making
13300 a move, but you can offer a draw at any time. */
13302 SendToICS(ics_prefix);
13303 SendToICS("draw\n");
13304 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
13305 } else if (cmailMsgLoaded) {
13306 if (currentMove == cmailOldMove &&
13307 commentList[cmailOldMove] != NULL &&
13308 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13309 "Black offers a draw" : "White offers a draw")) {
13310 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13311 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13312 } else if (currentMove == cmailOldMove + 1) {
13313 char *offer = WhiteOnMove(cmailOldMove) ?
13314 "White offers a draw" : "Black offers a draw";
13315 AppendComment(currentMove, offer, TRUE);
13316 DisplayComment(currentMove - 1, offer);
13317 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
13319 DisplayError(_("You must make your move before offering a draw"), 0);
13320 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13322 } else if (first.offeredDraw) {
13323 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
13325 if (first.sendDrawOffers) {
13326 SendToProgram("draw\n", &first);
13327 userOfferedDraw = TRUE;
13335 /* Offer Adjourn or accept pending Adjourn offer from opponent */
13337 if (appData.icsActive) {
13338 SendToICS(ics_prefix);
13339 SendToICS("adjourn\n");
13341 /* Currently GNU Chess doesn't offer or accept Adjourns */
13349 /* Offer Abort or accept pending Abort offer from opponent */
13351 if (appData.icsActive) {
13352 SendToICS(ics_prefix);
13353 SendToICS("abort\n");
13355 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
13362 /* Resign. You can do this even if it's not your turn. */
13364 if (appData.icsActive) {
13365 SendToICS(ics_prefix);
13366 SendToICS("resign\n");
13368 switch (gameMode) {
13369 case MachinePlaysWhite:
13370 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13372 case MachinePlaysBlack:
13373 GameEnds(BlackWins, "White resigns", GE_PLAYER);
13376 if (cmailMsgLoaded) {
13378 if (WhiteOnMove(cmailOldMove)) {
13379 GameEnds(BlackWins, "White resigns", GE_PLAYER);
13381 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13383 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
13394 StopObservingEvent()
13396 /* Stop observing current games */
13397 SendToICS(ics_prefix);
13398 SendToICS("unobserve\n");
13402 StopExaminingEvent()
13404 /* Stop observing current game */
13405 SendToICS(ics_prefix);
13406 SendToICS("unexamine\n");
13410 ForwardInner(target)
13415 if (appData.debugMode)
13416 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
13417 target, currentMove, forwardMostMove);
13419 if (gameMode == EditPosition)
13422 if (gameMode == PlayFromGameFile && !pausing)
13425 if (gameMode == IcsExamining && pausing)
13426 limit = pauseExamForwardMostMove;
13428 limit = forwardMostMove;
13430 if (target > limit) target = limit;
13432 if (target > 0 && moveList[target - 1][0]) {
13433 int fromX, fromY, toX, toY;
13434 toX = moveList[target - 1][2] - AAA;
13435 toY = moveList[target - 1][3] - ONE;
13436 if (moveList[target - 1][1] == '@') {
13437 if (appData.highlightLastMove) {
13438 SetHighlights(-1, -1, toX, toY);
13441 fromX = moveList[target - 1][0] - AAA;
13442 fromY = moveList[target - 1][1] - ONE;
13443 if (target == currentMove + 1) {
13444 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
13446 if (appData.highlightLastMove) {
13447 SetHighlights(fromX, fromY, toX, toY);
13451 if (gameMode == EditGame || gameMode == AnalyzeMode ||
13452 gameMode == Training || gameMode == PlayFromGameFile ||
13453 gameMode == AnalyzeFile) {
13454 while (currentMove < target) {
13455 SendMoveToProgram(currentMove++, &first);
13458 currentMove = target;
13461 if (gameMode == EditGame || gameMode == EndOfGame) {
13462 whiteTimeRemaining = timeRemaining[0][currentMove];
13463 blackTimeRemaining = timeRemaining[1][currentMove];
13465 DisplayBothClocks();
13466 DisplayMove(currentMove - 1);
13467 DrawPosition(FALSE, boards[currentMove]);
13468 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13469 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
13470 DisplayComment(currentMove - 1, commentList[currentMove]);
13478 if (gameMode == IcsExamining && !pausing) {
13479 SendToICS(ics_prefix);
13480 SendToICS("forward\n");
13482 ForwardInner(currentMove + 1);
13489 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13490 /* to optimze, we temporarily turn off analysis mode while we feed
13491 * the remaining moves to the engine. Otherwise we get analysis output
13494 if (first.analysisSupport) {
13495 SendToProgram("exit\nforce\n", &first);
13496 first.analyzing = FALSE;
13500 if (gameMode == IcsExamining && !pausing) {
13501 SendToICS(ics_prefix);
13502 SendToICS("forward 999999\n");
13504 ForwardInner(forwardMostMove);
13507 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13508 /* we have fed all the moves, so reactivate analysis mode */
13509 SendToProgram("analyze\n", &first);
13510 first.analyzing = TRUE;
13511 /*first.maybeThinking = TRUE;*/
13512 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13517 BackwardInner(target)
13520 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
13522 if (appData.debugMode)
13523 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
13524 target, currentMove, forwardMostMove);
13526 if (gameMode == EditPosition) return;
13527 if (currentMove <= backwardMostMove) {
13529 DrawPosition(full_redraw, boards[currentMove]);
13532 if (gameMode == PlayFromGameFile && !pausing)
13535 if (moveList[target][0]) {
13536 int fromX, fromY, toX, toY;
13537 toX = moveList[target][2] - AAA;
13538 toY = moveList[target][3] - ONE;
13539 if (moveList[target][1] == '@') {
13540 if (appData.highlightLastMove) {
13541 SetHighlights(-1, -1, toX, toY);
13544 fromX = moveList[target][0] - AAA;
13545 fromY = moveList[target][1] - ONE;
13546 if (target == currentMove - 1) {
13547 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
13549 if (appData.highlightLastMove) {
13550 SetHighlights(fromX, fromY, toX, toY);
13554 if (gameMode == EditGame || gameMode==AnalyzeMode ||
13555 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
13556 while (currentMove > target) {
13557 SendToProgram("undo\n", &first);
13561 currentMove = target;
13564 if (gameMode == EditGame || gameMode == EndOfGame) {
13565 whiteTimeRemaining = timeRemaining[0][currentMove];
13566 blackTimeRemaining = timeRemaining[1][currentMove];
13568 DisplayBothClocks();
13569 DisplayMove(currentMove - 1);
13570 DrawPosition(full_redraw, boards[currentMove]);
13571 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13572 // [HGM] PV info: routine tests if comment empty
13573 DisplayComment(currentMove - 1, commentList[currentMove]);
13579 if (gameMode == IcsExamining && !pausing) {
13580 SendToICS(ics_prefix);
13581 SendToICS("backward\n");
13583 BackwardInner(currentMove - 1);
13590 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13591 /* to optimize, we temporarily turn off analysis mode while we undo
13592 * all the moves. Otherwise we get analysis output after each undo.
13594 if (first.analysisSupport) {
13595 SendToProgram("exit\nforce\n", &first);
13596 first.analyzing = FALSE;
13600 if (gameMode == IcsExamining && !pausing) {
13601 SendToICS(ics_prefix);
13602 SendToICS("backward 999999\n");
13604 BackwardInner(backwardMostMove);
13607 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13608 /* we have fed all the moves, so reactivate analysis mode */
13609 SendToProgram("analyze\n", &first);
13610 first.analyzing = TRUE;
13611 /*first.maybeThinking = TRUE;*/
13612 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13619 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
13620 if (to >= forwardMostMove) to = forwardMostMove;
13621 if (to <= backwardMostMove) to = backwardMostMove;
13622 if (to < currentMove) {
13630 RevertEvent(Boolean annotate)
13632 if(PopTail(annotate)) { // [HGM] vari: restore old game tail
13635 if (gameMode != IcsExamining) {
13636 DisplayError(_("You are not examining a game"), 0);
13640 DisplayError(_("You can't revert while pausing"), 0);
13643 SendToICS(ics_prefix);
13644 SendToICS("revert\n");
13650 switch (gameMode) {
13651 case MachinePlaysWhite:
13652 case MachinePlaysBlack:
13653 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13654 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13657 if (forwardMostMove < 2) return;
13658 currentMove = forwardMostMove = forwardMostMove - 2;
13659 whiteTimeRemaining = timeRemaining[0][currentMove];
13660 blackTimeRemaining = timeRemaining[1][currentMove];
13661 DisplayBothClocks();
13662 DisplayMove(currentMove - 1);
13663 ClearHighlights();/*!! could figure this out*/
13664 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
13665 SendToProgram("remove\n", &first);
13666 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
13669 case BeginningOfGame:
13673 case IcsPlayingWhite:
13674 case IcsPlayingBlack:
13675 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
13676 SendToICS(ics_prefix);
13677 SendToICS("takeback 2\n");
13679 SendToICS(ics_prefix);
13680 SendToICS("takeback 1\n");
13689 ChessProgramState *cps;
13691 switch (gameMode) {
13692 case MachinePlaysWhite:
13693 if (!WhiteOnMove(forwardMostMove)) {
13694 DisplayError(_("It is your turn"), 0);
13699 case MachinePlaysBlack:
13700 if (WhiteOnMove(forwardMostMove)) {
13701 DisplayError(_("It is your turn"), 0);
13706 case TwoMachinesPlay:
13707 if (WhiteOnMove(forwardMostMove) ==
13708 (first.twoMachinesColor[0] == 'w')) {
13714 case BeginningOfGame:
13718 SendToProgram("?\n", cps);
13722 TruncateGameEvent()
13725 if (gameMode != EditGame) return;
13732 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13733 if (forwardMostMove > currentMove) {
13734 if (gameInfo.resultDetails != NULL) {
13735 free(gameInfo.resultDetails);
13736 gameInfo.resultDetails = NULL;
13737 gameInfo.result = GameUnfinished;
13739 forwardMostMove = currentMove;
13740 HistorySet(parseList, backwardMostMove, forwardMostMove,
13748 if (appData.noChessProgram) return;
13749 switch (gameMode) {
13750 case MachinePlaysWhite:
13751 if (WhiteOnMove(forwardMostMove)) {
13752 DisplayError(_("Wait until your turn"), 0);
13756 case BeginningOfGame:
13757 case MachinePlaysBlack:
13758 if (!WhiteOnMove(forwardMostMove)) {
13759 DisplayError(_("Wait until your turn"), 0);
13764 DisplayError(_("No hint available"), 0);
13767 SendToProgram("hint\n", &first);
13768 hintRequested = TRUE;
13774 if (appData.noChessProgram) return;
13775 switch (gameMode) {
13776 case MachinePlaysWhite:
13777 if (WhiteOnMove(forwardMostMove)) {
13778 DisplayError(_("Wait until your turn"), 0);
13782 case BeginningOfGame:
13783 case MachinePlaysBlack:
13784 if (!WhiteOnMove(forwardMostMove)) {
13785 DisplayError(_("Wait until your turn"), 0);
13790 EditPositionDone(TRUE);
13792 case TwoMachinesPlay:
13797 SendToProgram("bk\n", &first);
13798 bookOutput[0] = NULLCHAR;
13799 bookRequested = TRUE;
13805 char *tags = PGNTags(&gameInfo);
13806 TagsPopUp(tags, CmailMsg());
13810 /* end button procedures */
13813 PrintPosition(fp, move)
13819 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13820 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13821 char c = PieceToChar(boards[move][i][j]);
13822 fputc(c == 'x' ? '.' : c, fp);
13823 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13826 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13827 fprintf(fp, "white to play\n");
13829 fprintf(fp, "black to play\n");
13836 if (gameInfo.white != NULL) {
13837 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13843 /* Find last component of program's own name, using some heuristics */
13845 TidyProgramName(prog, host, buf)
13846 char *prog, *host, buf[MSG_SIZ];
13849 int local = (strcmp(host, "localhost") == 0);
13850 while (!local && (p = strchr(prog, ';')) != NULL) {
13852 while (*p == ' ') p++;
13855 if (*prog == '"' || *prog == '\'') {
13856 q = strchr(prog + 1, *prog);
13858 q = strchr(prog, ' ');
13860 if (q == NULL) q = prog + strlen(prog);
13862 while (p >= prog && *p != '/' && *p != '\\') p--;
13864 if(p == prog && *p == '"') p++;
13865 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13866 memcpy(buf, p, q - p);
13867 buf[q - p] = NULLCHAR;
13875 TimeControlTagValue()
13878 if (!appData.clockMode) {
13879 safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
13880 } else if (movesPerSession > 0) {
13881 snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
13882 } else if (timeIncrement == 0) {
13883 snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
13885 snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13887 return StrSave(buf);
13893 /* This routine is used only for certain modes */
13894 VariantClass v = gameInfo.variant;
13895 ChessMove r = GameUnfinished;
13898 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13899 r = gameInfo.result;
13900 p = gameInfo.resultDetails;
13901 gameInfo.resultDetails = NULL;
13903 ClearGameInfo(&gameInfo);
13904 gameInfo.variant = v;
13906 switch (gameMode) {
13907 case MachinePlaysWhite:
13908 gameInfo.event = StrSave( appData.pgnEventHeader );
13909 gameInfo.site = StrSave(HostName());
13910 gameInfo.date = PGNDate();
13911 gameInfo.round = StrSave("-");
13912 gameInfo.white = StrSave(first.tidy);
13913 gameInfo.black = StrSave(UserName());
13914 gameInfo.timeControl = TimeControlTagValue();
13917 case MachinePlaysBlack:
13918 gameInfo.event = StrSave( appData.pgnEventHeader );
13919 gameInfo.site = StrSave(HostName());
13920 gameInfo.date = PGNDate();
13921 gameInfo.round = StrSave("-");
13922 gameInfo.white = StrSave(UserName());
13923 gameInfo.black = StrSave(first.tidy);
13924 gameInfo.timeControl = TimeControlTagValue();
13927 case TwoMachinesPlay:
13928 gameInfo.event = StrSave( appData.pgnEventHeader );
13929 gameInfo.site = StrSave(HostName());
13930 gameInfo.date = PGNDate();
13933 snprintf(buf, MSG_SIZ, "%d", roundNr);
13934 gameInfo.round = StrSave(buf);
13936 gameInfo.round = StrSave("-");
13938 if (first.twoMachinesColor[0] == 'w') {
13939 gameInfo.white = StrSave(first.tidy);
13940 gameInfo.black = StrSave(second.tidy);
13942 gameInfo.white = StrSave(second.tidy);
13943 gameInfo.black = StrSave(first.tidy);
13945 gameInfo.timeControl = TimeControlTagValue();
13949 gameInfo.event = StrSave("Edited game");
13950 gameInfo.site = StrSave(HostName());
13951 gameInfo.date = PGNDate();
13952 gameInfo.round = StrSave("-");
13953 gameInfo.white = StrSave("-");
13954 gameInfo.black = StrSave("-");
13955 gameInfo.result = r;
13956 gameInfo.resultDetails = p;
13960 gameInfo.event = StrSave("Edited position");
13961 gameInfo.site = StrSave(HostName());
13962 gameInfo.date = PGNDate();
13963 gameInfo.round = StrSave("-");
13964 gameInfo.white = StrSave("-");
13965 gameInfo.black = StrSave("-");
13968 case IcsPlayingWhite:
13969 case IcsPlayingBlack:
13974 case PlayFromGameFile:
13975 gameInfo.event = StrSave("Game from non-PGN file");
13976 gameInfo.site = StrSave(HostName());
13977 gameInfo.date = PGNDate();
13978 gameInfo.round = StrSave("-");
13979 gameInfo.white = StrSave("?");
13980 gameInfo.black = StrSave("?");
13989 ReplaceComment(index, text)
13997 if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
13998 pvInfoList[index-1].depth == len &&
13999 fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
14000 (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
14001 while (*text == '\n') text++;
14002 len = strlen(text);
14003 while (len > 0 && text[len - 1] == '\n') len--;
14005 if (commentList[index] != NULL)
14006 free(commentList[index]);
14009 commentList[index] = NULL;
14012 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14013 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14014 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14015 commentList[index] = (char *) malloc(len + 2);
14016 strncpy(commentList[index], text, len);
14017 commentList[index][len] = '\n';
14018 commentList[index][len + 1] = NULLCHAR;
14020 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14022 commentList[index] = (char *) malloc(len + 7);
14023 safeStrCpy(commentList[index], "{\n", 3);
14024 safeStrCpy(commentList[index]+2, text, len+1);
14025 commentList[index][len+2] = NULLCHAR;
14026 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14027 strcat(commentList[index], "\n}\n");
14041 if (ch == '\r') continue;
14043 } while (ch != '\0');
14047 AppendComment(index, text, addBraces)
14050 Boolean addBraces; // [HGM] braces: tells if we should add {}
14055 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14056 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14059 while (*text == '\n') text++;
14060 len = strlen(text);
14061 while (len > 0 && text[len - 1] == '\n') len--;
14063 if (len == 0) return;
14065 if (commentList[index] != NULL) {
14066 old = commentList[index];
14067 oldlen = strlen(old);
14068 while(commentList[index][oldlen-1] == '\n')
14069 commentList[index][--oldlen] = NULLCHAR;
14070 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14071 safeStrCpy(commentList[index], old, oldlen + len + 6);
14073 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14074 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14075 if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14076 while (*text == '\n') { text++; len--; }
14077 commentList[index][--oldlen] = NULLCHAR;
14079 if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14080 else strcat(commentList[index], "\n");
14081 strcat(commentList[index], text);
14082 if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
14083 else strcat(commentList[index], "\n");
14085 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14087 safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14088 else commentList[index][0] = NULLCHAR;
14089 strcat(commentList[index], text);
14090 strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14091 if(addBraces == TRUE) strcat(commentList[index], "}\n");
14095 static char * FindStr( char * text, char * sub_text )
14097 char * result = strstr( text, sub_text );
14099 if( result != NULL ) {
14100 result += strlen( sub_text );
14106 /* [AS] Try to extract PV info from PGN comment */
14107 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14108 char *GetInfoFromComment( int index, char * text )
14110 char * sep = text, *p;
14112 if( text != NULL && index > 0 ) {
14115 int time = -1, sec = 0, deci;
14116 char * s_eval = FindStr( text, "[%eval " );
14117 char * s_emt = FindStr( text, "[%emt " );
14119 if( s_eval != NULL || s_emt != NULL ) {
14123 if( s_eval != NULL ) {
14124 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14128 if( delim != ']' ) {
14133 if( s_emt != NULL ) {
14138 /* We expect something like: [+|-]nnn.nn/dd */
14141 if(*text != '{') return text; // [HGM] braces: must be normal comment
14143 sep = strchr( text, '/' );
14144 if( sep == NULL || sep < (text+4) ) {
14149 if(p[1] == '(') { // comment starts with PV
14150 p = strchr(p, ')'); // locate end of PV
14151 if(p == NULL || sep < p+5) return text;
14152 // at this point we have something like "{(.*) +0.23/6 ..."
14153 p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14154 *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14155 // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14157 time = -1; sec = -1; deci = -1;
14158 if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14159 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14160 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14161 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
14165 if( score_lo < 0 || score_lo >= 100 ) {
14169 if(sec >= 0) time = 600*time + 10*sec; else
14170 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14172 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14174 /* [HGM] PV time: now locate end of PV info */
14175 while( *++sep >= '0' && *sep <= '9'); // strip depth
14177 while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14179 while( *++sep >= '0' && *sep <= '9'); // strip seconds
14181 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14182 while(*sep == ' ') sep++;
14193 pvInfoList[index-1].depth = depth;
14194 pvInfoList[index-1].score = score;
14195 pvInfoList[index-1].time = 10*time; // centi-sec
14196 if(*sep == '}') *sep = 0; else *--sep = '{';
14197 if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
14203 SendToProgram(message, cps)
14205 ChessProgramState *cps;
14207 int count, outCount, error;
14210 if (cps->pr == NULL) return;
14213 if (appData.debugMode) {
14216 fprintf(debugFP, "%ld >%-6s: %s",
14217 SubtractTimeMarks(&now, &programStartTime),
14218 cps->which, message);
14221 count = strlen(message);
14222 outCount = OutputToProcess(cps->pr, message, count, &error);
14223 if (outCount < count && !exiting
14224 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
14225 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14226 snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
14227 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14228 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14229 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14230 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14231 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14233 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14234 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14235 gameInfo.result = res;
14237 gameInfo.resultDetails = StrSave(buf);
14239 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14240 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14245 ReceiveFromProgram(isr, closure, message, count, error)
14246 InputSourceRef isr;
14254 ChessProgramState *cps = (ChessProgramState *)closure;
14256 if (isr != cps->isr) return; /* Killed intentionally */
14259 RemoveInputSource(cps->isr);
14260 snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
14261 _(cps->which), cps->program);
14262 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14263 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14264 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14265 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14266 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14268 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14269 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14270 gameInfo.result = res;
14272 gameInfo.resultDetails = StrSave(buf);
14274 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14275 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
14277 snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
14278 _(cps->which), cps->program);
14279 RemoveInputSource(cps->isr);
14281 /* [AS] Program is misbehaving badly... kill it */
14282 if( count == -2 ) {
14283 DestroyChildProcess( cps->pr, 9 );
14287 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14292 if ((end_str = strchr(message, '\r')) != NULL)
14293 *end_str = NULLCHAR;
14294 if ((end_str = strchr(message, '\n')) != NULL)
14295 *end_str = NULLCHAR;
14297 if (appData.debugMode) {
14298 TimeMark now; int print = 1;
14299 char *quote = ""; char c; int i;
14301 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
14302 char start = message[0];
14303 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
14304 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
14305 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
14306 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
14307 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
14308 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
14309 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
14310 sscanf(message, "setboard %c", &c)!=1 && sscanf(message, "setup %c", &c)!=1 &&
14311 sscanf(message, "hint: %c", &c)!=1 &&
14312 sscanf(message, "pong %c", &c)!=1 && start != '#') {
14313 quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
14314 print = (appData.engineComments >= 2);
14316 message[0] = start; // restore original message
14320 fprintf(debugFP, "%ld <%-6s: %s%s\n",
14321 SubtractTimeMarks(&now, &programStartTime), cps->which,
14327 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
14328 if (appData.icsEngineAnalyze) {
14329 if (strstr(message, "whisper") != NULL ||
14330 strstr(message, "kibitz") != NULL ||
14331 strstr(message, "tellics") != NULL) return;
14334 HandleMachineMove(message, cps);
14339 SendTimeControl(cps, mps, tc, inc, sd, st)
14340 ChessProgramState *cps;
14341 int mps, inc, sd, st;
14347 if( timeControl_2 > 0 ) {
14348 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
14349 tc = timeControl_2;
14352 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
14353 inc /= cps->timeOdds;
14354 st /= cps->timeOdds;
14356 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
14359 /* Set exact time per move, normally using st command */
14360 if (cps->stKludge) {
14361 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
14363 if (seconds == 0) {
14364 snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
14366 snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
14369 snprintf(buf, MSG_SIZ, "st %d\n", st);
14372 /* Set conventional or incremental time control, using level command */
14373 if (seconds == 0) {
14374 /* Note old gnuchess bug -- minutes:seconds used to not work.
14375 Fixed in later versions, but still avoid :seconds
14376 when seconds is 0. */
14377 snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
14379 snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
14380 seconds, inc/1000.);
14383 SendToProgram(buf, cps);
14385 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
14386 /* Orthogonally, limit search to given depth */
14388 if (cps->sdKludge) {
14389 snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
14391 snprintf(buf, MSG_SIZ, "sd %d\n", sd);
14393 SendToProgram(buf, cps);
14396 if(cps->nps >= 0) { /* [HGM] nps */
14397 if(cps->supportsNPS == FALSE)
14398 cps->nps = -1; // don't use if engine explicitly says not supported!
14400 snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
14401 SendToProgram(buf, cps);
14406 ChessProgramState *WhitePlayer()
14407 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
14409 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
14410 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
14416 SendTimeRemaining(cps, machineWhite)
14417 ChessProgramState *cps;
14418 int /*boolean*/ machineWhite;
14420 char message[MSG_SIZ];
14423 /* Note: this routine must be called when the clocks are stopped
14424 or when they have *just* been set or switched; otherwise
14425 it will be off by the time since the current tick started.
14427 if (machineWhite) {
14428 time = whiteTimeRemaining / 10;
14429 otime = blackTimeRemaining / 10;
14431 time = blackTimeRemaining / 10;
14432 otime = whiteTimeRemaining / 10;
14434 /* [HGM] translate opponent's time by time-odds factor */
14435 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
14436 if (appData.debugMode) {
14437 fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
14440 if (time <= 0) time = 1;
14441 if (otime <= 0) otime = 1;
14443 snprintf(message, MSG_SIZ, "time %ld\n", time);
14444 SendToProgram(message, cps);
14446 snprintf(message, MSG_SIZ, "otim %ld\n", otime);
14447 SendToProgram(message, cps);
14451 BoolFeature(p, name, loc, cps)
14455 ChessProgramState *cps;
14458 int len = strlen(name);
14461 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14463 sscanf(*p, "%d", &val);
14465 while (**p && **p != ' ')
14467 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14468 SendToProgram(buf, cps);
14475 IntFeature(p, name, loc, cps)
14479 ChessProgramState *cps;
14482 int len = strlen(name);
14483 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14485 sscanf(*p, "%d", loc);
14486 while (**p && **p != ' ') (*p)++;
14487 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14488 SendToProgram(buf, cps);
14495 StringFeature(p, name, loc, cps)
14499 ChessProgramState *cps;
14502 int len = strlen(name);
14503 if (strncmp((*p), name, len) == 0
14504 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
14506 sscanf(*p, "%[^\"]", loc);
14507 while (**p && **p != '\"') (*p)++;
14508 if (**p == '\"') (*p)++;
14509 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14510 SendToProgram(buf, cps);
14517 ParseOption(Option *opt, ChessProgramState *cps)
14518 // [HGM] options: process the string that defines an engine option, and determine
14519 // name, type, default value, and allowed value range
14521 char *p, *q, buf[MSG_SIZ];
14522 int n, min = (-1)<<31, max = 1<<31, def;
14524 if(p = strstr(opt->name, " -spin ")) {
14525 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14526 if(max < min) max = min; // enforce consistency
14527 if(def < min) def = min;
14528 if(def > max) def = max;
14533 } else if((p = strstr(opt->name, " -slider "))) {
14534 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
14535 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14536 if(max < min) max = min; // enforce consistency
14537 if(def < min) def = min;
14538 if(def > max) def = max;
14542 opt->type = Spin; // Slider;
14543 } else if((p = strstr(opt->name, " -string "))) {
14544 opt->textValue = p+9;
14545 opt->type = TextBox;
14546 } else if((p = strstr(opt->name, " -file "))) {
14547 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14548 opt->textValue = p+7;
14549 opt->type = FileName; // FileName;
14550 } else if((p = strstr(opt->name, " -path "))) {
14551 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14552 opt->textValue = p+7;
14553 opt->type = PathName; // PathName;
14554 } else if(p = strstr(opt->name, " -check ")) {
14555 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
14556 opt->value = (def != 0);
14557 opt->type = CheckBox;
14558 } else if(p = strstr(opt->name, " -combo ")) {
14559 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
14560 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
14561 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
14562 opt->value = n = 0;
14563 while(q = StrStr(q, " /// ")) {
14564 n++; *q = 0; // count choices, and null-terminate each of them
14566 if(*q == '*') { // remember default, which is marked with * prefix
14570 cps->comboList[cps->comboCnt++] = q;
14572 cps->comboList[cps->comboCnt++] = NULL;
14574 opt->type = ComboBox;
14575 } else if(p = strstr(opt->name, " -button")) {
14576 opt->type = Button;
14577 } else if(p = strstr(opt->name, " -save")) {
14578 opt->type = SaveButton;
14579 } else return FALSE;
14580 *p = 0; // terminate option name
14581 // now look if the command-line options define a setting for this engine option.
14582 if(cps->optionSettings && cps->optionSettings[0])
14583 p = strstr(cps->optionSettings, opt->name); else p = NULL;
14584 if(p && (p == cps->optionSettings || p[-1] == ',')) {
14585 snprintf(buf, MSG_SIZ, "option %s", p);
14586 if(p = strstr(buf, ",")) *p = 0;
14587 if(q = strchr(buf, '=')) switch(opt->type) {
14589 for(n=0; n<opt->max; n++)
14590 if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
14593 safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
14597 opt->value = atoi(q+1);
14602 SendToProgram(buf, cps);
14608 FeatureDone(cps, val)
14609 ChessProgramState* cps;
14612 DelayedEventCallback cb = GetDelayedEvent();
14613 if ((cb == InitBackEnd3 && cps == &first) ||
14614 (cb == SettingsMenuIfReady && cps == &second) ||
14615 (cb == LoadEngine) ||
14616 (cb == TwoMachinesEventIfReady)) {
14617 CancelDelayedEvent();
14618 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
14620 cps->initDone = val;
14623 /* Parse feature command from engine */
14625 ParseFeatures(args, cps)
14627 ChessProgramState *cps;
14635 while (*p == ' ') p++;
14636 if (*p == NULLCHAR) return;
14638 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
14639 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
14640 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
14641 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
14642 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
14643 if (BoolFeature(&p, "reuse", &val, cps)) {
14644 /* Engine can disable reuse, but can't enable it if user said no */
14645 if (!val) cps->reuse = FALSE;
14648 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
14649 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
14650 if (gameMode == TwoMachinesPlay) {
14651 DisplayTwoMachinesTitle();
14657 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
14658 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
14659 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
14660 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
14661 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
14662 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
14663 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
14664 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
14665 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
14666 if (IntFeature(&p, "done", &val, cps)) {
14667 FeatureDone(cps, val);
14670 /* Added by Tord: */
14671 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
14672 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
14673 /* End of additions by Tord */
14675 /* [HGM] added features: */
14676 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
14677 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
14678 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
14679 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
14680 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14681 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
14682 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
14683 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
14684 snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
14685 SendToProgram(buf, cps);
14688 if(cps->nrOptions >= MAX_OPTIONS) {
14690 snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
14691 DisplayError(buf, 0);
14695 /* End of additions by HGM */
14697 /* unknown feature: complain and skip */
14699 while (*q && *q != '=') q++;
14700 snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
14701 SendToProgram(buf, cps);
14707 while (*p && *p != '\"') p++;
14708 if (*p == '\"') p++;
14710 while (*p && *p != ' ') p++;
14718 PeriodicUpdatesEvent(newState)
14721 if (newState == appData.periodicUpdates)
14724 appData.periodicUpdates=newState;
14726 /* Display type changes, so update it now */
14727 // DisplayAnalysis();
14729 /* Get the ball rolling again... */
14731 AnalysisPeriodicEvent(1);
14732 StartAnalysisClock();
14737 PonderNextMoveEvent(newState)
14740 if (newState == appData.ponderNextMove) return;
14741 if (gameMode == EditPosition) EditPositionDone(TRUE);
14743 SendToProgram("hard\n", &first);
14744 if (gameMode == TwoMachinesPlay) {
14745 SendToProgram("hard\n", &second);
14748 SendToProgram("easy\n", &first);
14749 thinkOutput[0] = NULLCHAR;
14750 if (gameMode == TwoMachinesPlay) {
14751 SendToProgram("easy\n", &second);
14754 appData.ponderNextMove = newState;
14758 NewSettingEvent(option, feature, command, value)
14760 int option, value, *feature;
14764 if (gameMode == EditPosition) EditPositionDone(TRUE);
14765 snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
14766 if(feature == NULL || *feature) SendToProgram(buf, &first);
14767 if (gameMode == TwoMachinesPlay) {
14768 if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14773 ShowThinkingEvent()
14774 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14776 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
14777 int newState = appData.showThinking
14778 // [HGM] thinking: other features now need thinking output as well
14779 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
14781 if (oldState == newState) return;
14782 oldState = newState;
14783 if (gameMode == EditPosition) EditPositionDone(TRUE);
14785 SendToProgram("post\n", &first);
14786 if (gameMode == TwoMachinesPlay) {
14787 SendToProgram("post\n", &second);
14790 SendToProgram("nopost\n", &first);
14791 thinkOutput[0] = NULLCHAR;
14792 if (gameMode == TwoMachinesPlay) {
14793 SendToProgram("nopost\n", &second);
14796 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
14800 AskQuestionEvent(title, question, replyPrefix, which)
14801 char *title; char *question; char *replyPrefix; char *which;
14803 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
14804 if (pr == NoProc) return;
14805 AskQuestion(title, question, replyPrefix, pr);
14809 TypeInEvent(char firstChar)
14811 if ((gameMode == BeginningOfGame && !appData.icsActive) ||
\r
14812 gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
\r
14813 gameMode == AnalyzeMode || gameMode == EditGame ||
\r
14814 gameMode == EditPosition || gameMode == IcsExamining ||
\r
14815 gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
\r
14816 isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
\r
14817 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
\r
14818 gameMode == IcsObserving || gameMode == TwoMachinesPlay ) ||
\r
14819 gameMode == Training) PopUpMoveDialog(firstChar);
14823 TypeInDoneEvent(char *move)
14826 int n, fromX, fromY, toX, toY;
14828 ChessMove moveType;
\r
14831 if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
\r
14832 EditPositionPasteFEN(move);
\r
14835 // [HGM] movenum: allow move number to be typed in any mode
\r
14836 if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
\r
14837 ToNrEvent(2*n-1);
\r
14841 if (gameMode != EditGame && currentMove != forwardMostMove &&
\r
14842 gameMode != Training) {
\r
14843 DisplayMoveError(_("Displayed move is not current"));
\r
14845 int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
\r
14846 &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
\r
14847 if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
\r
14848 if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
\r
14849 &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
\r
14850 UserMoveEvent(fromX, fromY, toX, toY, promoChar);
\r
14852 DisplayMoveError(_("Could not parse move"));
\r
14858 DisplayMove(moveNumber)
14861 char message[MSG_SIZ];
14863 char cpThinkOutput[MSG_SIZ];
14865 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
14867 if (moveNumber == forwardMostMove - 1 ||
14868 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14870 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
14872 if (strchr(cpThinkOutput, '\n')) {
14873 *strchr(cpThinkOutput, '\n') = NULLCHAR;
14876 *cpThinkOutput = NULLCHAR;
14879 /* [AS] Hide thinking from human user */
14880 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
14881 *cpThinkOutput = NULLCHAR;
14882 if( thinkOutput[0] != NULLCHAR ) {
14885 for( i=0; i<=hiddenThinkOutputState; i++ ) {
14886 cpThinkOutput[i] = '.';
14888 cpThinkOutput[i] = NULLCHAR;
14889 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
14893 if (moveNumber == forwardMostMove - 1 &&
14894 gameInfo.resultDetails != NULL) {
14895 if (gameInfo.resultDetails[0] == NULLCHAR) {
14896 snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
14898 snprintf(res, MSG_SIZ, " {%s} %s",
14899 T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
14905 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14906 DisplayMessage(res, cpThinkOutput);
14908 snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
14909 WhiteOnMove(moveNumber) ? " " : ".. ",
14910 parseList[moveNumber], res);
14911 DisplayMessage(message, cpThinkOutput);
14916 DisplayComment(moveNumber, text)
14920 char title[MSG_SIZ];
14921 char buf[8000]; // comment can be long!
14924 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14925 safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
14927 snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
14928 WhiteOnMove(moveNumber) ? " " : ".. ",
14929 parseList[moveNumber]);
14931 // [HGM] PV info: display PV info together with (or as) comment
14932 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14933 if(text == NULL) text = "";
14934 score = pvInfoList[moveNumber].score;
14935 snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14936 depth, (pvInfoList[moveNumber].time+50)/100, text);
14939 if (text != NULL && (appData.autoDisplayComment || commentUp))
14940 CommentPopUp(title, text);
14943 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14944 * might be busy thinking or pondering. It can be omitted if your
14945 * gnuchess is configured to stop thinking immediately on any user
14946 * input. However, that gnuchess feature depends on the FIONREAD
14947 * ioctl, which does not work properly on some flavors of Unix.
14951 ChessProgramState *cps;
14954 if (!cps->useSigint) return;
14955 if (appData.noChessProgram || (cps->pr == NoProc)) return;
14956 switch (gameMode) {
14957 case MachinePlaysWhite:
14958 case MachinePlaysBlack:
14959 case TwoMachinesPlay:
14960 case IcsPlayingWhite:
14961 case IcsPlayingBlack:
14964 /* Skip if we know it isn't thinking */
14965 if (!cps->maybeThinking) return;
14966 if (appData.debugMode)
14967 fprintf(debugFP, "Interrupting %s\n", cps->which);
14968 InterruptChildProcess(cps->pr);
14969 cps->maybeThinking = FALSE;
14974 #endif /*ATTENTION*/
14980 if (whiteTimeRemaining <= 0) {
14983 if (appData.icsActive) {
14984 if (appData.autoCallFlag &&
14985 gameMode == IcsPlayingBlack && !blackFlag) {
14986 SendToICS(ics_prefix);
14987 SendToICS("flag\n");
14991 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14993 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14994 if (appData.autoCallFlag) {
14995 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
15002 if (blackTimeRemaining <= 0) {
15005 if (appData.icsActive) {
15006 if (appData.autoCallFlag &&
15007 gameMode == IcsPlayingWhite && !whiteFlag) {
15008 SendToICS(ics_prefix);
15009 SendToICS("flag\n");
15013 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15015 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15016 if (appData.autoCallFlag) {
15017 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15030 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15031 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15034 * add time to clocks when time control is achieved ([HGM] now also used for increment)
15036 if ( !WhiteOnMove(forwardMostMove) ) {
15037 /* White made time control */
15038 lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15039 whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15040 /* [HGM] time odds: correct new time quota for time odds! */
15041 / WhitePlayer()->timeOdds;
15042 lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15044 lastBlack -= blackTimeRemaining;
15045 /* Black made time control */
15046 blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15047 / WhitePlayer()->other->timeOdds;
15048 lastWhite = whiteTimeRemaining;
15053 DisplayBothClocks()
15055 int wom = gameMode == EditPosition ?
15056 !blackPlaysFirst : WhiteOnMove(currentMove);
15057 DisplayWhiteClock(whiteTimeRemaining, wom);
15058 DisplayBlackClock(blackTimeRemaining, !wom);
15062 /* Timekeeping seems to be a portability nightmare. I think everyone
15063 has ftime(), but I'm really not sure, so I'm including some ifdefs
15064 to use other calls if you don't. Clocks will be less accurate if
15065 you have neither ftime nor gettimeofday.
15068 /* VS 2008 requires the #include outside of the function */
15069 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15070 #include <sys/timeb.h>
15073 /* Get the current time as a TimeMark */
15078 #if HAVE_GETTIMEOFDAY
15080 struct timeval timeVal;
15081 struct timezone timeZone;
15083 gettimeofday(&timeVal, &timeZone);
15084 tm->sec = (long) timeVal.tv_sec;
15085 tm->ms = (int) (timeVal.tv_usec / 1000L);
15087 #else /*!HAVE_GETTIMEOFDAY*/
15090 // include <sys/timeb.h> / moved to just above start of function
15091 struct timeb timeB;
15094 tm->sec = (long) timeB.time;
15095 tm->ms = (int) timeB.millitm;
15097 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15098 tm->sec = (long) time(NULL);
15104 /* Return the difference in milliseconds between two
15105 time marks. We assume the difference will fit in a long!
15108 SubtractTimeMarks(tm2, tm1)
15109 TimeMark *tm2, *tm1;
15111 return 1000L*(tm2->sec - tm1->sec) +
15112 (long) (tm2->ms - tm1->ms);
15117 * Code to manage the game clocks.
15119 * In tournament play, black starts the clock and then white makes a move.
15120 * We give the human user a slight advantage if he is playing white---the
15121 * clocks don't run until he makes his first move, so it takes zero time.
15122 * Also, we don't account for network lag, so we could get out of sync
15123 * with GNU Chess's clock -- but then, referees are always right.
15126 static TimeMark tickStartTM;
15127 static long intendedTickLength;
15130 NextTickLength(timeRemaining)
15131 long timeRemaining;
15133 long nominalTickLength, nextTickLength;
15135 if (timeRemaining > 0L && timeRemaining <= 10000L)
15136 nominalTickLength = 100L;
15138 nominalTickLength = 1000L;
15139 nextTickLength = timeRemaining % nominalTickLength;
15140 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15142 return nextTickLength;
15145 /* Adjust clock one minute up or down */
15147 AdjustClock(Boolean which, int dir)
15149 if(which) blackTimeRemaining += 60000*dir;
15150 else whiteTimeRemaining += 60000*dir;
15151 DisplayBothClocks();
15154 /* Stop clocks and reset to a fresh time control */
15158 (void) StopClockTimer();
15159 if (appData.icsActive) {
15160 whiteTimeRemaining = blackTimeRemaining = 0;
15161 } else if (searchTime) {
15162 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15163 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15164 } else { /* [HGM] correct new time quote for time odds */
15165 whiteTC = blackTC = fullTimeControlString;
15166 whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15167 blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15169 if (whiteFlag || blackFlag) {
15171 whiteFlag = blackFlag = FALSE;
15173 lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15174 DisplayBothClocks();
15177 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15179 /* Decrement running clock by amount of time that has passed */
15183 long timeRemaining;
15184 long lastTickLength, fudge;
15187 if (!appData.clockMode) return;
15188 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15192 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15194 /* Fudge if we woke up a little too soon */
15195 fudge = intendedTickLength - lastTickLength;
15196 if (fudge < 0 || fudge > FUDGE) fudge = 0;
15198 if (WhiteOnMove(forwardMostMove)) {
15199 if(whiteNPS >= 0) lastTickLength = 0;
15200 timeRemaining = whiteTimeRemaining -= lastTickLength;
15201 if(timeRemaining < 0 && !appData.icsActive) {
15202 GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15203 if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15204 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15205 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15208 DisplayWhiteClock(whiteTimeRemaining - fudge,
15209 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15211 if(blackNPS >= 0) lastTickLength = 0;
15212 timeRemaining = blackTimeRemaining -= lastTickLength;
15213 if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15214 GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15216 blackStartMove = forwardMostMove;
15217 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
15220 DisplayBlackClock(blackTimeRemaining - fudge,
15221 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15223 if (CheckFlags()) return;
15226 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
15227 StartClockTimer(intendedTickLength);
15229 /* if the time remaining has fallen below the alarm threshold, sound the
15230 * alarm. if the alarm has sounded and (due to a takeback or time control
15231 * with increment) the time remaining has increased to a level above the
15232 * threshold, reset the alarm so it can sound again.
15235 if (appData.icsActive && appData.icsAlarm) {
15237 /* make sure we are dealing with the user's clock */
15238 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
15239 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
15242 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
15243 alarmSounded = FALSE;
15244 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
15246 alarmSounded = TRUE;
15252 /* A player has just moved, so stop the previously running
15253 clock and (if in clock mode) start the other one.
15254 We redisplay both clocks in case we're in ICS mode, because
15255 ICS gives us an update to both clocks after every move.
15256 Note that this routine is called *after* forwardMostMove
15257 is updated, so the last fractional tick must be subtracted
15258 from the color that is *not* on move now.
15261 SwitchClocks(int newMoveNr)
15263 long lastTickLength;
15265 int flagged = FALSE;
15269 if (StopClockTimer() && appData.clockMode) {
15270 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15271 if (!WhiteOnMove(forwardMostMove)) {
15272 if(blackNPS >= 0) lastTickLength = 0;
15273 blackTimeRemaining -= lastTickLength;
15274 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15275 // if(pvInfoList[forwardMostMove].time == -1)
15276 pvInfoList[forwardMostMove].time = // use GUI time
15277 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
15279 if(whiteNPS >= 0) lastTickLength = 0;
15280 whiteTimeRemaining -= lastTickLength;
15281 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15282 // if(pvInfoList[forwardMostMove].time == -1)
15283 pvInfoList[forwardMostMove].time =
15284 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
15286 flagged = CheckFlags();
15288 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
15289 CheckTimeControl();
15291 if (flagged || !appData.clockMode) return;
15293 switch (gameMode) {
15294 case MachinePlaysBlack:
15295 case MachinePlaysWhite:
15296 case BeginningOfGame:
15297 if (pausing) return;
15301 case PlayFromGameFile:
15309 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
15310 if(WhiteOnMove(forwardMostMove))
15311 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15312 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15316 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15317 whiteTimeRemaining : blackTimeRemaining);
15318 StartClockTimer(intendedTickLength);
15322 /* Stop both clocks */
15326 long lastTickLength;
15329 if (!StopClockTimer()) return;
15330 if (!appData.clockMode) return;
15334 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15335 if (WhiteOnMove(forwardMostMove)) {
15336 if(whiteNPS >= 0) lastTickLength = 0;
15337 whiteTimeRemaining -= lastTickLength;
15338 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
15340 if(blackNPS >= 0) lastTickLength = 0;
15341 blackTimeRemaining -= lastTickLength;
15342 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
15347 /* Start clock of player on move. Time may have been reset, so
15348 if clock is already running, stop and restart it. */
15352 (void) StopClockTimer(); /* in case it was running already */
15353 DisplayBothClocks();
15354 if (CheckFlags()) return;
15356 if (!appData.clockMode) return;
15357 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
15359 GetTimeMark(&tickStartTM);
15360 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15361 whiteTimeRemaining : blackTimeRemaining);
15363 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
15364 whiteNPS = blackNPS = -1;
15365 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
15366 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
15367 whiteNPS = first.nps;
15368 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
15369 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
15370 blackNPS = first.nps;
15371 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
15372 whiteNPS = second.nps;
15373 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
15374 blackNPS = second.nps;
15375 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
15377 StartClockTimer(intendedTickLength);
15384 long second, minute, hour, day;
15386 static char buf[32];
15388 if (ms > 0 && ms <= 9900) {
15389 /* convert milliseconds to tenths, rounding up */
15390 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
15392 snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
15396 /* convert milliseconds to seconds, rounding up */
15397 /* use floating point to avoid strangeness of integer division
15398 with negative dividends on many machines */
15399 second = (long) floor(((double) (ms + 999L)) / 1000.0);
15406 day = second / (60 * 60 * 24);
15407 second = second % (60 * 60 * 24);
15408 hour = second / (60 * 60);
15409 second = second % (60 * 60);
15410 minute = second / 60;
15411 second = second % 60;
15414 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
15415 sign, day, hour, minute, second);
15417 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
15419 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
15426 * This is necessary because some C libraries aren't ANSI C compliant yet.
15429 StrStr(string, match)
15430 char *string, *match;
15434 length = strlen(match);
15436 for (i = strlen(string) - length; i >= 0; i--, string++)
15437 if (!strncmp(match, string, length))
15444 StrCaseStr(string, match)
15445 char *string, *match;
15449 length = strlen(match);
15451 for (i = strlen(string) - length; i >= 0; i--, string++) {
15452 for (j = 0; j < length; j++) {
15453 if (ToLower(match[j]) != ToLower(string[j]))
15456 if (j == length) return string;
15470 c1 = ToLower(*s1++);
15471 c2 = ToLower(*s2++);
15472 if (c1 > c2) return 1;
15473 if (c1 < c2) return -1;
15474 if (c1 == NULLCHAR) return 0;
15483 return isupper(c) ? tolower(c) : c;
15491 return islower(c) ? toupper(c) : c;
15493 #endif /* !_amigados */
15501 if ((ret = (char *) malloc(strlen(s) + 1)))
15503 safeStrCpy(ret, s, strlen(s)+1);
15509 StrSavePtr(s, savePtr)
15510 char *s, **savePtr;
15515 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
15516 safeStrCpy(*savePtr, s, strlen(s)+1);
15528 clock = time((time_t *)NULL);
15529 tm = localtime(&clock);
15530 snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
15531 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
15532 return StrSave(buf);
15537 PositionToFEN(move, overrideCastling)
15539 char *overrideCastling;
15541 int i, j, fromX, fromY, toX, toY;
15548 whiteToPlay = (gameMode == EditPosition) ?
15549 !blackPlaysFirst : (move % 2 == 0);
15552 /* Piece placement data */
15553 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15555 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15556 if (boards[move][i][j] == EmptySquare) {
15558 } else { ChessSquare piece = boards[move][i][j];
15559 if (emptycount > 0) {
15560 if(emptycount<10) /* [HGM] can be >= 10 */
15561 *p++ = '0' + emptycount;
15562 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15565 if(PieceToChar(piece) == '+') {
15566 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
15568 piece = (ChessSquare)(DEMOTED piece);
15570 *p++ = PieceToChar(piece);
15572 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
15573 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
15578 if (emptycount > 0) {
15579 if(emptycount<10) /* [HGM] can be >= 10 */
15580 *p++ = '0' + emptycount;
15581 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15588 /* [HGM] print Crazyhouse or Shogi holdings */
15589 if( gameInfo.holdingsWidth ) {
15590 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
15592 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
15593 piece = boards[move][i][BOARD_WIDTH-1];
15594 if( piece != EmptySquare )
15595 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
15596 *p++ = PieceToChar(piece);
15598 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
15599 piece = boards[move][BOARD_HEIGHT-i-1][0];
15600 if( piece != EmptySquare )
15601 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
15602 *p++ = PieceToChar(piece);
15605 if( q == p ) *p++ = '-';
15611 *p++ = whiteToPlay ? 'w' : 'b';
15614 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
15615 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
15617 if(nrCastlingRights) {
15619 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
15620 /* [HGM] write directly from rights */
15621 if(boards[move][CASTLING][2] != NoRights &&
15622 boards[move][CASTLING][0] != NoRights )
15623 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
15624 if(boards[move][CASTLING][2] != NoRights &&
15625 boards[move][CASTLING][1] != NoRights )
15626 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
15627 if(boards[move][CASTLING][5] != NoRights &&
15628 boards[move][CASTLING][3] != NoRights )
15629 *p++ = boards[move][CASTLING][3] + AAA;
15630 if(boards[move][CASTLING][5] != NoRights &&
15631 boards[move][CASTLING][4] != NoRights )
15632 *p++ = boards[move][CASTLING][4] + AAA;
15635 /* [HGM] write true castling rights */
15636 if( nrCastlingRights == 6 ) {
15637 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
15638 boards[move][CASTLING][2] != NoRights ) *p++ = 'K';
15639 if(boards[move][CASTLING][1] == BOARD_LEFT &&
15640 boards[move][CASTLING][2] != NoRights ) *p++ = 'Q';
15641 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
15642 boards[move][CASTLING][5] != NoRights ) *p++ = 'k';
15643 if(boards[move][CASTLING][4] == BOARD_LEFT &&
15644 boards[move][CASTLING][5] != NoRights ) *p++ = 'q';
15647 if (q == p) *p++ = '-'; /* No castling rights */
15651 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
15652 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15653 /* En passant target square */
15654 if (move > backwardMostMove) {
15655 fromX = moveList[move - 1][0] - AAA;
15656 fromY = moveList[move - 1][1] - ONE;
15657 toX = moveList[move - 1][2] - AAA;
15658 toY = moveList[move - 1][3] - ONE;
15659 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
15660 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
15661 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
15663 /* 2-square pawn move just happened */
15665 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15669 } else if(move == backwardMostMove) {
15670 // [HGM] perhaps we should always do it like this, and forget the above?
15671 if((signed char)boards[move][EP_STATUS] >= 0) {
15672 *p++ = boards[move][EP_STATUS] + AAA;
15673 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15684 /* [HGM] find reversible plies */
15685 { int i = 0, j=move;
15687 if (appData.debugMode) { int k;
15688 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
15689 for(k=backwardMostMove; k<=forwardMostMove; k++)
15690 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
15694 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
15695 if( j == backwardMostMove ) i += initialRulePlies;
15696 sprintf(p, "%d ", i);
15697 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
15699 /* Fullmove number */
15700 sprintf(p, "%d", (move / 2) + 1);
15702 return StrSave(buf);
15706 ParseFEN(board, blackPlaysFirst, fen)
15708 int *blackPlaysFirst;
15718 /* [HGM] by default clear Crazyhouse holdings, if present */
15719 if(gameInfo.holdingsWidth) {
15720 for(i=0; i<BOARD_HEIGHT; i++) {
15721 board[i][0] = EmptySquare; /* black holdings */
15722 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
15723 board[i][1] = (ChessSquare) 0; /* black counts */
15724 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
15728 /* Piece placement data */
15729 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15732 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
15733 if (*p == '/') p++;
15734 emptycount = gameInfo.boardWidth - j;
15735 while (emptycount--)
15736 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15738 #if(BOARD_FILES >= 10)
15739 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
15740 p++; emptycount=10;
15741 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15742 while (emptycount--)
15743 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15745 } else if (isdigit(*p)) {
15746 emptycount = *p++ - '0';
15747 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
15748 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15749 while (emptycount--)
15750 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15751 } else if (*p == '+' || isalpha(*p)) {
15752 if (j >= gameInfo.boardWidth) return FALSE;
15754 piece = CharToPiece(*++p);
15755 if(piece == EmptySquare) return FALSE; /* unknown piece */
15756 piece = (ChessSquare) (PROMOTED piece ); p++;
15757 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
15758 } else piece = CharToPiece(*p++);
15760 if(piece==EmptySquare) return FALSE; /* unknown piece */
15761 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
15762 piece = (ChessSquare) (PROMOTED piece);
15763 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
15766 board[i][(j++)+gameInfo.holdingsWidth] = piece;
15772 while (*p == '/' || *p == ' ') p++;
15774 /* [HGM] look for Crazyhouse holdings here */
15775 while(*p==' ') p++;
15776 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
15778 if(*p == '-' ) p++; /* empty holdings */ else {
15779 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
15780 /* if we would allow FEN reading to set board size, we would */
15781 /* have to add holdings and shift the board read so far here */
15782 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
15784 if((int) piece >= (int) BlackPawn ) {
15785 i = (int)piece - (int)BlackPawn;
15786 i = PieceToNumber((ChessSquare)i);
15787 if( i >= gameInfo.holdingsSize ) return FALSE;
15788 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
15789 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
15791 i = (int)piece - (int)WhitePawn;
15792 i = PieceToNumber((ChessSquare)i);
15793 if( i >= gameInfo.holdingsSize ) return FALSE;
15794 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
15795 board[i][BOARD_WIDTH-2]++; /* black holdings */
15802 while(*p == ' ') p++;
15806 if(appData.colorNickNames) {
15807 if( c == appData.colorNickNames[0] ) c = 'w'; else
15808 if( c == appData.colorNickNames[1] ) c = 'b';
15812 *blackPlaysFirst = FALSE;
15815 *blackPlaysFirst = TRUE;
15821 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
15822 /* return the extra info in global variiables */
15824 /* set defaults in case FEN is incomplete */
15825 board[EP_STATUS] = EP_UNKNOWN;
15826 for(i=0; i<nrCastlingRights; i++ ) {
15827 board[CASTLING][i] =
15828 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
15829 } /* assume possible unless obviously impossible */
15830 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
15831 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
15832 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
15833 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
15834 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
15835 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
15836 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
15837 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
15840 while(*p==' ') p++;
15841 if(nrCastlingRights) {
15842 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
15843 /* castling indicator present, so default becomes no castlings */
15844 for(i=0; i<nrCastlingRights; i++ ) {
15845 board[CASTLING][i] = NoRights;
15848 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
15849 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
15850 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
15851 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
15852 char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
15854 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
15855 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
15856 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
15858 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
15859 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
15860 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
15861 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
15862 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
15863 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
15866 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
15867 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
15868 board[CASTLING][2] = whiteKingFile;
15871 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
15872 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
15873 board[CASTLING][2] = whiteKingFile;
15876 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
15877 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
15878 board[CASTLING][5] = blackKingFile;
15881 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
15882 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
15883 board[CASTLING][5] = blackKingFile;
15886 default: /* FRC castlings */
15887 if(c >= 'a') { /* black rights */
15888 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15889 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
15890 if(i == BOARD_RGHT) break;
15891 board[CASTLING][5] = i;
15893 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
15894 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
15896 board[CASTLING][3] = c;
15898 board[CASTLING][4] = c;
15899 } else { /* white rights */
15900 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15901 if(board[0][i] == WhiteKing) break;
15902 if(i == BOARD_RGHT) break;
15903 board[CASTLING][2] = i;
15904 c -= AAA - 'a' + 'A';
15905 if(board[0][c] >= WhiteKing) break;
15907 board[CASTLING][0] = c;
15909 board[CASTLING][1] = c;
15913 for(i=0; i<nrCastlingRights; i++)
15914 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
15915 if (appData.debugMode) {
15916 fprintf(debugFP, "FEN castling rights:");
15917 for(i=0; i<nrCastlingRights; i++)
15918 fprintf(debugFP, " %d", board[CASTLING][i]);
15919 fprintf(debugFP, "\n");
15922 while(*p==' ') p++;
15925 /* read e.p. field in games that know e.p. capture */
15926 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
15927 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15929 p++; board[EP_STATUS] = EP_NONE;
15931 char c = *p++ - AAA;
15933 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
15934 if(*p >= '0' && *p <='9') p++;
15935 board[EP_STATUS] = c;
15940 if(sscanf(p, "%d", &i) == 1) {
15941 FENrulePlies = i; /* 50-move ply counter */
15942 /* (The move number is still ignored) */
15949 EditPositionPasteFEN(char *fen)
15952 Board initial_position;
15954 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15955 DisplayError(_("Bad FEN position in clipboard"), 0);
15958 int savedBlackPlaysFirst = blackPlaysFirst;
15959 EditPositionEvent();
15960 blackPlaysFirst = savedBlackPlaysFirst;
15961 CopyBoard(boards[0], initial_position);
15962 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15963 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15964 DisplayBothClocks();
15965 DrawPosition(FALSE, boards[currentMove]);
15970 static char cseq[12] = "\\ ";
15972 Boolean set_cont_sequence(char *new_seq)
15977 // handle bad attempts to set the sequence
15979 return 0; // acceptable error - no debug
15981 len = strlen(new_seq);
15982 ret = (len > 0) && (len < sizeof(cseq));
15984 safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
15985 else if (appData.debugMode)
15986 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
15991 reformat a source message so words don't cross the width boundary. internal
15992 newlines are not removed. returns the wrapped size (no null character unless
15993 included in source message). If dest is NULL, only calculate the size required
15994 for the dest buffer. lp argument indicats line position upon entry, and it's
15995 passed back upon exit.
15997 int wrap(char *dest, char *src, int count, int width, int *lp)
15999 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
16001 cseq_len = strlen(cseq);
16002 old_line = line = *lp;
16003 ansi = len = clen = 0;
16005 for (i=0; i < count; i++)
16007 if (src[i] == '\033')
16010 // if we hit the width, back up
16011 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16013 // store i & len in case the word is too long
16014 old_i = i, old_len = len;
16016 // find the end of the last word
16017 while (i && src[i] != ' ' && src[i] != '\n')
16023 // word too long? restore i & len before splitting it
16024 if ((old_i-i+clen) >= width)
16031 if (i && src[i-1] == ' ')
16034 if (src[i] != ' ' && src[i] != '\n')
16041 // now append the newline and continuation sequence
16046 strncpy(dest+len, cseq, cseq_len);
16054 dest[len] = src[i];
16058 if (src[i] == '\n')
16063 if (dest && appData.debugMode)
16065 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16066 count, width, line, len, *lp);
16067 show_bytes(debugFP, src, count);
16068 fprintf(debugFP, "\ndest: ");
16069 show_bytes(debugFP, dest, len);
16070 fprintf(debugFP, "\n");
16072 *lp = dest ? line : old_line;
16077 // [HGM] vari: routines for shelving variations
16080 PushInner(int firstMove, int lastMove)
16082 int i, j, nrMoves = lastMove - firstMove;
16084 // push current tail of game on stack
16085 savedResult[storedGames] = gameInfo.result;
16086 savedDetails[storedGames] = gameInfo.resultDetails;
16087 gameInfo.resultDetails = NULL;
16088 savedFirst[storedGames] = firstMove;
16089 savedLast [storedGames] = lastMove;
16090 savedFramePtr[storedGames] = framePtr;
16091 framePtr -= nrMoves; // reserve space for the boards
16092 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16093 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16094 for(j=0; j<MOVE_LEN; j++)
16095 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16096 for(j=0; j<2*MOVE_LEN; j++)
16097 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16098 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16099 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16100 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16101 pvInfoList[firstMove+i-1].depth = 0;
16102 commentList[framePtr+i] = commentList[firstMove+i];
16103 commentList[firstMove+i] = NULL;
16107 forwardMostMove = firstMove; // truncate game so we can start variation
16111 PushTail(int firstMove, int lastMove)
16113 if(appData.icsActive) { // only in local mode
16114 forwardMostMove = currentMove; // mimic old ICS behavior
16117 if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16119 PushInner(firstMove, lastMove);
16120 if(storedGames == 1) GreyRevert(FALSE);
16124 PopInner(Boolean annotate)
16127 char buf[8000], moveBuf[20];
16130 ToNrEvent(savedFirst[storedGames]); // sets currentMove
16131 nrMoves = savedLast[storedGames] - currentMove;
16134 if(!WhiteOnMove(currentMove))
16135 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16136 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16137 for(i=currentMove; i<forwardMostMove; i++) {
16139 snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16140 else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16141 strcat(buf, moveBuf);
16142 if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16143 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16147 for(i=1; i<=nrMoves; i++) { // copy last variation back
16148 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16149 for(j=0; j<MOVE_LEN; j++)
16150 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16151 for(j=0; j<2*MOVE_LEN; j++)
16152 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16153 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16154 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16155 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16156 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16157 commentList[currentMove+i] = commentList[framePtr+i];
16158 commentList[framePtr+i] = NULL;
16160 if(annotate) AppendComment(currentMove+1, buf, FALSE);
16161 framePtr = savedFramePtr[storedGames];
16162 gameInfo.result = savedResult[storedGames];
16163 if(gameInfo.resultDetails != NULL) {
16164 free(gameInfo.resultDetails);
16166 gameInfo.resultDetails = savedDetails[storedGames];
16167 forwardMostMove = currentMove + nrMoves;
16171 PopTail(Boolean annotate)
16173 if(appData.icsActive) return FALSE; // only in local mode
16174 if(!storedGames) return FALSE; // sanity
16175 CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16177 PopInner(annotate);
16179 if(storedGames == 0) GreyRevert(TRUE);
16185 { // remove all shelved variations
16187 for(i=0; i<storedGames; i++) {
16188 if(savedDetails[i])
16189 free(savedDetails[i]);
16190 savedDetails[i] = NULL;
16192 for(i=framePtr; i<MAX_MOVES; i++) {
16193 if(commentList[i]) free(commentList[i]);
16194 commentList[i] = NULL;
16196 framePtr = MAX_MOVES-1;
16201 LoadVariation(int index, char *text)
16202 { // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16203 char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16204 int level = 0, move;
16206 if(gameMode != EditGame && gameMode != AnalyzeMode) return;
16207 // first find outermost bracketing variation
16208 while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16209 if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16210 if(*p == '{') wait = '}'; else
16211 if(*p == '[') wait = ']'; else
16212 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16213 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16215 if(*p == wait) wait = NULLCHAR; // closing ]} found
16218 if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16219 if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16220 end[1] = NULLCHAR; // clip off comment beyond variation
16221 ToNrEvent(currentMove-1);
16222 PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
16223 // kludge: use ParsePV() to append variation to game
16224 move = currentMove;
16225 ParsePV(start, TRUE);
16226 forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
16227 ClearPremoveHighlights();
16229 ToNrEvent(currentMove+1);