* Massachusetts.
*
* Enhancements Copyright 1992-2001, 2002, 2003, 2004, 2005, 2006,
- * 2007, 2008, 2009 Free Software Foundation, Inc.
+ * 2007, 2008, 2009, 2010 Free Software Foundation, Inc.
*
* Enhancements Copyright 2005 Alessandro Scotti
*
/*char*/int promoChar));
void BackwardInner P((int target));
void ForwardInner P((int target));
+int Adjudicate P((ChessProgramState *cps));
void GameEnds P((ChessMove result, char *resultDetails, int whosays));
void EditPositionDone P((Boolean fakeRights));
void PrintOpponents P((FILE *fp));
void ParseGameHistory P((char *game));
void ParseBoard12 P((char *string));
+void KeepAlive P((void));
void StartClocks P((void));
-void SwitchClocks P((void));
+void SwitchClocks P((int nr));
void StopClocks P((void));
void ResetClocks P((void));
char *PGNDate P((void));
char *SendMoveToBookUser P((int nr, ChessProgramState *cps, int initial)); // [HGM] book
void ics_update_width P((int new_width));
extern char installDir[MSG_SIZ];
+VariantClass startVariant; /* [HGM] nicks: initial variant */
extern int tinyLayout, smallLayout;
ChessProgramStats programStats;
+char lastPV[2][2*MSG_SIZ]; /* [HGM] pv: last PV in thinking output of each engine */
+int endPV = -1;
static int exiting = 0; /* [HGM] moved to top */
static int setboardSpoiledMachineBlack = 0 /*, errorExitFlag = 0*/;
int startedFromPositionFile = FALSE; Board filePosition; /* [HGM] loadPos */
+Board partnerBoard; /* [HGM] bughouse: for peeking at partner game */
+int partnerHighlight[2];
+Boolean partnerBoardValid = 0;
+char partnerStatus[MSG_SIZ];
+Boolean partnerUp;
+Boolean originalFlip;
+Boolean twoBoards = 0;
char endingGame = 0; /* [HGM] crash: flag to prevent recursion of GameEnds() */
int whiteNPS, blackNPS; /* [HGM] nps: for easily making clocks aware of NPS */
VariantClass currentlyInitializedVariant; /* [HGM] variantswitch */
int lastIndex = 0; /* [HGM] autoinc: last game/position used in match mode */
+Boolean connectionAlive;/* [HGM] alive: ICS connection status from probing */
int opponentKibitzes;
int lastSavedGame; /* [HGM] save: ID of game */
char chatPartner[MAX_CHAT][MSG_SIZ]; /* [HGM] chat: list of chatting partners */
extern int chatCount;
int chattingPartner;
+char marker[BOARD_RANKS][BOARD_FILES]; /* [HGM] marks for target squares */
/* States for ics_getting_history */
#define H_FALSE 0
case VariantNoCastle:
case VariantShatranj:
case VariantCourier:
+ case VariantMakruk:
flags &= ~F_ALL_CASTLE_OK;
break;
default:
int shuffleOpenings;
int mute; // mute all sounds
+// [HGM] vari: next 12 to save and restore variations
+#define MAX_VARIATIONS 10
+int framePtr = MAX_MOVES-1; // points to free stack entry
+int storedGames = 0;
+int savedFirst[MAX_VARIATIONS];
+int savedLast[MAX_VARIATIONS];
+int savedFramePtr[MAX_VARIATIONS];
+char *savedDetails[MAX_VARIATIONS];
+ChessMove savedResult[MAX_VARIATIONS];
+
+void PushTail P((int firstMove, int lastMove));
+Boolean PopTail P((Boolean annotate));
+void CleanupTail P((void));
+
ChessSquare FIDEArray[2][BOARD_FILES] = {
{ WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
};
ChessSquare fairyArray[2][BOARD_FILES] = { /* [HGM] Queen side differs from King side */
- { WhiteCannon, WhiteNightrider, WhiteAlfil, WhiteQueen,
+ { WhiteRook, WhiteKnight, WhiteBishop, WhiteQueen,
WhiteKing, WhiteBishop, WhiteKnight, WhiteRook },
- { BlackCannon, BlackNightrider, BlackAlfil, BlackQueen,
- BlackKing, BlackBishop, BlackKnight, BlackRook }
+ { BlackLance, BlackAlfil, BlackMarshall, BlackAngel,
+ BlackKing, BlackMarshall, BlackAlfil, BlackLance }
};
ChessSquare ShatranjArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
BlackFerz, BlackAlfil, BlackKnight, BlackRook }
};
+ChessSquare makrukArray[2][BOARD_FILES] = { /* [HGM] (movGen knows about Shatranj Q and P) */
+ { WhiteRook, WhiteKnight, WhiteMan, WhiteKing,
+ WhiteFerz, WhiteMan, WhiteKnight, WhiteRook },
+ { BlackRook, BlackKnight, BlackMan, BlackFerz,
+ BlackKing, BlackMan, BlackKnight, BlackRook }
+};
+
#if (BOARD_FILES>=10)
ChessSquare ShogiArray[2][BOARD_FILES] = {
int matched, min, sec;
ShowThinkingEvent(); // [HGM] thinking: make sure post/nopost state is set according to options
+ startVariant = StringToVariant(appData.variant); // [HGM] nicks: remember original variant
GetTimeMark(&programStartTime);
srandom((programStartTime.ms + 1000*programStartTime.sec)*0x1001001); // [HGM] book: makes sure random is unpredictabe to msec level
{
int i, j;
- for( i=0; i<MAX_MOVES; i++ ) {
+ for( i=0; i<=framePtr; i++ ) {
pvInfoList[i].depth = -1;
boards[i][EP_STATUS] = EP_NONE;
for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
/* [HGM] time odds: set factor for each machine */
first.timeOdds = appData.firstTimeOdds;
second.timeOdds = appData.secondTimeOdds;
- { int norm = 1;
+ { float norm = 1;
if(appData.timeOddsMode) {
norm = first.timeOdds;
if(norm > second.timeOdds) norm = second.timeOdds;
case VariantAtomic: /* should work except for win condition */
case Variant3Check: /* should work except for win condition */
case VariantShatranj: /* should work except for all win conditions */
+ case VariantMakruk: /* should work except for daw countdown */
case VariantBerolina: /* might work if TestLegality is off */
case VariantCapaRandom: /* should work */
case VariantJanus: /* should work */
InitChessProgram(&first, startedFromSetupPosition);
+ if(!appData.noChessProgram) { /* [HGM] tidy: redo program version to use name from myname feature */
+ free(programVersion);
+ programVersion = (char*) malloc(8 + strlen(PACKAGE_STRING) + strlen(first.tidy));
+ sprintf(programVersion, "%s + %s", PACKAGE_STRING, first.tidy);
+ }
if (appData.icsActive) {
#ifdef WIN32
AddInputSource(icsPR, FALSE, read_from_ics, &telnetISR);
fromUserISR =
AddInputSource(NoProc, FALSE, read_from_player, &fromUserISR);
+ if(appData.keepAlive) // [HGM] alive: schedule sending of dummy 'date' command
+ ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
} else if (appData.noChessProgram) {
SetNCPMode();
} else {
}
}
+void EscapeExpand(char *p, char *q)
+{ // [HGM] initstring: routine to shape up string arguments
+ while(*p++ = *q++) if(p[-1] == '\\')
+ switch(*q++) {
+ case 'n': p[-1] = '\n'; break;
+ case 'r': p[-1] = '\r'; break;
+ case 't': p[-1] = '\t'; break;
+ case '\\': p[-1] = '\\'; break;
+ case 0: *p = 0; return;
+ default: p[-1] = q[-1]; break;
+ }
+}
+
void
show_bytes(fp, buf, count)
FILE *fp;
void
KeepAlive()
{ // [HGM] alive: periodically send dummy (date) command to ICS to prevent time-out
+ if(!connectionAlive) DisplayFatalError("No response from ICS", 0, 1);
+ connectionAlive = FALSE; // only sticks if no response to 'date' command.
SendToICS("date\n");
if(appData.keepAlive) ScheduleDelayedEvent(KeepAlive, appData.keepAlive*60*1000);
}
VariantSwitch(Board board, VariantClass newVariant)
{
int newHoldingsWidth, newWidth = 8, newHeight = 8, i, j;
- Board oldBoard;
+ static Board oldBoard;
startedFromPositionFile = FALSE;
if(gameInfo.variant == newVariant) return;
ColorClass curColor = ColorNormal;
int suppressKibitz = 0;
+// [HGM] seekgraph
+Boolean soughtPending = FALSE;
+Boolean seekGraphUp;
+#define MAX_SEEK_ADS 200
+#define SQUARE 0x80
+char *seekAdList[MAX_SEEK_ADS];
+int ratingList[MAX_SEEK_ADS], xList[MAX_SEEK_ADS], yList[MAX_SEEK_ADS], seekNrList[MAX_SEEK_ADS], zList[MAX_SEEK_ADS];
+float tcList[MAX_SEEK_ADS];
+char colorList[MAX_SEEK_ADS];
+int nrOfSeekAds = 0;
+int minRating = 1010, maxRating = 2800;
+int hMargin = 10, vMargin = 20, h, w;
+extern int squareSize, lineGap;
+
+void
+PlotSeekAd(int i)
+{
+ int x, y, color = 0, r = ratingList[i]; float tc = tcList[i];
+ xList[i] = yList[i] = -100; // outside graph, so cannot be clicked
+ if(r < minRating+100 && r >=0 ) r = minRating+100;
+ if(r > maxRating) r = maxRating;
+ if(tc < 1.) tc = 1.;
+ if(tc > 95.) tc = 95.;
+ x = (w-hMargin-squareSize/8-7)* log(tc)/log(95.) + hMargin;
+ y = ((double)r - minRating)/(maxRating - minRating)
+ * (h-vMargin-squareSize/8-1) + vMargin;
+ if(ratingList[i] < 0) y = vMargin + squareSize/4;
+ if(strstr(seekAdList[i], " u ")) color = 1;
+ if(!strstr(seekAdList[i], "lightning") && // for now all wilds same color
+ !strstr(seekAdList[i], "bullet") &&
+ !strstr(seekAdList[i], "blitz") &&
+ !strstr(seekAdList[i], "standard") ) color = 2;
+ if(strstr(seekAdList[i], "(C) ")) color |= SQUARE; // plot computer seeks as squares
+ DrawSeekDot(xList[i]=x+3*(color&~SQUARE), yList[i]=h-1-y, colorList[i]=color);
+}
+
+void
+AddAd(char *handle, char *rating, int base, int inc, char rated, char *type, int nr, Boolean plot)
+{
+ char buf[MSG_SIZ], *ext = "";
+ VariantClass v = StringToVariant(type);
+ if(strstr(type, "wild")) {
+ ext = type + 4; // append wild number
+ if(v == VariantFischeRandom) type = "chess960"; else
+ if(v == VariantLoadable) type = "setup"; else
+ type = VariantName(v);
+ }
+ sprintf(buf, "%s (%s) %d %d %c %s%s", handle, rating, base, inc, rated, type, ext);
+ if(nrOfSeekAds < MAX_SEEK_ADS-1) {
+ if(seekAdList[nrOfSeekAds]) free(seekAdList[nrOfSeekAds]);
+ ratingList[nrOfSeekAds] = -1; // for if seeker has no rating
+ sscanf(rating, "%d", &ratingList[nrOfSeekAds]);
+ tcList[nrOfSeekAds] = base + (2./3.)*inc;
+ seekNrList[nrOfSeekAds] = nr;
+ zList[nrOfSeekAds] = 0;
+ seekAdList[nrOfSeekAds++] = StrSave(buf);
+ if(plot) PlotSeekAd(nrOfSeekAds-1);
+ }
+}
+
+void
+EraseSeekDot(int i)
+{
+ int x = xList[i], y = yList[i], d=squareSize/4, k;
+ DrawSeekBackground(x-squareSize/8, y-squareSize/8, x+squareSize/8+1, y+squareSize/8+1);
+ if(x < hMargin+d) DrawSeekAxis(hMargin, y-squareSize/8, hMargin, y+squareSize/8+1);
+ // now replot every dot that overlapped
+ for(k=0; k<nrOfSeekAds; k++) if(k != i) {
+ int xx = xList[k], yy = yList[k];
+ if(xx <= x+d && xx > x-d && yy <= y+d && yy > y-d)
+ DrawSeekDot(xx, yy, colorList[k]);
+ }
+}
+
+void
+RemoveSeekAd(int nr)
+{
+ int i;
+ for(i=0; i<nrOfSeekAds; i++) if(seekNrList[i] == nr) {
+ EraseSeekDot(i);
+ if(seekAdList[i]) free(seekAdList[i]);
+ seekAdList[i] = seekAdList[--nrOfSeekAds];
+ seekNrList[i] = seekNrList[nrOfSeekAds];
+ ratingList[i] = ratingList[nrOfSeekAds];
+ colorList[i] = colorList[nrOfSeekAds];
+ tcList[i] = tcList[nrOfSeekAds];
+ xList[i] = xList[nrOfSeekAds];
+ yList[i] = yList[nrOfSeekAds];
+ zList[i] = zList[nrOfSeekAds];
+ seekAdList[nrOfSeekAds] = NULL;
+ break;
+ }
+}
+
+Boolean
+MatchSoughtLine(char *line)
+{
+ char handle[MSG_SIZ], rating[MSG_SIZ], type[MSG_SIZ];
+ int nr, base, inc, u=0; char dummy;
+
+ if(sscanf(line, "%d %s %s %d %d rated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
+ sscanf(line, "%d %s %s %s %d %d rated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7 ||
+ (u=1) &&
+ (sscanf(line, "%d %s %s %d %d unrated %s", &nr, rating, handle, &base, &inc, type) == 6 ||
+ sscanf(line, "%d %s %s %s %d %d unrated %c", &nr, rating, handle, type, &base, &inc, &dummy) == 7) ) {
+ // match: compact and save the line
+ AddAd(handle, rating, base, inc, u ? 'u' : 'r', type, nr, FALSE);
+ return TRUE;
+ }
+ return FALSE;
+}
+
+int
+DrawSeekGraph()
+{
+ if(!seekGraphUp) return FALSE;
+ int i;
+ h = BOARD_HEIGHT * (squareSize + lineGap) + lineGap;
+ w = BOARD_WIDTH * (squareSize + lineGap) + lineGap;
+
+ DrawSeekBackground(0, 0, w, h);
+ DrawSeekAxis(hMargin, h-1-vMargin, w-5, h-1-vMargin);
+ DrawSeekAxis(hMargin, h-1-vMargin, hMargin, 5);
+ for(i=0; i<4000; i+= 100) if(i>=minRating && i<maxRating) {
+ int yy =((double)i - minRating)/(maxRating - minRating)*(h-vMargin-squareSize/8-1) + vMargin;
+ yy = h-1-yy;
+ DrawSeekAxis(hMargin+5*(i%500==0), yy, hMargin-5, yy); // rating ticks
+ if(i%500 == 0) {
+ char buf[MSG_SIZ];
+ sprintf(buf, "%d", i);
+ DrawSeekText(buf, hMargin+squareSize/8+7, yy);
+ }
+ }
+ DrawSeekText("unrated", hMargin+squareSize/8+7, h-1-vMargin-squareSize/4);
+ for(i=1; i<100; i+=(i<10?1:5)) {
+ int xx = (w-hMargin-squareSize/8-7)* log((double)i)/log(95.) + hMargin;
+ DrawSeekAxis(xx, h-1-vMargin, xx, h-6-vMargin-3*(i%10==0)); // TC ticks
+ if(i<=5 || (i>40 ? i%20 : i%10) == 0) {
+ char buf[MSG_SIZ];
+ sprintf(buf, "%d", i);
+ DrawSeekText(buf, xx-2-3*(i>9), h-1-vMargin/2);
+ }
+ }
+ for(i=0; i<nrOfSeekAds; i++) PlotSeekAd(i);
+ return TRUE;
+}
+
+int SeekGraphClick(ClickType click, int x, int y, int moving)
+{
+ static int lastDown = 0, displayed = 0, lastSecond;
+ if(!seekGraphUp) { // initiate cration of seek graph by requesting seek-ad list
+ if(click == Release || moving) return FALSE;
+ nrOfSeekAds = 0;
+ soughtPending = TRUE;
+ SendToICS(ics_prefix);
+ SendToICS("sought\n"); // should this be "sought all"?
+ } else { // issue challenge based on clicked ad
+ int dist = 10000; int i, closest = 0, second = 0;
+ for(i=0; i<nrOfSeekAds; i++) {
+ int d = (x-xList[i])*(x-xList[i]) + (y-yList[i])*(y-yList[i]) + zList[i];
+ if(d < dist) { dist = d; closest = i; }
+ second += (d - zList[i] < 120); // count in-range ads
+ if(click == Press && moving != 1 && zList[i]>0) zList[i] *= 0.8; // age priority
+ }
+ if(dist < 120) {
+ char buf[MSG_SIZ];
+ second = (second > 1);
+ if(displayed != closest || second != lastSecond) {
+ DisplayMessage(second ? "!" : "", seekAdList[closest]);
+ lastSecond = second; displayed = closest;
+ }
+ if(click == Press) {
+ if(moving == 2) zList[closest] = 100; // right-click; push to back on press
+ lastDown = closest;
+ return TRUE;
+ } // on press 'hit', only show info
+ if(moving == 2) return TRUE; // ignore right up-clicks on dot
+ sprintf(buf, "play %d\n", seekNrList[closest]);
+ SendToICS(ics_prefix);
+ SendToICS(buf);
+ return TRUE; // let incoming board of started game pop down the graph
+ } else if(click == Release) { // release 'miss' is ignored
+ zList[lastDown] = 100; // make future selection of the rejected ad more difficult
+ if(moving == 2) { // right up-click
+ nrOfSeekAds = 0; // refresh graph
+ soughtPending = TRUE;
+ SendToICS(ics_prefix);
+ SendToICS("sought\n"); // should this be "sought all"?
+ }
+ return TRUE;
+ } else if(moving) { if(displayed >= 0) DisplayMessage("", ""); displayed = -1; return TRUE; }
+ // press miss or release hit 'pop down' seek graph
+ seekGraphUp = FALSE;
+ DrawPosition(TRUE, NULL);
+ }
+ return TRUE;
+}
+
void
read_from_ics(isr, closure, data, count, error)
InputSourceRef isr;
int count;
int error;
{
-#define BUF_SIZE 8192
+#define BUF_SIZE (16*1024) /* overflowed at 8K with "inchannel 1" on FICS? */
#define STARTED_NONE 0
#define STARTED_MOVES 1
#define STARTED_BOARD 2
char talker[MSG_SIZ]; // [HGM] chat
int channel;
+ connectionAlive = TRUE; // [HGM] alive: I think, therefore I am...
+
if (appData.debugMode) {
if (!error) {
fprintf(debugFP, "<ICS: ");
}
buf[buf_len] = NULLCHAR;
- next_out = leftover_len;
+// next_out = leftover_len; // [HGM] should we set this to 0, and not print it in advance?
+ next_out = 0;
leftover_start = 0;
i = 0;
sprintf(str,
"/set-quietly interface %s\n/set-quietly style 12\n",
programVersion);
+ if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
+ strcat(str, "/set-2 51 1\n/set seek 1\n");
} else if (ics_type == ICS_CHESSNET) {
sprintf(str, "/style 12\n");
} else {
strcpy(str, "alias $ @\n$set interface ");
strcat(str, programVersion);
strcat(str, "\n$iset startpos 1\n$iset ms 1\n");
+ if(appData.seekGraph && appData.autoRefresh) // [HGM] seekgraph
+ strcat(str, "$iset seekremove 1\n$set seek 1\n");
#ifdef WIN32
strcat(str, "$iset nohighlight 1\n");
#endif
sprintf(mess, "%s%s", talker, parse);
OutputChatMessage(chattingPartner, mess);
chattingPartner = -1;
+ next_out = i+1; // [HGM] suppress printing in ICS window
} else
if(!suppressKibitz) // [HGM] kibitz
- AppendComment(forwardMostMove, StripHighlight(parse));
+ AppendComment(forwardMostMove, StripHighlight(parse), TRUE);
else { // [HGM kibitz: divert memorized engine kibitz to engine-output window
- int nrDigit = 0, nrAlph = 0, i;
+ int nrDigit = 0, nrAlph = 0, j;
if(parse_pos > MSG_SIZ - 30) // defuse unreasonably long input
{ parse_pos = MSG_SIZ-30; parse[parse_pos - 1] = '\n'; }
parse[parse_pos] = NULLCHAR;
// try to be smart: if it does not look like search info, it should go to
// ICS interaction window after all, not to engine-output window.
- for(i=0; i<parse_pos; i++) { // count letters and digits
- nrDigit += (parse[i] >= '0' && parse[i] <= '9');
- nrAlph += (parse[i] >= 'a' && parse[i] <= 'z');
- nrAlph += (parse[i] >= 'A' && parse[i] <= 'Z');
+ for(j=0; j<parse_pos; j++) { // count letters and digits
+ nrDigit += (parse[j] >= '0' && parse[j] <= '9');
+ nrAlph += (parse[j] >= 'a' && parse[j] <= 'z');
+ nrAlph += (parse[j] >= 'A' && parse[j] <= 'Z');
}
if(nrAlph < 9*nrDigit) { // if more than 10% digit we assume search info
int depth=0; float score;
sprintf(tmp, _("your opponent kibitzes: %s"), parse);
SendToPlayer(tmp, strlen(tmp));
}
+ next_out = i+1; // [HGM] suppress printing in ICS window
}
started = STARTED_NONE;
} else {
- /* Don't match patterns against characters in chatter */
+ /* Don't match patterns against characters in comment */
i++;
continue;
}
continue;
}
started = STARTED_NONE;
+ if(suppressKibitz) next_out = i+1;
}
/* Kludge to deal with rcmd protocol */
continue;
}
+ oldi = i;
+ // [HGM] seekgraph: recognize sought lines and end-of-sought message
+ if(appData.seekGraph) {
+ if(soughtPending && MatchSoughtLine(buf+i)) {
+ i = strstr(buf+i, "rated") - buf;
+ if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
+ next_out = leftover_start = i;
+ started = STARTED_CHATTER;
+ suppressKibitz = TRUE;
+ continue;
+ }
+ if((gameMode == IcsIdle || gameMode == BeginningOfGame)
+ && looking_at(buf, &i, "* ads displayed")) {
+ soughtPending = FALSE;
+ seekGraphUp = TRUE;
+ DrawSeekGraph();
+ continue;
+ }
+ if(appData.autoRefresh) {
+ if(looking_at(buf, &i, "* (*) seeking * * * * *\"play *\" to respond)\n")) {
+ int s = (ics_type == ICS_ICC); // ICC format differs
+ if(seekGraphUp)
+ AddAd(star_match[0], star_match[1], atoi(star_match[2+s]), atoi(star_match[3+s]),
+ star_match[4+s][0], star_match[5-3*s], atoi(star_match[7]), TRUE);
+ looking_at(buf, &i, "*% "); // eat prompt
+ if(oldi > 0 && buf[oldi-1] == '\n') oldi--; // suppress preceding LF, if any
+ if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
+ next_out = i; // suppress
+ continue;
+ }
+ if(looking_at(buf, &i, "\nAds removed: *\n") || looking_at(buf, &i, "\031(51 * *\031)")) {
+ char *p = star_match[0];
+ while(*p) {
+ if(seekGraphUp) RemoveSeekAd(atoi(p));
+ while(*p && *p++ != ' '); // next
+ }
+ looking_at(buf, &i, "*% "); // eat prompt
+ if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
+ next_out = i;
+ continue;
+ }
+ }
+ }
+
/* skip formula vars */
if (started == STARTED_NONE &&
buf[i] == 'f' && isdigit(buf[i+1]) && buf[i+2] == ':') {
continue;
}
- oldi = i;
// [HGM] kibitz: try to recognize opponent engine-score kibitzes, to divert them to engine-output window
if (appData.autoKibitz && started == STARTED_NONE &&
!appData.icsEngineAnalyze && // [HGM] [DM] ICS analyze
(gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack || gameMode == IcsObserving)) {
- if(looking_at(buf, &i, "* kibitzes: ") &&
+ if((looking_at(buf, &i, "* kibitzes: ") || looking_at(buf, &i, "* whispers: ")) &&
(StrStr(star_match[0], gameInfo.white) == star_match[0] ||
StrStr(star_match[0], gameInfo.black) == star_match[0] )) { // kibitz of self or opponent
suppressKibitz = TRUE;
+ if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
+ next_out = i;
if((StrStr(star_match[0], gameInfo.white) == star_match[0]
&& (gameMode == IcsPlayingWhite)) ||
(StrStr(star_match[0], gameInfo.black) == star_match[0]
}
continue;
} else
- if(looking_at(buf, &i, "kibitzed to")) { // suppress the acknowledgements of our own autoKibitz
- started = STARTED_CHATTER;
- suppressKibitz = TRUE;
+ if((looking_at(buf, &i, "\nkibitzed to *\n") || looking_at(buf, &i, "kibitzed to *\n") ||
+ looking_at(buf, &i, "\n(kibitzed to *\n") || looking_at(buf, &i, "(kibitzed to *\n"))
+ && atoi(star_match[0])) {
+ // suppress the acknowledgements of our own autoKibitz
+ char *p;
+ if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
+ if(p = strchr(star_match[0], ' ')) p[1] = NULLCHAR; // clip off "players)" on FICS
+ SendToPlayer(star_match[0], strlen(star_match[0]));
+ if(looking_at(buf, &i, "*% ")) // eat prompt
+ suppressKibitz = FALSE;
+ next_out = i;
+ continue;
}
} // [HGM] kibitz: end of patch
-//if(appData.debugMode) fprintf(debugFP, "hunt for tell, buf = %s\n", buf+i);
-
// [HGM] chat: intercept tells by users for which we have an open chat window
channel = -1;
if(started == STARTED_NONE && (looking_at(buf, &i, "* tells you:") || looking_at(buf, &i, "* says:") ||
looking_at(buf, &i, "* whispers:") ||
+ looking_at(buf, &i, "* kibitzes:") ||
+ looking_at(buf, &i, "* shouts:") ||
+ looking_at(buf, &i, "* c-shouts:") ||
+ looking_at(buf, &i, "--> * ") ||
looking_at(buf, &i, "*(*):") && (sscanf(star_match[1], "%d", &channel),1) ||
- looking_at(buf, &i, "*(*)(*):") && sscanf(star_match[2], "%d", &channel) == 1 )) {
+ looking_at(buf, &i, "*(*)(*):") && (sscanf(star_match[2], "%d", &channel),1) ||
+ looking_at(buf, &i, "*(*)(*)(*):") && (sscanf(star_match[3], "%d", &channel),1) ||
+ looking_at(buf, &i, "*(*)(*)(*)(*):") && sscanf(star_match[4], "%d", &channel) == 1 )) {
int p;
sscanf(star_match[0], "%[^(]", talker+1); // strip (C) or (U) off ICS handle
chattingPartner = -1;
if(channel >= 0) // channel broadcast; look if there is a chatbox for this channel
for(p=0; p<MAX_CHAT; p++) {
if(channel == atoi(chatPartner[p])) {
- talker[0] = '['; strcat(talker, "]");
+ talker[0] = '['; strcat(talker, "] ");
+ Colorize(channel == 1 ? ColorChannel1 : ColorChannel, FALSE);
chattingPartner = p; break;
}
} else
+ if(buf[i-3] == 'e') // kibitz; look if there is a KIBITZ chatbox
+ for(p=0; p<MAX_CHAT; p++) {
+ if(!strcmp("kibitzes", chatPartner[p])) {
+ talker[0] = '['; strcat(talker, "] ");
+ chattingPartner = p; break;
+ }
+ } else
if(buf[i-3] == 'r') // whisper; look if there is a WHISPER chatbox
for(p=0; p<MAX_CHAT; p++) {
- if(!strcmp("WHISPER", chatPartner[p])) {
- talker[0] = '['; strcat(talker, "]");
+ if(!strcmp("whispers", chatPartner[p])) {
+ talker[0] = '['; strcat(talker, "] ");
chattingPartner = p; break;
}
+ } else
+ if(buf[i-3] == 't' || buf[oldi+2] == '>') {// shout, c-shout or it; look if there is a 'shouts' chatbox
+ if(buf[i-8] == '-' && buf[i-3] == 't')
+ for(p=0; p<MAX_CHAT; p++) { // c-shout; check if dedicatesd c-shout box exists
+ if(!strcmp("c-shouts", chatPartner[p])) {
+ talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE);
+ chattingPartner = p; break;
+ }
+ }
+ if(chattingPartner < 0)
+ for(p=0; p<MAX_CHAT; p++) {
+ if(!strcmp("shouts", chatPartner[p])) {
+ if(buf[oldi+2] == '>') { talker[0] = '<'; strcat(talker, "> "); Colorize(ColorShout, FALSE); }
+ else if(buf[i-8] == '-') { talker[0] = '('; strcat(talker, ") "); Colorize(ColorSShout, FALSE); }
+ else { talker[0] = '['; strcat(talker, "] "); Colorize(ColorShout, FALSE); }
+ chattingPartner = p; break;
+ }
+ }
}
if(chattingPartner<0) // if not, look if there is a chatbox for this indivdual
for(p=0; p<MAX_CHAT; p++) if(!StrCaseCmp(talker+1, chatPartner[p])) {
- talker[0] = 0;
+ talker[0] = 0; Colorize(ColorTell, FALSE);
chattingPartner = p; break;
}
if(chattingPartner<0) i = oldi; else {
+ Colorize(curColor, TRUE); // undo the bogus colorations we just made to trigger the souds
+ if(oldi > 0 && buf[oldi-1] == '\n') oldi--;
+ if (oldi > next_out) SendToPlayer(&buf[next_out], oldi - next_out);
started = STARTED_COMMENT;
parse_pos = 0; parse[0] = NULLCHAR;
- savingComment = TRUE;
+ savingComment = 3 + chattingPartner; // counts as TRUE
suppressKibitz = TRUE;
+ continue;
}
} // [HGM] chat: end of patch
/* [DM] Backup address for color zippy lines */
backup = i;
#if ZIPPY
- #ifdef WIN32
if (loggedOn == TRUE)
if (ZippyControl(buf, &backup) || ZippyConverse(buf, &backup) ||
(appData.zippyPlay && ZippyMatch(buf, &backup)));
- #else
- if (ZippyControl(buf, &i) ||
- ZippyConverse(buf, &i) ||
- (appData.zippyPlay && ZippyMatch(buf, &i))) {
- loggedOn = TRUE;
- if (!appData.colorize) continue;
- }
- #endif
#endif
} // [DM] 'else { ' deleted
if (
memcpy(parse, &buf[oldi], parse_pos);
parse[parse_pos] = NULLCHAR;
started = STARTED_COMMENT;
+ if(savingComment >= 3) // [HGM] chat: continuation of line for chat box
+ chattingPartner = savingComment - 3; // kludge to remember the box
} else {
started = STARTED_CHATTER;
}
if (looking_at(buf, &i, "% ") ||
((started == STARTED_MOVES || started == STARTED_MOVES_NOHIDE)
&& looking_at(buf, &i, "}*"))) { char *bookHit = NULL; // [HGM] book
+ if(ics_type == ICS_ICC && soughtPending) { // [HGM] seekgraph: on ICC sought-list has no termination line
+ soughtPending = FALSE;
+ seekGraphUp = TRUE;
+ DrawSeekGraph();
+ }
+ if(suppressKibitz) next_out = i;
savingComment = FALSE;
+ suppressKibitz = 0;
switch (started) {
case STARTED_MOVES:
case STARTED_MOVES_NOHIDE:
looking_at(buf, &i, "It is not your move")) {
/* Illegal move */
if (ics_user_moved && forwardMostMove > backwardMostMove) { // only backup if we already moved
- currentMove = --forwardMostMove;
+ currentMove = forwardMostMove-1;
DisplayMove(currentMove - 1); /* before DMError */
DrawPosition(FALSE, boards[currentMove]);
- SwitchClocks();
+ SwitchClocks(forwardMostMove-1); // [HGM] race
DisplayBothClocks();
}
DisplayMoveError(_("Illegal move (rejected by ICS)")); // [HGM] but always relay error msg
strncmp(why, "Continuing ", 11) == 0) {
gs_gamenum = gamenum;
strcpy(gs_kind, strchr(why, ' ') + 1);
+ VariantSwitch(boards[currentMove], StringToVariant(gs_kind)); // [HGM] variantswitch: even before we get first board
#if ZIPPY
if (appData.zippyPlay) {
ZippyGameStart(whitename, blackname);
}
#endif /*ZIPPY*/
+ partnerBoardValid = FALSE; // [HGM] bughouse
continue;
}
Reset(TRUE, TRUE);
}
#endif /*ZIPPY*/
+ if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
continue;
}
while(looking_at(buf, &i, "\n")); // [HGM] skip empty lines
if (looking_at(buf, &i, "*% ")) {
savingComment = FALSE;
+ suppressKibitz = 0;
}
}
next_out = i;
if (appData.debugMode)
fprintf(debugFP, "Parsing holdings: %s, currentMove = %d\n",
parse, currentMove);
- if (sscanf(parse, " game %d", &gamenum) == 1 &&
- gamenum == ics_gamenum) {
+ if (sscanf(parse, " game %d", &gamenum) == 1) {
+ if(gamenum == ics_gamenum) { // [HGM] bughouse: old code if part of foreground game
if (gameInfo.variant == VariantNormal) {
/* [HGM] We seem to switch variant during a game!
* Presumably no holdings were displayed, so we have
gameInfo.white, white_holding,
gameInfo.black, black_holding);
}
-
+ if(!partnerUp) // [HGM] bughouse: when peeking at partner game we already know what he captured...
DrawPosition(FALSE, boards[currentMove]);
DisplayTitle(str);
+ } else if(appData.bgObserve) { // [HGM] bughouse: holdings of other game => background
+ sscanf(parse, "game %d white [%s black [%s <- %s",
+ &gamenum, white_holding, black_holding,
+ new_piece);
+ white_holding[strlen(white_holding)-1] = NULLCHAR;
+ black_holding[strlen(black_holding)-1] = NULLCHAR;
+ /* [HGM] copy holdings to partner-board holdings area */
+ CopyHoldings(partnerBoard, white_holding, WhitePawn);
+ CopyHoldings(partnerBoard, black_holding, BlackPawn);
+ if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual: always draw
+ if(partnerUp) DrawPosition(FALSE, partnerBoard);
+ if(twoBoards) { partnerUp = 0; flipView = !flipView; }
+ }
}
/* Suppress following prompt */
if (looking_at(buf, &i, "*% ")) {
if(strchr(star_match[0], 7)) SendToPlayer("\007", 1); // Bell(); // FICS fuses bell for next board with prompt in zh captures
savingComment = FALSE;
+ suppressKibitz = 0;
}
next_out = i;
}
i++; /* skip unparsed character and loop back */
}
- if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz suppress printing in ICS interaction window
- started != STARTED_HOLDINGS && i > next_out) {
- SendToPlayer(&buf[next_out], i - next_out);
+ if (started != STARTED_MOVES && started != STARTED_BOARD && !suppressKibitz && // [HGM] kibitz
+// started != STARTED_HOLDINGS && i > next_out) { // [HGM] should we compare to leftover_start in stead of i?
+// SendToPlayer(&buf[next_out], i - next_out);
+ started != STARTED_HOLDINGS && leftover_start > next_out) {
+ SendToPlayer(&buf[next_out], leftover_start - next_out);
next_out = i;
}
- suppressKibitz = FALSE; // [HGM] kibitz: has done its duty in if-statement above
leftover_len = buf_len - leftover_start;
/* if buffer ends with something we couldn't parse,
/* Convert the move number to internal form */
moveNum = (moveNum - 1) * 2;
if (to_play == 'B') moveNum++;
- if (moveNum >= MAX_MOVES) {
+ if (moveNum > framePtr) { // [HGM] vari: do not run into saved variations
DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
0, 1);
return;
break;
}
+ if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
+ && newGameMode == IcsObserving && gamenum != ics_gamenum && appData.bgObserve) {
+ // [HGM] bughouse: don't act on alien boards while we play. Just parse the board and save it */
+ char *toSqr;
+ for (k = 0; k < ranks; k++) {
+ for (j = 0; j < files; j++)
+ board[k][j+gameInfo.holdingsWidth] = CharToPiece(board_chars[(ranks-1-k)*(files+1) + j]);
+ if(gameInfo.holdingsWidth > 1) {
+ board[k][0] = board[k][BOARD_WIDTH-1] = EmptySquare;
+ board[k][1] = board[k][BOARD_WIDTH-2] = (ChessSquare) 0;;
+ }
+ }
+ CopyBoard(partnerBoard, board);
+ if(toSqr = strchr(str, '/')) { // extract highlights from long move
+ partnerBoard[EP_STATUS-3] = toSqr[1] - AAA; // kludge: hide highlighting info in board
+ partnerBoard[EP_STATUS-4] = toSqr[2] - ONE;
+ } else partnerBoard[EP_STATUS-4] = partnerBoard[EP_STATUS-3] = -1;
+ if(toSqr = strchr(str, '-')) {
+ partnerBoard[EP_STATUS-1] = toSqr[1] - AAA;
+ partnerBoard[EP_STATUS-2] = toSqr[2] - ONE;
+ } else partnerBoard[EP_STATUS-1] = partnerBoard[EP_STATUS-2] = -1;
+ if(appData.dualBoard && !twoBoards) { twoBoards = 1; InitDrawingSizes(-2,0); }
+ if(twoBoards) { partnerUp = 1; flipView = !flipView; } // [HGM] dual
+ if(partnerUp) DrawPosition(FALSE, partnerBoard);
+ if(twoBoards) { partnerUp = 0; flipView = !flipView; } // [HGM] dual
+ sprintf(partnerStatus, "W: %d:%02d B: %d:%02d (%d-%d) %c", white_time/60000, (white_time%60000)/1000,
+ (black_time/60000), (black_time%60000)/1000, white_stren, black_stren, to_play);
+ DisplayMessage(partnerStatus, "");
+ partnerBoardValid = TRUE;
+ return;
+ }
+
/* Modify behavior for initial board display on move listing
of wild games.
*/
if(moveNum == 0 || gameInfo.variant != VariantFischeRandom)
{ int i, j; ChessSquare wKing = WhiteKing, bKing = BlackKing;
- for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
+ for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
if(board[0][i] == WhiteRook) j = i;
initialRights[0] = boards[moveNum][CASTLING][0] = (castle_ws == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
- for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
+ for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
if(board[0][i] == WhiteRook) j = i;
initialRights[1] = boards[moveNum][CASTLING][1] = (castle_wl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
- for(i=BOARD_LEFT, j= -1; i<BOARD_RGHT; i++)
+ for(i=BOARD_LEFT, j=NoRights; i<BOARD_RGHT; i++)
if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
initialRights[3] = boards[moveNum][CASTLING][3] = (castle_bs == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
- for(i=BOARD_RGHT-1, j= -1; i>=BOARD_LEFT; i--)
+ for(i=BOARD_RGHT-1, j=NoRights; i>=BOARD_LEFT; i--)
if(board[BOARD_HEIGHT-1][i] == BlackRook) j = i;
initialRights[4] = boards[moveNum][CASTLING][4] = (castle_bl == 0 && gameInfo.variant != VariantFischeRandom ? NoRights : j);
for(k=BOARD_LEFT; k<BOARD_RGHT; k++)
if(board[BOARD_HEIGHT-1][k] == bKing)
initialRights[5] = boards[moveNum][CASTLING][5] = k;
+ if(gameInfo.variant == VariantTwoKings) {
+ // In TwoKings looking for a King does not work, so always give castling rights to a King on e1/e8
+ if(board[0][4] == wKing) initialRights[2] = boards[moveNum][CASTLING][2] = 4;
+ if(board[BOARD_HEIGHT-1][4] == bKing) initialRights[5] = boards[moveNum][CASTLING][5] = 4;
+ }
} else { int r;
r = boards[moveNum][CASTLING][0] = initialRights[0];
if(board[0][r] != WhiteRook) boards[moveNum][CASTLING][0] = NoRights;
}
}
-
+
/* Display the board */
if (!pausing && !appData.noGUI) {
((gameMode == IcsPlayingBlack) && (!WhiteOnMove(currentMove))))
ClearPremoveHighlights();
- DrawPosition(FALSE, boards[currentMove]);
+ j = seekGraphUp; seekGraphUp = FALSE; // [HGM] seekgraph: when we draw a board, it overwrites the seek graph
+ if(partnerUp) { flipView = originalFlip; partnerUp = FALSE; j = TRUE; } // [HGM] bughouse: restore view
+ DrawPosition(j, boards[currentMove]);
+
DisplayMove(moveNum - 1);
if (appData.ringBellAfterMoves && /*!ics_user_moved*/ // [HGM] use absolute method to recognize own move
!((gameMode == IcsPlayingWhite) && (!WhiteOnMove(moveNum)) ||
case BlackPromotionChancellor:
case WhitePromotionArchbishop:
case BlackPromotionArchbishop:
- if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier)
+ if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk)
sprintf(user_move, "%c%c%c%c=%c\n",
AAA + fromX, ONE + fromY, AAA + toX, ONE + toY,
PieceToChar(WhiteFerz));
}
void
+UploadGameEvent()
+{ // [HGM] upload: send entire stored game to ICS as long-algebraic moves.
+ int i, last = forwardMostMove; // make sure ICS reply cannot pre-empt us by clearing fmm
+ static char *castlingStrings[4] = { "none", "kside", "qside", "both" };
+ if(gameMode == IcsObserving || gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite) {
+ DisplayError("You cannot do this while you are playing or observing", 0);
+ return;
+ }
+ if(gameMode != IcsExamining) { // is this ever not the case?
+ char buf[MSG_SIZ], *p, *fen, command[MSG_SIZ], bsetup = 0;
+
+ if(ics_type == ICS_ICC) { // on ICC match ourselves in applicable variant
+ sprintf(command, "match %s", ics_handle);
+ } else { // on FICS we must first go to general examine mode
+ strcpy(command, "examine\nbsetup"); // and specify variant within it with bsetups
+ }
+ if(gameInfo.variant != VariantNormal) {
+ // try figure out wild number, as xboard names are not always valid on ICS
+ for(i=1; i<=36; i++) {
+ sprintf(buf, "wild/%d", i);
+ if(StringToVariant(buf) == gameInfo.variant) break;
+ }
+ if(i<=36 && ics_type == ICS_ICC) sprintf(buf, "%s w%d\n", command, i);
+ else if(i == 22) sprintf(buf, "%s fr\n", command);
+ else sprintf(buf, "%s %s\n", command, VariantName(gameInfo.variant));
+ } else sprintf(buf, "%s\n", ics_type == ICS_ICC ? command : "examine\n"); // match yourself or examine
+ SendToICS(ics_prefix);
+ SendToICS(buf);
+ if(startedFromSetupPosition || backwardMostMove != 0) {
+ fen = PositionToFEN(backwardMostMove, NULL);
+ if(ics_type == ICS_ICC) { // on ICC we can simply send a complete FEN to set everything
+ sprintf(buf, "loadfen %s\n", fen);
+ SendToICS(buf);
+ } else { // FICS: everything has to set by separate bsetup commands
+ p = strchr(fen, ' '); p[0] = NULLCHAR; // cut after board
+ sprintf(buf, "bsetup fen %s\n", fen);
+ SendToICS(buf);
+ if(!WhiteOnMove(backwardMostMove)) {
+ SendToICS("bsetup tomove black\n");
+ }
+ i = (strchr(p+3, 'K') != NULL) + 2*(strchr(p+3, 'Q') != NULL);
+ sprintf(buf, "bsetup wcastle %s\n", castlingStrings[i]);
+ SendToICS(buf);
+ i = (strchr(p+3, 'k') != NULL) + 2*(strchr(p+3, 'q') != NULL);
+ sprintf(buf, "bsetup bcastle %s\n", castlingStrings[i]);
+ SendToICS(buf);
+ i = boards[backwardMostMove][EP_STATUS];
+ if(i >= 0) { // set e.p.
+ sprintf(buf, "bsetup eppos %c\n", i+AAA);
+ SendToICS(buf);
+ }
+ bsetup++;
+ }
+ }
+ if(bsetup || ics_type != ICS_ICC && gameInfo.variant != VariantNormal)
+ SendToICS("bsetup done\n"); // switch to normal examining.
+ }
+ for(i = backwardMostMove; i<last; i++) {
+ char buf[20];
+ sprintf(buf, "%s\n", parseList[i]);
+ SendToICS(buf);
+ }
+ SendToICS(ics_prefix);
+ SendToICS(ics_type == ICS_ICC ? "tag result Game in progress\n" : "commit\n");
+}
+
+void
CoordsToComputerAlgebraic(rf, ff, rt, ft, promoChar, move)
int rf, ff, rt, ft;
char promoChar;
}
}
+char yy_textstr[8000];
+
/* Parser for moves from gnuchess, ICS, or user typein box */
Boolean
ParseOneMove(move, moveNum, moveType, fromX, fromY, toX, toY, promoChar)
if (appData.debugMode) {
fprintf(debugFP, "move to parse: %s\n", move);
}
- *moveType = yylexstr(moveNum, move);
+ *moveType = yylexstr(moveNum, move, yy_textstr, sizeof yy_textstr);
switch (*moveType) {
case WhitePromotionChancellor:
if (appData.testLegality) {
return (*moveType != IllegalMove);
} else {
- return !(fromX == fromY && toX == toY);
+ return !(*fromX == *toX && *fromY == *toY) && boards[moveNum][*fromY][*fromX] != EmptySquare &&
+ WhiteOnMove(moveNum) == (boards[moveNum][*fromY][*fromX] < BlackPawn);
}
case WhiteDrop:
}
}
+
+void
+ParsePV(char *pv, Boolean storeComments)
+{ // Parse a string of PV moves, and append to current game, behind forwardMostMove
+ int fromX, fromY, toX, toY; char promoChar;
+ ChessMove moveType;
+ Boolean valid;
+ int nr = 0;
+
+ endPV = forwardMostMove;
+ do {
+ while(*pv == ' ' || *pv == '\n' || *pv == '\t') pv++; // must still read away whitespace
+ if(nr == 0 && !storeComments && *pv == '(') pv++; // first (ponder) move can be in parentheses
+ valid = ParseOneMove(pv, endPV, &moveType, &fromX, &fromY, &toX, &toY, &promoChar);
+if(appData.debugMode){
+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);
+}
+ if(!valid && nr == 0 &&
+ ParseOneMove(pv, endPV-1, &moveType, &fromX, &fromY, &toX, &toY, &promoChar)){
+ nr++; moveType = Comment; // First move has been played; kludge to make sure we continue
+ // Hande case where played move is different from leading PV move
+ CopyBoard(boards[endPV+1], boards[endPV-1]); // tentatively unplay last game move
+ CopyBoard(boards[endPV+2], boards[endPV-1]); // and play first move of PV
+ ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV+2]);
+ if(!CompareBoards(boards[endPV], boards[endPV+2])) {
+ endPV += 2; // if position different, keep this
+ moveList[endPV-1][0] = fromX + AAA;
+ moveList[endPV-1][1] = fromY + ONE;
+ moveList[endPV-1][2] = toX + AAA;
+ moveList[endPV-1][3] = toY + ONE;
+ parseList[endPV-1][0] = NULLCHAR;
+ strcpy(moveList[endPV-2], "_0_0"); // suppress premove highlight on takeback move
+ }
+ }
+ pv = strstr(pv, yy_textstr) + strlen(yy_textstr); // skip what we parsed
+ if(nr == 0 && !storeComments && *pv == ')') pv++; // closing parenthesis of ponder move;
+ if(moveType == Comment && storeComments) AppendComment(endPV, yy_textstr, FALSE);
+ if(moveType == Comment || moveType == NAG || moveType == ElapsedTime) {
+ valid++; // allow comments in PV
+ continue;
+ }
+ nr++;
+ if(endPV+1 > framePtr) break; // no space, truncate
+ if(!valid) break;
+ endPV++;
+ CopyBoard(boards[endPV], boards[endPV-1]);
+ ApplyMove(fromX, fromY, toX, toY, promoChar, boards[endPV]);
+ moveList[endPV-1][0] = fromX + AAA;
+ moveList[endPV-1][1] = fromY + ONE;
+ moveList[endPV-1][2] = toX + AAA;
+ moveList[endPV-1][3] = toY + ONE;
+ if(storeComments)
+ CoordsToAlgebraic(boards[endPV - 1],
+ PosFlags(endPV - 1),
+ fromY, fromX, toY, toX, promoChar,
+ parseList[endPV - 1]);
+ else
+ parseList[endPV-1][0] = NULLCHAR;
+ } while(valid);
+ currentMove = endPV;
+ if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
+ SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
+ moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
+ DrawPosition(TRUE, boards[currentMove]);
+}
+
+static int lastX, lastY;
+
+Boolean
+LoadMultiPV(int x, int y, char *buf, int index, int *start, int *end)
+{
+ int startPV;
+ char *p;
+
+ if(index < 0 || index >= strlen(buf)) return FALSE; // sanity
+ lastX = x; lastY = y;
+ while(index > 0 && buf[index-1] != '\n') index--; // beginning of line
+ startPV = index;
+ while(buf[index] != '\n') if(buf[index++] == '\t') startPV = index;
+ if(index == startPV && (p = StrCaseStr(buf+index, "PV="))) startPV = p - buf + 3;
+ index = startPV;
+ do{ while(buf[index] && buf[index] != '\n') index++;
+ } while(buf[index] == '\n' && buf[index+1] == '\\' && buf[index+2] == ' ' && index++); // join kibitzed PV continuation line
+ buf[index] = 0;
+ ParsePV(buf+startPV, FALSE);
+ *start = startPV; *end = index-1;
+ return TRUE;
+}
+
+Boolean
+LoadPV(int x, int y)
+{ // called on right mouse click to load PV
+ int which = gameMode == TwoMachinesPlay && (WhiteOnMove(forwardMostMove) == (second.twoMachinesColor[0] == 'w'));
+ lastX = x; lastY = y;
+ ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
+ return TRUE;
+}
+
+void
+UnLoadPV()
+{
+ if(endPV < 0) return;
+ endPV = -1;
+ currentMove = forwardMostMove;
+ ClearPremoveHighlights();
+ DrawPosition(TRUE, boards[currentMove]);
+}
+
+void
+MovePV(int x, int y, int h)
+{ // step through PV based on mouse coordinates (called on mouse move)
+ int margin = h>>3, step = 0;
+
+ if(endPV < 0) return;
+ // we must somehow check if right button is still down (might be released off board!)
+ if(y < margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = 1; else
+ if(y > h - margin && (abs(x - lastX) > 6 || abs(y - lastY) > 6)) step = -1; else
+ if( y > lastY + 6 ) step = -1; else if(y < lastY - 6) step = 1;
+ if(!step) return;
+ lastX = x; lastY = y;
+ if(currentMove + step > endPV || currentMove + step < forwardMostMove) step = 0;
+ currentMove += step;
+ if(currentMove == forwardMostMove) ClearPremoveHighlights(); else
+ SetPremoveHighlights(moveList[currentMove-1][0]-AAA, moveList[currentMove-1][1]-ONE,
+ moveList[currentMove-1][2]-AAA, moveList[currentMove-1][3]-ONE);
+ DrawPosition(FALSE, boards[currentMove]);
+}
+
+
// [HGM] shuffle: a general way to suffle opening setups, applicable to arbitrary variants.
// All positions will have equal probability, but the current method will not provide a unique
// numbering scheme for arrays that contain 3 or more pieces of the same kind.
// Last King gets castling rights
while(piecesLeft[(int)WhiteUnicorn]) {
i = put(board, WhiteUnicorn, 0, piecesLeft[(int)WhiteRook]/2, ANY);
- initialRights[2] = initialRights[5] = boards[0][CASTLING][2] = boards[0][CASTLING][5] = i;
+ initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
}
while(piecesLeft[(int)WhiteKing]) {
i = put(board, WhiteKing, 0, piecesLeft[(int)WhiteRook]/2, ANY);
- initialRights[2] = initialRights[5] = boards[0][CASTLING][2] = boards[0][CASTLING][5] = i;
+ initialRights[2] = initialRights[5] = board[CASTLING][2] = board[CASTLING][5] = i;
}
if(PosFlags(0) & F_FRC_TYPE_CASTLING) { // first and last Rook get FRC castling rights
if(first) {
first=0;
- initialRights[1] = initialRights[4] = boards[0][CASTLING][1] = boards[0][CASTLING][4] = i;
+ initialRights[1] = initialRights[4] = board[CASTLING][1] = board[CASTLING][4] = i;
}
- initialRights[0] = initialRights[3] = boards[0][CASTLING][0] = boards[0][CASTLING][3] = i;
+ initialRights[0] = initialRights[3] = board[CASTLING][0] = board[CASTLING][3] = i;
}
}
for(i=BOARD_LEFT; i<BOARD_RGHT; i++) { // copy black from white
/* [AS] Initialize pv info list [HGM] and game status */
{
- for( i=0; i<MAX_MOVES; i++ ) {
+ for( i=0; i<=framePtr; i++ ) { // [HGM] vari: spare saved variations
pvInfoList[i].depth = 0;
boards[i][EP_STATUS] = EP_NONE;
for( j=0; j<BOARD_FILES-2; j++ ) boards[i][CASTLING][j] = NoRights;
for(i=0; i<BOARD_FILES-2; i++)
initialPosition[CASTLING][i] = initialRights[i] = NoRights; /* but no rights yet */
initialPosition[EP_STATUS] = EP_NONE;
- SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
+ SetCharTable(pieceToChar, "PNBRQ...........Kpnbrq...........k");
+ if(startVariant == gameInfo.variant) // [HGM] nicks: enable nicknames in original variant
+ SetCharTable(pieceNickName, appData.pieceNickNames);
+ else SetCharTable(pieceNickName, "............");
switch (gameInfo.variant) {
case VariantFischeRandom:
nrCastlingRights = 0;
SetCharTable(pieceToChar, "PN.R.QB...Kpn.r.qb...k");
break;
+ case VariantMakruk:
+ pieces = makrukArray;
+ nrCastlingRights = 0;
+ startedFromSetupPosition = TRUE;
+ SetCharTable(pieceToChar, "PN.R.M....SKpn.r.m....sk");
+ break;
case VariantTwoKings:
pieces = twoKingsArray;
break;
break;
case VariantFairy:
pieces = fairyArray;
- SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVSLUKpnbrqfeacwmohijgdvsluk");
+ SetCharTable(pieceToChar, "PNBRQFEACWMOHIJGDVLSUKpnbrqfeacwmohijgdvlsuk");
break;
case VariantGreat:
pieces = GreatArray;
pawnRow = gameInfo.boardHeight - 7; /* seems to work in all common variants */
if(pawnRow < 1) pawnRow = 1;
+ if(gameInfo.variant == VariantMakruk) pawnRow = 2;
/* User pieceToChar list overrules defaults */
if(appData.pieceToCharTable != NULL)
setboardSpoiledMachineBlack = 0; /* [HGM] assume WB 4.2.7 already solves this after sending setboard */
}
+static int autoQueen; // [HGM] oneclick
+
int
HasPromotionChoice(int fromX, int fromY, int toX, int toY, char *promoChoice)
{
if(gameInfo.variant == VariantShogi) {
promotionZoneSize = 3;
highestPromotingPiece = (int)WhiteFerz;
+ } else if(gameInfo.variant == VariantMakruk) {
+ promotionZoneSize = 3;
}
// next weed out all moves that do not touch the promotion zone at all
}
// we either have a choice what to promote to, or (in Shogi) whether to promote
- if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier) {
+ if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantCourier || gameInfo.variant == VariantMakruk) {
*promoChoice = PieceToChar(BlackFerz); // no choice
return FALSE;
}
- if(appData.alwaysPromoteToQueen) { // predetermined
+ if(autoQueen) { // predetermined
if(gameInfo.variant == VariantSuicide || gameInfo.variant == VariantLosers)
*promoChoice = PieceToChar(BlackKing); // in Suicide Q is the last thing we want
else *promoChoice = PieceToChar(BlackQueen);
/* Could disallow this or prompt for confirmation */
cmailOldMove = -1;
}
- if (currentMove < forwardMostMove) {
- /* Discarding moves */
- /* Could prompt for confirmation here,
- but I don't think that's such a good idea */
- forwardMostMove = currentMove;
- }
break;
case BeginningOfGame:
break;
}
if (currentMove != forwardMostMove && gameMode != AnalyzeMode
+ && gameMode != EditGame // [HGM] vari: treat as AnalyzeMode
&& gameMode != AnalyzeFile && gameMode != Training) {
DisplayMoveError(_("Displayed position is not current"));
return FALSE;
return TRUE;
}
+Boolean
+OnlyMove(int *x, int *y, Boolean captures) {
+ DisambiguateClosure cl;
+ if (appData.zippyPlay) return FALSE;
+ switch(gameMode) {
+ case MachinePlaysBlack:
+ case IcsPlayingWhite:
+ case BeginningOfGame:
+ if(!WhiteOnMove(currentMove)) return FALSE;
+ break;
+ case MachinePlaysWhite:
+ case IcsPlayingBlack:
+ if(WhiteOnMove(currentMove)) return FALSE;
+ break;
+ default:
+ return FALSE;
+ }
+ cl.pieceIn = EmptySquare;
+ cl.rfIn = *y;
+ cl.ffIn = *x;
+ cl.rtIn = -1;
+ cl.ftIn = -1;
+ cl.promoCharIn = NULLCHAR;
+ Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
+ if( cl.kind == NormalMove ||
+ cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
+ cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen ||
+ cl.kind == WhitePromotionKnight || cl.kind == BlackPromotionKnight ||
+ cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
+ fromX = cl.ff;
+ fromY = cl.rf;
+ *x = cl.ft;
+ *y = cl.rt;
+ return TRUE;
+ }
+ if(cl.kind != ImpossibleMove) return FALSE;
+ cl.pieceIn = EmptySquare;
+ cl.rfIn = -1;
+ cl.ffIn = -1;
+ cl.rtIn = *y;
+ cl.ftIn = *x;
+ cl.promoCharIn = NULLCHAR;
+ Disambiguate(boards[currentMove], PosFlags(currentMove), &cl);
+ if( cl.kind == NormalMove ||
+ cl.kind == AmbiguousMove && captures && cl.captures == 1 ||
+ cl.kind == WhitePromotionQueen || cl.kind == BlackPromotionQueen ||
+ cl.kind == WhitePromotionKnight || cl.kind == BlackPromotionKnight ||
+ cl.kind == WhiteCapturesEnPassant || cl.kind == BlackCapturesEnPassant) {
+ fromX = cl.ff;
+ fromY = cl.rf;
+ *x = cl.ft;
+ *y = cl.rt;
+ autoQueen = TRUE; // act as if autoQueen on when we click to-square
+ return TRUE;
+ }
+ return FALSE;
+}
+
FILE *lastLoadGameFP = NULL, *lastLoadPositionFP = NULL;
int lastLoadGameNumber = 0, lastLoadPositionNumber = 0;
int lastLoadGameUseList = FALSE;
return AmbiguousMove;
} else if (toX >= 0 && toY >= 0) {
boards[0][toY][toX] = boards[0][fromY][fromX];
+ if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
+ if(boards[0][fromY][0] != EmptySquare) {
+ if(boards[0][fromY][1]) boards[0][fromY][1]--;
+ if(boards[0][fromY][1] == 0) boards[0][fromY][0] = EmptySquare;
+ }
+ } else
+ if(fromX == BOARD_RGHT+1) {
+ if(boards[0][fromY][BOARD_WIDTH-1] != EmptySquare) {
+ if(boards[0][fromY][BOARD_WIDTH-2]) boards[0][fromY][BOARD_WIDTH-2]--;
+ if(boards[0][fromY][BOARD_WIDTH-2] == 0) boards[0][fromY][BOARD_WIDTH-1] = EmptySquare;
+ }
+ } else
boards[0][fromY][fromX] = EmptySquare;
return AmbiguousMove;
}
return WhiteDrop; /* Not needed to specify white or black yet */
}
- userOfferedDraw = FALSE;
-
/* [HGM] always test for legality, to get promotion info */
moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
fromY, fromX, toY, toX, promoChar);
return ImpossibleMove;
}
}
-if(appData.debugMode) fprintf(debugFP, "moveType 3 = %d, promochar = %x\n", moveType, promoChar);
+
return moveType;
/* [HGM] <popupFix> in stead of calling FinishMove directly, this
function is made into one that returns an OK move type if FinishMove
/*char*/int promoChar;
{
char *bookHit = 0;
-if(appData.debugMode) fprintf(debugFP, "moveType 5 = %d, promochar = %x\n", moveType, promoChar);
+
if((gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) && promoChar != NULLCHAR) {
// [HGM] superchess: suppress promotions to non-available piece
int k = PieceToNumber(CharToPiece(ToUpper(promoChar)));
move type in caller when we know the move is a legal promotion */
if(moveType == NormalMove && promoChar)
moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
-if(appData.debugMode) fprintf(debugFP, "moveType 1 = %d, promochar = %x\n", moveType, promoChar);
+
/* [HGM] convert drag-and-drop piece drops to standard form */
- if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
+ if( (fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) && fromY != DROP_RANK ){
moveType = WhiteOnMove(currentMove) ? WhiteDrop : BlackDrop;
if(appData.debugMode) fprintf(debugFP, "Drop move %d, curr=%d, x=%d,y=%d, p=%d\n",
moveType, currentMove, fromX, fromY, boards[currentMove][fromY][fromX]);
-// fromX = boards[currentMove][fromY][fromX];
// holdings might not be sent yet in ICS play; we have to figure out which piece belongs here
if(fromX == 0) fromY = BOARD_HEIGHT-1 - fromY; // black holdings upside-down
fromX = fromX ? WhitePawn : BlackPawn; // first piece type in selected holdings
}
/* [HGM] <popupFix> The following if has been moved here from
- UserMoveEvent(). Because it seemed to belon here (why not allow
+ UserMoveEvent(). Because it seemed to belong here (why not allow
piece drops in training games?), and because it can only be
performed after it is known to what we promote. */
if (gameMode == Training) {
/* Ok, now we know that the move is good, so we can kill
the previous line in Analysis Mode */
- if (gameMode == AnalyzeMode && currentMove < forwardMostMove) {
- forwardMostMove = currentMove;
+ if ((gameMode == AnalyzeMode || gameMode == EditGame)
+ && currentMove < forwardMostMove) {
+ PushTail(currentMove, forwardMostMove); // [HGM] vari: save tail of game
}
/* If we need the chess program but it's dead, restart it */
MakeMove(fromX, fromY, toX, toY, promoChar); /*updates forwardMostMove*/
+ if(Adjudicate(NULL)) return 1; // [HGM] adjudicate: take care of automtic game end
+
if (gameMode == BeginningOfGame) {
if (appData.noChessProgram) {
gameMode = EditGame;
}
ModeHighlight();
}
-if(appData.debugMode) fprintf(debugFP, "moveType 2 = %d, promochar = %x\n", moveType, promoChar);
+
/* Relay move to ICS or chess engine */
if (appData.icsActive) {
if (gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack ||
gameMode == IcsExamining) {
+ if(userOfferedDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
+ SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
+ SendToICS("draw ");
+ SendMoveToICS(moveType, fromX, fromY, toX, toY);
+ }
+ // also send plain move, in case ICS does not understand atomic claims
SendMoveToICS(moveType, fromX, fromY, toX, toY);
ics_user_moved = 1;
}
break;
}
+ userOfferedDraw = FALSE; // [HGM] drawclaim: after move made, and tested for claimable draw
+
if(bookHit) { // [HGM] book: simulate book reply
static char bookMove[MSG_SIZ]; // a bit generous?
FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
}
+void
+Mark(board, flags, kind, rf, ff, rt, ft, closure)
+ Board board;
+ int flags;
+ ChessMove kind;
+ int rf, ff, rt, ft;
+ VOIDSTAR closure;
+{
+ typedef char Markers[BOARD_RANKS][BOARD_FILES];
+ Markers *m = (Markers *) closure;
+ if(rf == fromY && ff == fromX)
+ (*m)[rt][ft] = 1 + (board[rt][ft] != EmptySquare
+ || kind == WhiteCapturesEnPassant
+ || kind == BlackCapturesEnPassant);
+ else if(flags & F_MANDATORY_CAPTURE && board[rt][ft] != EmptySquare) (*m)[rt][ft] = 3;
+}
+
+void
+MarkTargetSquares(int clear)
+{
+ int x, y;
+ if(!appData.markers || !appData.highlightDragging ||
+ !appData.testLegality || gameMode == EditPosition) return;
+ if(clear) {
+ for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) marker[y][x] = 0;
+ } else {
+ int capt = 0;
+ GenLegal(boards[currentMove], PosFlags(currentMove), Mark, (void*) marker);
+ if(PosFlags(0) & F_MANDATORY_CAPTURE) {
+ for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x]>1) capt++;
+ if(capt)
+ for(x=0; x<BOARD_WIDTH; x++) for(y=0; y<BOARD_HEIGHT; y++) if(marker[y][x] == 1) marker[y][x] = 0;
+ }
+ }
+ DrawPosition(TRUE, NULL);
+}
+
void LeftClick(ClickType clickType, int xPix, int yPix)
{
int x, y;
Boolean saveAnimate;
- static int second = 0, promotionChoice = 0;
+ static int second = 0, promotionChoice = 0, dragging = 0;
char promoChoice = NULLCHAR;
+ if(appData.seekGraph && appData.icsActive && loggedOn &&
+ (gameMode == BeginningOfGame || gameMode == IcsIdle)) {
+ SeekGraphClick(clickType, xPix, yPix, 0);
+ return;
+ }
+
if (clickType == Press) ErrorPopDown();
+ MarkTargetSquares(1);
x = EventToSquare(xPix, BOARD_WIDTH);
y = EventToSquare(yPix, BOARD_HEIGHT);
|| x == BOARD_RGHT+1 && y >= gameInfo.holdingsSize) )
return;
+ autoQueen = appData.alwaysPromoteToQueen;
+
if (fromX == -1) {
+ if(!appData.oneClick || !OnlyMove(&x, &y, FALSE)) {
if (clickType == Press) {
/* First square */
if (OKToStartUserMove(x, y)) {
fromX = x;
fromY = y;
second = 0;
- DragPieceBegin(xPix, yPix);
+ MarkTargetSquares(0);
+ DragPieceBegin(xPix, yPix); dragging = 1;
if (appData.highlightDragging) {
SetHighlights(x, y, -1, -1);
}
}
+ } else if(dragging) { // [HGM] from-square must have been reset due to game end since last press
+ DragPieceEnd(xPix, yPix); dragging = 0;
+ DrawPosition(FALSE, NULL);
}
return;
+ }
}
/* fromX != -1 */
!(fromP == BlackKing && toP == BlackRook && frc))) {
/* Clicked again on same color piece -- changed his mind */
second = (x == fromX && y == fromY);
+ if(!second || appData.oneClick && !OnlyMove(&x, &y, TRUE)) {
if (appData.highlightDragging) {
SetHighlights(x, y, -1, -1);
} else {
}
if (OKToStartUserMove(x, y)) {
fromX = x;
- fromY = y;
+ fromY = y; dragging = 1;
+ MarkTargetSquares(0);
DragPieceBegin(xPix, yPix);
}
return;
+ }
}
// ignore clicks on holdings
if(x < BOARD_LEFT || x >= BOARD_RGHT) return;
}
if (clickType == Release && x == fromX && y == fromY) {
- DragPieceEnd(xPix, yPix);
+ DragPieceEnd(xPix, yPix); dragging = 0;
if (appData.animateDragging) {
/* Undo animation damage if any */
DrawPosition(FALSE, NULL);
} else {
ClearHighlights();
}
- DragPieceEnd(xPix, yPix);
+ DragPieceEnd(xPix, yPix); dragging = 0;
/* Don't animate move and drag both */
appData.animate = FALSE;
}
- // moves into holding are invalid for now (later perhaps allow in EditPosition)
+ // moves into holding are invalid for now (except in EditPosition, adapting to-square)
if(x >= 0 && x < BOARD_LEFT || x >= BOARD_RGHT) {
+ ChessSquare piece = boards[currentMove][fromY][fromX];
+ if(gameMode == EditPosition && piece != EmptySquare &&
+ fromX >= BOARD_LEFT && fromX < BOARD_RGHT) {
+ int n;
+
+ if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
+ n = PieceToNumber(piece - (int)BlackPawn);
+ if(n >= gameInfo.holdingsSize) { n = 0; piece = BlackPawn; }
+ boards[currentMove][BOARD_HEIGHT-1 - n][0] = piece;
+ boards[currentMove][BOARD_HEIGHT-1 - n][1]++;
+ } else
+ if(x == BOARD_RGHT+1 && piece < BlackPawn) {
+ n = PieceToNumber(piece);
+ if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
+ boards[currentMove][n][BOARD_WIDTH-1] = piece;
+ boards[currentMove][n][BOARD_WIDTH-2]++;
+ }
+ boards[currentMove][fromY][fromX] = EmptySquare;
+ }
ClearHighlights();
fromX = fromY = -1;
- DrawPosition(TRUE, NULL);
+ DrawPosition(TRUE, boards[currentMove]);
return;
}
}
}
+int RightClick(ClickType action, int x, int y, int *fromX, int *fromY)
+{ // front-end-free part taken out of PieceMenuPopup
+ int whichMenu; int xSqr, ySqr;
+
+ if(seekGraphUp) { // [HGM] seekgraph
+ if(action == Press) SeekGraphClick(Press, x, y, 2); // 2 indicates right-click: no pop-down on miss
+ if(action == Release) SeekGraphClick(Release, x, y, 2); // and no challenge on hit
+ return -2;
+ }
+
+ if((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack)
+ && !appData.zippyPlay && appData.bgObserve) { // [HGM] bughouse: show background game
+ if(!partnerBoardValid) return -2; // suppress display of uninitialized boards
+ if( appData.dualBoard) return -2; // [HGM] dual: is already displayed
+ if(action == Press) {
+ originalFlip = flipView;
+ flipView = !flipView; // temporarily flip board to see game from partners perspective
+ DrawPosition(TRUE, partnerBoard);
+ DisplayMessage(partnerStatus, "");
+ partnerUp = TRUE;
+ } else if(action == Release) {
+ flipView = originalFlip;
+ DrawPosition(TRUE, boards[currentMove]);
+ partnerUp = FALSE;
+ }
+ return -2;
+ }
+
+ xSqr = EventToSquare(x, BOARD_WIDTH);
+ ySqr = EventToSquare(y, BOARD_HEIGHT);
+ if (action == Release) UnLoadPV(); // [HGM] pv
+ if (action != Press) return -2; // return code to be ignored
+ switch (gameMode) {
+ case IcsExamining:
+ if(xSqr < BOARD_LEFT || xSqr >= BOARD_RGHT) return -1;\r
+ case EditPosition:
+ if (xSqr == BOARD_LEFT-1 || xSqr == BOARD_RGHT) return -1;\r
+ if (xSqr < 0 || ySqr < 0) return -1;\r
+ whichMenu = 0; // edit-position menu
+ break;
+ case IcsObserving:
+ if(!appData.icsEngineAnalyze) return -1;
+ case IcsPlayingWhite:
+ case IcsPlayingBlack:
+ if(!appData.zippyPlay) goto noZip;
+ case AnalyzeMode:
+ case AnalyzeFile:
+ case MachinePlaysWhite:
+ case MachinePlaysBlack:
+ case TwoMachinesPlay: // [HGM] pv: use for showing PV
+ if (!appData.dropMenu) {
+ LoadPV(x, y);
+ return 2; // flag front-end to grab mouse events
+ }
+ if(gameMode == TwoMachinesPlay || gameMode == AnalyzeMode ||
+ gameMode == AnalyzeFile || gameMode == IcsObserving) return -1;
+ case EditGame:
+ noZip:
+ if (xSqr < 0 || ySqr < 0) return -1;
+ if (!appData.dropMenu || appData.testLegality &&
+ gameInfo.variant != VariantBughouse &&
+ gameInfo.variant != VariantCrazyhouse) return -1;
+ whichMenu = 1; // drop menu
+ break;
+ default:
+ return -1;
+ }
+
+ if (((*fromX = xSqr) < 0) ||
+ ((*fromY = ySqr) < 0)) {
+ *fromX = *fromY = -1;
+ return -1;
+ }
+ if (flipView)
+ *fromX = BOARD_WIDTH - 1 - *fromX;
+ else
+ *fromY = BOARD_HEIGHT - 1 - *fromY;
+
+ return whichMenu;
+}
+
void SendProgramStatsToFrontend( ChessProgramState * cps, ChessProgramStats * cpstats )
{
// char * hint = lastHint;
stats.an_move_count = cpstats->nr_moves;
}
+ if(stats.pv && stats.pv[0]) strcpy(lastPV[stats.which], stats.pv); // [HGM] pv: remember last PV of each
+
SetProgramStats( &stats );
}
+void
+Count(Board board, int pCnt[], int *nW, int *nB, int *wStale, int *bStale, int *bishopColor)
+{ // count all piece types
+ int p, f, r;
+ *nB = *nW = *wStale = *bStale = *bishopColor = 0;
+ for(p=WhitePawn; p<=EmptySquare; p++) pCnt[p] = 0;
+ for(r=0; r<BOARD_HEIGHT; r++) for(f=BOARD_LEFT; f<BOARD_RGHT; f++) {
+ p = board[r][f];
+ if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
+ if(p == BlackPawn && r == 0) (*bStale)++; else pCnt[p]++; // count last-Rank Pawns (XQ) separately
+ if(p <= WhiteKing) (*nW)++; else if(p <= BlackKing) (*nB)++;
+ if(p == WhiteBishop || p == WhiteFerz || p == WhiteAlfil ||
+ p == BlackBishop || p == BlackFerz || p == BlackAlfil )
+ *bishopColor |= 1 << ((f^r)&1); // track square color of color-bound pieces
+ }
+}
+
+int
+MatingPotential(int pCnt[], int side, int nMine, int nHis, int stale, int bisColor)
+{
+ VariantClass v = gameInfo.variant;
+
+ if(v == VariantShogi || v == VariantCrazyhouse || v == VariantBughouse) return TRUE; // drop games always winnable
+ if(v == VariantShatranj) return TRUE; // always winnable through baring
+ if(v == VariantLosers || v == VariantSuicide || v == VariantGiveaway) return TRUE;
+ if(v == Variant3Check || v == VariantAtomic) return nMine > 1; // can win through checking / exploding King
+
+ if(v == VariantXiangqi) {
+ int majors = 5*pCnt[BlackKnight-side] + 7*pCnt[BlackCannon-side] + 7*pCnt[BlackRook-side];
+
+ nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side] + stale; // discount defensive pieces and back-rank Pawns
+ if(nMine + stale == 1) return (pCnt[BlackFerz-side] > 1 && pCnt[BlackKnight-side] > 0); // bare K can stalemate KHAA (!)
+ if(nMine > 2) return TRUE; // if we don't have P, H or R, we must have CC
+ if(nMine == 2 && pCnt[WhiteCannon+side] == 0) return TRUE; // We have at least one P, H or R
+ // if we get here, we must have KC... or KP..., possibly with additional A, E or last-rank P
+ if(stale) // we have at least one last-rank P plus perhaps C
+ return majors // KPKX
+ || pCnt[BlackFerz-side] && pCnt[BlackFerz-side] + pCnt[WhiteCannon+side] + stale > 2; // KPKAA, KPPKA and KCPKA
+ else // KCA*E*
+ return pCnt[WhiteFerz+side] // KCAK
+ || pCnt[WhiteAlfil+side] && pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackFerz-side] // KCEKA, KCEKX (X!=H)
+ || majors + (12*pCnt[BlackFerz-side] | 6*pCnt[BlackAlfil-side]) > 16; // KCKAA, KCKAX, KCKEEX, KCKEXX (XX!=HH), KCKXXX
+ // TO DO: cases wih an unpromoted f-Pawn acting as platform for an opponent Cannon
+
+ } else if(pCnt[WhiteKing] == 1 && pCnt[BlackKing] == 1) { // other variants with orthodox Kings
+ int nBishops = pCnt[WhiteBishop+side] + pCnt[WhiteFerz+side];
+
+ if(nMine == 1) return FALSE; // bare King
+ 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
+ nMine += (nBishops > 0) - nBishops; // By now all Bishops (and Ferz) on like-colored squares, so count as one
+ if(nMine > 2 && nMine != pCnt[WhiteAlfil+side] + 1) return TRUE; // At least two pieces, not all Alfils
+ // by now we have King + 1 piece (or multiple Bishops on the same color)
+ if(pCnt[WhiteKnight+side])
+ return (pCnt[BlackKnight-side] + pCnt[BlackBishop-side] + pCnt[BlackMan-side] +
+ pCnt[BlackWazir-side] + pCnt[BlackSilver-side] + bisColor // KNKN, KNKB, KNKF, KNKE, KNKW, KNKM, KNKS
+ || nHis > 3); // be sure to cover suffocation mates in corner (e.g. KNKQCA)
+ if(nBishops)
+ return (pCnt[BlackKnight-side]); // KBKN, KFKN
+ if(pCnt[WhiteAlfil+side])
+ return (nHis > 2); // Alfils can in general not reach a corner square, but there might be edge (suffocation) mates
+ if(pCnt[WhiteWazir+side])
+ return (pCnt[BlackKnight-side] + pCnt[BlackWazir-side] + pCnt[BlackAlfil-side]); // KWKN, KWKW, KWKE
+ }
+
+ return TRUE;
+}
+
+int
+Adjudicate(ChessProgramState *cps)
+{ // [HGM] some adjudications useful with buggy engines
+ // [HGM] adjudicate: made into separate routine, which now can be called after every move
+ // In any case it determnes if the game is a claimable draw (filling in EP_STATUS).
+ // Actually ending the game is now based on the additional internal condition canAdjudicate.
+ // Only when the game is ended, and the opponent is a computer, this opponent gets the move relayed.
+ int k, count = 0; static int bare = 1;
+ ChessProgramState *engineOpponent = (gameMode == TwoMachinesPlay ? cps->other : (cps ? NULL : &first));
+ Boolean canAdjudicate = !appData.icsActive;
+
+ // most tests only when we understand the game, i.e. legality-checking on, and (for the time being) no piece drops
+ if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
+ if( appData.testLegality )
+ { /* [HGM] Some more adjudications for obstinate engines */
+ int nrW, nrB, bishopColor, staleW, staleB, nr[EmptySquare+1], i;
+ static int moveCount = 6;
+ ChessMove result;
+ char *reason = NULL;
+
+ /* Count what is on board. */
+ Count(boards[forwardMostMove], nr, &nrW, &nrB, &staleW, &staleB, &bishopColor);
+
+ /* Some material-based adjudications that have to be made before stalemate test */
+ if(gameInfo.variant == VariantAtomic && nr[WhiteKing] + nr[BlackKing] < 2) {
+ // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
+ boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
+ if(canAdjudicate && appData.checkMates) {
+ if(engineOpponent)
+ SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
+ ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
+ GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
+ "Xboard adjudication: King destroyed", GE_XBOARD );
+ return 1;
+ }
+ }
+
+ /* Bare King in Shatranj (loses) or Losers (wins) */
+ if( nrW == 1 || nrB == 1) {
+ if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
+ boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
+ if(canAdjudicate && appData.checkMates) {
+ if(engineOpponent)
+ SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets to see move
+ ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
+ GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
+ "Xboard adjudication: Bare king", GE_XBOARD );
+ return 1;
+ }
+ } else
+ if( gameInfo.variant == VariantShatranj && --bare < 0)
+ { /* bare King */
+ boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
+ if(canAdjudicate && appData.checkMates) {
+ /* but only adjudicate if adjudication enabled */
+ if(engineOpponent)
+ SendMoveToProgram(forwardMostMove-1, engineOpponent); // make sure opponent gets move
+ ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
+ GameEnds( nrW > 1 ? WhiteWins : nrB > 1 ? BlackWins : GameIsDrawn,
+ "Xboard adjudication: Bare king", GE_XBOARD );
+ return 1;
+ }
+ }
+ } else bare = 1;
+
+
+ // don't wait for engine to announce game end if we can judge ourselves
+ switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
+ case MT_CHECK:
+ if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
+ int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
+ for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
+ if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
+ checkCnt++;
+ if(checkCnt >= 2) {
+ reason = "Xboard adjudication: 3rd check";
+ boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
+ break;
+ }
+ }
+ }
+ case MT_NONE:
+ default:
+ break;
+ case MT_STALEMATE:
+ case MT_STAINMATE:
+ reason = "Xboard adjudication: Stalemate";
+ if((signed char)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
+ boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
+ if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
+ boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
+ else if(gameInfo.variant == VariantSuicide) // in suicide it depends
+ boards[forwardMostMove][EP_STATUS] = nrW == nrB ? EP_STALEMATE :
+ ((nrW < nrB) != WhiteOnMove(forwardMostMove) ?
+ EP_CHECKMATE : EP_WINS);
+ else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
+ boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
+ }
+ break;
+ case MT_CHECKMATE:
+ reason = "Xboard adjudication: Checkmate";
+ boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
+ break;
+ }
+
+ switch(i = (signed char)boards[forwardMostMove][EP_STATUS]) {
+ case EP_STALEMATE:
+ result = GameIsDrawn; break;
+ case EP_CHECKMATE:
+ result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
+ case EP_WINS:
+ result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
+ default:
+ result = (ChessMove) 0;
+ }
+ if(canAdjudicate && appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
+ if(engineOpponent)
+ SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
+ ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
+ GameEnds( result, reason, GE_XBOARD );
+ return 1;
+ }
+
+ /* Next absolutely insufficient mating material. */
+ if(!MatingPotential(nr, WhitePawn, nrW, nrB, staleW, bishopColor) &&
+ !MatingPotential(nr, BlackPawn, nrB, nrW, staleB, bishopColor))
+ { /* includes KBK, KNK, KK of KBKB with like Bishops */
+
+ /* always flag draws, for judging claims */
+ boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
+
+ if(canAdjudicate && appData.materialDraws) {
+ /* but only adjudicate them if adjudication enabled */
+ if(engineOpponent) {
+ SendToProgram("force\n", engineOpponent); // suppress reply
+ SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see last move */
+ }
+ ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
+ GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
+ return 1;
+ }
+ }
+
+ /* Then some trivial draws (only adjudicate, cannot be claimed) */
+ if(nrW + nrB == 4 &&
+ ( nr[WhiteRook] == 1 && nr[BlackRook] == 1 /* KRKR */
+ || nr[WhiteQueen] && nr[BlackQueen]==1 /* KQKQ */
+ || nr[WhiteKnight]==2 || nr[BlackKnight]==2 /* KNNK */
+ || nr[WhiteKnight]+nr[WhiteBishop] == 1 && nr[BlackKnight]+nr[BlackBishop] == 1 /* KBKN, KBKB, KNKN */
+ ) ) {
+ if(--moveCount < 0 && appData.trivialDraws && canAdjudicate)
+ { /* if the first 3 moves do not show a tactical win, declare draw */
+ if(engineOpponent) {
+ SendToProgram("force\n", engineOpponent); // suppress reply
+ SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
+ }
+ ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
+ GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
+ return 1;
+ }
+ } else moveCount = 6;
+ }
+ }
+
+ if (appData.debugMode) { int i;
+ fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
+ forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
+ appData.drawRepeats);
+ for( i=forwardMostMove; i>=backwardMostMove; i-- )
+ fprintf(debugFP, "%d ep=%d\n", i, (signed char)boards[i][EP_STATUS]);
+
+ }
+
+ // Repetition draws and 50-move rule can be applied independently of legality testing
+
+ /* Check for rep-draws */
+ count = 0;
+ for(k = forwardMostMove-2;
+ k>=backwardMostMove && k>=forwardMostMove-100 &&
+ (signed char)boards[k][EP_STATUS] < EP_UNKNOWN &&
+ (signed char)boards[k+2][EP_STATUS] <= EP_NONE && (signed char)boards[k+1][EP_STATUS] <= EP_NONE;
+ k-=2)
+ { int rights=0;
+ if(CompareBoards(boards[k], boards[forwardMostMove])) {
+ /* compare castling rights */
+ if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
+ (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
+ rights++; /* King lost rights, while rook still had them */
+ if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
+ if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
+ boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
+ rights++; /* but at least one rook lost them */
+ }
+ if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
+ (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
+ rights++;
+ if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
+ if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
+ boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
+ rights++;
+ }
+ if( rights == 0 && ++count > appData.drawRepeats-2 && canAdjudicate
+ && appData.drawRepeats > 1) {
+ /* adjudicate after user-specified nr of repeats */
+ int result = GameIsDrawn;
+ char *details = "XBoard adjudication: repetition draw";
+ if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
+ // [HGM] xiangqi: check for forbidden perpetuals
+ int m, ourPerpetual = 1, hisPerpetual = 1;
+ for(m=forwardMostMove; m>k; m-=2) {
+ if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
+ ourPerpetual = 0; // the current mover did not always check
+ if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
+ hisPerpetual = 0; // the opponent did not always check
+ }
+ if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
+ ourPerpetual, hisPerpetual);
+ if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
+ result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
+ details = "Xboard adjudication: perpetual checking";
+ } else
+ if(hisPerpetual && !ourPerpetual) { // he is checking us, but did not repeat yet
+ break; // (or we would have caught him before). Abort repetition-checking loop.
+ } else
+ // Now check for perpetual chases
+ if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
+ hisPerpetual = PerpetualChase(k, forwardMostMove);
+ ourPerpetual = PerpetualChase(k+1, forwardMostMove);
+ if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
+ result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
+ details = "Xboard adjudication: perpetual chasing";
+ } else
+ if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
+ break; // Abort repetition-checking loop.
+ }
+ // if neither of us is checking or chasing all the time, or both are, it is draw
+ }
+ if(engineOpponent) {
+ SendToProgram("force\n", engineOpponent); // suppress reply
+ SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
+ }
+ ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
+ GameEnds( result, details, GE_XBOARD );
+ return 1;
+ }
+ if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
+ boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
+ }
+ }
+
+ /* Now we test for 50-move draws. Determine ply count */
+ count = forwardMostMove;
+ /* look for last irreversble move */
+ while( (signed char)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
+ count--;
+ /* if we hit starting position, add initial plies */
+ if( count == backwardMostMove )
+ count -= initialRulePlies;
+ count = forwardMostMove - count;
+ if(gameInfo.variant == VariantXiangqi && ( count >= 100 || count >= 2*appData.ruleMoves ) ) {
+ // adjust reversible move counter for checks in Xiangqi
+ int i = forwardMostMove - count, inCheck = 0, lastCheck;
+ if(i < backwardMostMove) i = backwardMostMove;
+ while(i <= forwardMostMove) {
+ lastCheck = inCheck; // check evasion does not count
+ inCheck = (MateTest(boards[i], PosFlags(i)) == MT_CHECK);
+ if(inCheck || lastCheck) count--; // check does not count
+ i++;
+ }
+ }
+ if( count >= 100)
+ boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
+ /* this is used to judge if draw claims are legal */
+ if(canAdjudicate && appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
+ if(engineOpponent) {
+ SendToProgram("force\n", engineOpponent); // suppress reply
+ SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
+ }
+ ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
+ GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
+ return 1;
+ }
+
+ /* if draw offer is pending, treat it as a draw claim
+ * when draw condition present, to allow engines a way to
+ * claim draws before making their move to avoid a race
+ * condition occurring after their move
+ */
+ if((gameMode == TwoMachinesPlay ? second.offeredDraw : userOfferedDraw) || first.offeredDraw ) {
+ char *p = NULL;
+ if((signed char)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
+ p = "Draw claim: 50-move rule";
+ if((signed char)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
+ p = "Draw claim: 3-fold repetition";
+ if((signed char)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
+ p = "Draw claim: insufficient mating material";
+ if( p != NULL && canAdjudicate) {
+ if(engineOpponent) {
+ SendToProgram("force\n", engineOpponent); // suppress reply
+ SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
+ }
+ ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
+ GameEnds( GameIsDrawn, p, GE_XBOARD );
+ return 1;
+ }
+ }
+
+ if( canAdjudicate && appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
+ if(engineOpponent) {
+ SendToProgram("force\n", engineOpponent); // suppress reply
+ SendMoveToProgram(forwardMostMove-1, engineOpponent); /* make sure opponent gets to see move */
+ }
+ ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
+ GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
+ return 1;
+ }
+ return 0;
+}
+
char *SendMoveToBookUser(int moveNr, ChessProgramState *cps, int initial)
{ // [HGM] book: this routine intercepts moves to simulate book replies
char *bookHit = NULL;
// after a book hit we never send 'go', and the code after the call to this routine
// has '&& !bookHit' added to suppress potential sending there (based on 'firstMove').
char buf[MSG_SIZ];
- if (cps->useUsermove) sprintf(buf, "usermove "); // sorry, no SAN yet :(
- sprintf(buf, "%s\n", bookHit); // force book move into program supposed to play it
+ sprintf(buf, "%s%s\n", (cps->useUsermove ? "usermove " : ""), bookHit); // force book move into program supposed to play it
SendToProgram(buf, cps);
if(!initial) firstMove = FALSE; // normally we would clear the firstMove condition after return & sending 'go'
} else if(initial) { // 'go' was needed irrespective of firstMove, and it has to be done in this routine
int machineWhite;
char *bookHit;
+ cps->userError = 0;
+
FakeBookMove: // [HGM] book: we jump here to simulate machine moves after book hit
/*
* Kludge to ignore BEL characters
if (cps->sendTime == 2) cps->sendTime = 1;
if (cps->offeredDraw) cps->offeredDraw--;
-#if ZIPPY
- if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
- first.initDone) {
- SendMoveToICS(moveType, fromX, fromY, toX, toY);
- ics_user_moved = 1;
- if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
- char buf[3*MSG_SIZ];
-
- sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
- programStats.score / 100.,
- programStats.depth,
- programStats.time / 100.,
- (unsigned int)programStats.nodes,
- (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
- programStats.movelist);
- SendToICS(buf);
-if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
- }
- }
-#endif
/* currentMoveString is set as a side-effect of ParseOneMove */
strcpy(machineMove, currentMoveString);
strcat(machineMove, "\n");
strcpy(moveList[forwardMostMove], machineMove);
- /* [AS] Save move info and clear stats for next move */
+ /* [AS] Save move info*/
pvInfoList[ forwardMostMove ].score = programStats.score;
pvInfoList[ forwardMostMove ].depth = programStats.depth;
pvInfoList[ forwardMostMove ].time = programStats.time; // [HGM] PGNtime: take time from engine stats
- ClearProgramStats();
- thinkOutput[0] = NULLCHAR;
- hiddenThinkOutputState = 0;
MakeMove(fromX, fromY, toX, toY, promoChar);/*updates forwardMostMove*/
}
}
- if( gameMode == TwoMachinesPlay ) {
- // [HGM] some adjudications useful with buggy engines
- int k, count = 0; static int bare = 1;
- if(gameInfo.holdingsSize == 0 || gameInfo.variant == VariantSuper || gameInfo.variant == VariantGreat) {
-
-
- if( appData.testLegality )
- { /* [HGM] Some more adjudications for obstinate engines */
- int NrWN=0, NrBN=0, NrWB=0, NrBB=0, NrWR=0, NrBR=0,
- NrWQ=0, NrBQ=0, NrW=0, NrK=0, bishopsColor = 0,
- NrPieces=0, NrPawns=0, PawnAdvance=0, i, j;
- static int moveCount = 6;
- ChessMove result;
- char *reason = NULL;
-
- /* Count what is on board. */
- for(i=0; i<BOARD_HEIGHT; i++) for(j=BOARD_LEFT; j<BOARD_RGHT; j++)
- { ChessSquare p = boards[forwardMostMove][i][j];
- int m=i;
-
- switch((int) p)
- { /* count B,N,R and other of each side */
- case WhiteKing:
- case BlackKing:
- NrK++; break; // [HGM] atomic: count Kings
- case WhiteKnight:
- NrWN++; break;
- case WhiteBishop:
- case WhiteFerz: // [HGM] shatranj: kludge to mke it work in shatranj
- bishopsColor |= 1 << ((i^j)&1);
- NrWB++; break;
- case BlackKnight:
- NrBN++; break;
- case BlackBishop:
- case BlackFerz: // [HGM] shatranj: kludge to mke it work in shatranj
- bishopsColor |= 1 << ((i^j)&1);
- NrBB++; break;
- case WhiteRook:
- NrWR++; break;
- case BlackRook:
- NrBR++; break;
- case WhiteQueen:
- NrWQ++; break;
- case BlackQueen:
- NrBQ++; break;
- case EmptySquare:
- break;
- case BlackPawn:
- m = 7-i;
- case WhitePawn:
- PawnAdvance += m; NrPawns++;
- }
- NrPieces += (p != EmptySquare);
- NrW += ((int)p < (int)BlackPawn);
- if(gameInfo.variant == VariantXiangqi &&
- (p == WhiteFerz || p == WhiteAlfil || p == BlackFerz || p == BlackAlfil)) {
- NrPieces--; // [HGM] XQ: do not count purely defensive pieces
- NrW -= ((int)p < (int)BlackPawn);
- }
- }
-
- /* Some material-based adjudications that have to be made before stalemate test */
- if(gameInfo.variant == VariantAtomic && NrK < 2) {
- // [HGM] atomic: stm must have lost his King on previous move, as destroying own K is illegal
- boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // make claimable as if stm is checkmated
- if(appData.checkMates) {
- SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
- ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
- GameEnds( WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins,
- "Xboard adjudication: King destroyed", GE_XBOARD );
- return;
- }
- }
-
- /* Bare King in Shatranj (loses) or Losers (wins) */
- if( NrW == 1 || NrPieces - NrW == 1) {
- if( gameInfo.variant == VariantLosers) { // [HGM] losers: bare King wins (stm must have it first)
- boards[forwardMostMove][EP_STATUS] = EP_WINS; // mark as win, so it becomes claimable
- if(appData.checkMates) {
- SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets to see move
- ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
- GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
- "Xboard adjudication: Bare king", GE_XBOARD );
- return;
- }
- } else
- if( gameInfo.variant == VariantShatranj && --bare < 0)
- { /* bare King */
- boards[forwardMostMove][EP_STATUS] = EP_WINS; // make claimable as win for stm
- if(appData.checkMates) {
- /* but only adjudicate if adjudication enabled */
- SendMoveToProgram(forwardMostMove-1, cps->other); // make sure opponent gets move
- ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
- GameEnds( NrW > 1 ? WhiteWins : NrPieces - NrW > 1 ? BlackWins : GameIsDrawn,
- "Xboard adjudication: Bare king", GE_XBOARD );
- return;
- }
- }
- } else bare = 1;
-
-
- // don't wait for engine to announce game end if we can judge ourselves
- switch (MateTest(boards[forwardMostMove], PosFlags(forwardMostMove)) ) {
- case MT_CHECK:
- if(gameInfo.variant == Variant3Check) { // [HGM] 3check: when in check, test if 3rd time
- int i, checkCnt = 0; // (should really be done by making nr of checks part of game state)
- for(i=forwardMostMove-2; i>=backwardMostMove; i-=2) {
- if(MateTest(boards[i], PosFlags(i)) == MT_CHECK)
- checkCnt++;
- if(checkCnt >= 2) {
- reason = "Xboard adjudication: 3rd check";
- boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE;
- break;
- }
- }
- }
- case MT_NONE:
- default:
- break;
- case MT_STALEMATE:
- case MT_STAINMATE:
- reason = "Xboard adjudication: Stalemate";
- if((int)boards[forwardMostMove][EP_STATUS] != EP_CHECKMATE) { // [HGM] don't touch win through baring or K-capt
- boards[forwardMostMove][EP_STATUS] = EP_STALEMATE; // default result for stalemate is draw
- if(gameInfo.variant == VariantLosers || gameInfo.variant == VariantGiveaway) // [HGM] losers:
- boards[forwardMostMove][EP_STATUS] = EP_WINS; // in these variants stalemated is always a win
- else if(gameInfo.variant == VariantSuicide) // in suicide it depends
- boards[forwardMostMove][EP_STATUS] = NrW == NrPieces-NrW ? EP_STALEMATE :
- ((NrW < NrPieces-NrW) != WhiteOnMove(forwardMostMove) ?
- EP_CHECKMATE : EP_WINS);
- else if(gameInfo.variant == VariantShatranj || gameInfo.variant == VariantXiangqi)
- boards[forwardMostMove][EP_STATUS] = EP_CHECKMATE; // and in these variants being stalemated loses
- }
- break;
- case MT_CHECKMATE:
- reason = "Xboard adjudication: Checkmate";
- boards[forwardMostMove][EP_STATUS] = (gameInfo.variant == VariantLosers ? EP_WINS : EP_CHECKMATE);
- break;
- }
-
- switch(i = (int)boards[forwardMostMove][EP_STATUS]) {
- case EP_STALEMATE:
- result = GameIsDrawn; break;
- case EP_CHECKMATE:
- result = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins; break;
- case EP_WINS:
- result = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins; break;
- default:
- result = (ChessMove) 0;
- }
- if(appData.checkMates && result) { // [HGM] mates: adjudicate finished games if requested
- SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
- ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
- GameEnds( result, reason, GE_XBOARD );
- return;
- }
-
- /* Next absolutely insufficient mating material. */
- if( NrPieces == 2 || gameInfo.variant != VariantXiangqi &&
- gameInfo.variant != VariantShatranj && // [HGM] baring will remain possible
- (NrPieces == 3 && NrWN+NrBN+NrWB+NrBB == 1 ||
- NrPieces == NrBB+NrWB+2 && bishopsColor != 3)) // [HGM] all Bishops (Ferz!) same color
- { /* KBK, KNK, KK of KBKB with like Bishops */
-
- /* always flag draws, for judging claims */
- boards[forwardMostMove][EP_STATUS] = EP_INSUF_DRAW;
-
- if(appData.materialDraws) {
- /* but only adjudicate them if adjudication enabled */
- SendToProgram("force\n", cps->other); // suppress reply
- SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see last move */
- ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
- GameEnds( GameIsDrawn, "Xboard adjudication: Insufficient mating material", GE_XBOARD );
- return;
- }
- }
+ if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
- /* Then some trivial draws (only adjudicate, cannot be claimed) */
- if(NrPieces == 4 &&
- ( NrWR == 1 && NrBR == 1 /* KRKR */
- || NrWQ==1 && NrBQ==1 /* KQKQ */
- || NrWN==2 || NrBN==2 /* KNNK */
- || NrWN+NrWB == 1 && NrBN+NrBB == 1 /* KBKN, KBKB, KNKN */
- ) ) {
- if(--moveCount < 0 && appData.trivialDraws)
- { /* if the first 3 moves do not show a tactical win, declare draw */
- SendToProgram("force\n", cps->other); // suppress reply
- SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
- ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
- GameEnds( GameIsDrawn, "Xboard adjudication: Trivial draw", GE_XBOARD );
- return;
- }
- } else moveCount = 6;
- }
- }
-
- if (appData.debugMode) { int i;
- fprintf(debugFP, "repeat test fmm=%d bmm=%d ep=%d, reps=%d\n",
- forwardMostMove, backwardMostMove, boards[backwardMostMove][EP_STATUS],
- appData.drawRepeats);
- for( i=forwardMostMove; i>=backwardMostMove; i-- )
- fprintf(debugFP, "%d ep=%d\n", i, (int)boards[i][EP_STATUS]);
-
+#if ZIPPY
+ if ((gameMode == IcsPlayingWhite || gameMode == IcsPlayingBlack) &&
+ first.initDone) {
+ if(cps->offeredDraw && (signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
+ SendToICS(ics_prefix); // [HGM] drawclaim: send caim and move on one line for FICS
+ SendToICS("draw ");
+ SendMoveToICS(moveType, fromX, fromY, toX, toY);
}
+ SendMoveToICS(moveType, fromX, fromY, toX, toY);
+ ics_user_moved = 1;
+ if(appData.autoKibitz && !appData.icsEngineAnalyze ) { /* [HGM] kibitz: send most-recent PV info to ICS */
+ char buf[3*MSG_SIZ];
- /* Check for rep-draws */
- count = 0;
- for(k = forwardMostMove-2;
- k>=backwardMostMove && k>=forwardMostMove-100 &&
- (int)boards[k][EP_STATUS] < EP_UNKNOWN &&
- (int)boards[k+2][EP_STATUS] <= EP_NONE && (int)boards[k+1][EP_STATUS] <= EP_NONE;
- k-=2)
- { int rights=0;
- if(CompareBoards(boards[k], boards[forwardMostMove])) {
- /* compare castling rights */
- if( boards[forwardMostMove][CASTLING][2] != boards[k][CASTLING][2] &&
- (boards[k][CASTLING][0] != NoRights || boards[k][CASTLING][1] != NoRights) )
- rights++; /* King lost rights, while rook still had them */
- if( boards[forwardMostMove][CASTLING][2] != NoRights ) { /* king has rights */
- if( boards[forwardMostMove][CASTLING][0] != boards[k][CASTLING][0] ||
- boards[forwardMostMove][CASTLING][1] != boards[k][CASTLING][1] )
- rights++; /* but at least one rook lost them */
- }
- if( boards[forwardMostMove][CASTLING][5] != boards[k][CASTLING][5] &&
- (boards[k][CASTLING][3] != NoRights || boards[k][CASTLING][4] != NoRights) )
- rights++;
- if( boards[forwardMostMove][CASTLING][5] != NoRights ) {
- if( boards[forwardMostMove][CASTLING][3] != boards[k][CASTLING][3] ||
- boards[forwardMostMove][CASTLING][4] != boards[k][CASTLING][4] )
- rights++;
- }
- if( rights == 0 && ++count > appData.drawRepeats-2
- && appData.drawRepeats > 1) {
- /* adjudicate after user-specified nr of repeats */
- SendToProgram("force\n", cps->other); // suppress reply
- SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
- ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
- if(gameInfo.variant == VariantXiangqi && appData.testLegality) {
- // [HGM] xiangqi: check for forbidden perpetuals
- int m, ourPerpetual = 1, hisPerpetual = 1;
- for(m=forwardMostMove; m>k; m-=2) {
- if(MateTest(boards[m], PosFlags(m)) != MT_CHECK)
- ourPerpetual = 0; // the current mover did not always check
- if(MateTest(boards[m-1], PosFlags(m-1)) != MT_CHECK)
- hisPerpetual = 0; // the opponent did not always check
- }
- if(appData.debugMode) fprintf(debugFP, "XQ perpetual test, our=%d, his=%d\n",
- ourPerpetual, hisPerpetual);
- if(ourPerpetual && !hisPerpetual) { // we are actively checking him: forfeit
- GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
- "Xboard adjudication: perpetual checking", GE_XBOARD );
- return;
- }
- if(hisPerpetual && !ourPerpetual) // he is checking us, but did not repeat yet
- break; // (or we would have caught him before). Abort repetition-checking loop.
- // Now check for perpetual chases
- if(!ourPerpetual && !hisPerpetual) { // no perpetual check, test for chase
- hisPerpetual = PerpetualChase(k, forwardMostMove);
- ourPerpetual = PerpetualChase(k+1, forwardMostMove);
- if(ourPerpetual && !hisPerpetual) { // we are actively chasing him: forfeit
- GameEnds( WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins,
- "Xboard adjudication: perpetual chasing", GE_XBOARD );
- return;
- }
- if(hisPerpetual && !ourPerpetual) // he is chasing us, but did not repeat yet
- break; // Abort repetition-checking loop.
- }
- // if neither of us is checking or chasing all the time, or both are, it is draw
- }
- GameEnds( GameIsDrawn, "Xboard adjudication: repetition draw", GE_XBOARD );
- return;
- }
- if( rights == 0 && count > 1 ) /* occurred 2 or more times before */
- boards[forwardMostMove][EP_STATUS] = EP_REP_DRAW;
- }
- }
-
- /* Now we test for 50-move draws. Determine ply count */
- count = forwardMostMove;
- /* look for last irreversble move */
- while( (int)boards[count][EP_STATUS] <= EP_NONE && count > backwardMostMove )
- count--;
- /* if we hit starting position, add initial plies */
- if( count == backwardMostMove )
- count -= initialRulePlies;
- count = forwardMostMove - count;
- if( count >= 100)
- boards[forwardMostMove][EP_STATUS] = EP_RULE_DRAW;
- /* this is used to judge if draw claims are legal */
- if(appData.ruleMoves > 0 && count >= 2*appData.ruleMoves) {
- SendToProgram("force\n", cps->other); // suppress reply
- SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
- ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
- GameEnds( GameIsDrawn, "Xboard adjudication: 50-move rule", GE_XBOARD );
- return;
- }
-
- /* if draw offer is pending, treat it as a draw claim
- * when draw condition present, to allow engines a way to
- * claim draws before making their move to avoid a race
- * condition occurring after their move
- */
- if( cps->other->offeredDraw || cps->offeredDraw ) {
- char *p = NULL;
- if((int)boards[forwardMostMove][EP_STATUS] == EP_RULE_DRAW)
- p = "Draw claim: 50-move rule";
- if((int)boards[forwardMostMove][EP_STATUS] == EP_REP_DRAW)
- p = "Draw claim: 3-fold repetition";
- if((int)boards[forwardMostMove][EP_STATUS] == EP_INSUF_DRAW)
- p = "Draw claim: insufficient mating material";
- if( p != NULL ) {
- SendToProgram("force\n", cps->other); // suppress reply
- SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
- GameEnds( GameIsDrawn, p, GE_XBOARD );
- ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
- return;
- }
- }
-
-
- if( appData.adjudicateDrawMoves > 0 && forwardMostMove > (2*appData.adjudicateDrawMoves) ) {
- SendToProgram("force\n", cps->other); // suppress reply
- SendMoveToProgram(forwardMostMove-1, cps->other); /* make sure opponent gets to see move */
- ShowMove(fromX, fromY, toX, toY); /*updates currentMove*/
-
- GameEnds( GameIsDrawn, "Xboard adjudication: long game", GE_XBOARD );
+ sprintf(buf, "kibitz !!! %+.2f/%d (%.2f sec, %u nodes, %.0f knps) PV=%s\n",
+ programStats.score / 100.,
+ programStats.depth,
+ programStats.time / 100.,
+ (unsigned int)programStats.nodes,
+ (unsigned int)programStats.nodes / (10*abs(programStats.time) + 1.),
+ programStats.movelist);
+ SendToICS(buf);
+if(appData.debugMode) fprintf(debugFP, "nodes = %d, %lld\n", (int) programStats.nodes, programStats.nodes);
+ }
+ }
+#endif
- return;
- }
- }
+ /* [AS] Clear stats for next move */
+ ClearProgramStats();
+ thinkOutput[0] = NULLCHAR;
+ hiddenThinkOutputState = 0;
bookHit = NULL;
if (gameMode == TwoMachinesPlay) {
* Look for communication commands
*/
if (!strncmp(message, "telluser ", 9)) {
+ EscapeExpand(message+9, message+9); // [HGM] esc: allow escape sequences in popup box
DisplayNote(message + 9);
return;
}
if (!strncmp(message, "tellusererror ", 14)) {
+ cps->userError = 1;
+ EscapeExpand(message+14, message+14); // [HGM] esc: allow escape sequences in popup box
DisplayError(message + 14, 0);
return;
}
gameMode = EditGame;
ModeHighlight();
}
- currentMove = --forwardMostMove;
+ currentMove = forwardMostMove-1;
DisplayMove(currentMove-1); /* before DisplayMoveError */
- SwitchClocks();
+ SwitchClocks(forwardMostMove-1); // [HGM] race
DisplayBothClocks();
sprintf(buf1, _("Illegal move \"%s\" (rejected by %s chess program)"),
parseList[currentMove], cps->which);
}
if (!ignore) {
+ ChessProgramStats tempStats = programStats; // [HGM] info: filter out info lines
buf1[0] = NULLCHAR;
if (sscanf(message, "%d%c %d %d " u64Display " %[^\n]\n",
&plylev, &plyext, &curscore, &time, &nodes, buf1) >= 5) {
/* [AS] Negate score if machine is playing black and reporting absolute scores */
if( cps->scoreIsAbsolute &&
- ((gameMode == MachinePlaysBlack) || (gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b')) )
+ ( gameMode == MachinePlaysBlack ||
+ gameMode == TwoMachinesPlay && cps->twoMachinesColor[0] == 'b' ||
+ gameMode == IcsPlayingBlack || // [HGM] also add other situations where engine should report black POV
+ (gameMode == AnalyzeMode || gameMode == AnalyzeFile || gameMode == IcsObserving && appData.icsEngineAnalyze) &&
+ !WhiteOnMove(currentMove)
+ ) )
{
curscore = -curscore;
}
- programStats.depth = plylev;
- programStats.nodes = nodes;
- programStats.time = time;
- programStats.score = curscore;
- programStats.got_only_move = 0;
+ tempStats.depth = plylev;
+ tempStats.nodes = nodes;
+ tempStats.time = time;
+ tempStats.score = curscore;
+ tempStats.got_only_move = 0;
if(cps->nps >= 0) { /* [HGM] nps: use engine nodes or time to decrement clock */
int ticklen;
/* Buffer overflow protection */
if (buf1[0] != NULLCHAR) {
- if (strlen(buf1) >= sizeof(programStats.movelist)
+ if (strlen(buf1) >= sizeof(tempStats.movelist)
&& appData.debugMode) {
fprintf(debugFP,
"PV is too long; using the first %u bytes.\n",
- (unsigned) sizeof(programStats.movelist) - 1);
+ (unsigned) sizeof(tempStats.movelist) - 1);
}
- safeStrCpy( programStats.movelist, buf1, sizeof(programStats.movelist) );
+ safeStrCpy( tempStats.movelist, buf1, sizeof(tempStats.movelist) );
} else {
- sprintf(programStats.movelist, " no PV\n");
+ sprintf(tempStats.movelist, " no PV\n");
}
- if (programStats.seen_stat) {
- programStats.ok_to_send = 1;
+ if (tempStats.seen_stat) {
+ tempStats.ok_to_send = 1;
}
- if (strchr(programStats.movelist, '(') != NULL) {
- programStats.line_is_book = 1;
- programStats.nr_moves = 0;
- programStats.moves_left = 0;
+ if (strchr(tempStats.movelist, '(') != NULL) {
+ tempStats.line_is_book = 1;
+ tempStats.nr_moves = 0;
+ tempStats.moves_left = 0;
} else {
- programStats.line_is_book = 0;
+ tempStats.line_is_book = 0;
}
- SendProgramStatsToFrontend( cps, &programStats );
+ if(tempStats.score != 0 || tempStats.nodes != 0 || tempStats.time != 0)
+ programStats = tempStats; // [HGM] info: only set stats if genuine PV and not an info line
+
+ SendProgramStatsToFrontend( cps, &tempStats );
/*
[AS] Protect the thinkOutput buffer from overflow... this
Board board;
{
ChessSquare captured = board[toY][toX], piece, king; int p, oldEP = EP_NONE, berolina = 0;
+ int promoRank = gameInfo.variant == VariantMakruk ? 3 : 1;
/* [HGM] compute & store e.p. status and castling rights for new position */
/* we can always do that 'in place', now pointers to these rights are passed to ApplyMove */
{ int i;
if(gameInfo.variant == VariantBerolina) berolina = EP_BEROLIN_A;
- oldEP = board[EP_STATUS];
+ oldEP = (signed char)board[EP_STATUS];
board[EP_STATUS] = EP_NONE;
if( board[toY][toX] != EmptySquare )
}
/* [HGM] In Shatranj and Courier all promotions are to Ferz */
- if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
+ if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
&& promoChar != 0) promoChar = PieceToChar(WhiteFerz);
if (fromX == toX && fromY == toY) return;
board[toY][toX+1] = board[fromY][BOARD_LEFT];
board[fromY][BOARD_LEFT] = EmptySquare;
} else if (board[fromY][fromX] == WhitePawn
- && toY == BOARD_HEIGHT-1
+ && toY >= BOARD_HEIGHT-promoRank
&& gameInfo.variant != VariantXiangqi
) {
/* white pawn promotion */
board[fromY][0] = EmptySquare;
board[toY][2] = BlackRook;
} else if (board[fromY][fromX] == BlackPawn
- && toY == 0
+ && toY < promoRank
&& gameInfo.variant != VariantXiangqi
) {
/* black pawn promotion */
- board[0][toX] = CharToPiece(ToLower(promoChar));
- if (board[0][toX] == EmptySquare) {
- board[0][toX] = BlackQueen;
+ board[toY][toX] = CharToPiece(ToLower(promoChar));
+ if (board[toY][toX] == EmptySquare) {
+ board[toY][toX] = BlackQueen;
}
if(gameInfo.variant==VariantBughouse ||
gameInfo.variant==VariantCrazyhouse) /* [HGM] use shadow piece */
if( (boards[forwardMostMove][fromY][fromX] == WhitePawn ||
boards[forwardMostMove][fromY][fromX] == BlackPawn ) &&
boards[forwardMostMove][toY][toX] == EmptySquare
- && fromX != toX )
+ && fromX != toX && fromY != toY)
fprintf(serverMoves, ":%c%c:%c%c", AAA+fromX, ONE+fromY, AAA+toX, ONE+fromY);
// promotion suffix
if(promoChar != NULLCHAR)
fflush(serverMoves);
}
- if (forwardMostMove+1 >= MAX_MOVES) {
+ if (forwardMostMove+1 > framePtr) { // [HGM] vari: do not run into saved variations
DisplayFatalError(_("Game too long; increase MAX_MOVES and recompile"),
0, 1);
return;
}
+ UnLoadPV(); // [HGM] pv: if we are looking at a PV, abort this
if (commentList[forwardMostMove+1] != NULL) {
free(commentList[forwardMostMove+1]);
commentList[forwardMostMove+1] = NULL;
}
CopyBoard(boards[forwardMostMove+1], boards[forwardMostMove]);
ApplyMove(fromX, fromY, toX, toY, promoChar, boards[forwardMostMove+1]);
- forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
- SwitchClocks(); // uses forwardMostMove, so must be done after incrementing it !
+ // forwardMostMove++; // [HGM] bare: moved to after ApplyMove, to make sure clock interrupt finds complete board
+ SwitchClocks(forwardMostMove+1); // [HGM] race: incrementing move nr inside
timeRemaining[0][forwardMostMove] = whiteTimeRemaining;
timeRemaining[1][forwardMostMove] = blackTimeRemaining;
gameInfo.result = GameUnfinished;
{
GameMode nextGameMode;
int isIcsGame;
- char buf[MSG_SIZ];
+ char buf[MSG_SIZ], popupRequested = 0;
if(endingGame) return; /* [HGM] crash: forbid recursion */
endingGame = 1;
-
+ if(twoBoards) { // [HGM] dual: switch back to one board
+ twoBoards = partnerUp = 0; InitDrawingSizes(-2, 0);
+ DrawPosition(TRUE, partnerBoard); // observed game becomes foreground
+ }
if (appData.debugMode) {
fprintf(debugFP, "GameEnds(%d, %s, %d)\n",
result, resultDetails ? resultDetails : "(null)", whosays);
}
+ fromX = fromY = -1; // [HGM] abort any move the user is entering.
+
if (appData.icsActive && (whosays == GE_ENGINE || whosays >= GE_ENGINE1)) {
/* If we are playing on ICS, the server decides when the
game is over, but the engine can offer to draw, claim
second.twoMachinesColor[0] ;
// [HGM] losers: because the logic is becoming a bit hairy, determine true result first
- if((int)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
+ if((signed char)boards[forwardMostMove][EP_STATUS] == EP_CHECKMATE) {
/* [HGM] verify: engine mate claims accepted if they were flagged */
trueResult = WhiteOnMove(forwardMostMove) ? BlackWins : WhiteWins;
} else
- if((int)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
+ if((signed char)boards[forwardMostMove][EP_STATUS] == EP_WINS) { // added code for games where being mated is a win
/* [HGM] verify: engine mate claims accepted if they were flagged */
trueResult = WhiteOnMove(forwardMostMove) ? WhiteWins : BlackWins;
} else
- if((int)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
+ if((signed char)boards[forwardMostMove][EP_STATUS] == EP_STALEMATE) { // only used to indicate draws now
trueResult = GameIsDrawn; // default; in variants where stalemate loses, Status is CHECKMATE
}
result == BlackWins && claimer == 'b' ) ) { // case to verify: engine claims own win
if (appData.debugMode) {
fprintf(debugFP, "result=%d sp=%d move=%d\n",
- result, (int)boards[forwardMostMove][EP_STATUS], forwardMostMove);
+ result, (signed char)boards[forwardMostMove][EP_STATUS], forwardMostMove);
}
if(result != trueResult) {
sprintf(buf, "False win claim: '%s'", resultDetails);
resultDetails = buf;
}
} else
- if( result == GameIsDrawn && (int)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
+ if( result == GameIsDrawn && (signed char)boards[forwardMostMove][EP_STATUS] > EP_DRAWS
&& (forwardMostMove <= backwardMostMove ||
- (int)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
+ (signed char)boards[forwardMostMove-1][EP_STATUS] > EP_DRAWS ||
(claimer=='b')==(forwardMostMove&1))
) {
/* [HGM] verify: draws that were not flagged are false claims */
&& result != GameIsDrawn)
{ int i, j, k=0, color = (result==WhiteWins ? (int)WhitePawn : (int)BlackPawn);
for(j=BOARD_LEFT; j<BOARD_RGHT; j++) for(i=0; i<BOARD_HEIGHT; i++) {
- int p = (int)boards[forwardMostMove][i][j] - color;
+ int p = (signed char)boards[forwardMostMove][i][j] - color;
if(p >= 0 && p <= (int)WhiteKing) k++;
}
if (appData.debugMode) {
first.tidy, second.tidy,
first.matchWins, second.matchWins,
appData.matchGames - (first.matchWins + second.matchWins));
- DisplayFatalError(buf, 0, 0);
+ popupRequested++; // [HGM] crash: postpone to after resetting endingGame
}
}
if ((gameMode == AnalyzeMode || gameMode == AnalyzeFile) &&
gameMode = nextGameMode;
ModeHighlight();
endingGame = 0; /* [HGM] crash */
+ if(popupRequested) DisplayFatalError(buf, 0, 0); // [HGM] crash: this call GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
}
/* Assumes program was just initialized (initString sent).
fprintf(debugFP, "Reset(%d, %d) from gameMode %d\n",
redraw, init, gameMode);
}
+ CleanupTail(); // [HGM] vari: delete any stored variations
pausing = pauseExamInvalid = FALSE;
startedFromSetupPosition = blackPlaysFirst = FALSE;
firstMove = TRUE;
if (appData.debugMode)
fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
p = yy_text;
- if (*p == '{' || *p == '[' || *p == '(') {
- p[strlen(p) - 1] = NULLCHAR;
- p++;
- }
/* append the comment but don't display it */
- while (*p == '\n') p++;
- AppendComment(currentMove, p);
+ AppendComment(currentMove, p, FALSE);
return TRUE;
case WhiteCapturesEnPassant:
/* [HGM] PGNvariant: automatically switch to variant given in PGN tag */
if(gameInfo.variant != oldVariant) {
startedFromPositionFile = FALSE; /* [HGM] loadPos: variant switch likely makes position invalid */
+ ResetFrontEnd(); // [HGM] might need other bitmaps. Cannot use Reset() because it clears gameInfo :-(
InitPosition(TRUE);
oldVariant = gameInfo.variant;
if (appData.debugMode)
if (appData.debugMode)
fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
p = yy_text;
- if (*p == '{' || *p == '[' || *p == '(') {
- p[strlen(p) - 1] = NULLCHAR;
- p++;
- }
- while (*p == '\n') p++;
- AppendComment(currentMove, p);
+ AppendComment(currentMove, p, FALSE);
yyboardindex = forwardMostMove;
cm = (ChessMove) yylex();
}
if (numPGNTags > 0){
char *tags;
if (gameInfo.variant == VariantNormal) {
- gameInfo.variant = StringToVariant(gameInfo.event);
+ VariantClass v = StringToVariant(gameInfo.event);
+ // [HGM] do not recognize variants from event tag that were introduced after supporting variant tag
+ if(v < VariantShogi) gameInfo.variant = v;
}
if (!matchMode) {
if( appData.autoDisplayTags ) {
if (appData.debugMode)
fprintf(debugFP, "Parsed Comment: %s\n", yy_text);
p = yy_text;
- if (*p == '{' || *p == '[' || *p == '(') {
- p[strlen(p) - 1] = NULLCHAR;
- p++;
- }
- while (*p == '\n') p++;
- AppendComment(currentMove, p);
+ AppendComment(currentMove, p, FALSE);
yyboardindex = forwardMostMove;
cm = (ChessMove) yylex();
}
/* Print comments preceding this move */
if (commentList[i] != NULL) {
if (linelen > 0) fprintf(f, "\n");
- fprintf(f, "{\n%s}\n", commentList[i]);
+ fprintf(f, "%s", commentList[i]);
linelen = 0;
newblock = TRUE;
}
/* [AS] Add PV info if present */
if( i >= 0 && appData.saveExtendedInfoInPGN && pvInfoList[i].depth > 0 ) {
/* [HGM] add time */
- char buf[MSG_SIZ]; int seconds = 0;
+ char buf[MSG_SIZ]; int seconds;
- if(i >= backwardMostMove) {
- if(WhiteOnMove(i))
- seconds = timeRemaining[0][i] - timeRemaining[0][i+1]
- + GetTimeQuota(i/2) / (1000*WhitePlayer()->timeOdds);
- else
- seconds = timeRemaining[1][i] - timeRemaining[1][i+1]
- + GetTimeQuota(i/2) / (1000*WhitePlayer()->other->timeOdds);
- }
- seconds = (seconds+50)/100; // deci-seconds, rounded to nearest
+ seconds = (pvInfoList[i].time+5)/10; // deci-seconds, rounded to nearest
if( seconds <= 0) buf[0] = 0; else
if( seconds < 30 ) sprintf(buf, " %3.1f%c", seconds/10., 0); else {
/* Print comments after last move */
if (commentList[i] != NULL) {
- fprintf(f, "{\n%s}\n", commentList[i]);
+ fprintf(f, "%s\n", commentList[i]);
}
/* Print result */
time_t tm;
char *fen;
+ if (gameMode == EditPosition) EditPositionDone(TRUE);
if (appData.oldSaveStyle) {
tm = time((time_t *) NULL);
break;
}
- forwardMostMove = currentMove;
+// forwardMostMove = currentMove;
+ TruncateGame(); // [HGM] vari: MachineWhite and MachineBlack do this...
ResurrectChessProgram(); /* in case first program isn't running */
if (second.pr == NULL) {
case EmptySquare:
if (gameMode == IcsExamining) {
+ if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
sprintf(buf, "%sx@%c%c\n", ics_prefix, AAA + x, ONE + y);
SendToICS(buf);
} else {
+ if(x < BOARD_LEFT || x >= BOARD_RGHT) {
+ if(x == BOARD_LEFT-2) {
+ if(y < BOARD_HEIGHT-1-gameInfo.holdingsSize) break;
+ boards[0][y][1] = 0;
+ } else
+ if(x == BOARD_RGHT+1) {
+ if(y >= gameInfo.holdingsSize) break;
+ boards[0][y][BOARD_WIDTH-2] = 0;
+ } else break;
+ }
boards[0][y][x] = EmptySquare;
DrawPosition(FALSE, boards[0]);
}
case BlackQueen:
if(gameInfo.variant == VariantShatranj ||
gameInfo.variant == VariantXiangqi ||
- gameInfo.variant == VariantCourier )
+ gameInfo.variant == VariantCourier ||
+ gameInfo.variant == VariantMakruk )
selection = (ChessSquare)((int)selection - (int)WhiteQueen + (int)WhiteFerz);
goto defaultlabel;
default:
defaultlabel:
if (gameMode == IcsExamining) {
+ if (x < BOARD_LEFT || x >= BOARD_RGHT) break; // [HGM] holdings
sprintf(buf, "%s%c@%c%c\n", ics_prefix,
PieceToChar(selection), AAA + x, ONE + y);
SendToICS(buf);
} else {
+ if(x < BOARD_LEFT || x >= BOARD_RGHT) {
+ int n;
+ if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
+ n = PieceToNumber(selection - BlackPawn);
+ if(n >= gameInfo.holdingsSize) { n = 0; selection = BlackPawn; }
+ boards[0][BOARD_HEIGHT-1-n][0] = selection;
+ boards[0][BOARD_HEIGHT-1-n][1]++;
+ } else
+ if(x == BOARD_RGHT+1 && selection < BlackPawn) {
+ n = PieceToNumber(selection);
+ if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
+ boards[0][n][BOARD_WIDTH-1] = selection;
+ boards[0][n][BOARD_WIDTH-2]++;
+ }
+ } else
boards[0][y][x] = selection;
- DrawPosition(FALSE, boards[0]);
+ DrawPosition(TRUE, boards[0]);
}
break;
}
StrStr(commentList[cmailOldMove], WhiteOnMove(cmailOldMove) ?
"Black offers a draw" : "White offers a draw")) {
#ifdef NOTDEF
- AppendComment(cmailOldMove, "Draw declined");
+ AppendComment(cmailOldMove, "Draw declined", TRUE);
DisplayComment(cmailOldMove - 1, "Draw declined");
#endif /*NOTDEF*/
} else {
SendToICS(ics_prefix);
SendToICS("draw\n");
+ userOfferedDraw = TRUE; // [HGM] drawclaim: also set flag in ICS play
} else if (cmailMsgLoaded) {
if (currentMove == cmailOldMove &&
commentList[cmailOldMove] != NULL &&
} else if (currentMove == cmailOldMove + 1) {
char *offer = WhiteOnMove(cmailOldMove) ?
"White offers a draw" : "Black offers a draw";
- AppendComment(currentMove, offer);
+ AppendComment(currentMove, offer, TRUE);
DisplayComment(currentMove - 1, offer);
cmailMoveType[lastLoadGameNumber - 1] = CMAIL_DRAW;
} else {
ToStartEvent()
{
if (gameMode == AnalyzeMode || gameMode == AnalyzeFile) {
- /* to optimze, we temporarily turn off analysis mode while we undo
+ /* to optimize, we temporarily turn off analysis mode while we undo
* all the moves. Otherwise we get analysis output after each undo.
*/
if (first.analysisSupport) {
}
void
-RevertEvent()
+RevertEvent(Boolean annotate)
{
+ if(PopTail(annotate)) { // [HGM] vari: restore old game tail
+ return;
+ }
if (gameMode != IcsExamining) {
DisplayError(_("You are not examining a game"), 0);
return;
void
TruncateGame()
{
+ CleanupTail(); // [HGM] vari: only keep current variation if we explicitly truncate
if (forwardMostMove > currentMove) {
if (gameInfo.resultDetails != NULL) {
free(gameInfo.resultDetails);
{
/* This routine is used only for certain modes */
VariantClass v = gameInfo.variant;
+ ChessMove r = GameUnfinished;
+ char *p = NULL;
+
+ if(gameMode == EditGame) { // [HGM] vari: do not erase result on EditGame
+ r = gameInfo.result;
+ p = gameInfo.resultDetails;
+ gameInfo.resultDetails = NULL;
+ }
ClearGameInfo(&gameInfo);
gameInfo.variant = v;
gameInfo.round = StrSave("-");
gameInfo.white = StrSave("-");
gameInfo.black = StrSave("-");
+ gameInfo.result = r;
+ gameInfo.resultDetails = p;
break;
case EditPosition:
commentList[index] = NULL;
return;
}
+ if( *text == '{' && strchr(text, '}') || // [HGM] braces: if certainy malformed, put braces
+ *text == '[' && strchr(text, ']') || // otherwise hope the user knows what he is doing
+ *text == '(' && strchr(text, ')')) { // (perhaps check if this parses as comment-only?)
commentList[index] = (char *) malloc(len + 2);
strncpy(commentList[index], text, len);
commentList[index][len] = '\n';
commentList[index][len + 1] = NULLCHAR;
+ } else {
+ // [HGM] braces: if text does not start with known OK delimiter, put braces around it.
+ char *p;
+ commentList[index] = (char *) malloc(len + 6);
+ strcpy(commentList[index], "{\n");
+ strncpy(commentList[index]+2, text, len);
+ commentList[index][len+2] = NULLCHAR;
+ while(p = strchr(commentList[index], '}')) *p = ')'; // kill all } to make it one comment
+ strcat(commentList[index], "\n}\n");
+ }
}
void
}
void
-AppendComment(index, text)
+AppendComment(index, text, addBraces)
int index;
char *text;
+ Boolean addBraces; // [HGM] braces: tells if we should add {}
{
int oldlen, len;
char *old;
+if(appData.debugMode) fprintf(debugFP, "Append: in='%s' %d\n", text, addBraces); fflush(debugFP);
text = GetInfoFromComment( index, text ); /* [HGM] PV time: strip PV info from comment */
CrushCRs(text);
if (commentList[index] != NULL) {
old = commentList[index];
oldlen = strlen(old);
- commentList[index] = (char *) malloc(oldlen + len + 2);
+ while(commentList[index][oldlen-1] == '\n')
+ commentList[index][--oldlen] = NULLCHAR;
+ commentList[index] = (char *) malloc(oldlen + len + 6); // might waste 4
strcpy(commentList[index], old);
free(old);
- strncpy(&commentList[index][oldlen], text, len);
- commentList[index][oldlen + len] = '\n';
- commentList[index][oldlen + len + 1] = NULLCHAR;
+ // [HGM] braces: join "{A\n}\n" + "{\nB}" as "{A\nB\n}"
+ if(commentList[index][oldlen-1] == '}' && (text[0] == '{' || addBraces)) {
+ if(addBraces) addBraces = FALSE; else { text++; len--; }
+ while (*text == '\n') { text++; len--; }
+ commentList[index][--oldlen] = NULLCHAR;
+ }
+ if(addBraces) strcat(commentList[index], "\n{\n");
+ else strcat(commentList[index], "\n");
+ strcat(commentList[index], text);
+ if(addBraces) strcat(commentList[index], "\n}\n");
+ else strcat(commentList[index], "\n");
} else {
- commentList[index] = (char *) malloc(len + 2);
- strncpy(commentList[index], text, len);
- commentList[index][len] = '\n';
- commentList[index][len + 1] = NULLCHAR;
+ commentList[index] = (char *) malloc(len + 6); // perhaps wastes 4...
+ if(addBraces)
+ strcpy(commentList[index], "{\n");
+ else commentList[index][0] = NULLCHAR;
+ strcat(commentList[index], text);
+ strcat(commentList[index], "\n");
+ if(addBraces) strcat(commentList[index], "}\n");
}
}
if( s_emt != NULL ) {
}
+ return text;
}
else {
/* We expect something like: [+|-]nnn.nn/dd */
int score_lo = 0;
+ if(*text != '{') return text; // [HGM] braces: must be normal comment
+
sep = strchr( text, '/' );
if( sep == NULL || sep < (text+4) ) {
return text;
}
time = -1; sec = -1; deci = -1;
- if( sscanf( text, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
- sscanf( text, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
- sscanf( text, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
- sscanf( text, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
+ if( sscanf( text+1, "%d.%d/%d %d:%d", &score, &score_lo, &depth, &time, &sec ) != 5 &&
+ sscanf( text+1, "%d.%d/%d %d.%d", &score, &score_lo, &depth, &time, &deci ) != 5 &&
+ sscanf( text+1, "%d.%d/%d %d", &score, &score_lo, &depth, &time ) != 4 &&
+ sscanf( text+1, "%d.%d/%d", &score, &score_lo, &depth ) != 3 ) {
return text;
}
pvInfoList[index-1].depth = depth;
pvInfoList[index-1].score = score;
pvInfoList[index-1].time = 10*time; // centi-sec
+ if(*sep == '}') *sep = 0; else *--sep = '{';
}
return sep;
}
&& !endingGame) { /* [HGM] crash: to not hang GameEnds() writing to deceased engines */
sprintf(buf, _("Error writing to %s chess program"), cps->which);
if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
- if((int)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
+ if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
sprintf(buf, "%s program exits in draw position (%s)", cps->which, cps->program);
} else {
gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
}
- gameInfo.resultDetails = buf;
+ gameInfo.resultDetails = StrSave(buf);
}
- DisplayFatalError(buf, error, 1);
+ if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
}
}
_("Error: %s chess program (%s) exited unexpectedly"),
cps->which, cps->program);
if(gameInfo.resultDetails==NULL) { /* [HGM] crash: if game in progress, give reason for abort */
- if((int)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
+ if((signed char)boards[forwardMostMove][EP_STATUS] <= EP_DRAWS) {
gameInfo.result = GameIsDrawn; /* [HGM] accept exit as draw claim */
sprintf(buf, _("%s program exits in draw position (%s)"), cps->which, cps->program);
} else {
gameInfo.result = cps->twoMachinesColor[0]=='w' ? BlackWins : WhiteWins;
}
- gameInfo.resultDetails = buf;
+ gameInfo.resultDetails = StrSave(buf);
}
RemoveInputSource(cps->isr);
- DisplayFatalError(buf, 0, 1);
+ if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, 0, 1); else errorExitStatus = 1;
} else {
sprintf(buf,
_("Error reading from %s chess program (%s)"),
cps->pr = NoProc;
}
- DisplayFatalError(buf, error, 1);
+ if(!cps->userError || !appData.popupExitMessage) DisplayFatalError(buf, error, 1); else errorExitStatus = 1;
}
return;
}
sscanf(message, "error %c", &c)!=1 && sscanf(message, "illegal %c", &c)!=1 &&
sscanf(message, "tell%c", &c)!=1 && sscanf(message, "0-1 %c", &c)!=1 &&
sscanf(message, "1-0 %c", &c)!=1 && sscanf(message, "1/2-1/2 %c", &c)!=1 &&
- sscanf(message, "pong %c", &c)!=1 && start != '#')
- { quote = "# "; print = (appData.engineComments == 2); }
+ sscanf(message, "pong %c", &c)!=1 && start != '#') {
+ quote = appData.engineComments == 2 ? "# " : "### NON-COMPLIANT! ### ";
+ print = (appData.engineComments >= 2);
+ }
message[0] = start; // restore original message
}
if(print) {
/* [HGM] translate opponent's time by time-odds factor */
otime = (otime * cps->other->timeOdds) / cps->timeOdds;
if (appData.debugMode) {
- fprintf(debugFP, "time odds: %d %d \n", cps->timeOdds, cps->other->timeOdds);
+ fprintf(debugFP, "time odds: %f %f \n", cps->timeOdds, cps->other->timeOdds);
}
if (time <= 0) time = 1;
}
continue;
}
- if (BoolFeature(&p, "smp", &cps->maxCores, cps)) continue;
/* End of additions by HGM */
/* unknown feature: complain and skip */
}
void
-NewSettingEvent(option, command, value)
+NewSettingEvent(option, feature, command, value)
char *command;
- int option, value;
+ int option, value, *feature;
{
char buf[MSG_SIZ];
if (gameMode == EditPosition) EditPositionDone(TRUE);
sprintf(buf, "%s%s %d\n", (option ? "option ": ""), command, value);
- SendToProgram(buf, &first);
+ if(feature == NULL || *feature) SendToProgram(buf, &first);
if (gameMode == TwoMachinesPlay) {
- SendToProgram(buf, &second);
+ if(feature == NULL || feature[(int*)&second - (int*)&first]) SendToProgram(buf, &second);
}
}
if(whiteNPS >= 0) lastTickLength = 0;
timeRemaining = whiteTimeRemaining -= lastTickLength;
DisplayWhiteClock(whiteTimeRemaining - fudge,
- WhiteOnMove(currentMove));
+ WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
} else {
if(blackNPS >= 0) lastTickLength = 0;
timeRemaining = blackTimeRemaining -= lastTickLength;
DisplayBlackClock(blackTimeRemaining - fudge,
- !WhiteOnMove(currentMove));
+ !WhiteOnMove(currentMove < forwardMostMove ? currentMove : forwardMostMove));
}
if (CheckFlags()) return;
from the color that is *not* on move now.
*/
void
-SwitchClocks()
+SwitchClocks(int newMoveNr)
{
long lastTickLength;
TimeMark now;
if (StopClockTimer() && appData.clockMode) {
lastTickLength = SubtractTimeMarks(&now, &tickStartTM);
- if (WhiteOnMove(forwardMostMove)) {
+ if (!WhiteOnMove(forwardMostMove)) {
if(blackNPS >= 0) lastTickLength = 0;
blackTimeRemaining -= lastTickLength;
/* [HGM] PGNtime: save time for PGN file if engine did not give it */
}
flagged = CheckFlags();
}
+ forwardMostMove = newMoveNr; // [HGM] race: change stm when no timer interrupt scheduled
CheckTimeControl();
if (flagged || !appData.clockMode) return;
*p++ = ' ';
if(q = overrideCastling) { // [HGM] FRC: override castling & e.p fields for non-compliant engines
- while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' ';
+ while(*p++ = *q++); if(q != overrideCastling+1) p[-1] = ' '; else --p;
} else {
if(nrCastlingRights) {
q = p;
}
if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
- gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
+ gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
/* En passant target square */
if (move > backwardMostMove) {
fromX = moveList[move - 1][0] - AAA;
}
} else if(move == backwardMostMove) {
// [HGM] perhaps we should always do it like this, and forget the above?
- if((int)boards[move][EP_STATUS] >= 0) {
+ if((signed char)boards[move][EP_STATUS] >= 0) {
*p++ = boards[move][EP_STATUS] + AAA;
*p++ = whiteToPlay ? '6'+BOARD_HEIGHT-8 : '3';
} else {
if (appData.debugMode) { int k;
fprintf(debugFP, "write FEN 50-move: %d %d %d\n", initialRulePlies, forwardMostMove, backwardMostMove);
for(k=backwardMostMove; k<=forwardMostMove; k++)
- fprintf(debugFP, "e%d. p=%d\n", k, (int)boards[k][EP_STATUS]);
+ fprintf(debugFP, "e%d. p=%d\n", k, (signed char)boards[k][EP_STATUS]);
}
- while(j > backwardMostMove && (int)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
+ while(j > backwardMostMove && (signed char)boards[j][EP_STATUS] <= EP_NONE) j--,i++;
if( j == backwardMostMove ) i += initialRulePlies;
sprintf(p, "%d ", i);
p += i>=100 ? 4 : i >= 10 ? 3 : 2;
board[CASTLING][i] =
gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom ? NoRights : initialRights[i];
} /* assume possible unless obviously impossible */
- if(initialRights[0]>=0 && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
- if(initialRights[1]>=0 && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
- if(initialRights[2]>=0 && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
- if(initialRights[3]>=0 && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
- if(initialRights[4]>=0 && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
- if(initialRights[5]>=0 && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
+ if(initialRights[0]!=NoRights && board[castlingRank[0]][initialRights[0]] != WhiteRook) board[CASTLING][0] = NoRights;
+ if(initialRights[1]!=NoRights && board[castlingRank[1]][initialRights[1]] != WhiteRook) board[CASTLING][1] = NoRights;
+ if(initialRights[2]!=NoRights && board[castlingRank[2]][initialRights[2]] != WhiteUnicorn
+ && board[castlingRank[2]][initialRights[2]] != WhiteKing) board[CASTLING][2] = NoRights;
+ if(initialRights[3]!=NoRights && board[castlingRank[3]][initialRights[3]] != BlackRook) board[CASTLING][3] = NoRights;
+ if(initialRights[4]!=NoRights && board[castlingRank[4]][initialRights[4]] != BlackRook) board[CASTLING][4] = NoRights;
+ if(initialRights[5]!=NoRights && board[castlingRank[5]][initialRights[5]] != BlackUnicorn
+ && board[castlingRank[5]][initialRights[5]] != BlackKing) board[CASTLING][5] = NoRights;
FENrulePlies = 0;
while(*p==' ') p++;
(gameInfo.variant == VariantFischeRandom || gameInfo.variant == VariantCapaRandom) &&
( *p >= 'a' && *p < 'a' + gameInfo.boardWidth) ||
( *p >= 'A' && *p < 'A' + gameInfo.boardWidth) ) {
- char c = *p++; int whiteKingFile=-1, blackKingFile=-1;
+ char c = *p++; int whiteKingFile=NoRights, blackKingFile=NoRights;
for(i=BOARD_LEFT; i<BOARD_RGHT; i++) {
if(board[BOARD_HEIGHT-1][i] == BlackKing) blackKingFile = i;
if(board[0 ][i] == WhiteKing) whiteKingFile = i;
}
+ if(gameInfo.variant == VariantTwoKings || gameInfo.variant == VariantKnightmate)
+ whiteKingFile = blackKingFile = BOARD_WIDTH >> 1; // for these variant scanning fails
+ if(whiteKingFile == NoRights || board[0][whiteKingFile] != WhiteUnicorn
+ && board[0][whiteKingFile] != WhiteKing) whiteKingFile = NoRights;
+ if(blackKingFile == NoRights || board[BOARD_HEIGHT-1][blackKingFile] != BlackUnicorn
+ && board[BOARD_HEIGHT-1][blackKingFile] != BlackKing) blackKingFile = NoRights;
switch(c) {
case'K':
for(i=BOARD_RGHT-1; board[0][i]!=WhiteRook && i>whiteKingFile; i--);
board[CASTLING][2] = whiteKingFile;
break;
case'Q':
- for(i=BOARD_LEFT; board[0][i]!=WhiteRook && i<whiteKingFile; i++);
+ for(i=BOARD_LEFT; i<BOARD_RGHT && board[0][i]!=WhiteRook && i<whiteKingFile; i++);
board[CASTLING][1] = i != whiteKingFile ? i : NoRights;
board[CASTLING][2] = whiteKingFile;
break;
board[CASTLING][5] = blackKingFile;
break;
case'q':
- for(i=BOARD_LEFT; board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
+ for(i=BOARD_LEFT; i<BOARD_RGHT && board[BOARD_HEIGHT-1][i]!=BlackRook && i<blackKingFile; i++);
board[CASTLING][4] = i != blackKingFile ? i : NoRights;
board[CASTLING][5] = blackKingFile;
case '-':
}
}
}
+ for(i=0; i<nrCastlingRights; i++)
+ if(board[CASTLING][i] != NoRights) initialRights[i] = board[CASTLING][i];
if (appData.debugMode) {
fprintf(debugFP, "FEN castling rights:");
for(i=0; i<nrCastlingRights; i++)
/* read e.p. field in games that know e.p. capture */
if(gameInfo.variant != VariantShogi && gameInfo.variant != VariantXiangqi &&
- gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier ) {
+ gameInfo.variant != VariantShatranj && gameInfo.variant != VariantCourier && gameInfo.variant != VariantMakruk ) {
if(*p=='-') {
p++; board[EP_STATUS] = EP_NONE;
} else {
return len;
}
+
+// [HGM] vari: routines for shelving variations
+
+void
+PushTail(int firstMove, int lastMove)
+{
+ int i, j, nrMoves = lastMove - firstMove;
+
+ if(appData.icsActive) { // only in local mode
+ forwardMostMove = currentMove; // mimic old ICS behavior
+ return;
+ }
+ if(storedGames >= MAX_VARIATIONS-1) return;
+
+ // push current tail of game on stack
+ savedResult[storedGames] = gameInfo.result;
+ savedDetails[storedGames] = gameInfo.resultDetails;
+ gameInfo.resultDetails = NULL;
+ savedFirst[storedGames] = firstMove;
+ savedLast [storedGames] = lastMove;
+ savedFramePtr[storedGames] = framePtr;
+ framePtr -= nrMoves; // reserve space for the boards
+ for(i=nrMoves; i>=1; i--) { // copy boards to stack, working downwards, in case of overlap
+ CopyBoard(boards[framePtr+i], boards[firstMove+i]);
+ for(j=0; j<MOVE_LEN; j++)
+ moveList[framePtr+i][j] = moveList[firstMove+i-1][j];
+ for(j=0; j<2*MOVE_LEN; j++)
+ parseList[framePtr+i][j] = parseList[firstMove+i-1][j];
+ timeRemaining[0][framePtr+i] = timeRemaining[0][firstMove+i];
+ timeRemaining[1][framePtr+i] = timeRemaining[1][firstMove+i];
+ pvInfoList[framePtr+i] = pvInfoList[firstMove+i-1];
+ pvInfoList[firstMove+i-1].depth = 0;
+ commentList[framePtr+i] = commentList[firstMove+i];
+ commentList[firstMove+i] = NULL;
+ }
+
+ storedGames++;
+ forwardMostMove = firstMove; // truncate game so we can start variation
+ if(storedGames == 1) GreyRevert(FALSE);
+}
+
+Boolean
+PopTail(Boolean annotate)
+{
+ int i, j, nrMoves;
+ char buf[8000], moveBuf[20];
+
+ if(appData.icsActive) return FALSE; // only in local mode
+ if(!storedGames) return FALSE; // sanity
+ CommentPopDown(); // make sure no stale variation comments to the destroyed line can remain open
+
+ storedGames--;
+ ToNrEvent(savedFirst[storedGames]); // sets currentMove
+ nrMoves = savedLast[storedGames] - currentMove;
+ if(annotate) {
+ int cnt = 10;
+ if(!WhiteOnMove(currentMove)) sprintf(buf, "(%d...", currentMove+2>>1);
+ else strcpy(buf, "(");
+ for(i=currentMove; i<forwardMostMove; i++) {
+ if(WhiteOnMove(i))
+ sprintf(moveBuf, " %d. %s", i+2>>1, SavePart(parseList[i]));
+ else sprintf(moveBuf, " %s", SavePart(parseList[i]));
+ strcat(buf, moveBuf);
+ if(commentList[i]) { strcat(buf, " "); strcat(buf, commentList[i]); }
+ if(!--cnt) { strcat(buf, "\n"); cnt = 10; }
+ }
+ strcat(buf, ")");
+ }
+ for(i=1; i<=nrMoves; i++) { // copy last variation back
+ CopyBoard(boards[currentMove+i], boards[framePtr+i]);
+ for(j=0; j<MOVE_LEN; j++)
+ moveList[currentMove+i-1][j] = moveList[framePtr+i][j];
+ for(j=0; j<2*MOVE_LEN; j++)
+ parseList[currentMove+i-1][j] = parseList[framePtr+i][j];
+ timeRemaining[0][currentMove+i] = timeRemaining[0][framePtr+i];
+ timeRemaining[1][currentMove+i] = timeRemaining[1][framePtr+i];
+ pvInfoList[currentMove+i-1] = pvInfoList[framePtr+i];
+ if(commentList[currentMove+i]) free(commentList[currentMove+i]);
+ commentList[currentMove+i] = commentList[framePtr+i];
+ commentList[framePtr+i] = NULL;
+ }
+ if(annotate) AppendComment(currentMove+1, buf, FALSE);
+ framePtr = savedFramePtr[storedGames];
+ gameInfo.result = savedResult[storedGames];
+ if(gameInfo.resultDetails != NULL) {
+ free(gameInfo.resultDetails);
+ }
+ gameInfo.resultDetails = savedDetails[storedGames];
+ forwardMostMove = currentMove + nrMoves;
+ if(storedGames == 0) GreyRevert(TRUE);
+ return TRUE;
+}
+
+void
+CleanupTail()
+{ // remove all shelved variations
+ int i;
+ for(i=0; i<storedGames; i++) {
+ if(savedDetails[i])
+ free(savedDetails[i]);
+ savedDetails[i] = NULL;
+ }
+ for(i=framePtr; i<MAX_MOVES; i++) {
+ if(commentList[i]) free(commentList[i]);
+ commentList[i] = NULL;
+ }
+ framePtr = MAX_MOVES-1;
+ storedGames = 0;
+}
+
+void
+LoadVariation(int index, char *text)
+{ // [HGM] vari: shelve previous line and load new variation, parsed from text around text[index]
+ char *p = text, *start = NULL, *end = NULL, wait = NULLCHAR;
+ int level = 0, move;
+
+ if(gameMode != EditGame && gameMode != AnalyzeMode) return;
+ // first find outermost bracketing variation
+ while(*p) { // hope I got this right... Non-nesting {} and [] can screen each other and nesting ()
+ if(!wait) { // while inside [] pr {}, ignore everyting except matching closing ]}
+ if(*p == '{') wait = '}'; else
+ if(*p == '[') wait = ']'; else
+ if(*p == '(' && level++ == 0 && p-text < index) start = p+1;
+ if(*p == ')' && level > 0 && --level == 0 && p-text > index && end == NULL) end = p-1;
+ }
+ if(*p == wait) wait = NULLCHAR; // closing ]} found
+ p++;
+ }
+ if(!start || !end) return; // no variation found, or syntax error in PGN: ignore click
+ if(appData.debugMode) fprintf(debugFP, "at move %d load variation '%s'\n", currentMove, start);
+ end[1] = NULLCHAR; // clip off comment beyond variation
+ ToNrEvent(currentMove-1);
+ PushTail(currentMove, forwardMostMove); // shelve main variation. This truncates game
+ // kludge: use ParsePV() to append variation to game
+ move = currentMove;
+ ParsePV(start, TRUE);
+ forwardMostMove = endPV; endPV = -1; currentMove = move; // cleanup what ParsePV did
+ ClearPremoveHighlights();
+ CommentPopDown();
+ ToNrEvent(currentMove+1);
+}
+