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 NULL; // 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) DisplayFatalError(ranking ? ranking : buf, 0, 0); else {
10068 matchMode = FALSE; appData.matchGames = matchGame = roundNr = 0;
10069 DisplayNote(ranking ? ranking : buf);
10071 if(ranking) free(ranking);
10075 /* Assumes program was just initialized (initString sent).
10076 Leaves program in force mode. */
10078 FeedMovesToProgram(cps, upto)
10079 ChessProgramState *cps;
10084 if (appData.debugMode)
10085 fprintf(debugFP, "Feeding %smoves %d through %d to %s chess program\n",
10086 startedFromSetupPosition ? "position and " : "",
10087 backwardMostMove, upto, cps->which);
10088 if(currentlyInitializedVariant != gameInfo.variant) {
10090 // [HGM] variantswitch: make engine aware of new variant
10091 if(cps->protocolVersion > 1 && StrStr(cps->variants, VariantName(gameInfo.variant)) == NULL)
10092 return; // [HGM] refrain from feeding moves altogether if variant is unsupported!
10093 snprintf(buf, MSG_SIZ, "variant %s\n", VariantName(gameInfo.variant));
10094 SendToProgram(buf, cps);
10095 currentlyInitializedVariant = gameInfo.variant;
10097 SendToProgram("force\n", cps);
10098 if (startedFromSetupPosition) {
10099 SendBoard(cps, backwardMostMove);
10100 if (appData.debugMode) {
10101 fprintf(debugFP, "feedMoves\n");
10104 for (i = backwardMostMove; i < upto; i++) {
10105 SendMoveToProgram(i, cps);
10111 ResurrectChessProgram()
10113 /* The chess program may have exited.
10114 If so, restart it and feed it all the moves made so far. */
10115 static int doInit = 0;
10117 if (appData.noChessProgram) return 1;
10119 if(matchMode && appData.tourneyFile[0]) { // [HGM] tourney: make sure we get features after engine replacement. (Should we always do this?)
10120 if(WaitForEngine(&first, TwoMachinesEventIfReady)) { doInit = 1; return 0; } // request to do init on next visit
10121 if(!doInit) return 1; // this replaces testing first.pr != NoProc, which is true when we get here, but first time no reason to abort
10122 doInit = 0; // we fell through (first time after starting the engine); make sure it doesn't happen again
10124 if (first.pr != NoProc) return 1;
10125 StartChessProgram(&first);
10127 InitChessProgram(&first, FALSE);
10128 FeedMovesToProgram(&first, currentMove);
10130 if (!first.sendTime) {
10131 /* can't tell gnuchess what its clock should read,
10132 so we bow to its notion. */
10134 timeRemaining[0][currentMove] = whiteTimeRemaining;
10135 timeRemaining[1][currentMove] = blackTimeRemaining;
10138 if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile ||
10139 appData.icsEngineAnalyze) && first.analysisSupport) {
10140 SendToProgram("analyze\n", &first);
10141 first.analyzing = TRUE;
10147 * Button procedures
10150 Reset(redraw, init)
10155 if (appData.debugMode) {
10156 fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
10157 redraw, init, gameMode);
10159 CleanupTail(); // [HGM] vari: delete any stored variations
10160 pausing = pauseExamInvalid = FALSE;
10161 startedFromSetupPosition = blackPlaysFirst = FALSE;
10163 whiteFlag = blackFlag = FALSE;
10164 userOfferedDraw = FALSE;
10165 hintRequested = bookRequested = FALSE;
10166 first.maybeThinking = FALSE;
10167 second.maybeThinking = FALSE;
10168 first.bookSuspend = FALSE; // [HGM] book
10169 second.bookSuspend = FALSE;
10170 thinkOutput[0] = NULLCHAR;
10171 lastHint[0] = NULLCHAR;
10172 ClearGameInfo(&gameInfo);
10173 gameInfo.variant = StringToVariant(appData.variant);
10174 ics_user_moved = ics_clock_paused = FALSE;
10175 ics_getting_history = H_FALSE;
10177 white_holding[0] = black_holding[0] = NULLCHAR;
10178 ClearProgramStats();
10179 opponentKibitzes = FALSE; // [HGM] kibitz: do not reserve space in engine-output window in zippy mode
10183 flipView = appData.flipView;
10184 ClearPremoveHighlights();
10185 gotPremove = FALSE;
10186 alarmSounded = FALSE;
10188 GameEnds(EndOfFile, NULL, GE_PLAYER);
10189 if(appData.serverMovesName != NULL) {
10190 /* [HGM] prepare to make moves file for broadcasting */
10191 clock_t t = clock();
10192 if(serverMoves != NULL) fclose(serverMoves);
10193 serverMoves = fopen(appData.serverMovesName, "r");
10194 if(serverMoves != NULL) {
10195 fclose(serverMoves);
10196 /* delay 15 sec before overwriting, so all clients can see end */
10197 while(clock()-t < appData.serverPause*CLOCKS_PER_SEC);
10199 serverMoves = fopen(appData.serverMovesName, "w");
10203 gameMode = BeginningOfGame;
10205 if(appData.icsActive) gameInfo.variant = VariantNormal;
10206 currentMove = forwardMostMove = backwardMostMove = 0;
10207 InitPosition(redraw);
10208 for (i = 0; i < MAX_MOVES; i++) {
10209 if (commentList[i] != NULL) {
10210 free(commentList[i]);
10211 commentList[i] = NULL;
10215 timeRemaining[0][0] = whiteTimeRemaining;
10216 timeRemaining[1][0] = blackTimeRemaining;
10218 if (first.pr == NULL) {
10219 StartChessProgram(&first);
10222 InitChessProgram(&first, startedFromSetupPosition);
10225 DisplayMessage("", "");
10226 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
10227 lastSavedGame = 0; // [HGM] save: make sure next game counts as unsaved
10234 if (!AutoPlayOneMove())
10236 if (matchMode || appData.timeDelay == 0)
10238 if (appData.timeDelay < 0)
10240 StartLoadGameTimer((long)(1000.0 * appData.timeDelay));
10249 int fromX, fromY, toX, toY;
10251 if (appData.debugMode) {
10252 fprintf(debugFP, "AutoPlayOneMove(): current %d\n", currentMove);
10255 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile)
10258 if (gameMode == AnalyzeFile && currentMove > backwardMostMove) {
10259 pvInfoList[currentMove].depth = programStats.depth;
10260 pvInfoList[currentMove].score = programStats.score;
10261 pvInfoList[currentMove].time = 0;
10262 if(currentMove < forwardMostMove) AppendComment(currentMove+1, lastPV[0], 2);
10265 if (currentMove >= forwardMostMove) {
10266 if(gameMode == AnalyzeFile) { ExitAnalyzeMode(); SendToProgram("force\n", &first); }
10267 gameMode = EditGame;
10270 /* [AS] Clear current move marker at the end of a game */
10271 /* HistorySet(parseList, backwardMostMove, forwardMostMove, -1); */
10276 toX = moveList[currentMove][2] - AAA;
10277 toY = moveList[currentMove][3] - ONE;
10279 if (moveList[currentMove][1] == '@') {
10280 if (appData.highlightLastMove) {
10281 SetHighlights(-1, -1, toX, toY);
10284 fromX = moveList[currentMove][0] - AAA;
10285 fromY = moveList[currentMove][1] - ONE;
10287 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove); /* [AS] */
10289 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
10291 if (appData.highlightLastMove) {
10292 SetHighlights(fromX, fromY, toX, toY);
10295 DisplayMove(currentMove);
10296 SendMoveToProgram(currentMove++, &first);
10297 DisplayBothClocks();
10298 DrawPosition(FALSE, boards[currentMove]);
10299 // [HGM] PV info: always display, routine tests if empty
10300 DisplayComment(currentMove - 1, commentList[currentMove]);
10306 LoadGameOneMove(readAhead)
10307 ChessMove readAhead;
10309 int fromX = 0, fromY = 0, toX = 0, toY = 0, done;
10310 char promoChar = NULLCHAR;
10311 ChessMove moveType;
10312 char move[MSG_SIZ];
10315 if (gameMode != PlayFromGameFile && gameMode != AnalyzeFile &&
10316 gameMode != AnalyzeMode && gameMode != Training) {
10321 yyboardindex = forwardMostMove;
10322 if (readAhead != EndOfFile) {
10323 moveType = readAhead;
10325 if (gameFileFP == NULL)
10327 moveType = (ChessMove) Myylex();
10331 switch (moveType) {
10333 if (appData.debugMode)
10334 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10337 /* append the comment but don't display it */
10338 AppendComment(currentMove, p, FALSE);
10341 case WhiteCapturesEnPassant:
10342 case BlackCapturesEnPassant:
10343 case WhitePromotion:
10344 case BlackPromotion:
10345 case WhiteNonPromotion:
10346 case BlackNonPromotion:
10348 case WhiteKingSideCastle:
10349 case WhiteQueenSideCastle:
10350 case BlackKingSideCastle:
10351 case BlackQueenSideCastle:
10352 case WhiteKingSideCastleWild:
10353 case WhiteQueenSideCastleWild:
10354 case BlackKingSideCastleWild:
10355 case BlackQueenSideCastleWild:
10357 case WhiteHSideCastleFR:
10358 case WhiteASideCastleFR:
10359 case BlackHSideCastleFR:
10360 case BlackASideCastleFR:
10362 if (appData.debugMode)
10363 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10364 fromX = currentMoveString[0] - AAA;
10365 fromY = currentMoveString[1] - ONE;
10366 toX = currentMoveString[2] - AAA;
10367 toY = currentMoveString[3] - ONE;
10368 promoChar = currentMoveString[4];
10373 if (appData.debugMode)
10374 fprintf(debugFP, "Parsed %s into %s\n", yy_text, currentMoveString);
10375 fromX = moveType == WhiteDrop ?
10376 (int) CharToPiece(ToUpper(currentMoveString[0])) :
10377 (int) CharToPiece(ToLower(currentMoveString[0]));
10379 toX = currentMoveString[2] - AAA;
10380 toY = currentMoveString[3] - ONE;
10386 case GameUnfinished:
10387 if (appData.debugMode)
10388 fprintf(debugFP, "Parsed game end: %s\n", yy_text);
10389 p = strchr(yy_text, '{');
10390 if (p == NULL) p = strchr(yy_text, '(');
10393 if (p[0] == '0' || p[0] == '1' || p[0] == '*') p = "";
10395 q = strchr(p, *p == '{' ? '}' : ')');
10396 if (q != NULL) *q = NULLCHAR;
10399 while(q = strchr(p, '\n')) *q = ' '; // [HGM] crush linefeeds in result message
10400 GameEnds(moveType, p, GE_FILE);
10402 if (cmailMsgLoaded) {
10404 flipView = WhiteOnMove(currentMove);
10405 if (moveType == GameUnfinished) flipView = !flipView;
10406 if (appData.debugMode)
10407 fprintf(debugFP, "Setting flipView to %d\n", flipView) ;
10412 if (appData.debugMode)
10413 fprintf(debugFP, "Parser hit end of file\n");
10414 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10420 if (WhiteOnMove(currentMove)) {
10421 GameEnds(BlackWins, "Black mates", GE_FILE);
10423 GameEnds(WhiteWins, "White mates", GE_FILE);
10427 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10433 case MoveNumberOne:
10434 if (lastLoadGameStart == GNUChessGame) {
10435 /* GNUChessGames have numbers, but they aren't move numbers */
10436 if (appData.debugMode)
10437 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10438 yy_text, (int) moveType);
10439 return LoadGameOneMove(EndOfFile); /* tail recursion */
10441 /* else fall thru */
10446 /* Reached start of next game in file */
10447 if (appData.debugMode)
10448 fprintf(debugFP, "Parsed start of next game: %s\n", yy_text);
10449 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10455 if (WhiteOnMove(currentMove)) {
10456 GameEnds(BlackWins, "Black mates", GE_FILE);
10458 GameEnds(WhiteWins, "White mates", GE_FILE);
10462 GameEnds(GameIsDrawn, "Stalemate", GE_FILE);
10468 case PositionDiagram: /* should not happen; ignore */
10469 case ElapsedTime: /* ignore */
10470 case NAG: /* ignore */
10471 if (appData.debugMode)
10472 fprintf(debugFP, "Parser ignoring: '%s' (%d)\n",
10473 yy_text, (int) moveType);
10474 return LoadGameOneMove(EndOfFile); /* tail recursion */
10477 if (appData.testLegality) {
10478 if (appData.debugMode)
10479 fprintf(debugFP, "Parsed IllegalMove: %s\n", yy_text);
10480 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10481 (forwardMostMove / 2) + 1,
10482 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10483 DisplayError(move, 0);
10486 if (appData.debugMode)
10487 fprintf(debugFP, "Parsed %s into IllegalMove %s\n",
10488 yy_text, currentMoveString);
10489 fromX = currentMoveString[0] - AAA;
10490 fromY = currentMoveString[1] - ONE;
10491 toX = currentMoveString[2] - AAA;
10492 toY = currentMoveString[3] - ONE;
10493 promoChar = currentMoveString[4];
10497 case AmbiguousMove:
10498 if (appData.debugMode)
10499 fprintf(debugFP, "Parsed AmbiguousMove: %s\n", yy_text);
10500 snprintf(move, MSG_SIZ, _("Ambiguous move: %d.%s%s"),
10501 (forwardMostMove / 2) + 1,
10502 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10503 DisplayError(move, 0);
10508 case ImpossibleMove:
10509 if (appData.debugMode)
10510 fprintf(debugFP, "Parsed ImpossibleMove (type = %d): %s\n", moveType, yy_text);
10511 snprintf(move, MSG_SIZ, _("Illegal move: %d.%s%s"),
10512 (forwardMostMove / 2) + 1,
10513 WhiteOnMove(forwardMostMove) ? " " : ".. ", yy_text);
10514 DisplayError(move, 0);
10520 if (appData.matchMode || (appData.timeDelay == 0 && !pausing)) {
10521 DrawPosition(FALSE, boards[currentMove]);
10522 DisplayBothClocks();
10523 if (!appData.matchMode) // [HGM] PV info: routine tests if empty
10524 DisplayComment(currentMove - 1, commentList[currentMove]);
10526 (void) StopLoadGameTimer();
10528 cmailOldMove = forwardMostMove;
10531 /* currentMoveString is set as a side-effect of yylex */
10533 thinkOutput[0] = NULLCHAR;
10534 MakeMove(fromX, fromY, toX, toY, promoChar);
10535 currentMove = forwardMostMove;
10540 /* Load the nth game from the given file */
10542 LoadGameFromFile(filename, n, title, useList)
10546 /*Boolean*/ int useList;
10551 if (strcmp(filename, "-") == 0) {
10555 f = fopen(filename, "rb");
10557 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
10558 DisplayError(buf, errno);
10562 if (fseek(f, 0, 0) == -1) {
10563 /* f is not seekable; probably a pipe */
10566 if (useList && n == 0) {
10567 int error = GameListBuild(f);
10569 DisplayError(_("Cannot build game list"), error);
10570 } else if (!ListEmpty(&gameList) &&
10571 ((ListGame *) gameList.tailPred)->number > 1) {
10572 GameListPopUp(f, title);
10579 return LoadGame(f, n, title, FALSE);
10584 MakeRegisteredMove()
10586 int fromX, fromY, toX, toY;
10588 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10589 switch (cmailMoveType[lastLoadGameNumber - 1]) {
10592 if (appData.debugMode)
10593 fprintf(debugFP, "Restoring %s for game %d\n",
10594 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
10596 thinkOutput[0] = NULLCHAR;
10597 safeStrCpy(moveList[currentMove], cmailMove[lastLoadGameNumber - 1], sizeof(moveList[currentMove])/sizeof(moveList[currentMove][0]));
10598 fromX = cmailMove[lastLoadGameNumber - 1][0] - AAA;
10599 fromY = cmailMove[lastLoadGameNumber - 1][1] - ONE;
10600 toX = cmailMove[lastLoadGameNumber - 1][2] - AAA;
10601 toY = cmailMove[lastLoadGameNumber - 1][3] - ONE;
10602 promoChar = cmailMove[lastLoadGameNumber - 1][4];
10603 MakeMove(fromX, fromY, toX, toY, promoChar);
10604 ShowMove(fromX, fromY, toX, toY);
10606 switch (MateTest(boards[currentMove], PosFlags(currentMove)) ) {
10613 if (WhiteOnMove(currentMove)) {
10614 GameEnds(BlackWins, "Black mates", GE_PLAYER);
10616 GameEnds(WhiteWins, "White mates", GE_PLAYER);
10621 GameEnds(GameIsDrawn, "Stalemate", GE_PLAYER);
10628 if (WhiteOnMove(currentMove)) {
10629 GameEnds(BlackWins, "White resigns", GE_PLAYER);
10631 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
10636 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
10647 /* Wrapper around LoadGame for use when a Cmail message is loaded */
10649 CmailLoadGame(f, gameNumber, title, useList)
10657 if (gameNumber > nCmailGames) {
10658 DisplayError(_("No more games in this message"), 0);
10661 if (f == lastLoadGameFP) {
10662 int offset = gameNumber - lastLoadGameNumber;
10664 cmailMsg[0] = NULLCHAR;
10665 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
10666 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
10667 nCmailMovesRegistered--;
10669 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
10670 if (cmailResult[lastLoadGameNumber - 1] == CMAIL_NEW_RESULT) {
10671 cmailResult[lastLoadGameNumber - 1] = CMAIL_NOT_RESULT;
10674 if (! RegisterMove()) return FALSE;
10678 retVal = LoadGame(f, gameNumber, title, useList);
10680 /* Make move registered during previous look at this game, if any */
10681 MakeRegisteredMove();
10683 if (cmailCommentList[lastLoadGameNumber - 1] != NULL) {
10684 commentList[currentMove]
10685 = StrSave(cmailCommentList[lastLoadGameNumber - 1]);
10686 DisplayComment(currentMove - 1, commentList[currentMove]);
10692 /* Support for LoadNextGame, LoadPreviousGame, ReloadSameGame */
10697 int gameNumber = lastLoadGameNumber + offset;
10698 if (lastLoadGameFP == NULL) {
10699 DisplayError(_("No game has been loaded yet"), 0);
10702 if (gameNumber <= 0) {
10703 DisplayError(_("Can't back up any further"), 0);
10706 if (cmailMsgLoaded) {
10707 return CmailLoadGame(lastLoadGameFP, gameNumber,
10708 lastLoadGameTitle, lastLoadGameUseList);
10710 return LoadGame(lastLoadGameFP, gameNumber,
10711 lastLoadGameTitle, lastLoadGameUseList);
10717 /* Load the nth game from open file f */
10719 LoadGame(f, gameNumber, title, useList)
10727 int gn = gameNumber;
10728 ListGame *lg = NULL;
10729 int numPGNTags = 0;
10731 GameMode oldGameMode;
10732 VariantClass oldVariant = gameInfo.variant; /* [HGM] PGNvariant */
10734 if (appData.debugMode)
10735 fprintf(debugFP, "LoadGame(): on entry, gameMode %d\n", gameMode);
10737 if (gameMode == Training )
10738 SetTrainingModeOff();
10740 oldGameMode = gameMode;
10741 if (gameMode != BeginningOfGame) {
10742 Reset(FALSE, TRUE);
10746 if (lastLoadGameFP != NULL && lastLoadGameFP != f) {
10747 fclose(lastLoadGameFP);
10751 lg = (ListGame *) ListElem(&gameList, gameNumber-1);
10754 fseek(f, lg->offset, 0);
10755 GameListHighlight(gameNumber);
10759 DisplayError(_("Game number out of range"), 0);
10764 if (fseek(f, 0, 0) == -1) {
10765 if (f == lastLoadGameFP ?
10766 gameNumber == lastLoadGameNumber + 1 :
10770 DisplayError(_("Can't seek on game file"), 0);
10775 lastLoadGameFP = f;
10776 lastLoadGameNumber = gameNumber;
10777 safeStrCpy(lastLoadGameTitle, title, sizeof(lastLoadGameTitle)/sizeof(lastLoadGameTitle[0]));
10778 lastLoadGameUseList = useList;
10782 if (lg && lg->gameInfo.white && lg->gameInfo.black) {
10783 snprintf(buf, sizeof(buf), "%s vs. %s", lg->gameInfo.white,
10784 lg->gameInfo.black);
10786 } else if (*title != NULLCHAR) {
10787 if (gameNumber > 1) {
10788 snprintf(buf, MSG_SIZ, "%s %d", title, gameNumber);
10791 DisplayTitle(title);
10795 if (gameMode != AnalyzeFile && gameMode != AnalyzeMode) {
10796 gameMode = PlayFromGameFile;
10800 currentMove = forwardMostMove = backwardMostMove = 0;
10801 CopyBoard(boards[0], initialPosition);
10805 * Skip the first gn-1 games in the file.
10806 * Also skip over anything that precedes an identifiable
10807 * start of game marker, to avoid being confused by
10808 * garbage at the start of the file. Currently
10809 * recognized start of game markers are the move number "1",
10810 * the pattern "gnuchess .* game", the pattern
10811 * "^[#;%] [^ ]* game file", and a PGN tag block.
10812 * A game that starts with one of the latter two patterns
10813 * will also have a move number 1, possibly
10814 * following a position diagram.
10815 * 5-4-02: Let's try being more lenient and allowing a game to
10816 * start with an unnumbered move. Does that break anything?
10818 cm = lastLoadGameStart = EndOfFile;
10820 yyboardindex = forwardMostMove;
10821 cm = (ChessMove) Myylex();
10824 if (cmailMsgLoaded) {
10825 nCmailGames = CMAIL_MAX_GAMES - gn;
10828 DisplayError(_("Game not found in file"), 0);
10835 lastLoadGameStart = cm;
10838 case MoveNumberOne:
10839 switch (lastLoadGameStart) {
10844 case MoveNumberOne:
10846 gn--; /* count this game */
10847 lastLoadGameStart = cm;
10856 switch (lastLoadGameStart) {
10859 case MoveNumberOne:
10861 gn--; /* count this game */
10862 lastLoadGameStart = cm;
10865 lastLoadGameStart = cm; /* game counted already */
10873 yyboardindex = forwardMostMove;
10874 cm = (ChessMove) Myylex();
10875 } while (cm == PGNTag || cm == Comment);
10882 if (cmailMsgLoaded && (CMAIL_MAX_GAMES == lastLoadGameNumber)) {
10883 if ( cmailResult[CMAIL_MAX_GAMES - gn - 1]
10884 != CMAIL_OLD_RESULT) {
10886 cmailResult[ CMAIL_MAX_GAMES
10887 - gn - 1] = CMAIL_OLD_RESULT;
10893 /* Only a NormalMove can be at the start of a game
10894 * without a position diagram. */
10895 if (lastLoadGameStart == EndOfFile ) {
10897 lastLoadGameStart = MoveNumberOne;
10906 if (appData.debugMode)
10907 fprintf(debugFP, "Parsed game start '%s' (%d)\n", yy_text, (int) cm);
10909 if (cm == XBoardGame) {
10910 /* Skip any header junk before position diagram and/or move 1 */
10912 yyboardindex = forwardMostMove;
10913 cm = (ChessMove) Myylex();
10915 if (cm == EndOfFile ||
10916 cm == GNUChessGame || cm == XBoardGame) {
10917 /* Empty game; pretend end-of-file and handle later */
10922 if (cm == MoveNumberOne || cm == PositionDiagram ||
10923 cm == PGNTag || cm == Comment)
10926 } else if (cm == GNUChessGame) {
10927 if (gameInfo.event != NULL) {
10928 free(gameInfo.event);
10930 gameInfo.event = StrSave(yy_text);
10933 startedFromSetupPosition = FALSE;
10934 while (cm == PGNTag) {
10935 if (appData.debugMode)
10936 fprintf(debugFP, "Parsed PGNTag: %s\n", yy_text);
10937 err = ParsePGNTag(yy_text, &gameInfo);
10938 if (!err) numPGNTags++;
10940 /* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
10941 if(gameInfo.variant != oldVariant) {
10942 startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
10943 ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
10944 InitPosition(TRUE);
10945 oldVariant = gameInfo.variant;
10946 if (appData.debugMode)
10947 fprintf(debugFP, "New variant %d\n", (int) oldVariant);
10951 if (gameInfo.fen != NULL) {
10952 Board initial_position;
10953 startedFromSetupPosition = TRUE;
10954 if (!ParseFEN(initial_position, &blackPlaysFirst, gameInfo.fen)) {
10956 DisplayError(_("Bad FEN position in file"), 0);
10959 CopyBoard(boards[0], initial_position);
10960 if (blackPlaysFirst) {
10961 currentMove = forwardMostMove = backwardMostMove = 1;
10962 CopyBoard(boards[1], initial_position);
10963 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
10964 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
10965 timeRemaining[0][1] = whiteTimeRemaining;
10966 timeRemaining[1][1] = blackTimeRemaining;
10967 if (commentList[0] != NULL) {
10968 commentList[1] = commentList[0];
10969 commentList[0] = NULL;
10972 currentMove = forwardMostMove = backwardMostMove = 0;
10974 /* [HGM] copy FEN attributes as well. Bugfix 4.3.14m and 4.3.15e: moved to after 'blackPlaysFirst' */
10976 initialRulePlies = FENrulePlies;
10977 for( i=0; i< nrCastlingRights; i++ )
10978 initialRights[i] = initial_position[CASTLING][i];
10980 yyboardindex = forwardMostMove;
10981 free(gameInfo.fen);
10982 gameInfo.fen = NULL;
10985 yyboardindex = forwardMostMove;
10986 cm = (ChessMove) Myylex();
10988 /* Handle comments interspersed among the tags */
10989 while (cm == Comment) {
10991 if (appData.debugMode)
10992 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
10994 AppendComment(currentMove, p, FALSE);
10995 yyboardindex = forwardMostMove;
10996 cm = (ChessMove) Myylex();
11000 /* don't rely on existence of Event tag since if game was
11001 * pasted from clipboard the Event tag may not exist
11003 if (numPGNTags > 0){
11005 if (gameInfo.variant == VariantNormal) {
11006 VariantClass v = StringToVariant(gameInfo.event);
11007 // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
11008 if(v < VariantShogi) gameInfo.variant = v;
11011 if( appData.autoDisplayTags ) {
11012 tags = PGNTags(&gameInfo);
11013 TagsPopUp(tags, CmailMsg());
11018 /* Make something up, but don't display it now */
11023 if (cm == PositionDiagram) {
11026 Board initial_position;
11028 if (appData.debugMode)
11029 fprintf(debugFP, "Parsed PositionDiagram: %s\n", yy_text);
11031 if (!startedFromSetupPosition) {
11033 for (i = BOARD_HEIGHT - 1; i >= 0; i--)
11034 for (j = BOARD_LEFT; j < BOARD_RGHT; p++)
11045 initial_position[i][j++] = CharToPiece(*p);
11048 while (*p == ' ' || *p == '\t' ||
11049 *p == '\n' || *p == '\r') p++;
11051 if (strncmp(p, "black", strlen("black"))==0)
11052 blackPlaysFirst = TRUE;
11054 blackPlaysFirst = FALSE;
11055 startedFromSetupPosition = TRUE;
11057 CopyBoard(boards[0], initial_position);
11058 if (blackPlaysFirst) {
11059 currentMove = forwardMostMove = backwardMostMove = 1;
11060 CopyBoard(boards[1], initial_position);
11061 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11062 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11063 timeRemaining[0][1] = whiteTimeRemaining;
11064 timeRemaining[1][1] = blackTimeRemaining;
11065 if (commentList[0] != NULL) {
11066 commentList[1] = commentList[0];
11067 commentList[0] = NULL;
11070 currentMove = forwardMostMove = backwardMostMove = 0;
11073 yyboardindex = forwardMostMove;
11074 cm = (ChessMove) Myylex();
11077 if (first.pr == NoProc) {
11078 StartChessProgram(&first);
11080 InitChessProgram(&first, FALSE);
11081 SendToProgram("force\n", &first);
11082 if (startedFromSetupPosition) {
11083 SendBoard(&first, forwardMostMove);
11084 if (appData.debugMode) {
11085 fprintf(debugFP, "Load Game\n");
11087 DisplayBothClocks();
11090 /* [HGM] server: flag to write setup moves in broadcast file as one */
11091 loadFlag = appData.suppressLoadMoves;
11093 while (cm == Comment) {
11095 if (appData.debugMode)
11096 fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
11098 AppendComment(currentMove, p, FALSE);
11099 yyboardindex = forwardMostMove;
11100 cm = (ChessMove) Myylex();
11103 if ((cm == EndOfFile && lastLoadGameStart != EndOfFile ) ||
11104 cm == WhiteWins || cm == BlackWins ||
11105 cm == GameIsDrawn || cm == GameUnfinished) {
11106 DisplayMessage("", _("No moves in game"));
11107 if (cmailMsgLoaded) {
11108 if (appData.debugMode)
11109 fprintf(debugFP, "Setting flipView to %d.\n", FALSE);
11113 DrawPosition(FALSE, boards[currentMove]);
11114 DisplayBothClocks();
11115 gameMode = EditGame;
11122 // [HGM] PV info: routine tests if comment empty
11123 if (!matchMode && (pausing || appData.timeDelay != 0)) {
11124 DisplayComment(currentMove - 1, commentList[currentMove]);
11126 if (!matchMode && appData.timeDelay != 0)
11127 DrawPosition(FALSE, boards[currentMove]);
11129 if (gameMode == AnalyzeFile || gameMode == AnalyzeMode) {
11130 programStats.ok_to_send = 1;
11133 /* if the first token after the PGN tags is a move
11134 * and not move number 1, retrieve it from the parser
11136 if (cm != MoveNumberOne)
11137 LoadGameOneMove(cm);
11139 /* load the remaining moves from the file */
11140 while (LoadGameOneMove(EndOfFile)) {
11141 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
11142 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
11145 /* rewind to the start of the game */
11146 currentMove = backwardMostMove;
11148 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
11150 if (oldGameMode == AnalyzeFile ||
11151 oldGameMode == AnalyzeMode) {
11152 AnalyzeFileEvent();
11155 if (matchMode || appData.timeDelay == 0) {
11157 gameMode = EditGame;
11159 } else if (appData.timeDelay > 0) {
11160 AutoPlayGameLoop();
11163 if (appData.debugMode)
11164 fprintf(debugFP, "LoadGame(): on exit, gameMode %d\n", gameMode);
11166 loadFlag = 0; /* [HGM] true game starts */
11170 /* Support for LoadNextPosition, LoadPreviousPosition, ReloadSamePosition */
11172 ReloadPosition(offset)
11175 int positionNumber = lastLoadPositionNumber + offset;
11176 if (lastLoadPositionFP == NULL) {
11177 DisplayError(_("No position has been loaded yet"), 0);
11180 if (positionNumber <= 0) {
11181 DisplayError(_("Can't back up any further"), 0);
11184 return LoadPosition(lastLoadPositionFP, positionNumber,
11185 lastLoadPositionTitle);
11188 /* Load the nth position from the given file */
11190 LoadPositionFromFile(filename, n, title)
11198 if (strcmp(filename, "-") == 0) {
11199 return LoadPosition(stdin, n, "stdin");
11201 f = fopen(filename, "rb");
11203 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11204 DisplayError(buf, errno);
11207 return LoadPosition(f, n, title);
11212 /* Load the nth position from the given open file, and close it */
11214 LoadPosition(f, positionNumber, title)
11216 int positionNumber;
11219 char *p, line[MSG_SIZ];
11220 Board initial_position;
11221 int i, j, fenMode, pn;
11223 if (gameMode == Training )
11224 SetTrainingModeOff();
11226 if (gameMode != BeginningOfGame) {
11227 Reset(FALSE, TRUE);
11229 if (lastLoadPositionFP != NULL && lastLoadPositionFP != f) {
11230 fclose(lastLoadPositionFP);
11232 if (positionNumber == 0) positionNumber = 1;
11233 lastLoadPositionFP = f;
11234 lastLoadPositionNumber = positionNumber;
11235 safeStrCpy(lastLoadPositionTitle, title, sizeof(lastLoadPositionTitle)/sizeof(lastLoadPositionTitle[0]));
11236 if (first.pr == NoProc) {
11237 StartChessProgram(&first);
11238 InitChessProgram(&first, FALSE);
11240 pn = positionNumber;
11241 if (positionNumber < 0) {
11242 /* Negative position number means to seek to that byte offset */
11243 if (fseek(f, -positionNumber, 0) == -1) {
11244 DisplayError(_("Can't seek on position file"), 0);
11249 if (fseek(f, 0, 0) == -1) {
11250 if (f == lastLoadPositionFP ?
11251 positionNumber == lastLoadPositionNumber + 1 :
11252 positionNumber == 1) {
11255 DisplayError(_("Can't seek on position file"), 0);
11260 /* See if this file is FEN or old-style xboard */
11261 if (fgets(line, MSG_SIZ, f) == NULL) {
11262 DisplayError(_("Position not found in file"), 0);
11265 // [HGM] FEN can begin with digit, any piece letter valid in this variant, or a + for Shogi promoted pieces
11266 fenMode = line[0] >= '0' && line[0] <= '9' || line[0] == '+' || CharToPiece(line[0]) != EmptySquare;
11269 if (fenMode || line[0] == '#') pn--;
11271 /* skip positions before number pn */
11272 if (fgets(line, MSG_SIZ, f) == NULL) {
11274 DisplayError(_("Position not found in file"), 0);
11277 if (fenMode || line[0] == '#') pn--;
11282 if (!ParseFEN(initial_position, &blackPlaysFirst, line)) {
11283 DisplayError(_("Bad FEN position in file"), 0);
11287 (void) fgets(line, MSG_SIZ, f);
11288 (void) fgets(line, MSG_SIZ, f);
11290 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
11291 (void) fgets(line, MSG_SIZ, f);
11292 for (p = line, j = BOARD_LEFT; j < BOARD_RGHT; p++) {
11295 initial_position[i][j++] = CharToPiece(*p);
11299 blackPlaysFirst = FALSE;
11301 (void) fgets(line, MSG_SIZ, f);
11302 if (strncmp(line, "black", strlen("black"))==0)
11303 blackPlaysFirst = TRUE;
11306 startedFromSetupPosition = TRUE;
11308 SendToProgram("force\n", &first);
11309 CopyBoard(boards[0], initial_position);
11310 if (blackPlaysFirst) {
11311 currentMove = forwardMostMove = backwardMostMove = 1;
11312 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
11313 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
11314 CopyBoard(boards[1], initial_position);
11315 DisplayMessage("", _("Black to play"));
11317 currentMove = forwardMostMove = backwardMostMove = 0;
11318 DisplayMessage("", _("White to play"));
11320 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
11321 SendBoard(&first, forwardMostMove);
11322 if (appData.debugMode) {
11324 for(i=0;i<2;i++){for(j=0;j<6;j++)fprintf(debugFP, " %d", boards[i][CASTLING][j]);fprintf(debugFP,"\n");}
11325 for(j=0;j<6;j++)fprintf(debugFP, " %d", initialRights[j]);fprintf(debugFP,"\n");
11326 fprintf(debugFP, "Load Position\n");
11329 if (positionNumber > 1) {
11330 snprintf(line, MSG_SIZ, "%s %d", title, positionNumber);
11331 DisplayTitle(line);
11333 DisplayTitle(title);
11335 gameMode = EditGame;
11338 timeRemaining[0][1] = whiteTimeRemaining;
11339 timeRemaining[1][1] = blackTimeRemaining;
11340 DrawPosition(FALSE, boards[currentMove]);
11347 CopyPlayerNameIntoFileName(dest, src)
11350 while (*src != NULLCHAR && *src != ',') {
11355 *(*dest)++ = *src++;
11360 char *DefaultFileName(ext)
11363 static char def[MSG_SIZ];
11366 if (gameInfo.white != NULL && gameInfo.white[0] != '-') {
11368 CopyPlayerNameIntoFileName(&p, gameInfo.white);
11370 CopyPlayerNameIntoFileName(&p, gameInfo.black);
11372 safeStrCpy(p, ext, MSG_SIZ-2-strlen(gameInfo.white)-strlen(gameInfo.black));
11379 /* Save the current game to the given file */
11381 SaveGameToFile(filename, append)
11389 if (strcmp(filename, "-") == 0) {
11390 return SaveGame(stdout, 0, NULL);
11392 f = fopen(filename, append ? "a" : "w");
11394 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11395 DisplayError(buf, errno);
11398 safeStrCpy(buf, lastMsg, MSG_SIZ);
11399 DisplayMessage(_("Waiting for access to save file"), "");
11400 flock(fileno(f), LOCK_EX); // [HGM] lock: lock file while we are writing
11401 DisplayMessage(_("Saving game"), "");
11402 if(lseek(fileno(f), 0, SEEK_END) == -1) DisplayError("Bad Seek", errno); // better safe than sorry...
11403 result = SaveGame(f, 0, NULL);
11404 DisplayMessage(buf, "");
11414 static char buf[MSG_SIZ];
11417 p = strchr(str, ' ');
11418 if (p == NULL) return str;
11419 strncpy(buf, str, p - str);
11420 buf[p - str] = NULLCHAR;
11424 #define PGN_MAX_LINE 75
11426 #define PGN_SIDE_WHITE 0
11427 #define PGN_SIDE_BLACK 1
11430 static int FindFirstMoveOutOfBook( int side )
11434 if( backwardMostMove == 0 && ! startedFromSetupPosition) {
11435 int index = backwardMostMove;
11436 int has_book_hit = 0;
11438 if( (index % 2) != side ) {
11442 while( index < forwardMostMove ) {
11443 /* Check to see if engine is in book */
11444 int depth = pvInfoList[index].depth;
11445 int score = pvInfoList[index].score;
11451 else if( score == 0 && depth == 63 ) {
11452 in_book = 1; /* Zappa */
11454 else if( score == 2 && depth == 99 ) {
11455 in_book = 1; /* Abrok */
11458 has_book_hit += in_book;
11474 void GetOutOfBookInfo( char * buf )
11478 int offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11480 oob[0] = FindFirstMoveOutOfBook( PGN_SIDE_WHITE );
11481 oob[1] = FindFirstMoveOutOfBook( PGN_SIDE_BLACK );
11485 if( oob[0] >= 0 || oob[1] >= 0 ) {
11486 for( i=0; i<2; i++ ) {
11490 if( i > 0 && oob[0] >= 0 ) {
11491 strcat( buf, " " );
11494 sprintf( buf+strlen(buf), "%d%s. ", (idx - offset)/2 + 1, idx & 1 ? ".." : "" );
11495 sprintf( buf+strlen(buf), "%s%.2f",
11496 pvInfoList[idx].score >= 0 ? "+" : "",
11497 pvInfoList[idx].score / 100.0 );
11503 /* Save game in PGN style and close the file */
11508 int i, offset, linelen, newblock;
11512 int movelen, numlen, blank;
11513 char move_buffer[100]; /* [AS] Buffer for move+PV info */
11515 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11517 tm = time((time_t *) NULL);
11519 PrintPGNTags(f, &gameInfo);
11521 if (backwardMostMove > 0 || startedFromSetupPosition) {
11522 char *fen = PositionToFEN(backwardMostMove, NULL);
11523 fprintf(f, "[FEN \"%s\"]\n[SetUp \"1\"]\n", fen);
11524 fprintf(f, "\n{--------------\n");
11525 PrintPosition(f, backwardMostMove);
11526 fprintf(f, "--------------}\n");
11530 /* [AS] Out of book annotation */
11531 if( appData.saveOutOfBookInfo ) {
11534 GetOutOfBookInfo( buf );
11536 if( buf[0] != '\0' ) {
11537 fprintf( f, "[%s \"%s\"]\n", PGN_OUT_OF_BOOK, buf );
11544 i = backwardMostMove;
11548 while (i < forwardMostMove) {
11549 /* Print comments preceding this move */
11550 if (commentList[i] != NULL) {
11551 if (linelen > 0) fprintf(f, "\n");
11552 fprintf(f, "%s", commentList[i]);
11557 /* Format move number */
11559 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]),"%d.", (i - offset)/2 + 1);
11562 snprintf(numtext, sizeof(numtext)/sizeof(numtext[0]), "%d...", (i - offset)/2 + 1);
11564 numtext[0] = NULLCHAR;
11566 numlen = strlen(numtext);
11569 /* Print move number */
11570 blank = linelen > 0 && numlen > 0;
11571 if (linelen + (blank ? 1 : 0) + numlen > PGN_MAX_LINE) {
11580 fprintf(f, "%s", numtext);
11584 safeStrCpy(move_buffer, SavePart(parseList[i]), sizeof(move_buffer)/sizeof(move_buffer[0])); // [HGM] pgn: print move via buffer, so it can be edited
11585 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point before move */
11588 blank = linelen > 0 && movelen > 0;
11589 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11598 fprintf(f, "%s", move_buffer);
11599 linelen += movelen;
11601 /* [AS] Add PV info if present */
11602 if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
11603 /* [HGM] add time */
11604 char buf[MSG_SIZ]; int seconds;
11606 seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
11612 snprintf(buf, MSG_SIZ, " %3.1f%c", seconds/10., 0);
11615 seconds = (seconds + 4)/10; // round to full seconds
11617 snprintf(buf, MSG_SIZ, " %d%c", seconds, 0);
11619 snprintf(buf, MSG_SIZ, " %d:%02d%c", seconds/60, seconds%60, 0);
11622 snprintf( move_buffer, sizeof(move_buffer)/sizeof(move_buffer[0]),"{%s%.2f/%d%s}",
11623 pvInfoList[i].score >= 0 ? "+" : "",
11624 pvInfoList[i].score / 100.0,
11625 pvInfoList[i].depth,
11628 movelen = strlen(move_buffer); /* [HGM] pgn: line-break point after move */
11630 /* Print score/depth */
11631 blank = linelen > 0 && movelen > 0;
11632 if (linelen + (blank ? 1 : 0) + movelen > PGN_MAX_LINE) {
11641 fprintf(f, "%s", move_buffer);
11642 linelen += movelen;
11648 /* Start a new line */
11649 if (linelen > 0) fprintf(f, "\n");
11651 /* Print comments after last move */
11652 if (commentList[i] != NULL) {
11653 fprintf(f, "%s\n", commentList[i]);
11657 if (gameInfo.resultDetails != NULL &&
11658 gameInfo.resultDetails[0] != NULLCHAR) {
11659 fprintf(f, "{%s} %s\n\n", gameInfo.resultDetails,
11660 PGNResult(gameInfo.result));
11662 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11666 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11670 /* Save game in old style and close the file */
11672 SaveGameOldStyle(f)
11678 tm = time((time_t *) NULL);
11680 fprintf(f, "# %s game file -- %s", programName, ctime(&tm));
11683 if (backwardMostMove > 0 || startedFromSetupPosition) {
11684 fprintf(f, "\n[--------------\n");
11685 PrintPosition(f, backwardMostMove);
11686 fprintf(f, "--------------]\n");
11691 i = backwardMostMove;
11692 offset = backwardMostMove & (~1L); /* output move numbers start at 1 */
11694 while (i < forwardMostMove) {
11695 if (commentList[i] != NULL) {
11696 fprintf(f, "[%s]\n", commentList[i]);
11699 if ((i % 2) == 1) {
11700 fprintf(f, "%d. ... %s\n", (i - offset)/2 + 1, parseList[i]);
11703 fprintf(f, "%d. %s ", (i - offset)/2 + 1, parseList[i]);
11705 if (commentList[i] != NULL) {
11709 if (i >= forwardMostMove) {
11713 fprintf(f, "%s\n", parseList[i]);
11718 if (commentList[i] != NULL) {
11719 fprintf(f, "[%s]\n", commentList[i]);
11722 /* This isn't really the old style, but it's close enough */
11723 if (gameInfo.resultDetails != NULL &&
11724 gameInfo.resultDetails[0] != NULLCHAR) {
11725 fprintf(f, "%s (%s)\n\n", PGNResult(gameInfo.result),
11726 gameInfo.resultDetails);
11728 fprintf(f, "%s\n\n", PGNResult(gameInfo.result));
11735 /* Save the current game to open file f and close the file */
11737 SaveGame(f, dummy, dummy2)
11742 if (gameMode == EditPosition) EditPositionDone(TRUE);
11743 lastSavedGame = GameCheckSum(); // [HGM] save: remember ID of last saved game to prevent double saving
11744 if (appData.oldSaveStyle)
11745 return SaveGameOldStyle(f);
11747 return SaveGamePGN(f);
11750 /* Save the current position to the given file */
11752 SavePositionToFile(filename)
11758 if (strcmp(filename, "-") == 0) {
11759 return SavePosition(stdout, 0, NULL);
11761 f = fopen(filename, "a");
11763 snprintf(buf, sizeof(buf), _("Can't open \"%s\""), filename);
11764 DisplayError(buf, errno);
11767 safeStrCpy(buf, lastMsg, MSG_SIZ);
11768 DisplayMessage(_("Waiting for access to save file"), "");
11769 flock(fileno(f), LOCK_EX); // [HGM] lock
11770 DisplayMessage(_("Saving position"), "");
11771 lseek(fileno(f), 0, SEEK_END); // better safe than sorry...
11772 SavePosition(f, 0, NULL);
11773 DisplayMessage(buf, "");
11779 /* Save the current position to the given open file and close the file */
11781 SavePosition(f, dummy, dummy2)
11789 if (gameMode == EditPosition) EditPositionDone(TRUE);
11790 if (appData.oldSaveStyle) {
11791 tm = time((time_t *) NULL);
11793 fprintf(f, "# %s position file -- %s", programName, ctime(&tm));
11795 fprintf(f, "[--------------\n");
11796 PrintPosition(f, currentMove);
11797 fprintf(f, "--------------]\n");
11799 fen = PositionToFEN(currentMove, NULL);
11800 fprintf(f, "%s\n", fen);
11808 ReloadCmailMsgEvent(unregister)
11812 static char *inFilename = NULL;
11813 static char *outFilename;
11815 struct stat inbuf, outbuf;
11818 /* Any registered moves are unregistered if unregister is set, */
11819 /* i.e. invoked by the signal handler */
11821 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11822 cmailMoveRegistered[i] = FALSE;
11823 if (cmailCommentList[i] != NULL) {
11824 free(cmailCommentList[i]);
11825 cmailCommentList[i] = NULL;
11828 nCmailMovesRegistered = 0;
11831 for (i = 0; i < CMAIL_MAX_GAMES; i ++) {
11832 cmailResult[i] = CMAIL_NOT_RESULT;
11836 if (inFilename == NULL) {
11837 /* Because the filenames are static they only get malloced once */
11838 /* and they never get freed */
11839 inFilename = (char *) malloc(strlen(appData.cmailGameName) + 9);
11840 sprintf(inFilename, "%s.game.in", appData.cmailGameName);
11842 outFilename = (char *) malloc(strlen(appData.cmailGameName) + 5);
11843 sprintf(outFilename, "%s.out", appData.cmailGameName);
11846 status = stat(outFilename, &outbuf);
11848 cmailMailedMove = FALSE;
11850 status = stat(inFilename, &inbuf);
11851 cmailMailedMove = (inbuf.st_mtime < outbuf.st_mtime);
11854 /* LoadGameFromFile(CMAIL_MAX_GAMES) with cmailMsgLoaded == TRUE
11855 counts the games, notes how each one terminated, etc.
11857 It would be nice to remove this kludge and instead gather all
11858 the information while building the game list. (And to keep it
11859 in the game list nodes instead of having a bunch of fixed-size
11860 parallel arrays.) Note this will require getting each game's
11861 termination from the PGN tags, as the game list builder does
11862 not process the game moves. --mann
11864 cmailMsgLoaded = TRUE;
11865 LoadGameFromFile(inFilename, CMAIL_MAX_GAMES, "", FALSE);
11867 /* Load first game in the file or popup game menu */
11868 LoadGameFromFile(inFilename, 0, appData.cmailGameName, TRUE);
11870 #endif /* !WIN32 */
11878 char string[MSG_SIZ];
11880 if ( cmailMailedMove
11881 || (cmailResult[lastLoadGameNumber - 1] == CMAIL_OLD_RESULT)) {
11882 return TRUE; /* Allow free viewing */
11885 /* Unregister move to ensure that we don't leave RegisterMove */
11886 /* with the move registered when the conditions for registering no */
11888 if (cmailMoveRegistered[lastLoadGameNumber - 1]) {
11889 cmailMoveRegistered[lastLoadGameNumber - 1] = FALSE;
11890 nCmailMovesRegistered --;
11892 if (cmailCommentList[lastLoadGameNumber - 1] != NULL)
11894 free(cmailCommentList[lastLoadGameNumber - 1]);
11895 cmailCommentList[lastLoadGameNumber - 1] = NULL;
11899 if (cmailOldMove == -1) {
11900 DisplayError(_("You have edited the game history.\nUse Reload Same Game and make your move again."), 0);
11904 if (currentMove > cmailOldMove + 1) {
11905 DisplayError(_("You have entered too many moves.\nBack up to the correct position and try again."), 0);
11909 if (currentMove < cmailOldMove) {
11910 DisplayError(_("Displayed position is not current.\nStep forward to the correct position and try again."), 0);
11914 if (forwardMostMove > currentMove) {
11915 /* Silently truncate extra moves */
11919 if ( (currentMove == cmailOldMove + 1)
11920 || ( (currentMove == cmailOldMove)
11921 && ( (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_ACCEPT)
11922 || (cmailMoveType[lastLoadGameNumber - 1] == CMAIL_RESIGN)))) {
11923 if (gameInfo.result != GameUnfinished) {
11924 cmailResult[lastLoadGameNumber - 1] = CMAIL_NEW_RESULT;
11927 if (commentList[currentMove] != NULL) {
11928 cmailCommentList[lastLoadGameNumber - 1]
11929 = StrSave(commentList[currentMove]);
11931 safeStrCpy(cmailMove[lastLoadGameNumber - 1], moveList[currentMove - 1], sizeof(cmailMove[lastLoadGameNumber - 1])/sizeof(cmailMove[lastLoadGameNumber - 1][0]));
11933 if (appData.debugMode)
11934 fprintf(debugFP, "Saving %s for game %d\n",
11935 cmailMove[lastLoadGameNumber - 1], lastLoadGameNumber);
11937 snprintf(string, MSG_SIZ, "%s.game.out.%d", appData.cmailGameName, lastLoadGameNumber);
11939 f = fopen(string, "w");
11940 if (appData.oldSaveStyle) {
11941 SaveGameOldStyle(f); /* also closes the file */
11943 snprintf(string, MSG_SIZ, "%s.pos.out", appData.cmailGameName);
11944 f = fopen(string, "w");
11945 SavePosition(f, 0, NULL); /* also closes the file */
11947 fprintf(f, "{--------------\n");
11948 PrintPosition(f, currentMove);
11949 fprintf(f, "--------------}\n\n");
11951 SaveGame(f, 0, NULL); /* also closes the file*/
11954 cmailMoveRegistered[lastLoadGameNumber - 1] = TRUE;
11955 nCmailMovesRegistered ++;
11956 } else if (nCmailGames == 1) {
11957 DisplayError(_("You have not made a move yet"), 0);
11968 static char *partCommandString = "cmail -xv%s -remail -game %s 2>&1";
11969 FILE *commandOutput;
11970 char buffer[MSG_SIZ], msg[MSG_SIZ], string[MSG_SIZ];
11971 int nBytes = 0; /* Suppress warnings on uninitialized variables */
11977 if (! cmailMsgLoaded) {
11978 DisplayError(_("The cmail message is not loaded.\nUse Reload CMail Message and make your move again."), 0);
11982 if (nCmailGames == nCmailResults) {
11983 DisplayError(_("No unfinished games"), 0);
11987 #if CMAIL_PROHIBIT_REMAIL
11988 if (cmailMailedMove) {
11989 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);
11990 DisplayError(msg, 0);
11995 if (! (cmailMailedMove || RegisterMove())) return;
11997 if ( cmailMailedMove
11998 || (nCmailMovesRegistered + nCmailResults == nCmailGames)) {
11999 snprintf(string, MSG_SIZ, partCommandString,
12000 appData.debugMode ? " -v" : "", appData.cmailGameName);
12001 commandOutput = popen(string, "r");
12003 if (commandOutput == NULL) {
12004 DisplayError(_("Failed to invoke cmail"), 0);
12006 for (nBuffers = 0; (! feof(commandOutput)); nBuffers ++) {
12007 nBytes = fread(buffer, 1, MSG_SIZ - 1, commandOutput);
12009 if (nBuffers > 1) {
12010 (void) memcpy(msg, buffer + nBytes, MSG_SIZ - nBytes - 1);
12011 (void) memcpy(msg + MSG_SIZ - nBytes - 1, buffer, nBytes);
12012 nBytes = MSG_SIZ - 1;
12014 (void) memcpy(msg, buffer, nBytes);
12016 *(msg + nBytes) = '\0'; /* \0 for end-of-string*/
12018 if(StrStr(msg, "Mailed cmail message to ") != NULL) {
12019 cmailMailedMove = TRUE; /* Prevent >1 moves */
12022 for (i = 0; i < nCmailGames; i ++) {
12023 if (cmailResult[i] == CMAIL_NOT_RESULT) {
12028 && ( (arcDir = (char *) getenv("CMAIL_ARCDIR"))
12030 snprintf(buffer, MSG_SIZ, "%s/%s.%s.archive",
12032 appData.cmailGameName,
12034 LoadGameFromFile(buffer, 1, buffer, FALSE);
12035 cmailMsgLoaded = FALSE;
12039 DisplayInformation(msg);
12040 pclose(commandOutput);
12043 if ((*cmailMsg) != '\0') {
12044 DisplayInformation(cmailMsg);
12049 #endif /* !WIN32 */
12058 int prependComma = 0;
12060 char string[MSG_SIZ]; /* Space for game-list */
12063 if (!cmailMsgLoaded) return "";
12065 if (cmailMailedMove) {
12066 snprintf(cmailMsg, MSG_SIZ, _("Waiting for reply from opponent\n"));
12068 /* Create a list of games left */
12069 snprintf(string, MSG_SIZ, "[");
12070 for (i = 0; i < nCmailGames; i ++) {
12071 if (! ( cmailMoveRegistered[i]
12072 || (cmailResult[i] == CMAIL_OLD_RESULT))) {
12073 if (prependComma) {
12074 snprintf(number, sizeof(number)/sizeof(number[0]), ",%d", i + 1);
12076 snprintf(number, sizeof(number)/sizeof(number[0]), "%d", i + 1);
12080 strcat(string, number);
12083 strcat(string, "]");
12085 if (nCmailMovesRegistered + nCmailResults == 0) {
12086 switch (nCmailGames) {
12088 snprintf(cmailMsg, MSG_SIZ, _("Still need to make move for game\n"));
12092 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for both games\n"));
12096 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for all %d games\n"),
12101 switch (nCmailGames - nCmailMovesRegistered - nCmailResults) {
12103 snprintf(cmailMsg, MSG_SIZ, _("Still need to make a move for game %s\n"),
12108 if (nCmailResults == nCmailGames) {
12109 snprintf(cmailMsg, MSG_SIZ, _("No unfinished games\n"));
12111 snprintf(cmailMsg, MSG_SIZ, _("Ready to send mail\n"));
12116 snprintf(cmailMsg, MSG_SIZ, _("Still need to make moves for games %s\n"),
12128 if (gameMode == Training)
12129 SetTrainingModeOff();
12132 cmailMsgLoaded = FALSE;
12133 if (appData.icsActive) {
12134 SendToICS(ics_prefix);
12135 SendToICS("refresh\n");
12145 /* Give up on clean exit */
12149 /* Keep trying for clean exit */
12153 if (appData.icsActive && appData.colorize) Colorize(ColorNone, FALSE);
12155 if (telnetISR != NULL) {
12156 RemoveInputSource(telnetISR);
12158 if (icsPR != NoProc) {
12159 DestroyChildProcess(icsPR, TRUE);
12162 /* [HGM] crash: leave writing PGN and position entirely to GameEnds() */
12163 GameEnds(gameInfo.result, gameInfo.resultDetails==NULL ? "xboard exit" : gameInfo.resultDetails, GE_PLAYER);
12165 /* [HGM] crash: the above GameEnds() is a dud if another one was running */
12166 /* make sure this other one finishes before killing it! */
12167 if(endingGame) { int count = 0;
12168 if(appData.debugMode) fprintf(debugFP, "ExitEvent() during GameEnds(), wait\n");
12169 while(endingGame && count++ < 10) DoSleep(1);
12170 if(appData.debugMode && endingGame) fprintf(debugFP, "GameEnds() seems stuck, proceed exiting\n");
12173 /* Kill off chess programs */
12174 if (first.pr != NoProc) {
12177 DoSleep( appData.delayBeforeQuit );
12178 SendToProgram("quit\n", &first);
12179 DoSleep( appData.delayAfterQuit );
12180 DestroyChildProcess(first.pr, 10 /* [AS] first.useSigterm */ );
12182 if (second.pr != NoProc) {
12183 DoSleep( appData.delayBeforeQuit );
12184 SendToProgram("quit\n", &second);
12185 DoSleep( appData.delayAfterQuit );
12186 DestroyChildProcess(second.pr, 10 /* [AS] second.useSigterm */ );
12188 if (first.isr != NULL) {
12189 RemoveInputSource(first.isr);
12191 if (second.isr != NULL) {
12192 RemoveInputSource(second.isr);
12195 ShutDownFrontEnd();
12202 if (appData.debugMode)
12203 fprintf(debugFP, "PauseEvent(): pausing %d\n", pausing);
12207 if (gameMode == MachinePlaysWhite ||
12208 gameMode == MachinePlaysBlack) {
12211 DisplayBothClocks();
12213 if (gameMode == PlayFromGameFile) {
12214 if (appData.timeDelay >= 0)
12215 AutoPlayGameLoop();
12216 } else if (gameMode == IcsExamining && pauseExamInvalid) {
12217 Reset(FALSE, TRUE);
12218 SendToICS(ics_prefix);
12219 SendToICS("refresh\n");
12220 } else if (currentMove < forwardMostMove) {
12221 ForwardInner(forwardMostMove);
12223 pauseExamInvalid = FALSE;
12225 switch (gameMode) {
12229 pauseExamForwardMostMove = forwardMostMove;
12230 pauseExamInvalid = FALSE;
12233 case IcsPlayingWhite:
12234 case IcsPlayingBlack:
12238 case PlayFromGameFile:
12239 (void) StopLoadGameTimer();
12243 case BeginningOfGame:
12244 if (appData.icsActive) return;
12245 /* else fall through */
12246 case MachinePlaysWhite:
12247 case MachinePlaysBlack:
12248 case TwoMachinesPlay:
12249 if (forwardMostMove == 0)
12250 return; /* don't pause if no one has moved */
12251 if ((gameMode == MachinePlaysWhite &&
12252 !WhiteOnMove(forwardMostMove)) ||
12253 (gameMode == MachinePlaysBlack &&
12254 WhiteOnMove(forwardMostMove))) {
12267 char title[MSG_SIZ];
12269 if (currentMove < 1 || parseList[currentMove - 1][0] == NULLCHAR) {
12270 safeStrCpy(title, _("Edit comment"), sizeof(title)/sizeof(title[0]));
12272 snprintf(title, MSG_SIZ, _("Edit comment on %d.%s%s"), (currentMove - 1) / 2 + 1,
12273 WhiteOnMove(currentMove - 1) ? " " : ".. ",
12274 parseList[currentMove - 1]);
12277 EditCommentPopUp(currentMove, title, commentList[currentMove]);
12284 char *tags = PGNTags(&gameInfo);
12285 EditTagsPopUp(tags, NULL);
12292 if (appData.noChessProgram || gameMode == AnalyzeMode)
12295 if (gameMode != AnalyzeFile) {
12296 if (!appData.icsEngineAnalyze) {
12298 if (gameMode != EditGame) return;
12300 ResurrectChessProgram();
12301 SendToProgram("analyze\n", &first);
12302 first.analyzing = TRUE;
12303 /*first.maybeThinking = TRUE;*/
12304 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12305 EngineOutputPopUp();
12307 if (!appData.icsEngineAnalyze) gameMode = AnalyzeMode;
12312 StartAnalysisClock();
12313 GetTimeMark(&lastNodeCountTime);
12320 if (appData.noChessProgram || gameMode == AnalyzeFile)
12323 if (gameMode != AnalyzeMode) {
12325 if (gameMode != EditGame) return;
12326 ResurrectChessProgram();
12327 SendToProgram("analyze\n", &first);
12328 first.analyzing = TRUE;
12329 /*first.maybeThinking = TRUE;*/
12330 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
12331 EngineOutputPopUp();
12333 gameMode = AnalyzeFile;
12338 StartAnalysisClock();
12339 GetTimeMark(&lastNodeCountTime);
12344 MachineWhiteEvent()
12347 char *bookHit = NULL;
12349 if (appData.noChessProgram || (gameMode == MachinePlaysWhite))
12353 if (gameMode == PlayFromGameFile ||
12354 gameMode == TwoMachinesPlay ||
12355 gameMode == Training ||
12356 gameMode == AnalyzeMode ||
12357 gameMode == EndOfGame)
12360 if (gameMode == EditPosition)
12361 EditPositionDone(TRUE);
12363 if (!WhiteOnMove(currentMove)) {
12364 DisplayError(_("It is not White's turn"), 0);
12368 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12371 if (gameMode == EditGame || gameMode == AnalyzeMode ||
12372 gameMode == AnalyzeFile)
12375 ResurrectChessProgram(); /* in case it isn't running */
12376 if(gameMode == BeginningOfGame) { /* [HGM] time odds: to get right odds in human mode */
12377 gameMode = MachinePlaysWhite;
12380 gameMode = MachinePlaysWhite;
12384 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12386 if (first.sendName) {
12387 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.black);
12388 SendToProgram(buf, &first);
12390 if (first.sendTime) {
12391 if (first.useColors) {
12392 SendToProgram("black\n", &first); /*gnu kludge*/
12394 SendTimeRemaining(&first, TRUE);
12396 if (first.useColors) {
12397 SendToProgram("white\n", &first); // [HGM] book: send 'go' separately
12399 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12400 SetMachineThinkingEnables();
12401 first.maybeThinking = TRUE;
12405 if (appData.autoFlipView && !flipView) {
12406 flipView = !flipView;
12407 DrawPosition(FALSE, NULL);
12408 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
12411 if(bookHit) { // [HGM] book: simulate book reply
12412 static char bookMove[MSG_SIZ]; // a bit generous?
12414 programStats.nodes = programStats.depth = programStats.time =
12415 programStats.score = programStats.got_only_move = 0;
12416 sprintf(programStats.movelist, "%s (xbook)", bookHit);
12418 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12419 strcat(bookMove, bookHit);
12420 HandleMachineMove(bookMove, &first);
12425 MachineBlackEvent()
12428 char *bookHit = NULL;
12430 if (appData.noChessProgram || (gameMode == MachinePlaysBlack))
12434 if (gameMode == PlayFromGameFile ||
12435 gameMode == TwoMachinesPlay ||
12436 gameMode == Training ||
12437 gameMode == AnalyzeMode ||
12438 gameMode == EndOfGame)
12441 if (gameMode == EditPosition)
12442 EditPositionDone(TRUE);
12444 if (WhiteOnMove(currentMove)) {
12445 DisplayError(_("It is not Black's turn"), 0);
12449 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile)
12452 if (gameMode == EditGame || gameMode == AnalyzeMode ||
12453 gameMode == AnalyzeFile)
12456 ResurrectChessProgram(); /* in case it isn't running */
12457 gameMode = MachinePlaysBlack;
12461 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12463 if (first.sendName) {
12464 snprintf(buf, MSG_SIZ, "name %s\n", gameInfo.white);
12465 SendToProgram(buf, &first);
12467 if (first.sendTime) {
12468 if (first.useColors) {
12469 SendToProgram("white\n", &first); /*gnu kludge*/
12471 SendTimeRemaining(&first, FALSE);
12473 if (first.useColors) {
12474 SendToProgram("black\n", &first); // [HGM] book: 'go' sent separately
12476 bookHit = SendMoveToBookUser(forwardMostMove-1, &first, TRUE); // [HGM] book: send go or retrieve book move
12477 SetMachineThinkingEnables();
12478 first.maybeThinking = TRUE;
12481 if (appData.autoFlipView && flipView) {
12482 flipView = !flipView;
12483 DrawPosition(FALSE, NULL);
12484 DisplayBothClocks(); // [HGM] logo: clocks might have to be exchanged;
12486 if(bookHit) { // [HGM] book: simulate book reply
12487 static char bookMove[MSG_SIZ]; // a bit generous?
12489 programStats.nodes = programStats.depth = programStats.time =
12490 programStats.score = programStats.got_only_move = 0;
12491 sprintf(programStats.movelist, "%s (xbook)", bookHit);
12493 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12494 strcat(bookMove, bookHit);
12495 HandleMachineMove(bookMove, &first);
12501 DisplayTwoMachinesTitle()
12504 if (appData.matchGames > 0) {
12505 if (first.twoMachinesColor[0] == 'w') {
12506 snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12507 gameInfo.white, gameInfo.black,
12508 first.matchWins, second.matchWins,
12509 matchGame - 1 - (first.matchWins + second.matchWins));
12511 snprintf(buf, MSG_SIZ, "%s vs. %s (%d-%d-%d)",
12512 gameInfo.white, gameInfo.black,
12513 second.matchWins, first.matchWins,
12514 matchGame - 1 - (first.matchWins + second.matchWins));
12517 snprintf(buf, MSG_SIZ, "%s vs. %s", gameInfo.white, gameInfo.black);
12523 SettingsMenuIfReady()
12525 if (second.lastPing != second.lastPong) {
12526 DisplayMessage("", _("Waiting for second chess program"));
12527 ScheduleDelayedEvent(SettingsMenuIfReady, 10); // [HGM] fast: lowered from 1000
12531 DisplayMessage("", "");
12532 SettingsPopUp(&second);
12536 WaitForEngine(ChessProgramState *cps, DelayedEventCallback retry)
12539 if (cps->pr == NULL) {
12540 StartChessProgram(cps);
12541 if (cps->protocolVersion == 1) {
12544 /* kludge: allow timeout for initial "feature" command */
12546 snprintf(buf, MSG_SIZ, _("Starting %s chess program"), cps->which);
12547 DisplayMessage("", buf);
12548 ScheduleDelayedEvent(retry, FEATURE_TIMEOUT);
12556 TwoMachinesEvent P((void))
12560 ChessProgramState *onmove;
12561 char *bookHit = NULL;
12562 static int stalling = 0;
12566 if (appData.noChessProgram) return;
12568 switch (gameMode) {
12569 case TwoMachinesPlay:
12571 case MachinePlaysWhite:
12572 case MachinePlaysBlack:
12573 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
12574 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
12578 case BeginningOfGame:
12579 case PlayFromGameFile:
12582 if (gameMode != EditGame) return;
12585 EditPositionDone(TRUE);
12596 // forwardMostMove = currentMove;
12597 TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
12599 if(!ResurrectChessProgram()) return; /* in case first program isn't running (unbalances its ping due to InitChessProgram!) */
12601 if(WaitForEngine(&second, TwoMachinesEventIfReady)) return; // (if needed:) started up second engine, so wait for features
12602 if(first.lastPing != first.lastPong) { // [HGM] wait till we are sure first engine has set up position
12603 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12607 InitChessProgram(&second, FALSE); // unbalances ping of second engine
12608 SendToProgram("force\n", &second);
12610 ScheduleDelayedEvent(TwoMachinesEventIfReady, 10);
12613 GetTimeMark(&now); // [HGM] matchpause: implement match pause after engine load
12614 if(appData.matchPause>10000 || appData.matchPause<10)
12615 appData.matchPause = 10000; /* [HGM] make pause adjustable */
12616 wait = SubtractTimeMarks(&now, &pauseStart);
12617 if(wait < appData.matchPause) {
12618 ScheduleDelayedEvent(TwoMachinesEventIfReady, appData.matchPause - wait);
12622 DisplayMessage("", "");
12623 if (startedFromSetupPosition) {
12624 SendBoard(&second, backwardMostMove);
12625 if (appData.debugMode) {
12626 fprintf(debugFP, "Two Machines\n");
12629 for (i = backwardMostMove; i < forwardMostMove; i++) {
12630 SendMoveToProgram(i, &second);
12633 gameMode = TwoMachinesPlay;
12637 DisplayTwoMachinesTitle();
12639 if ((first.twoMachinesColor[0] == 'w') == WhiteOnMove(forwardMostMove)) {
12644 if(appData.debugMode) fprintf(debugFP, "New game (%d): %s-%s (%c)\n", matchGame, first.tidy, second.tidy, first.twoMachinesColor[0]);
12645 SendToProgram(first.computerString, &first);
12646 if (first.sendName) {
12647 snprintf(buf, MSG_SIZ, "name %s\n", second.tidy);
12648 SendToProgram(buf, &first);
12650 SendToProgram(second.computerString, &second);
12651 if (second.sendName) {
12652 snprintf(buf, MSG_SIZ, "name %s\n", first.tidy);
12653 SendToProgram(buf, &second);
12657 if (!first.sendTime || !second.sendTime) {
12658 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12659 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12661 if (onmove->sendTime) {
12662 if (onmove->useColors) {
12663 SendToProgram(onmove->other->twoMachinesColor, onmove); /*gnu kludge*/
12665 SendTimeRemaining(onmove, WhiteOnMove(forwardMostMove));
12667 if (onmove->useColors) {
12668 SendToProgram(onmove->twoMachinesColor, onmove);
12670 bookHit = SendMoveToBookUser(forwardMostMove-1, onmove, TRUE); // [HGM] book: send go or retrieve book move
12671 // SendToProgram("go\n", onmove);
12672 onmove->maybeThinking = TRUE;
12673 SetMachineThinkingEnables();
12677 if(bookHit) { // [HGM] book: simulate book reply
12678 static char bookMove[MSG_SIZ]; // a bit generous?
12680 programStats.nodes = programStats.depth = programStats.time =
12681 programStats.score = programStats.got_only_move = 0;
12682 sprintf(programStats.movelist, "%s (xbook)", bookHit);
12684 safeStrCpy(bookMove, "move ", sizeof(bookMove)/sizeof(bookMove[0]));
12685 strcat(bookMove, bookHit);
12686 savedMessage = bookMove; // args for deferred call
12687 savedState = onmove;
12688 ScheduleDelayedEvent(DeferredBookMove, 1);
12695 if (gameMode == Training) {
12696 SetTrainingModeOff();
12697 gameMode = PlayFromGameFile;
12698 DisplayMessage("", _("Training mode off"));
12700 gameMode = Training;
12701 animateTraining = appData.animate;
12703 /* make sure we are not already at the end of the game */
12704 if (currentMove < forwardMostMove) {
12705 SetTrainingModeOn();
12706 DisplayMessage("", _("Training mode on"));
12708 gameMode = PlayFromGameFile;
12709 DisplayError(_("Already at end of game"), 0);
12718 if (!appData.icsActive) return;
12719 switch (gameMode) {
12720 case IcsPlayingWhite:
12721 case IcsPlayingBlack:
12724 case BeginningOfGame:
12732 EditPositionDone(TRUE);
12745 gameMode = IcsIdle;
12756 switch (gameMode) {
12758 SetTrainingModeOff();
12760 case MachinePlaysWhite:
12761 case MachinePlaysBlack:
12762 case BeginningOfGame:
12763 SendToProgram("force\n", &first);
12764 SetUserThinkingEnables();
12766 case PlayFromGameFile:
12767 (void) StopLoadGameTimer();
12768 if (gameFileFP != NULL) {
12773 EditPositionDone(TRUE);
12778 SendToProgram("force\n", &first);
12780 case TwoMachinesPlay:
12781 GameEnds(EndOfFile, NULL, GE_PLAYER);
12782 ResurrectChessProgram();
12783 SetUserThinkingEnables();
12786 ResurrectChessProgram();
12788 case IcsPlayingBlack:
12789 case IcsPlayingWhite:
12790 DisplayError(_("Warning: You are still playing a game"), 0);
12793 DisplayError(_("Warning: You are still observing a game"), 0);
12796 DisplayError(_("Warning: You are still examining a game"), 0);
12807 first.offeredDraw = second.offeredDraw = 0;
12809 if (gameMode == PlayFromGameFile) {
12810 whiteTimeRemaining = timeRemaining[0][currentMove];
12811 blackTimeRemaining = timeRemaining[1][currentMove];
12815 if (gameMode == MachinePlaysWhite ||
12816 gameMode == MachinePlaysBlack ||
12817 gameMode == TwoMachinesPlay ||
12818 gameMode == EndOfGame) {
12819 i = forwardMostMove;
12820 while (i > currentMove) {
12821 SendToProgram("undo\n", &first);
12824 whiteTimeRemaining = timeRemaining[0][currentMove];
12825 blackTimeRemaining = timeRemaining[1][currentMove];
12826 DisplayBothClocks();
12827 if (whiteFlag || blackFlag) {
12828 whiteFlag = blackFlag = 0;
12833 gameMode = EditGame;
12840 EditPositionEvent()
12842 if (gameMode == EditPosition) {
12848 if (gameMode != EditGame) return;
12850 gameMode = EditPosition;
12853 if (currentMove > 0)
12854 CopyBoard(boards[0], boards[currentMove]);
12856 blackPlaysFirst = !WhiteOnMove(currentMove);
12858 currentMove = forwardMostMove = backwardMostMove = 0;
12859 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12866 /* [DM] icsEngineAnalyze - possible call from other functions */
12867 if (appData.icsEngineAnalyze) {
12868 appData.icsEngineAnalyze = FALSE;
12870 DisplayMessage("",_("Close ICS engine analyze..."));
12872 if (first.analysisSupport && first.analyzing) {
12873 SendToProgram("exit\n", &first);
12874 first.analyzing = FALSE;
12876 thinkOutput[0] = NULLCHAR;
12880 EditPositionDone(Boolean fakeRights)
12882 int king = gameInfo.variant == VariantKnightmate ? WhiteUnicorn : WhiteKing;
12884 startedFromSetupPosition = TRUE;
12885 InitChessProgram(&first, FALSE);
12886 if(fakeRights) { // [HGM] suppress this if we just pasted a FEN.
12887 boards[0][EP_STATUS] = EP_NONE;
12888 boards[0][CASTLING][2] = boards[0][CASTLING][5] = BOARD_WIDTH>>1;
12889 if(boards[0][0][BOARD_WIDTH>>1] == king) {
12890 boards[0][CASTLING][1] = boards[0][0][BOARD_LEFT] == WhiteRook ? 0 : NoRights;
12891 boards[0][CASTLING][0] = boards[0][0][BOARD_RGHT-1] == WhiteRook ? BOARD_RGHT-1 : NoRights;
12892 } else boards[0][CASTLING][2] = NoRights;
12893 if(boards[0][BOARD_HEIGHT-1][BOARD_WIDTH>>1] == WHITE_TO_BLACK king) {
12894 boards[0][CASTLING][4] = boards[0][BOARD_HEIGHT-1][BOARD_LEFT] == BlackRook ? 0 : NoRights;
12895 boards[0][CASTLING][3] = boards[0][BOARD_HEIGHT-1][BOARD_RGHT-1] == BlackRook ? BOARD_RGHT-1 : NoRights;
12896 } else boards[0][CASTLING][5] = NoRights;
12898 SendToProgram("force\n", &first);
12899 if (blackPlaysFirst) {
12900 safeStrCpy(moveList[0], "", sizeof(moveList[0])/sizeof(moveList[0][0]));
12901 safeStrCpy(parseList[0], "", sizeof(parseList[0])/sizeof(parseList[0][0]));
12902 currentMove = forwardMostMove = backwardMostMove = 1;
12903 CopyBoard(boards[1], boards[0]);
12905 currentMove = forwardMostMove = backwardMostMove = 0;
12907 SendBoard(&first, forwardMostMove);
12908 if (appData.debugMode) {
12909 fprintf(debugFP, "EditPosDone\n");
12912 timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
12913 timeRemaining[1][forwardMostMove] = blackTimeRemaining;
12914 gameMode = EditGame;
12916 HistorySet(parseList, backwardMostMove, forwardMostMove, currentMove-1);
12917 ClearHighlights(); /* [AS] */
12920 /* Pause for `ms' milliseconds */
12921 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12931 } while (SubtractTimeMarks(&m2, &m1) < ms);
12934 /* !! Ugh, this is a kludge. Fix it sometime. --tpm */
12936 SendMultiLineToICS(buf)
12939 char temp[MSG_SIZ+1], *p;
12946 strncpy(temp, buf, len);
12951 if (*p == '\n' || *p == '\r')
12956 strcat(temp, "\n");
12958 SendToPlayer(temp, strlen(temp));
12962 SetWhiteToPlayEvent()
12964 if (gameMode == EditPosition) {
12965 blackPlaysFirst = FALSE;
12966 DisplayBothClocks(); /* works because currentMove is 0 */
12967 } else if (gameMode == IcsExamining) {
12968 SendToICS(ics_prefix);
12969 SendToICS("tomove white\n");
12974 SetBlackToPlayEvent()
12976 if (gameMode == EditPosition) {
12977 blackPlaysFirst = TRUE;
12978 currentMove = 1; /* kludge */
12979 DisplayBothClocks();
12981 } else if (gameMode == IcsExamining) {
12982 SendToICS(ics_prefix);
12983 SendToICS("tomove black\n");
12988 EditPositionMenuEvent(selection, x, y)
12989 ChessSquare selection;
12993 ChessSquare piece = boards[0][y][x];
12995 if (gameMode != EditPosition && gameMode != IcsExamining) return;
12997 switch (selection) {
12999 if (gameMode == IcsExamining && ics_type == ICS_FICS) {
13000 SendToICS(ics_prefix);
13001 SendToICS("bsetup clear\n");
13002 } else if (gameMode == IcsExamining && ics_type == ICS_ICC) {
13003 SendToICS(ics_prefix);
13004 SendToICS("clearboard\n");
13006 for (x = 0; x < BOARD_WIDTH; x++) { ChessSquare p = EmptySquare;
13007 if(x == BOARD_LEFT-1 || x == BOARD_RGHT) p = (ChessSquare) 0; /* [HGM] holdings */
13008 for (y = 0; y < BOARD_HEIGHT; y++) {
13009 if (gameMode == IcsExamining) {
13010 if (boards[currentMove][y][x] != EmptySquare) {
13011 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix,
13016 boards[0][y][x] = p;
13021 if (gameMode == EditPosition) {
13022 DrawPosition(FALSE, boards[0]);
13027 SetWhiteToPlayEvent();
13031 SetBlackToPlayEvent();
13035 if (gameMode == IcsExamining) {
13036 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13037 snprintf(buf, MSG_SIZ, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
13040 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13041 if(x == BOARD_LEFT-2) {
13042 if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
13043 boards[0][y][1] = 0;
13045 if(x == BOARD_RGHT+1) {
13046 if(y >= gameInfo.holdingsSize) break;
13047 boards[0][y][BOARD_WIDTH-2] = 0;
13050 boards[0][y][x] = EmptySquare;
13051 DrawPosition(FALSE, boards[0]);
13056 if(piece >= (int)WhitePawn && piece < (int)WhiteMan ||
13057 piece >= (int)BlackPawn && piece < (int)BlackMan ) {
13058 selection = (ChessSquare) (PROMOTED piece);
13059 } else if(piece == EmptySquare) selection = WhiteSilver;
13060 else selection = (ChessSquare)((int)piece - 1);
13064 if(piece > (int)WhiteMan && piece <= (int)WhiteKing ||
13065 piece > (int)BlackMan && piece <= (int)BlackKing ) {
13066 selection = (ChessSquare) (DEMOTED piece);
13067 } else if(piece == EmptySquare) selection = BlackSilver;
13068 else selection = (ChessSquare)((int)piece + 1);
13073 if(gameInfo.variant == VariantShatranj ||
13074 gameInfo.variant == VariantXiangqi ||
13075 gameInfo.variant == VariantCourier ||
13076 gameInfo.variant == VariantMakruk )
13077 selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
13082 if(gameInfo.variant == VariantXiangqi)
13083 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteWazir);
13084 if(gameInfo.variant == VariantKnightmate)
13085 selection = (ChessSquare)((int)selection - (int)WhiteKing + (int)WhiteUnicorn);
13088 if (gameMode == IcsExamining) {
13089 if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
13090 snprintf(buf, MSG_SIZ, "%s%c@%c%c\n", ics_prefix,
13091 PieceToChar(selection), AAA + x, ONE + y);
13094 if(x < BOARD_LEFT || x >= BOARD_RGHT) {
13096 if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
13097 n = PieceToNumber(selection - BlackPawn);
13098 if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
13099 boards[0][BOARD_HEIGHT-1-n][0] = selection;
13100 boards[0][BOARD_HEIGHT-1-n][1]++;
13102 if(x == BOARD_RGHT+1 && selection < BlackPawn) {
13103 n = PieceToNumber(selection);
13104 if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
13105 boards[0][n][BOARD_WIDTH-1] = selection;
13106 boards[0][n][BOARD_WIDTH-2]++;
13109 boards[0][y][x] = selection;
13110 DrawPosition(TRUE, boards[0]);
13118 DropMenuEvent(selection, x, y)
13119 ChessSquare selection;
13122 ChessMove moveType;
13124 switch (gameMode) {
13125 case IcsPlayingWhite:
13126 case MachinePlaysBlack:
13127 if (!WhiteOnMove(currentMove)) {
13128 DisplayMoveError(_("It is Black's turn"));
13131 moveType = WhiteDrop;
13133 case IcsPlayingBlack:
13134 case MachinePlaysWhite:
13135 if (WhiteOnMove(currentMove)) {
13136 DisplayMoveError(_("It is White's turn"));
13139 moveType = BlackDrop;
13142 moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
13148 if (moveType == BlackDrop && selection < BlackPawn) {
13149 selection = (ChessSquare) ((int) selection
13150 + (int) BlackPawn - (int) WhitePawn);
13152 if (boards[currentMove][y][x] != EmptySquare) {
13153 DisplayMoveError(_("That square is occupied"));
13157 FinishMove(moveType, (int) selection, DROP_RANK, x, y, NULLCHAR);
13163 /* Accept a pending offer of any kind from opponent */
13165 if (appData.icsActive) {
13166 SendToICS(ics_prefix);
13167 SendToICS("accept\n");
13168 } else if (cmailMsgLoaded) {
13169 if (currentMove == cmailOldMove &&
13170 commentList[cmailOldMove] != NULL &&
13171 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13172 "Black offers a draw" : "White offers a draw")) {
13174 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13175 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13177 DisplayError(_("There is no pending offer on this move"), 0);
13178 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13181 /* Not used for offers from chess program */
13188 /* Decline a pending offer of any kind from opponent */
13190 if (appData.icsActive) {
13191 SendToICS(ics_prefix);
13192 SendToICS("decline\n");
13193 } else if (cmailMsgLoaded) {
13194 if (currentMove == cmailOldMove &&
13195 commentList[cmailOldMove] != NULL &&
13196 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13197 "Black offers a draw" : "White offers a draw")) {
13199 AppendComment(cmailOldMove, "Draw declined", TRUE);
13200 DisplayComment(cmailOldMove - 1, "Draw declined");
13203 DisplayError(_("There is no pending offer on this move"), 0);
13206 /* Not used for offers from chess program */
13213 /* Issue ICS rematch command */
13214 if (appData.icsActive) {
13215 SendToICS(ics_prefix);
13216 SendToICS("rematch\n");
13223 /* Call your opponent's flag (claim a win on time) */
13224 if (appData.icsActive) {
13225 SendToICS(ics_prefix);
13226 SendToICS("flag\n");
13228 switch (gameMode) {
13231 case MachinePlaysWhite:
13234 GameEnds(GameIsDrawn, "Both players ran out of time",
13237 GameEnds(BlackWins, "Black wins on time", GE_PLAYER);
13239 DisplayError(_("Your opponent is not out of time"), 0);
13242 case MachinePlaysBlack:
13245 GameEnds(GameIsDrawn, "Both players ran out of time",
13248 GameEnds(WhiteWins, "White wins on time", GE_PLAYER);
13250 DisplayError(_("Your opponent is not out of time"), 0);
13258 ClockClick(int which)
13259 { // [HGM] code moved to back-end from winboard.c
13260 if(which) { // black clock
13261 if (gameMode == EditPosition || gameMode == IcsExamining) {
13262 if(!appData.pieceMenu && blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13263 SetBlackToPlayEvent();
13264 } else if (gameMode == EditGame || shiftKey) {
13265 AdjustClock(which, -1);
13266 } else if (gameMode == IcsPlayingWhite ||
13267 gameMode == MachinePlaysBlack) {
13270 } else { // white clock
13271 if (gameMode == EditPosition || gameMode == IcsExamining) {
13272 if(!appData.pieceMenu && !blackPlaysFirst) EditPositionMenuEvent(ClearBoard, 0, 0);
13273 SetWhiteToPlayEvent();
13274 } else if (gameMode == EditGame || shiftKey) {
13275 AdjustClock(which, -1);
13276 } else if (gameMode == IcsPlayingBlack ||
13277 gameMode == MachinePlaysWhite) {
13286 /* Offer draw or accept pending draw offer from opponent */
13288 if (appData.icsActive) {
13289 /* Note: tournament rules require draw offers to be
13290 made after you make your move but before you punch
13291 your clock. Currently ICS doesn't let you do that;
13292 instead, you immediately punch your clock after making
13293 a move, but you can offer a draw at any time. */
13295 SendToICS(ics_prefix);
13296 SendToICS("draw\n");
13297 userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
13298 } else if (cmailMsgLoaded) {
13299 if (currentMove == cmailOldMove &&
13300 commentList[cmailOldMove] != NULL &&
13301 StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
13302 "Black offers a draw" : "White offers a draw")) {
13303 GameEnds(GameIsDrawn, "Draw agreed", GE_PLAYER);
13304 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_ACCEPT;
13305 } else if (currentMove == cmailOldMove + 1) {
13306 char *offer = WhiteOnMove(cmailOldMove) ?
13307 "White offers a draw" : "Black offers a draw";
13308 AppendComment(currentMove, offer, TRUE);
13309 DisplayComment(currentMove - 1, offer);
13310 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
13312 DisplayError(_("You must make your move before offering a draw"), 0);
13313 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_MOVE;
13315 } else if (first.offeredDraw) {
13316 GameEnds(GameIsDrawn, "Draw agreed", GE_XBOARD);
13318 if (first.sendDrawOffers) {
13319 SendToProgram("draw\n", &first);
13320 userOfferedDraw = TRUE;
13328 /* Offer Adjourn or accept pending Adjourn offer from opponent */
13330 if (appData.icsActive) {
13331 SendToICS(ics_prefix);
13332 SendToICS("adjourn\n");
13334 /* Currently GNU Chess doesn't offer or accept Adjourns */
13342 /* Offer Abort or accept pending Abort offer from opponent */
13344 if (appData.icsActive) {
13345 SendToICS(ics_prefix);
13346 SendToICS("abort\n");
13348 GameEnds(GameUnfinished, "Game aborted", GE_PLAYER);
13355 /* Resign. You can do this even if it's not your turn. */
13357 if (appData.icsActive) {
13358 SendToICS(ics_prefix);
13359 SendToICS("resign\n");
13361 switch (gameMode) {
13362 case MachinePlaysWhite:
13363 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13365 case MachinePlaysBlack:
13366 GameEnds(BlackWins, "White resigns", GE_PLAYER);
13369 if (cmailMsgLoaded) {
13371 if (WhiteOnMove(cmailOldMove)) {
13372 GameEnds(BlackWins, "White resigns", GE_PLAYER);
13374 GameEnds(WhiteWins, "Black resigns", GE_PLAYER);
13376 cmailMoveType[lastLoadGameNumber - 1] = CMAIL_RESIGN;
13387 StopObservingEvent()
13389 /* Stop observing current games */
13390 SendToICS(ics_prefix);
13391 SendToICS("unobserve\n");
13395 StopExaminingEvent()
13397 /* Stop observing current game */
13398 SendToICS(ics_prefix);
13399 SendToICS("unexamine\n");
13403 ForwardInner(target)
13408 if (appData.debugMode)
13409 fprintf(debugFP, "ForwardInner(%d), current %d, forward %d\n",
13410 target, currentMove, forwardMostMove);
13412 if (gameMode == EditPosition)
13415 if (gameMode == PlayFromGameFile && !pausing)
13418 if (gameMode == IcsExamining && pausing)
13419 limit = pauseExamForwardMostMove;
13421 limit = forwardMostMove;
13423 if (target > limit) target = limit;
13425 if (target > 0 && moveList[target - 1][0]) {
13426 int fromX, fromY, toX, toY;
13427 toX = moveList[target - 1][2] - AAA;
13428 toY = moveList[target - 1][3] - ONE;
13429 if (moveList[target - 1][1] == '@') {
13430 if (appData.highlightLastMove) {
13431 SetHighlights(-1, -1, toX, toY);
13434 fromX = moveList[target - 1][0] - AAA;
13435 fromY = moveList[target - 1][1] - ONE;
13436 if (target == currentMove + 1) {
13437 AnimateMove(boards[currentMove], fromX, fromY, toX, toY);
13439 if (appData.highlightLastMove) {
13440 SetHighlights(fromX, fromY, toX, toY);
13444 if (gameMode == EditGame || gameMode == AnalyzeMode ||
13445 gameMode == Training || gameMode == PlayFromGameFile ||
13446 gameMode == AnalyzeFile) {
13447 while (currentMove < target) {
13448 SendMoveToProgram(currentMove++, &first);
13451 currentMove = target;
13454 if (gameMode == EditGame || gameMode == EndOfGame) {
13455 whiteTimeRemaining = timeRemaining[0][currentMove];
13456 blackTimeRemaining = timeRemaining[1][currentMove];
13458 DisplayBothClocks();
13459 DisplayMove(currentMove - 1);
13460 DrawPosition(FALSE, boards[currentMove]);
13461 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13462 if ( !matchMode && gameMode != Training) { // [HGM] PV info: routine tests if empty
13463 DisplayComment(currentMove - 1, commentList[currentMove]);
13471 if (gameMode == IcsExamining && !pausing) {
13472 SendToICS(ics_prefix);
13473 SendToICS("forward\n");
13475 ForwardInner(currentMove + 1);
13482 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13483 /* to optimze, we temporarily turn off analysis mode while we feed
13484 * the remaining moves to the engine. Otherwise we get analysis output
13487 if (first.analysisSupport) {
13488 SendToProgram("exit\nforce\n", &first);
13489 first.analyzing = FALSE;
13493 if (gameMode == IcsExamining && !pausing) {
13494 SendToICS(ics_prefix);
13495 SendToICS("forward 999999\n");
13497 ForwardInner(forwardMostMove);
13500 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13501 /* we have fed all the moves, so reactivate analysis mode */
13502 SendToProgram("analyze\n", &first);
13503 first.analyzing = TRUE;
13504 /*first.maybeThinking = TRUE;*/
13505 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13510 BackwardInner(target)
13513 int full_redraw = TRUE; /* [AS] Was FALSE, had to change it! */
13515 if (appData.debugMode)
13516 fprintf(debugFP, "BackwardInner(%d), current %d, forward %d\n",
13517 target, currentMove, forwardMostMove);
13519 if (gameMode == EditPosition) return;
13520 if (currentMove <= backwardMostMove) {
13522 DrawPosition(full_redraw, boards[currentMove]);
13525 if (gameMode == PlayFromGameFile && !pausing)
13528 if (moveList[target][0]) {
13529 int fromX, fromY, toX, toY;
13530 toX = moveList[target][2] - AAA;
13531 toY = moveList[target][3] - ONE;
13532 if (moveList[target][1] == '@') {
13533 if (appData.highlightLastMove) {
13534 SetHighlights(-1, -1, toX, toY);
13537 fromX = moveList[target][0] - AAA;
13538 fromY = moveList[target][1] - ONE;
13539 if (target == currentMove - 1) {
13540 AnimateMove(boards[currentMove], toX, toY, fromX, fromY);
13542 if (appData.highlightLastMove) {
13543 SetHighlights(fromX, fromY, toX, toY);
13547 if (gameMode == EditGame || gameMode==AnalyzeMode ||
13548 gameMode == PlayFromGameFile || gameMode == AnalyzeFile) {
13549 while (currentMove > target) {
13550 SendToProgram("undo\n", &first);
13554 currentMove = target;
13557 if (gameMode == EditGame || gameMode == EndOfGame) {
13558 whiteTimeRemaining = timeRemaining[0][currentMove];
13559 blackTimeRemaining = timeRemaining[1][currentMove];
13561 DisplayBothClocks();
13562 DisplayMove(currentMove - 1);
13563 DrawPosition(full_redraw, boards[currentMove]);
13564 HistorySet(parseList,backwardMostMove,forwardMostMove,currentMove-1);
13565 // [HGM] PV info: routine tests if comment empty
13566 DisplayComment(currentMove - 1, commentList[currentMove]);
13572 if (gameMode == IcsExamining && !pausing) {
13573 SendToICS(ics_prefix);
13574 SendToICS("backward\n");
13576 BackwardInner(currentMove - 1);
13583 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13584 /* to optimize, we temporarily turn off analysis mode while we undo
13585 * all the moves. Otherwise we get analysis output after each undo.
13587 if (first.analysisSupport) {
13588 SendToProgram("exit\nforce\n", &first);
13589 first.analyzing = FALSE;
13593 if (gameMode == IcsExamining && !pausing) {
13594 SendToICS(ics_prefix);
13595 SendToICS("backward 999999\n");
13597 BackwardInner(backwardMostMove);
13600 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
13601 /* we have fed all the moves, so reactivate analysis mode */
13602 SendToProgram("analyze\n", &first);
13603 first.analyzing = TRUE;
13604 /*first.maybeThinking = TRUE;*/
13605 first.maybeThinking = FALSE; /* avoid killing GNU Chess */
13612 if (gameMode == PlayFromGameFile && !pausing) PauseEvent();
13613 if (to >= forwardMostMove) to = forwardMostMove;
13614 if (to <= backwardMostMove) to = backwardMostMove;
13615 if (to < currentMove) {
13623 RevertEvent(Boolean annotate)
13625 if(PopTail(annotate)) { // [HGM] vari: restore old game tail
13628 if (gameMode != IcsExamining) {
13629 DisplayError(_("You are not examining a game"), 0);
13633 DisplayError(_("You can't revert while pausing"), 0);
13636 SendToICS(ics_prefix);
13637 SendToICS("revert\n");
13643 switch (gameMode) {
13644 case MachinePlaysWhite:
13645 case MachinePlaysBlack:
13646 if (WhiteOnMove(forwardMostMove) == (gameMode == MachinePlaysWhite)) {
13647 DisplayError(_("Wait until your turn,\nor select Move Now"), 0);
13650 if (forwardMostMove < 2) return;
13651 currentMove = forwardMostMove = forwardMostMove - 2;
13652 whiteTimeRemaining = timeRemaining[0][currentMove];
13653 blackTimeRemaining = timeRemaining[1][currentMove];
13654 DisplayBothClocks();
13655 DisplayMove(currentMove - 1);
13656 ClearHighlights();/*!! could figure this out*/
13657 DrawPosition(TRUE, boards[currentMove]); /* [AS] Changed to full redraw! */
13658 SendToProgram("remove\n", &first);
13659 /*first.maybeThinking = TRUE;*/ /* GNU Chess does not ponder here */
13662 case BeginningOfGame:
13666 case IcsPlayingWhite:
13667 case IcsPlayingBlack:
13668 if (WhiteOnMove(forwardMostMove) == (gameMode == IcsPlayingWhite)) {
13669 SendToICS(ics_prefix);
13670 SendToICS("takeback 2\n");
13672 SendToICS(ics_prefix);
13673 SendToICS("takeback 1\n");
13682 ChessProgramState *cps;
13684 switch (gameMode) {
13685 case MachinePlaysWhite:
13686 if (!WhiteOnMove(forwardMostMove)) {
13687 DisplayError(_("It is your turn"), 0);
13692 case MachinePlaysBlack:
13693 if (WhiteOnMove(forwardMostMove)) {
13694 DisplayError(_("It is your turn"), 0);
13699 case TwoMachinesPlay:
13700 if (WhiteOnMove(forwardMostMove) ==
13701 (first.twoMachinesColor[0] == 'w')) {
13707 case BeginningOfGame:
13711 SendToProgram("?\n", cps);
13715 TruncateGameEvent()
13718 if (gameMode != EditGame) return;
13725 CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
13726 if (forwardMostMove > currentMove) {
13727 if (gameInfo.resultDetails != NULL) {
13728 free(gameInfo.resultDetails);
13729 gameInfo.resultDetails = NULL;
13730 gameInfo.result = GameUnfinished;
13732 forwardMostMove = currentMove;
13733 HistorySet(parseList, backwardMostMove, forwardMostMove,
13741 if (appData.noChessProgram) return;
13742 switch (gameMode) {
13743 case MachinePlaysWhite:
13744 if (WhiteOnMove(forwardMostMove)) {
13745 DisplayError(_("Wait until your turn"), 0);
13749 case BeginningOfGame:
13750 case MachinePlaysBlack:
13751 if (!WhiteOnMove(forwardMostMove)) {
13752 DisplayError(_("Wait until your turn"), 0);
13757 DisplayError(_("No hint available"), 0);
13760 SendToProgram("hint\n", &first);
13761 hintRequested = TRUE;
13767 if (appData.noChessProgram) return;
13768 switch (gameMode) {
13769 case MachinePlaysWhite:
13770 if (WhiteOnMove(forwardMostMove)) {
13771 DisplayError(_("Wait until your turn"), 0);
13775 case BeginningOfGame:
13776 case MachinePlaysBlack:
13777 if (!WhiteOnMove(forwardMostMove)) {
13778 DisplayError(_("Wait until your turn"), 0);
13783 EditPositionDone(TRUE);
13785 case TwoMachinesPlay:
13790 SendToProgram("bk\n", &first);
13791 bookOutput[0] = NULLCHAR;
13792 bookRequested = TRUE;
13798 char *tags = PGNTags(&gameInfo);
13799 TagsPopUp(tags, CmailMsg());
13803 /* end button procedures */
13806 PrintPosition(fp, move)
13812 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
13813 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
13814 char c = PieceToChar(boards[move][i][j]);
13815 fputc(c == 'x' ? '.' : c, fp);
13816 fputc(j == BOARD_RGHT - 1 ? '\n' : ' ', fp);
13819 if ((gameMode == EditPosition) ? !blackPlaysFirst : (move % 2 == 0))
13820 fprintf(fp, "white to play\n");
13822 fprintf(fp, "black to play\n");
13829 if (gameInfo.white != NULL) {
13830 fprintf(fp, "\t%s vs. %s\n", gameInfo.white, gameInfo.black);
13836 /* Find last component of program's own name, using some heuristics */
13838 TidyProgramName(prog, host, buf)
13839 char *prog, *host, buf[MSG_SIZ];
13842 int local = (strcmp(host, "localhost") == 0);
13843 while (!local && (p = strchr(prog, ';')) != NULL) {
13845 while (*p == ' ') p++;
13848 if (*prog == '"' || *prog == '\'') {
13849 q = strchr(prog + 1, *prog);
13851 q = strchr(prog, ' ');
13853 if (q == NULL) q = prog + strlen(prog);
13855 while (p >= prog && *p != '/' && *p != '\\') p--;
13857 if(p == prog && *p == '"') p++;
13858 if (q - p >= 4 && StrCaseCmp(q - 4, ".exe") == 0) q -= 4;
13859 memcpy(buf, p, q - p);
13860 buf[q - p] = NULLCHAR;
13868 TimeControlTagValue()
13871 if (!appData.clockMode) {
13872 safeStrCpy(buf, "-", sizeof(buf)/sizeof(buf[0]));
13873 } else if (movesPerSession > 0) {
13874 snprintf(buf, MSG_SIZ, "%d/%ld", movesPerSession, timeControl/1000);
13875 } else if (timeIncrement == 0) {
13876 snprintf(buf, MSG_SIZ, "%ld", timeControl/1000);
13878 snprintf(buf, MSG_SIZ, "%ld+%ld", timeControl/1000, timeIncrement/1000);
13880 return StrSave(buf);
13886 /* This routine is used only for certain modes */
13887 VariantClass v = gameInfo.variant;
13888 ChessMove r = GameUnfinished;
13891 if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
13892 r = gameInfo.result;
13893 p = gameInfo.resultDetails;
13894 gameInfo.resultDetails = NULL;
13896 ClearGameInfo(&gameInfo);
13897 gameInfo.variant = v;
13899 switch (gameMode) {
13900 case MachinePlaysWhite:
13901 gameInfo.event = StrSave( appData.pgnEventHeader );
13902 gameInfo.site = StrSave(HostName());
13903 gameInfo.date = PGNDate();
13904 gameInfo.round = StrSave("-");
13905 gameInfo.white = StrSave(first.tidy);
13906 gameInfo.black = StrSave(UserName());
13907 gameInfo.timeControl = TimeControlTagValue();
13910 case MachinePlaysBlack:
13911 gameInfo.event = StrSave( appData.pgnEventHeader );
13912 gameInfo.site = StrSave(HostName());
13913 gameInfo.date = PGNDate();
13914 gameInfo.round = StrSave("-");
13915 gameInfo.white = StrSave(UserName());
13916 gameInfo.black = StrSave(first.tidy);
13917 gameInfo.timeControl = TimeControlTagValue();
13920 case TwoMachinesPlay:
13921 gameInfo.event = StrSave( appData.pgnEventHeader );
13922 gameInfo.site = StrSave(HostName());
13923 gameInfo.date = PGNDate();
13926 snprintf(buf, MSG_SIZ, "%d", roundNr);
13927 gameInfo.round = StrSave(buf);
13929 gameInfo.round = StrSave("-");
13931 if (first.twoMachinesColor[0] == 'w') {
13932 gameInfo.white = StrSave(first.tidy);
13933 gameInfo.black = StrSave(second.tidy);
13935 gameInfo.white = StrSave(second.tidy);
13936 gameInfo.black = StrSave(first.tidy);
13938 gameInfo.timeControl = TimeControlTagValue();
13942 gameInfo.event = StrSave("Edited game");
13943 gameInfo.site = StrSave(HostName());
13944 gameInfo.date = PGNDate();
13945 gameInfo.round = StrSave("-");
13946 gameInfo.white = StrSave("-");
13947 gameInfo.black = StrSave("-");
13948 gameInfo.result = r;
13949 gameInfo.resultDetails = p;
13953 gameInfo.event = StrSave("Edited position");
13954 gameInfo.site = StrSave(HostName());
13955 gameInfo.date = PGNDate();
13956 gameInfo.round = StrSave("-");
13957 gameInfo.white = StrSave("-");
13958 gameInfo.black = StrSave("-");
13961 case IcsPlayingWhite:
13962 case IcsPlayingBlack:
13967 case PlayFromGameFile:
13968 gameInfo.event = StrSave("Game from non-PGN file");
13969 gameInfo.site = StrSave(HostName());
13970 gameInfo.date = PGNDate();
13971 gameInfo.round = StrSave("-");
13972 gameInfo.white = StrSave("?");
13973 gameInfo.black = StrSave("?");
13982 ReplaceComment(index, text)
13990 if(index && sscanf(text, "%f/%d", &score, &len) == 2 &&
13991 pvInfoList[index-1].depth == len &&
13992 fabs(pvInfoList[index-1].score - score*100.) < 0.5 &&
13993 (p = strchr(text, '\n'))) text = p; // [HGM] strip off first line with PV info, if any
13994 while (*text == '\n') text++;
13995 len = strlen(text);
13996 while (len > 0 && text[len - 1] == '\n') len--;
13998 if (commentList[index] != NULL)
13999 free(commentList[index]);
14002 commentList[index] = NULL;
14005 if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
14006 *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
14007 *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
14008 commentList[index] = (char *) malloc(len + 2);
14009 strncpy(commentList[index], text, len);
14010 commentList[index][len] = '\n';
14011 commentList[index][len + 1] = NULLCHAR;
14013 // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
14015 commentList[index] = (char *) malloc(len + 7);
14016 safeStrCpy(commentList[index], "{\n", 3);
14017 safeStrCpy(commentList[index]+2, text, len+1);
14018 commentList[index][len+2] = NULLCHAR;
14019 while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
14020 strcat(commentList[index], "\n}\n");
14034 if (ch == '\r') continue;
14036 } while (ch != '\0');
14040 AppendComment(index, text, addBraces)
14043 Boolean addBraces; // [HGM] braces: tells if we should add {}
14048 if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
14049 text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
14052 while (*text == '\n') text++;
14053 len = strlen(text);
14054 while (len > 0 && text[len - 1] == '\n') len--;
14056 if (len == 0) return;
14058 if (commentList[index] != NULL) {
14059 old = commentList[index];
14060 oldlen = strlen(old);
14061 while(commentList[index][oldlen-1] == '\n')
14062 commentList[index][--oldlen] = NULLCHAR;
14063 commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
14064 safeStrCpy(commentList[index], old, oldlen + len + 6);
14066 // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
14067 if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces == TRUE)) {
14068 if(addBraces == TRUE) addBraces = FALSE; else { text++; len--; }
14069 while (*text == '\n') { text++; len--; }
14070 commentList[index][--oldlen] = NULLCHAR;
14072 if(addBraces) strcat(commentList[index], addBraces == 2 ? "\n(" : "\n{\n");
14073 else strcat(commentList[index], "\n");
14074 strcat(commentList[index], text);
14075 if(addBraces) strcat(commentList[index], addBraces == 2 ? ")\n" : "\n}\n");
14076 else strcat(commentList[index], "\n");
14078 commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
14080 safeStrCpy(commentList[index], addBraces == 2 ? "(" : "{\n", 3);
14081 else commentList[index][0] = NULLCHAR;
14082 strcat(commentList[index], text);
14083 strcat(commentList[index], addBraces == 2 ? ")\n" : "\n");
14084 if(addBraces == TRUE) strcat(commentList[index], "}\n");
14088 static char * FindStr( char * text, char * sub_text )
14090 char * result = strstr( text, sub_text );
14092 if( result != NULL ) {
14093 result += strlen( sub_text );
14099 /* [AS] Try to extract PV info from PGN comment */
14100 /* [HGM] PV time: and then remove it, to prevent it appearing twice */
14101 char *GetInfoFromComment( int index, char * text )
14103 char * sep = text, *p;
14105 if( text != NULL && index > 0 ) {
14108 int time = -1, sec = 0, deci;
14109 char * s_eval = FindStr( text, "[%eval " );
14110 char * s_emt = FindStr( text, "[%emt " );
14112 if( s_eval != NULL || s_emt != NULL ) {
14116 if( s_eval != NULL ) {
14117 if( sscanf( s_eval, "%d,%d%c", &score, &depth, &delim ) != 3 ) {
14121 if( delim != ']' ) {
14126 if( s_emt != NULL ) {
14131 /* We expect something like: [+|-]nnn.nn/dd */
14134 if(*text != '{') return text; // [HGM] braces: must be normal comment
14136 sep = strchr( text, '/' );
14137 if( sep == NULL || sep < (text+4) ) {
14142 if(p[1] == '(') { // comment starts with PV
14143 p = strchr(p, ')'); // locate end of PV
14144 if(p == NULL || sep < p+5) return text;
14145 // at this point we have something like "{(.*) +0.23/6 ..."
14146 p = text; while(*++p != ')') p[-1] = *p; p[-1] = ')';
14147 *p = '\n'; while(*p == ' ' || *p == '\n') p++; *--p = '{';
14148 // we now moved the brace to behind the PV: "(.*) {+0.23/6 ..."
14150 time = -1; sec = -1; deci = -1;
14151 if( sscanf( p+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
14152 sscanf( p+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
14153 sscanf( p+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
14154 sscanf( p+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
14158 if( score_lo < 0 || score_lo >= 100 ) {
14162 if(sec >= 0) time = 600*time + 10*sec; else
14163 if(deci >= 0) time = 10*time + deci; else time *= 10; // deci-sec
14165 score = score >= 0 ? score*100 + score_lo : score*100 - score_lo;
14167 /* [HGM] PV time: now locate end of PV info */
14168 while( *++sep >= '0' && *sep <= '9'); // strip depth
14170 while( *++sep >= '0' && *sep <= '9' || *sep == '\n'); // strip time
14172 while( *++sep >= '0' && *sep <= '9'); // strip seconds
14174 while( *++sep >= '0' && *sep <= '9'); // strip fractional seconds
14175 while(*sep == ' ') sep++;
14186 pvInfoList[index-1].depth = depth;
14187 pvInfoList[index-1].score = score;
14188 pvInfoList[index-1].time = 10*time; // centi-sec
14189 if(*sep == '}') *sep = 0; else *--sep = '{';
14190 if(p != text) { while(*p++ = *sep++); sep = text; } // squeeze out space between PV and comment, and return both
14196 SendToProgram(message, cps)
14198 ChessProgramState *cps;
14200 int count, outCount, error;
14203 if (cps->pr == NULL) return;
14206 if (appData.debugMode) {
14209 fprintf(debugFP, "%ld >%-6s: %s",
14210 SubtractTimeMarks(&now, &programStartTime),
14211 cps->which, message);
14214 count = strlen(message);
14215 outCount = OutputToProcess(cps->pr, message, count, &error);
14216 if (outCount < count && !exiting
14217 && !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
14218 if(!cps->initDone) return; // [HGM] should not generate fatal error during engine load
14219 snprintf(buf, MSG_SIZ, _("Error writing to %s chess program"), _(cps->which));
14220 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14221 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14222 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14223 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14224 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14226 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14227 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14228 gameInfo.result = res;
14230 gameInfo.resultDetails = StrSave(buf);
14232 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14233 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14238 ReceiveFromProgram(isr, closure, message, count, error)
14239 InputSourceRef isr;
14247 ChessProgramState *cps = (ChessProgramState *)closure;
14249 if (isr != cps->isr) return; /* Killed intentionally */
14252 RemoveInputSource(cps->isr);
14253 snprintf(buf, MSG_SIZ, _("Error: %s chess program (%s) exited unexpectedly"),
14254 _(cps->which), cps->program);
14255 if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
14256 if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
14257 snprintf(buf, MSG_SIZ, _("%s program exits in draw position (%s)"), _(cps->which), cps->program);
14258 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(GameIsDrawn, buf, GE_XBOARD); return; }
14259 gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
14261 ChessMove res = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
14262 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; GameEnds(res, buf, GE_XBOARD); return; }
14263 gameInfo.result = res;
14265 gameInfo.resultDetails = StrSave(buf);
14267 if(matchMode && appData.tourneyFile[0]) { cps->pr = NoProc; return; }
14268 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
14270 snprintf(buf, MSG_SIZ, _("Error reading from %s chess program (%s)"),
14271 _(cps->which), cps->program);
14272 RemoveInputSource(cps->isr);
14274 /* [AS] Program is misbehaving badly... kill it */
14275 if( count == -2 ) {
14276 DestroyChildProcess( cps->pr, 9 );
14280 if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
14285 if ((end_str = strchr(message, '\r')) != NULL)
14286 *end_str = NULLCHAR;
14287 if ((end_str = strchr(message, '\n')) != NULL)
14288 *end_str = NULLCHAR;
14290 if (appData.debugMode) {
14291 TimeMark now; int print = 1;
14292 char *quote = ""; char c; int i;
14294 if(appData.engineComments != 1) { /* [HGM] debug: decide if protocol-violating output is written */
14295 char start = message[0];
14296 if(start >='A' && start <= 'Z') start += 'a' - 'A'; // be tolerant to capitalizing
14297 if(sscanf(message, "%d%c%d%d%d", &i, &c, &i, &i, &i) != 5 &&
14298 sscanf(message, "move %c", &c)!=1 && sscanf(message, "offer%c", &c)!=1 &&
14299 sscanf(message, "resign%c", &c)!=1 && sscanf(message, "feature %c", &c)!=1 &&
14300 sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
14301 sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
14302 sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
14303 sscanf(message, "setboard %c", &c)!=1 && sscanf(message, "setup %c", &c)!=1 &&
14304 sscanf(message, "hint: %c", &c)!=1 &&
14305 sscanf(message, "pong %c", &c)!=1 && start != '#') {
14306 quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
14307 print = (appData.engineComments >= 2);
14309 message[0] = start; // restore original message
14313 fprintf(debugFP, "%ld <%-6s: %s%s\n",
14314 SubtractTimeMarks(&now, &programStartTime), cps->which,
14320 /* [DM] if icsEngineAnalyze is active we block all whisper and kibitz output, because nobody want to see this */
14321 if (appData.icsEngineAnalyze) {
14322 if (strstr(message, "whisper") != NULL ||
14323 strstr(message, "kibitz") != NULL ||
14324 strstr(message, "tellics") != NULL) return;
14327 HandleMachineMove(message, cps);
14332 SendTimeControl(cps, mps, tc, inc, sd, st)
14333 ChessProgramState *cps;
14334 int mps, inc, sd, st;
14340 if( timeControl_2 > 0 ) {
14341 if( (gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b') ) {
14342 tc = timeControl_2;
14345 tc /= cps->timeOdds; /* [HGM] time odds: apply before telling engine */
14346 inc /= cps->timeOdds;
14347 st /= cps->timeOdds;
14349 seconds = (tc / 1000) % 60; /* [HGM] displaced to after applying odds */
14352 /* Set exact time per move, normally using st command */
14353 if (cps->stKludge) {
14354 /* GNU Chess 4 has no st command; uses level in a nonstandard way */
14356 if (seconds == 0) {
14357 snprintf(buf, MSG_SIZ, "level 1 %d\n", st/60);
14359 snprintf(buf, MSG_SIZ, "level 1 %d:%02d\n", st/60, seconds);
14362 snprintf(buf, MSG_SIZ, "st %d\n", st);
14365 /* Set conventional or incremental time control, using level command */
14366 if (seconds == 0) {
14367 /* Note old gnuchess bug -- minutes:seconds used to not work.
14368 Fixed in later versions, but still avoid :seconds
14369 when seconds is 0. */
14370 snprintf(buf, MSG_SIZ, "level %d %ld %g\n", mps, tc/60000, inc/1000.);
14372 snprintf(buf, MSG_SIZ, "level %d %ld:%02d %g\n", mps, tc/60000,
14373 seconds, inc/1000.);
14376 SendToProgram(buf, cps);
14378 /* Orthoganally (except for GNU Chess 4), limit time to st seconds */
14379 /* Orthogonally, limit search to given depth */
14381 if (cps->sdKludge) {
14382 snprintf(buf, MSG_SIZ, "depth\n%d\n", sd);
14384 snprintf(buf, MSG_SIZ, "sd %d\n", sd);
14386 SendToProgram(buf, cps);
14389 if(cps->nps >= 0) { /* [HGM] nps */
14390 if(cps->supportsNPS == FALSE)
14391 cps->nps = -1; // don't use if engine explicitly says not supported!
14393 snprintf(buf, MSG_SIZ, "nps %d\n", cps->nps);
14394 SendToProgram(buf, cps);
14399 ChessProgramState *WhitePlayer()
14400 /* [HGM] return pointer to 'first' or 'second', depending on who plays white */
14402 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b' ||
14403 gameMode == BeginningOfGame || gameMode == MachinePlaysBlack)
14409 SendTimeRemaining(cps, machineWhite)
14410 ChessProgramState *cps;
14411 int /*boolean*/ machineWhite;
14413 char message[MSG_SIZ];
14416 /* Note: this routine must be called when the clocks are stopped
14417 or when they have *just* been set or switched; otherwise
14418 it will be off by the time since the current tick started.
14420 if (machineWhite) {
14421 time = whiteTimeRemaining / 10;
14422 otime = blackTimeRemaining / 10;
14424 time = blackTimeRemaining / 10;
14425 otime = whiteTimeRemaining / 10;
14427 /* [HGM] translate opponent's time by time-odds factor */
14428 otime = (otime * cps->other->timeOdds) / cps->timeOdds;
14429 if (appData.debugMode) {
14430 fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
14433 if (time <= 0) time = 1;
14434 if (otime <= 0) otime = 1;
14436 snprintf(message, MSG_SIZ, "time %ld\n", time);
14437 SendToProgram(message, cps);
14439 snprintf(message, MSG_SIZ, "otim %ld\n", otime);
14440 SendToProgram(message, cps);
14444 BoolFeature(p, name, loc, cps)
14448 ChessProgramState *cps;
14451 int len = strlen(name);
14454 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14456 sscanf(*p, "%d", &val);
14458 while (**p && **p != ' ')
14460 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14461 SendToProgram(buf, cps);
14468 IntFeature(p, name, loc, cps)
14472 ChessProgramState *cps;
14475 int len = strlen(name);
14476 if (strncmp((*p), name, len) == 0 && (*p)[len] == '=') {
14478 sscanf(*p, "%d", loc);
14479 while (**p && **p != ' ') (*p)++;
14480 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14481 SendToProgram(buf, cps);
14488 StringFeature(p, name, loc, cps)
14492 ChessProgramState *cps;
14495 int len = strlen(name);
14496 if (strncmp((*p), name, len) == 0
14497 && (*p)[len] == '=' && (*p)[len+1] == '\"') {
14499 sscanf(*p, "%[^\"]", loc);
14500 while (**p && **p != '\"') (*p)++;
14501 if (**p == '\"') (*p)++;
14502 snprintf(buf, MSG_SIZ, "accepted %s\n", name);
14503 SendToProgram(buf, cps);
14510 ParseOption(Option *opt, ChessProgramState *cps)
14511 // [HGM] options: process the string that defines an engine option, and determine
14512 // name, type, default value, and allowed value range
14514 char *p, *q, buf[MSG_SIZ];
14515 int n, min = (-1)<<31, max = 1<<31, def;
14517 if(p = strstr(opt->name, " -spin ")) {
14518 if((n = sscanf(p, " -spin %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14519 if(max < min) max = min; // enforce consistency
14520 if(def < min) def = min;
14521 if(def > max) def = max;
14526 } else if((p = strstr(opt->name, " -slider "))) {
14527 // for now -slider is a synonym for -spin, to already provide compatibility with future polyglots
14528 if((n = sscanf(p, " -slider %d %d %d", &def, &min, &max)) < 3 ) return FALSE;
14529 if(max < min) max = min; // enforce consistency
14530 if(def < min) def = min;
14531 if(def > max) def = max;
14535 opt->type = Spin; // Slider;
14536 } else if((p = strstr(opt->name, " -string "))) {
14537 opt->textValue = p+9;
14538 opt->type = TextBox;
14539 } else if((p = strstr(opt->name, " -file "))) {
14540 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14541 opt->textValue = p+7;
14542 opt->type = FileName; // FileName;
14543 } else if((p = strstr(opt->name, " -path "))) {
14544 // for now -file is a synonym for -string, to already provide compatibility with future polyglots
14545 opt->textValue = p+7;
14546 opt->type = PathName; // PathName;
14547 } else if(p = strstr(opt->name, " -check ")) {
14548 if(sscanf(p, " -check %d", &def) < 1) return FALSE;
14549 opt->value = (def != 0);
14550 opt->type = CheckBox;
14551 } else if(p = strstr(opt->name, " -combo ")) {
14552 opt->textValue = (char*) (&cps->comboList[cps->comboCnt]); // cheat with pointer type
14553 cps->comboList[cps->comboCnt++] = q = p+8; // holds possible choices
14554 if(*q == '*') cps->comboList[cps->comboCnt-1]++;
14555 opt->value = n = 0;
14556 while(q = StrStr(q, " /// ")) {
14557 n++; *q = 0; // count choices, and null-terminate each of them
14559 if(*q == '*') { // remember default, which is marked with * prefix
14563 cps->comboList[cps->comboCnt++] = q;
14565 cps->comboList[cps->comboCnt++] = NULL;
14567 opt->type = ComboBox;
14568 } else if(p = strstr(opt->name, " -button")) {
14569 opt->type = Button;
14570 } else if(p = strstr(opt->name, " -save")) {
14571 opt->type = SaveButton;
14572 } else return FALSE;
14573 *p = 0; // terminate option name
14574 // now look if the command-line options define a setting for this engine option.
14575 if(cps->optionSettings && cps->optionSettings[0])
14576 p = strstr(cps->optionSettings, opt->name); else p = NULL;
14577 if(p && (p == cps->optionSettings || p[-1] == ',')) {
14578 snprintf(buf, MSG_SIZ, "option %s", p);
14579 if(p = strstr(buf, ",")) *p = 0;
14580 if(q = strchr(buf, '=')) switch(opt->type) {
14582 for(n=0; n<opt->max; n++)
14583 if(!strcmp(((char**)opt->textValue)[n], q+1)) opt->value = n;
14586 safeStrCpy(opt->textValue, q+1, MSG_SIZ - (opt->textValue - opt->name));
14590 opt->value = atoi(q+1);
14595 SendToProgram(buf, cps);
14601 FeatureDone(cps, val)
14602 ChessProgramState* cps;
14605 DelayedEventCallback cb = GetDelayedEvent();
14606 if ((cb == InitBackEnd3 && cps == &first) ||
14607 (cb == SettingsMenuIfReady && cps == &second) ||
14608 (cb == LoadEngine) ||
14609 (cb == TwoMachinesEventIfReady)) {
14610 CancelDelayedEvent();
14611 ScheduleDelayedEvent(cb, val ? 1 : 3600000);
14613 cps->initDone = val;
14616 /* Parse feature command from engine */
14618 ParseFeatures(args, cps)
14620 ChessProgramState *cps;
14628 while (*p == ' ') p++;
14629 if (*p == NULLCHAR) return;
14631 if (BoolFeature(&p, "setboard", &cps->useSetboard, cps)) continue;
14632 if (BoolFeature(&p, "time", &cps->sendTime, cps)) continue;
14633 if (BoolFeature(&p, "draw", &cps->sendDrawOffers, cps)) continue;
14634 if (BoolFeature(&p, "sigint", &cps->useSigint, cps)) continue;
14635 if (BoolFeature(&p, "sigterm", &cps->useSigterm, cps)) continue;
14636 if (BoolFeature(&p, "reuse", &val, cps)) {
14637 /* Engine can disable reuse, but can't enable it if user said no */
14638 if (!val) cps->reuse = FALSE;
14641 if (BoolFeature(&p, "analyze", &cps->analysisSupport, cps)) continue;
14642 if (StringFeature(&p, "myname", &cps->tidy, cps)) {
14643 if (gameMode == TwoMachinesPlay) {
14644 DisplayTwoMachinesTitle();
14650 if (StringFeature(&p, "variants", &cps->variants, cps)) continue;
14651 if (BoolFeature(&p, "san", &cps->useSAN, cps)) continue;
14652 if (BoolFeature(&p, "ping", &cps->usePing, cps)) continue;
14653 if (BoolFeature(&p, "playother", &cps->usePlayother, cps)) continue;
14654 if (BoolFeature(&p, "colors", &cps->useColors, cps)) continue;
14655 if (BoolFeature(&p, "usermove", &cps->useUsermove, cps)) continue;
14656 if (BoolFeature(&p, "ics", &cps->sendICS, cps)) continue;
14657 if (BoolFeature(&p, "name", &cps->sendName, cps)) continue;
14658 if (BoolFeature(&p, "pause", &val, cps)) continue; /* unused at present */
14659 if (IntFeature(&p, "done", &val, cps)) {
14660 FeatureDone(cps, val);
14663 /* Added by Tord: */
14664 if (BoolFeature(&p, "fen960", &cps->useFEN960, cps)) continue;
14665 if (BoolFeature(&p, "oocastle", &cps->useOOCastle, cps)) continue;
14666 /* End of additions by Tord */
14668 /* [HGM] added features: */
14669 if (BoolFeature(&p, "debug", &cps->debug, cps)) continue;
14670 if (BoolFeature(&p, "nps", &cps->supportsNPS, cps)) continue;
14671 if (IntFeature(&p, "level", &cps->maxNrOfSessions, cps)) continue;
14672 if (BoolFeature(&p, "memory", &cps->memSize, cps)) continue;
14673 if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
14674 if (StringFeature(&p, "egt", &cps->egtFormats, cps)) continue;
14675 if (StringFeature(&p, "option", &(cps->option[cps->nrOptions].name), cps)) {
14676 if(!ParseOption(&(cps->option[cps->nrOptions++]), cps)) { // [HGM] options: add option feature
14677 snprintf(buf, MSG_SIZ, "rejected option %s\n", cps->option[--cps->nrOptions].name);
14678 SendToProgram(buf, cps);
14681 if(cps->nrOptions >= MAX_OPTIONS) {
14683 snprintf(buf, MSG_SIZ, _("%s engine has too many options\n"), _(cps->which));
14684 DisplayError(buf, 0);
14688 /* End of additions by HGM */
14690 /* unknown feature: complain and skip */
14692 while (*q && *q != '=') q++;
14693 snprintf(buf, MSG_SIZ,"rejected %.*s\n", (int)(q-p), p);
14694 SendToProgram(buf, cps);
14700 while (*p && *p != '\"') p++;
14701 if (*p == '\"') p++;
14703 while (*p && *p != ' ') p++;
14711 PeriodicUpdatesEvent(newState)
14714 if (newState == appData.periodicUpdates)
14717 appData.periodicUpdates=newState;
14719 /* Display type changes, so update it now */
14720 // DisplayAnalysis();
14722 /* Get the ball rolling again... */
14724 AnalysisPeriodicEvent(1);
14725 StartAnalysisClock();
14730 PonderNextMoveEvent(newState)
14733 if (newState == appData.ponderNextMove) return;
14734 if (gameMode == EditPosition) EditPositionDone(TRUE);
14736 SendToProgram("hard\n", &first);
14737 if (gameMode == TwoMachinesPlay) {
14738 SendToProgram("hard\n", &second);
14741 SendToProgram("easy\n", &first);
14742 thinkOutput[0] = NULLCHAR;
14743 if (gameMode == TwoMachinesPlay) {
14744 SendToProgram("easy\n", &second);
14747 appData.ponderNextMove = newState;
14751 NewSettingEvent(option, feature, command, value)
14753 int option, value, *feature;
14757 if (gameMode == EditPosition) EditPositionDone(TRUE);
14758 snprintf(buf, MSG_SIZ,"%s%s %d\n", (option ? "option ": ""), command, value);
14759 if(feature == NULL || *feature) SendToProgram(buf, &first);
14760 if (gameMode == TwoMachinesPlay) {
14761 if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
14766 ShowThinkingEvent()
14767 // [HGM] thinking: this routine is now also called from "Options -> Engine..." popup
14769 static int oldState = 2; // kludge alert! Neither true nor fals, so first time oldState is always updated
14770 int newState = appData.showThinking
14771 // [HGM] thinking: other features now need thinking output as well
14772 || !appData.hideThinkingFromHuman || appData.adjudicateLossThreshold != 0 || EngineOutputIsUp();
14774 if (oldState == newState) return;
14775 oldState = newState;
14776 if (gameMode == EditPosition) EditPositionDone(TRUE);
14778 SendToProgram("post\n", &first);
14779 if (gameMode == TwoMachinesPlay) {
14780 SendToProgram("post\n", &second);
14783 SendToProgram("nopost\n", &first);
14784 thinkOutput[0] = NULLCHAR;
14785 if (gameMode == TwoMachinesPlay) {
14786 SendToProgram("nopost\n", &second);
14789 // appData.showThinking = newState; // [HGM] thinking: responsible option should already have be changed when calling this routine!
14793 AskQuestionEvent(title, question, replyPrefix, which)
14794 char *title; char *question; char *replyPrefix; char *which;
14796 ProcRef pr = (which[0] == '1') ? first.pr : second.pr;
14797 if (pr == NoProc) return;
14798 AskQuestion(title, question, replyPrefix, pr);
14802 TypeInEvent(char firstChar)
14804 if ((gameMode == BeginningOfGame && !appData.icsActive) ||
\r
14805 gameMode == MachinePlaysWhite || gameMode == MachinePlaysBlack ||
\r
14806 gameMode == AnalyzeMode || gameMode == EditGame ||
\r
14807 gameMode == EditPosition || gameMode == IcsExamining ||
\r
14808 gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
\r
14809 isdigit(firstChar) && // [HGM] movenum: allow typing in of move nr in 'passive' modes
\r
14810 ( gameMode == AnalyzeFile || gameMode == PlayFromGameFile ||
\r
14811 gameMode == IcsObserving || gameMode == TwoMachinesPlay ) ||
\r
14812 gameMode == Training) PopUpMoveDialog(firstChar);
14816 TypeInDoneEvent(char *move)
14819 int n, fromX, fromY, toX, toY;
14821 ChessMove moveType;
\r
14824 if(gameMode == EditPosition && ParseFEN(board, &n, move) ) {
\r
14825 EditPositionPasteFEN(move);
\r
14828 // [HGM] movenum: allow move number to be typed in any mode
\r
14829 if(sscanf(move, "%d", &n) == 1 && n != 0 ) {
\r
14830 ToNrEvent(2*n-1);
\r
14834 if (gameMode != EditGame && currentMove != forwardMostMove &&
\r
14835 gameMode != Training) {
\r
14836 DisplayMoveError(_("Displayed move is not current"));
\r
14838 int ok = ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
\r
14839 &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
\r
14840 if(!ok && move[0] >= 'a') { move[0] += 'A' - 'a'; ok = 2; } // [HGM] try also capitalized
\r
14841 if (ok==1 || ok && ParseOneMove(move, gameMode == EditPosition ? blackPlaysFirst : currentMove,
\r
14842 &moveType, &fromX, &fromY, &toX, &toY, &promoChar)) {
\r
14843 UserMoveEvent(fromX, fromY, toX, toY, promoChar);
\r
14845 DisplayMoveError(_("Could not parse move"));
\r
14851 DisplayMove(moveNumber)
14854 char message[MSG_SIZ];
14856 char cpThinkOutput[MSG_SIZ];
14858 if(appData.noGUI) return; // [HGM] fast: suppress display of moves
14860 if (moveNumber == forwardMostMove - 1 ||
14861 gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
14863 safeStrCpy(cpThinkOutput, thinkOutput, sizeof(cpThinkOutput)/sizeof(cpThinkOutput[0]));
14865 if (strchr(cpThinkOutput, '\n')) {
14866 *strchr(cpThinkOutput, '\n') = NULLCHAR;
14869 *cpThinkOutput = NULLCHAR;
14872 /* [AS] Hide thinking from human user */
14873 if( appData.hideThinkingFromHuman && gameMode != TwoMachinesPlay ) {
14874 *cpThinkOutput = NULLCHAR;
14875 if( thinkOutput[0] != NULLCHAR ) {
14878 for( i=0; i<=hiddenThinkOutputState; i++ ) {
14879 cpThinkOutput[i] = '.';
14881 cpThinkOutput[i] = NULLCHAR;
14882 hiddenThinkOutputState = (hiddenThinkOutputState + 1) % 3;
14886 if (moveNumber == forwardMostMove - 1 &&
14887 gameInfo.resultDetails != NULL) {
14888 if (gameInfo.resultDetails[0] == NULLCHAR) {
14889 snprintf(res, MSG_SIZ, " %s", PGNResult(gameInfo.result));
14891 snprintf(res, MSG_SIZ, " {%s} %s",
14892 T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
14898 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14899 DisplayMessage(res, cpThinkOutput);
14901 snprintf(message, MSG_SIZ, "%d.%s%s%s", moveNumber / 2 + 1,
14902 WhiteOnMove(moveNumber) ? " " : ".. ",
14903 parseList[moveNumber], res);
14904 DisplayMessage(message, cpThinkOutput);
14909 DisplayComment(moveNumber, text)
14913 char title[MSG_SIZ];
14914 char buf[8000]; // comment can be long!
14917 if (moveNumber < 0 || parseList[moveNumber][0] == NULLCHAR) {
14918 safeStrCpy(title, "Comment", sizeof(title)/sizeof(title[0]));
14920 snprintf(title,MSG_SIZ, "Comment on %d.%s%s", moveNumber / 2 + 1,
14921 WhiteOnMove(moveNumber) ? " " : ".. ",
14922 parseList[moveNumber]);
14924 // [HGM] PV info: display PV info together with (or as) comment
14925 if(moveNumber >= 0 && (depth = pvInfoList[moveNumber].depth) > 0) {
14926 if(text == NULL) text = "";
14927 score = pvInfoList[moveNumber].score;
14928 snprintf(buf,sizeof(buf)/sizeof(buf[0]), "%s%.2f/%d %d\n%s", score>0 ? "+" : "", score/100.,
14929 depth, (pvInfoList[moveNumber].time+50)/100, text);
14932 if (text != NULL && (appData.autoDisplayComment || commentUp))
14933 CommentPopUp(title, text);
14936 /* This routine sends a ^C interrupt to gnuchess, to awaken it if it
14937 * might be busy thinking or pondering. It can be omitted if your
14938 * gnuchess is configured to stop thinking immediately on any user
14939 * input. However, that gnuchess feature depends on the FIONREAD
14940 * ioctl, which does not work properly on some flavors of Unix.
14944 ChessProgramState *cps;
14947 if (!cps->useSigint) return;
14948 if (appData.noChessProgram || (cps->pr == NoProc)) return;
14949 switch (gameMode) {
14950 case MachinePlaysWhite:
14951 case MachinePlaysBlack:
14952 case TwoMachinesPlay:
14953 case IcsPlayingWhite:
14954 case IcsPlayingBlack:
14957 /* Skip if we know it isn't thinking */
14958 if (!cps->maybeThinking) return;
14959 if (appData.debugMode)
14960 fprintf(debugFP, "Interrupting %s\n", cps->which);
14961 InterruptChildProcess(cps->pr);
14962 cps->maybeThinking = FALSE;
14967 #endif /*ATTENTION*/
14973 if (whiteTimeRemaining <= 0) {
14976 if (appData.icsActive) {
14977 if (appData.autoCallFlag &&
14978 gameMode == IcsPlayingBlack && !blackFlag) {
14979 SendToICS(ics_prefix);
14980 SendToICS("flag\n");
14984 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
14986 if(gameMode != TwoMachinesPlay) DisplayTitle(_("White's flag fell"));
14987 if (appData.autoCallFlag) {
14988 GameEnds(BlackWins, "Black wins on time", GE_XBOARD);
14995 if (blackTimeRemaining <= 0) {
14998 if (appData.icsActive) {
14999 if (appData.autoCallFlag &&
15000 gameMode == IcsPlayingWhite && !whiteFlag) {
15001 SendToICS(ics_prefix);
15002 SendToICS("flag\n");
15006 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Both flags fell"));
15008 if(gameMode != TwoMachinesPlay) DisplayTitle(_("Black's flag fell"));
15009 if (appData.autoCallFlag) {
15010 GameEnds(WhiteWins, "White wins on time", GE_XBOARD);
15023 if (!appData.clockMode || appData.icsActive || searchTime || // [HGM] st: no inc in st mode
15024 gameMode == PlayFromGameFile || forwardMostMove == 0) return;
15027 * add time to clocks when time control is achieved ([HGM] now also used for increment)
15029 if ( !WhiteOnMove(forwardMostMove) ) {
15030 /* White made time control */
15031 lastWhite -= whiteTimeRemaining; // [HGM] contains start time, socalculate thinking time
15032 whiteTimeRemaining += GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, lastWhite, whiteTC)
15033 /* [HGM] time odds: correct new time quota for time odds! */
15034 / WhitePlayer()->timeOdds;
15035 lastBlack = blackTimeRemaining; // [HGM] leave absolute time (after quota), so next switch we can us it to calculate thinking time
15037 lastBlack -= blackTimeRemaining;
15038 /* Black made time control */
15039 blackTimeRemaining += GetTimeQuota((forwardMostMove-blackStartMove-1)/2, lastBlack, blackTC)
15040 / WhitePlayer()->other->timeOdds;
15041 lastWhite = whiteTimeRemaining;
15046 DisplayBothClocks()
15048 int wom = gameMode == EditPosition ?
15049 !blackPlaysFirst : WhiteOnMove(currentMove);
15050 DisplayWhiteClock(whiteTimeRemaining, wom);
15051 DisplayBlackClock(blackTimeRemaining, !wom);
15055 /* Timekeeping seems to be a portability nightmare. I think everyone
15056 has ftime(), but I'm really not sure, so I'm including some ifdefs
15057 to use other calls if you don't. Clocks will be less accurate if
15058 you have neither ftime nor gettimeofday.
15061 /* VS 2008 requires the #include outside of the function */
15062 #if !HAVE_GETTIMEOFDAY && HAVE_FTIME
15063 #include <sys/timeb.h>
15066 /* Get the current time as a TimeMark */
15071 #if HAVE_GETTIMEOFDAY
15073 struct timeval timeVal;
15074 struct timezone timeZone;
15076 gettimeofday(&timeVal, &timeZone);
15077 tm->sec = (long) timeVal.tv_sec;
15078 tm->ms = (int) (timeVal.tv_usec / 1000L);
15080 #else /*!HAVE_GETTIMEOFDAY*/
15083 // include <sys/timeb.h> / moved to just above start of function
15084 struct timeb timeB;
15087 tm->sec = (long) timeB.time;
15088 tm->ms = (int) timeB.millitm;
15090 #else /*!HAVE_FTIME && !HAVE_GETTIMEOFDAY*/
15091 tm->sec = (long) time(NULL);
15097 /* Return the difference in milliseconds between two
15098 time marks. We assume the difference will fit in a long!
15101 SubtractTimeMarks(tm2, tm1)
15102 TimeMark *tm2, *tm1;
15104 return 1000L*(tm2->sec - tm1->sec) +
15105 (long) (tm2->ms - tm1->ms);
15110 * Code to manage the game clocks.
15112 * In tournament play, black starts the clock and then white makes a move.
15113 * We give the human user a slight advantage if he is playing white---the
15114 * clocks don't run until he makes his first move, so it takes zero time.
15115 * Also, we don't account for network lag, so we could get out of sync
15116 * with GNU Chess's clock -- but then, referees are always right.
15119 static TimeMark tickStartTM;
15120 static long intendedTickLength;
15123 NextTickLength(timeRemaining)
15124 long timeRemaining;
15126 long nominalTickLength, nextTickLength;
15128 if (timeRemaining > 0L && timeRemaining <= 10000L)
15129 nominalTickLength = 100L;
15131 nominalTickLength = 1000L;
15132 nextTickLength = timeRemaining % nominalTickLength;
15133 if (nextTickLength <= 0) nextTickLength += nominalTickLength;
15135 return nextTickLength;
15138 /* Adjust clock one minute up or down */
15140 AdjustClock(Boolean which, int dir)
15142 if(which) blackTimeRemaining += 60000*dir;
15143 else whiteTimeRemaining += 60000*dir;
15144 DisplayBothClocks();
15147 /* Stop clocks and reset to a fresh time control */
15151 (void) StopClockTimer();
15152 if (appData.icsActive) {
15153 whiteTimeRemaining = blackTimeRemaining = 0;
15154 } else if (searchTime) {
15155 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15156 blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15157 } else { /* [HGM] correct new time quote for time odds */
15158 whiteTC = blackTC = fullTimeControlString;
15159 whiteTimeRemaining = GetTimeQuota(-1, 0, whiteTC) / WhitePlayer()->timeOdds;
15160 blackTimeRemaining = GetTimeQuota(-1, 0, blackTC) / WhitePlayer()->other->timeOdds;
15162 if (whiteFlag || blackFlag) {
15164 whiteFlag = blackFlag = FALSE;
15166 lastWhite = lastBlack = whiteStartMove = blackStartMove = 0;
15167 DisplayBothClocks();
15170 #define FUDGE 25 /* 25ms = 1/40 sec; should be plenty even for 50 Hz clocks */
15172 /* Decrement running clock by amount of time that has passed */
15176 long timeRemaining;
15177 long lastTickLength, fudge;
15180 if (!appData.clockMode) return;
15181 if (gameMode==AnalyzeMode || gameMode == AnalyzeFile) return;
15185 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15187 /* Fudge if we woke up a little too soon */
15188 fudge = intendedTickLength - lastTickLength;
15189 if (fudge < 0 || fudge > FUDGE) fudge = 0;
15191 if (WhiteOnMove(forwardMostMove)) {
15192 if(whiteNPS >= 0) lastTickLength = 0;
15193 timeRemaining = whiteTimeRemaining -= lastTickLength;
15194 if(timeRemaining < 0 && !appData.icsActive) {
15195 GetTimeQuota((forwardMostMove-whiteStartMove-1)/2, 0, whiteTC); // sets suddenDeath & nextSession;
15196 if(suddenDeath) { // [HGM] if we run out of a non-last incremental session, go to the next
15197 whiteStartMove = forwardMostMove; whiteTC = nextSession;
15198 lastWhite= timeRemaining = whiteTimeRemaining += GetTimeQuota(-1, 0, whiteTC);
15201 DisplayWhiteClock(whiteTimeRemaining - fudge,
15202 WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15204 if(blackNPS >= 0) lastTickLength = 0;
15205 timeRemaining = blackTimeRemaining -= lastTickLength;
15206 if(timeRemaining < 0 && !appData.icsActive) { // [HGM] if we run out of a non-last incremental session, go to the next
15207 GetTimeQuota((forwardMostMove-blackStartMove-1)/2, 0, blackTC);
15209 blackStartMove = forwardMostMove;
15210 lastBlack = timeRemaining = blackTimeRemaining += GetTimeQuota(-1, 0, blackTC=nextSession);
15213 DisplayBlackClock(blackTimeRemaining - fudge,
15214 !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
15216 if (CheckFlags()) return;
15219 intendedTickLength = NextTickLength(timeRemaining - fudge) + fudge;
15220 StartClockTimer(intendedTickLength);
15222 /* if the time remaining has fallen below the alarm threshold, sound the
15223 * alarm. if the alarm has sounded and (due to a takeback or time control
15224 * with increment) the time remaining has increased to a level above the
15225 * threshold, reset the alarm so it can sound again.
15228 if (appData.icsActive && appData.icsAlarm) {
15230 /* make sure we are dealing with the user's clock */
15231 if (!( ((gameMode == IcsPlayingWhite) && WhiteOnMove(currentMove)) ||
15232 ((gameMode == IcsPlayingBlack) && !WhiteOnMove(currentMove))
15235 if (alarmSounded && (timeRemaining > appData.icsAlarmTime)) {
15236 alarmSounded = FALSE;
15237 } else if (!alarmSounded && (timeRemaining <= appData.icsAlarmTime)) {
15239 alarmSounded = TRUE;
15245 /* A player has just moved, so stop the previously running
15246 clock and (if in clock mode) start the other one.
15247 We redisplay both clocks in case we're in ICS mode, because
15248 ICS gives us an update to both clocks after every move.
15249 Note that this routine is called *after* forwardMostMove
15250 is updated, so the last fractional tick must be subtracted
15251 from the color that is *not* on move now.
15254 SwitchClocks(int newMoveNr)
15256 long lastTickLength;
15258 int flagged = FALSE;
15262 if (StopClockTimer() && appData.clockMode) {
15263 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15264 if (!WhiteOnMove(forwardMostMove)) {
15265 if(blackNPS >= 0) lastTickLength = 0;
15266 blackTimeRemaining -= lastTickLength;
15267 /* [HGM] PGNtime: save time for PGN file if engine did not give it */
15268 // if(pvInfoList[forwardMostMove].time == -1)
15269 pvInfoList[forwardMostMove].time = // use GUI time
15270 (timeRemaining[1][forwardMostMove-1] - blackTimeRemaining)/10;
15272 if(whiteNPS >= 0) lastTickLength = 0;
15273 whiteTimeRemaining -= 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 =
15277 (timeRemaining[0][forwardMostMove-1] - whiteTimeRemaining)/10;
15279 flagged = CheckFlags();
15281 forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
15282 CheckTimeControl();
15284 if (flagged || !appData.clockMode) return;
15286 switch (gameMode) {
15287 case MachinePlaysBlack:
15288 case MachinePlaysWhite:
15289 case BeginningOfGame:
15290 if (pausing) return;
15294 case PlayFromGameFile:
15302 if (searchTime) { // [HGM] st: set clock of player that has to move to max time
15303 if(WhiteOnMove(forwardMostMove))
15304 whiteTimeRemaining = 1000*searchTime / WhitePlayer()->timeOdds;
15305 else blackTimeRemaining = 1000*searchTime / WhitePlayer()->other->timeOdds;
15309 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15310 whiteTimeRemaining : blackTimeRemaining);
15311 StartClockTimer(intendedTickLength);
15315 /* Stop both clocks */
15319 long lastTickLength;
15322 if (!StopClockTimer()) return;
15323 if (!appData.clockMode) return;
15327 lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
15328 if (WhiteOnMove(forwardMostMove)) {
15329 if(whiteNPS >= 0) lastTickLength = 0;
15330 whiteTimeRemaining -= lastTickLength;
15331 DisplayWhiteClock(whiteTimeRemaining, WhiteOnMove(currentMove));
15333 if(blackNPS >= 0) lastTickLength = 0;
15334 blackTimeRemaining -= lastTickLength;
15335 DisplayBlackClock(blackTimeRemaining, !WhiteOnMove(currentMove));
15340 /* Start clock of player on move. Time may have been reset, so
15341 if clock is already running, stop and restart it. */
15345 (void) StopClockTimer(); /* in case it was running already */
15346 DisplayBothClocks();
15347 if (CheckFlags()) return;
15349 if (!appData.clockMode) return;
15350 if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) return;
15352 GetTimeMark(&tickStartTM);
15353 intendedTickLength = NextTickLength(WhiteOnMove(forwardMostMove) ?
15354 whiteTimeRemaining : blackTimeRemaining);
15356 /* [HGM] nps: figure out nps factors, by determining which engine plays white and/or black once and for all */
15357 whiteNPS = blackNPS = -1;
15358 if(gameMode == MachinePlaysWhite || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w'
15359 || appData.zippyPlay && gameMode == IcsPlayingBlack) // first (perhaps only) engine has white
15360 whiteNPS = first.nps;
15361 if(gameMode == MachinePlaysBlack || gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b'
15362 || appData.zippyPlay && gameMode == IcsPlayingWhite) // first (perhaps only) engine has black
15363 blackNPS = first.nps;
15364 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'b') // second only used in Two-Machines mode
15365 whiteNPS = second.nps;
15366 if(gameMode == TwoMachinesPlay && first.twoMachinesColor[0] == 'w')
15367 blackNPS = second.nps;
15368 if(appData.debugMode) fprintf(debugFP, "nps: w=%d, b=%d\n", whiteNPS, blackNPS);
15370 StartClockTimer(intendedTickLength);
15377 long second, minute, hour, day;
15379 static char buf[32];
15381 if (ms > 0 && ms <= 9900) {
15382 /* convert milliseconds to tenths, rounding up */
15383 double tenths = floor( ((double)(ms + 99L)) / 100.00 );
15385 snprintf(buf,sizeof(buf)/sizeof(buf[0]), " %03.1f ", tenths/10.0);
15389 /* convert milliseconds to seconds, rounding up */
15390 /* use floating point to avoid strangeness of integer division
15391 with negative dividends on many machines */
15392 second = (long) floor(((double) (ms + 999L)) / 1000.0);
15399 day = second / (60 * 60 * 24);
15400 second = second % (60 * 60 * 24);
15401 hour = second / (60 * 60);
15402 second = second % (60 * 60);
15403 minute = second / 60;
15404 second = second % 60;
15407 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld:%02ld ",
15408 sign, day, hour, minute, second);
15410 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%ld:%02ld:%02ld ", sign, hour, minute, second);
15412 snprintf(buf, sizeof(buf)/sizeof(buf[0]), " %s%2ld:%02ld ", sign, minute, second);
15419 * This is necessary because some C libraries aren't ANSI C compliant yet.
15422 StrStr(string, match)
15423 char *string, *match;
15427 length = strlen(match);
15429 for (i = strlen(string) - length; i >= 0; i--, string++)
15430 if (!strncmp(match, string, length))
15437 StrCaseStr(string, match)
15438 char *string, *match;
15442 length = strlen(match);
15444 for (i = strlen(string) - length; i >= 0; i--, string++) {
15445 for (j = 0; j < length; j++) {
15446 if (ToLower(match[j]) != ToLower(string[j]))
15449 if (j == length) return string;
15463 c1 = ToLower(*s1++);
15464 c2 = ToLower(*s2++);
15465 if (c1 > c2) return 1;
15466 if (c1 < c2) return -1;
15467 if (c1 == NULLCHAR) return 0;
15476 return isupper(c) ? tolower(c) : c;
15484 return islower(c) ? toupper(c) : c;
15486 #endif /* !_amigados */
15494 if ((ret = (char *) malloc(strlen(s) + 1)))
15496 safeStrCpy(ret, s, strlen(s)+1);
15502 StrSavePtr(s, savePtr)
15503 char *s, **savePtr;
15508 if ((*savePtr = (char *) malloc(strlen(s) + 1))) {
15509 safeStrCpy(*savePtr, s, strlen(s)+1);
15521 clock = time((time_t *)NULL);
15522 tm = localtime(&clock);
15523 snprintf(buf, MSG_SIZ, "%04d.%02d.%02d",
15524 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday);
15525 return StrSave(buf);
15530 PositionToFEN(move, overrideCastling)
15532 char *overrideCastling;
15534 int i, j, fromX, fromY, toX, toY;
15541 whiteToPlay = (gameMode == EditPosition) ?
15542 !blackPlaysFirst : (move % 2 == 0);
15545 /* Piece placement data */
15546 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15548 for (j = BOARD_LEFT; j < BOARD_RGHT; j++) {
15549 if (boards[move][i][j] == EmptySquare) {
15551 } else { ChessSquare piece = boards[move][i][j];
15552 if (emptycount > 0) {
15553 if(emptycount<10) /* [HGM] can be >= 10 */
15554 *p++ = '0' + emptycount;
15555 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15558 if(PieceToChar(piece) == '+') {
15559 /* [HGM] write promoted pieces as '+<unpromoted>' (Shogi) */
15561 piece = (ChessSquare)(DEMOTED piece);
15563 *p++ = PieceToChar(piece);
15565 /* [HGM] flag promoted pieces as '<promoted>~' (Crazyhouse) */
15566 p[-1] = PieceToChar((ChessSquare)(DEMOTED piece));
15571 if (emptycount > 0) {
15572 if(emptycount<10) /* [HGM] can be >= 10 */
15573 *p++ = '0' + emptycount;
15574 else { *p++ = '0' + emptycount/10; *p++ = '0' + emptycount%10; }
15581 /* [HGM] print Crazyhouse or Shogi holdings */
15582 if( gameInfo.holdingsWidth ) {
15583 *(p-1) = '['; /* if we wanted to support BFEN, this could be '/' */
15585 for(i=0; i<gameInfo.holdingsSize; i++) { /* white holdings */
15586 piece = boards[move][i][BOARD_WIDTH-1];
15587 if( piece != EmptySquare )
15588 for(j=0; j<(int) boards[move][i][BOARD_WIDTH-2]; j++)
15589 *p++ = PieceToChar(piece);
15591 for(i=0; i<gameInfo.holdingsSize; i++) { /* black holdings */
15592 piece = boards[move][BOARD_HEIGHT-i-1][0];
15593 if( piece != EmptySquare )
15594 for(j=0; j<(int) boards[move][BOARD_HEIGHT-i-1][1]; j++)
15595 *p++ = PieceToChar(piece);
15598 if( q == p ) *p++ = '-';
15604 *p++ = whiteToPlay ? 'w' : 'b';
15607 if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
15608 while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
15610 if(nrCastlingRights) {
15612 if(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) {
15613 /* [HGM] write directly from rights */
15614 if(boards[move][CASTLING][2] != NoRights &&
15615 boards[move][CASTLING][0] != NoRights )
15616 *p++ = boards[move][CASTLING][0] + AAA + 'A' - 'a';
15617 if(boards[move][CASTLING][2] != NoRights &&
15618 boards[move][CASTLING][1] != NoRights )
15619 *p++ = boards[move][CASTLING][1] + AAA + 'A' - 'a';
15620 if(boards[move][CASTLING][5] != NoRights &&
15621 boards[move][CASTLING][3] != NoRights )
15622 *p++ = boards[move][CASTLING][3] + AAA;
15623 if(boards[move][CASTLING][5] != NoRights &&
15624 boards[move][CASTLING][4] != NoRights )
15625 *p++ = boards[move][CASTLING][4] + AAA;
15628 /* [HGM] write true castling rights */
15629 if( nrCastlingRights == 6 ) {
15630 if(boards[move][CASTLING][0] == BOARD_RGHT-1 &&
15631 boards[move][CASTLING][2] != NoRights ) *p++ = 'K';
15632 if(boards[move][CASTLING][1] == BOARD_LEFT &&
15633 boards[move][CASTLING][2] != NoRights ) *p++ = 'Q';
15634 if(boards[move][CASTLING][3] == BOARD_RGHT-1 &&
15635 boards[move][CASTLING][5] != NoRights ) *p++ = 'k';
15636 if(boards[move][CASTLING][4] == BOARD_LEFT &&
15637 boards[move][CASTLING][5] != NoRights ) *p++ = 'q';
15640 if (q == p) *p++ = '-'; /* No castling rights */
15644 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
15645 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15646 /* En passant target square */
15647 if (move > backwardMostMove) {
15648 fromX = moveList[move - 1][0] - AAA;
15649 fromY = moveList[move - 1][1] - ONE;
15650 toX = moveList[move - 1][2] - AAA;
15651 toY = moveList[move - 1][3] - ONE;
15652 if (fromY == (whiteToPlay ? BOARD_HEIGHT-2 : 1) &&
15653 toY == (whiteToPlay ? BOARD_HEIGHT-4 : 3) &&
15654 boards[move][toY][toX] == (whiteToPlay ? BlackPawn : WhitePawn) &&
15656 /* 2-square pawn move just happened */
15658 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15662 } else if(move == backwardMostMove) {
15663 // [HGM] perhaps we should always do it like this, and forget the above?
15664 if((signed char)boards[move][EP_STATUS] >= 0) {
15665 *p++ = boards[move][EP_STATUS] + AAA;
15666 *p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
15677 /* [HGM] find reversible plies */
15678 { int i = 0, j=move;
15680 if (appData.debugMode) { int k;
15681 fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
15682 for(k=backwardMostMove; k<=forwardMostMove; k++)
15683 fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
15687 while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
15688 if( j == backwardMostMove ) i += initialRulePlies;
15689 sprintf(p, "%d ", i);
15690 p += i>=100 ? 4 : i >= 10 ? 3 : 2;
15692 /* Fullmove number */
15693 sprintf(p, "%d", (move / 2) + 1);
15695 return StrSave(buf);
15699 ParseFEN(board, blackPlaysFirst, fen)
15701 int *blackPlaysFirst;
15711 /* [HGM] by default clear Crazyhouse holdings, if present */
15712 if(gameInfo.holdingsWidth) {
15713 for(i=0; i<BOARD_HEIGHT; i++) {
15714 board[i][0] = EmptySquare; /* black holdings */
15715 board[i][BOARD_WIDTH-1] = EmptySquare; /* white holdings */
15716 board[i][1] = (ChessSquare) 0; /* black counts */
15717 board[i][BOARD_WIDTH-2] = (ChessSquare) 0; /* white counts */
15721 /* Piece placement data */
15722 for (i = BOARD_HEIGHT - 1; i >= 0; i--) {
15725 if (*p == '/' || *p == ' ' || (*p == '[' && i == 0) ) {
15726 if (*p == '/') p++;
15727 emptycount = gameInfo.boardWidth - j;
15728 while (emptycount--)
15729 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15731 #if(BOARD_FILES >= 10)
15732 } else if(*p=='x' || *p=='X') { /* [HGM] X means 10 */
15733 p++; emptycount=10;
15734 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15735 while (emptycount--)
15736 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15738 } else if (isdigit(*p)) {
15739 emptycount = *p++ - '0';
15740 while(isdigit(*p)) emptycount = 10*emptycount + *p++ - '0'; /* [HGM] allow > 9 */
15741 if (j + emptycount > gameInfo.boardWidth) return FALSE;
15742 while (emptycount--)
15743 board[i][(j++)+gameInfo.holdingsWidth] = EmptySquare;
15744 } else if (*p == '+' || isalpha(*p)) {
15745 if (j >= gameInfo.boardWidth) return FALSE;
15747 piece = CharToPiece(*++p);
15748 if(piece == EmptySquare) return FALSE; /* unknown piece */
15749 piece = (ChessSquare) (PROMOTED piece ); p++;
15750 if(PieceToChar(piece) != '+') return FALSE; /* unpromotable piece */
15751 } else piece = CharToPiece(*p++);
15753 if(piece==EmptySquare) return FALSE; /* unknown piece */
15754 if(*p == '~') { /* [HGM] make it a promoted piece for Crazyhouse */
15755 piece = (ChessSquare) (PROMOTED piece);
15756 if(PieceToChar(piece) != '~') return FALSE; /* cannot be a promoted piece */
15759 board[i][(j++)+gameInfo.holdingsWidth] = piece;
15765 while (*p == '/' || *p == ' ') p++;
15767 /* [HGM] look for Crazyhouse holdings here */
15768 while(*p==' ') p++;
15769 if( gameInfo.holdingsWidth && p[-1] == '/' || *p == '[') {
15771 if(*p == '-' ) p++; /* empty holdings */ else {
15772 if( !gameInfo.holdingsWidth ) return FALSE; /* no room to put holdings! */
15773 /* if we would allow FEN reading to set board size, we would */
15774 /* have to add holdings and shift the board read so far here */
15775 while( (piece = CharToPiece(*p) ) != EmptySquare ) {
15777 if((int) piece >= (int) BlackPawn ) {
15778 i = (int)piece - (int)BlackPawn;
15779 i = PieceToNumber((ChessSquare)i);
15780 if( i >= gameInfo.holdingsSize ) return FALSE;
15781 board[BOARD_HEIGHT-1-i][0] = piece; /* black holdings */
15782 board[BOARD_HEIGHT-1-i][1]++; /* black counts */
15784 i = (int)piece - (int)WhitePawn;
15785 i = PieceToNumber((ChessSquare)i);
15786 if( i >= gameInfo.holdingsSize ) return FALSE;
15787 board[i][BOARD_WIDTH-1] = piece; /* white holdings */
15788 board[i][BOARD_WIDTH-2]++; /* black holdings */
15795 while(*p == ' ') p++;
15799 if(appData.colorNickNames) {
15800 if( c == appData.colorNickNames[0] ) c = 'w'; else
15801 if( c == appData.colorNickNames[1] ) c = 'b';
15805 *blackPlaysFirst = FALSE;
15808 *blackPlaysFirst = TRUE;
15814 /* [HGM] We NO LONGER ignore the rest of the FEN notation */
15815 /* return the extra info in global variiables */
15817 /* set defaults in case FEN is incomplete */
15818 board[EP_STATUS] = EP_UNKNOWN;
15819 for(i=0; i<nrCastlingRights; i++ ) {
15820 board[CASTLING][i] =
15821 gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
15822 } /* assume possible unless obviously impossible */
15823 if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
15824 if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
15825 if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
15826 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
15827 if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
15828 if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
15829 if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
15830 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
15833 while(*p==' ') p++;
15834 if(nrCastlingRights) {
15835 if(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-') {
15836 /* castling indicator present, so default becomes no castlings */
15837 for(i=0; i<nrCastlingRights; i++ ) {
15838 board[CASTLING][i] = NoRights;
15841 while(*p=='K' || *p=='Q' || *p=='k' || *p=='q' || *p=='-' ||
15842 (gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
15843 ( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
15844 ( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
15845 char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
15847 for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
15848 if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
15849 if(board[0 ][i] == WhiteKing) whiteKingFile = i;
15851 if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
15852 whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
15853 if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
15854 && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
15855 if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
15856 && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
15859 for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
15860 board[CASTLING][0] = i != whiteKingFile ? i : NoRights;
15861 board[CASTLING][2] = whiteKingFile;
15864 for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
15865 board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
15866 board[CASTLING][2] = whiteKingFile;
15869 for(i=BOARD_RGHT-1; board[BOARD_HEIGHT-1][i]!=BlackRook && i>blackKingFile; i--);
15870 board[CASTLING][3] = i != blackKingFile ? i : NoRights;
15871 board[CASTLING][5] = blackKingFile;
15874 for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
15875 board[CASTLING][4] = i != blackKingFile ? i : NoRights;
15876 board[CASTLING][5] = blackKingFile;
15879 default: /* FRC castlings */
15880 if(c >= 'a') { /* black rights */
15881 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15882 if(board[BOARD_HEIGHT-1][i] == BlackKing) break;
15883 if(i == BOARD_RGHT) break;
15884 board[CASTLING][5] = i;
15886 if(board[BOARD_HEIGHT-1][c] < BlackPawn ||
15887 board[BOARD_HEIGHT-1][c] >= BlackKing ) break;
15889 board[CASTLING][3] = c;
15891 board[CASTLING][4] = c;
15892 } else { /* white rights */
15893 for(i=BOARD_LEFT; i<BOARD_RGHT; i++)
15894 if(board[0][i] == WhiteKing) break;
15895 if(i == BOARD_RGHT) break;
15896 board[CASTLING][2] = i;
15897 c -= AAA - 'a' + 'A';
15898 if(board[0][c] >= WhiteKing) break;
15900 board[CASTLING][0] = c;
15902 board[CASTLING][1] = c;
15906 for(i=0; i<nrCastlingRights; i++)
15907 if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
15908 if (appData.debugMode) {
15909 fprintf(debugFP, "FEN castling rights:");
15910 for(i=0; i<nrCastlingRights; i++)
15911 fprintf(debugFP, " %d", board[CASTLING][i]);
15912 fprintf(debugFP, "\n");
15915 while(*p==' ') p++;
15918 /* read e.p. field in games that know e.p. capture */
15919 if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
15920 gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
15922 p++; board[EP_STATUS] = EP_NONE;
15924 char c = *p++ - AAA;
15926 if(c < BOARD_LEFT || c >= BOARD_RGHT) return TRUE;
15927 if(*p >= '0' && *p <='9') p++;
15928 board[EP_STATUS] = c;
15933 if(sscanf(p, "%d", &i) == 1) {
15934 FENrulePlies = i; /* 50-move ply counter */
15935 /* (The move number is still ignored) */
15942 EditPositionPasteFEN(char *fen)
15945 Board initial_position;
15947 if (!ParseFEN(initial_position, &blackPlaysFirst, fen)) {
15948 DisplayError(_("Bad FEN position in clipboard"), 0);
15951 int savedBlackPlaysFirst = blackPlaysFirst;
15952 EditPositionEvent();
15953 blackPlaysFirst = savedBlackPlaysFirst;
15954 CopyBoard(boards[0], initial_position);
15955 initialRulePlies = FENrulePlies; /* [HGM] copy FEN attributes as well */
15956 EditPositionDone(FALSE); // [HGM] fake: do not fake rights if we had FEN
15957 DisplayBothClocks();
15958 DrawPosition(FALSE, boards[currentMove]);
15963 static char cseq[12] = "\\ ";
15965 Boolean set_cont_sequence(char *new_seq)
15970 // handle bad attempts to set the sequence
15972 return 0; // acceptable error - no debug
15974 len = strlen(new_seq);
15975 ret = (len > 0) && (len < sizeof(cseq));
15977 safeStrCpy(cseq, new_seq, sizeof(cseq)/sizeof(cseq[0]));
15978 else if (appData.debugMode)
15979 fprintf(debugFP, "Invalid continuation sequence \"%s\" (maximum length is: %u)\n", new_seq, (unsigned) sizeof(cseq)-1);
15984 reformat a source message so words don't cross the width boundary. internal
15985 newlines are not removed. returns the wrapped size (no null character unless
15986 included in source message). If dest is NULL, only calculate the size required
15987 for the dest buffer. lp argument indicats line position upon entry, and it's
15988 passed back upon exit.
15990 int wrap(char *dest, char *src, int count, int width, int *lp)
15992 int len, i, ansi, cseq_len, line, old_line, old_i, old_len, clen;
15994 cseq_len = strlen(cseq);
15995 old_line = line = *lp;
15996 ansi = len = clen = 0;
15998 for (i=0; i < count; i++)
16000 if (src[i] == '\033')
16003 // if we hit the width, back up
16004 if (!ansi && (line >= width) && src[i] != '\n' && src[i] != ' ')
16006 // store i & len in case the word is too long
16007 old_i = i, old_len = len;
16009 // find the end of the last word
16010 while (i && src[i] != ' ' && src[i] != '\n')
16016 // word too long? restore i & len before splitting it
16017 if ((old_i-i+clen) >= width)
16024 if (i && src[i-1] == ' ')
16027 if (src[i] != ' ' && src[i] != '\n')
16034 // now append the newline and continuation sequence
16039 strncpy(dest+len, cseq, cseq_len);
16047 dest[len] = src[i];
16051 if (src[i] == '\n')
16056 if (dest && appData.debugMode)
16058 fprintf(debugFP, "wrap(count:%d,width:%d,line:%d,len:%d,*lp:%d,src: ",
16059 count, width, line, len, *lp);
16060 show_bytes(debugFP, src, count);
16061 fprintf(debugFP, "\ndest: ");
16062 show_bytes(debugFP, dest, len);
16063 fprintf(debugFP, "\n");
16065 *lp = dest ? line : old_line;
16070 // [HGM] vari: routines for shelving variations
16073 PushInner(int firstMove, int lastMove)
16075 int i, j, nrMoves = lastMove - firstMove;
16077 // push current tail of game on stack
16078 savedResult[storedGames] = gameInfo.result;
16079 savedDetails[storedGames] = gameInfo.resultDetails;
16080 gameInfo.resultDetails = NULL;
16081 savedFirst[storedGames] = firstMove;
16082 savedLast [storedGames] = lastMove;
16083 savedFramePtr[storedGames] = framePtr;
16084 framePtr -= nrMoves; // reserve space for the boards
16085 for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
16086 CopyBoard(boards[framePtr+i], boards[firstMove+i]);
16087 for(j=0; j<MOVE_LEN; j++)
16088 moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
16089 for(j=0; j<2*MOVE_LEN; j++)
16090 parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
16091 timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
16092 timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
16093 pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
16094 pvInfoList[firstMove+i-1].depth = 0;
16095 commentList[framePtr+i] = commentList[firstMove+i];
16096 commentList[firstMove+i] = NULL;
16100 forwardMostMove = firstMove; // truncate game so we can start variation
16104 PushTail(int firstMove, int lastMove)
16106 if(appData.icsActive) { // only in local mode
16107 forwardMostMove = currentMove; // mimic old ICS behavior
16110 if(storedGames >= MAX_VARIATIONS-2) return; // leave one for PV-walk
16112 PushInner(firstMove, lastMove);
16113 if(storedGames == 1) GreyRevert(FALSE);
16117 PopInner(Boolean annotate)
16120 char buf[8000], moveBuf[20];
16123 ToNrEvent(savedFirst[storedGames]); // sets currentMove
16124 nrMoves = savedLast[storedGames] - currentMove;
16127 if(!WhiteOnMove(currentMove))
16128 snprintf(buf, sizeof(buf)/sizeof(buf[0]),"(%d...", (currentMove+2)>>1);
16129 else safeStrCpy(buf, "(", sizeof(buf)/sizeof(buf[0]));
16130 for(i=currentMove; i<forwardMostMove; i++) {
16132 snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0]), " %d. %s", (i+2)>>1, SavePart(parseList[i]));
16133 else snprintf(moveBuf, sizeof(moveBuf)/sizeof(moveBuf[0])," %s", SavePart(parseList[i]));
16134 strcat(buf, moveBuf);
16135 if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
16136 if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
16140 for(i=1; i<=nrMoves; i++) { // copy last variation back
16141 CopyBoard(boards[currentMove+i], boards[framePtr+i]);
16142 for(j=0; j<MOVE_LEN; j++)
16143 moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
16144 for(j=0; j<2*MOVE_LEN; j++)
16145 parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
16146 timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
16147 timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
16148 pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
16149 if(commentList[currentMove+i]) free(commentList[currentMove+i]);
16150 commentList[currentMove+i] = commentList[framePtr+i];
16151 commentList[framePtr+i] = NULL;
16153 if(annotate) AppendComment(currentMove+1, buf, FALSE);
16154 framePtr = savedFramePtr[storedGames];
16155 gameInfo.result = savedResult[storedGames];
16156 if(gameInfo.resultDetails != NULL) {
16157 free(gameInfo.resultDetails);
16159 gameInfo.resultDetails = savedDetails[storedGames];
16160 forwardMostMove = currentMove + nrMoves;
16164 PopTail(Boolean annotate)
16166 if(appData.icsActive) return FALSE; // only in local mode
16167 if(!storedGames) return FALSE; // sanity
16168 CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
16170 PopInner(annotate);
16172 if(storedGames == 0) GreyRevert(TRUE);
16178 { // remove all shelved variations
16180 for(i=0; i<storedGames; i++) {
16181 if(savedDetails[i])
16182 free(savedDetails[i]);
16183 savedDetails[i] = NULL;
16185 for(i=framePtr; i<MAX_MOVES; i++) {
16186 if(commentList[i]) free(commentList[i]);
16187 commentList[i] = NULL;
16189 framePtr = MAX_MOVES-1;
16194 LoadVariation(int index, char *text)
16195 { // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
16196 char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
16197 int level = 0, move;
16199 if(gameMode != EditGame && gameMode != AnalyzeMode) return;
16200 // first find outermost bracketing variation
16201 while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
16202 if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
16203 if(*p == '{') wait = '}'; else
16204 if(*p == '[') wait = ']'; else
16205 if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
16206 if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
16208 if(*p == wait) wait = NULLCHAR; // closing ]} found
16211 if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
16212 if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
16213 end[1] = NULLCHAR; // clip off comment beyond variation
16214 ToNrEvent(currentMove-1);
16215 PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
16216 // kludge: use ParsePV() to append variation to game
16217 move = currentMove;
16218 ParsePV(start, TRUE);
16219 forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
16220 ClearPremoveHighlights();
16222 ToNrEvent(currentMove+1);