* 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
*
#ifdef ENABLE_NLS
# define _(s) gettext (s)
# define N_(s) gettext_noop (s)
+# define T_(s) gettext(s)
#else
-# define _(s) (s)
-# define N_(s) s
+# ifdef WIN32
+# define _(s) T_(s)
+# define N_(s) s
+# else
+# define _(s) (s)
+# define N_(s) s
+# define T_(s) s
+# endif
#endif
/*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;
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 */
case VariantNoCastle:
case VariantShatranj:
case VariantCourier:
+ case VariantMakruk:
flags &= ~F_ALL_CASTLE_OK;
break;
default:
};
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
/* [AS] Adjudication threshold */
adjudicateLossThreshold = appData.adjudicateLossThreshold;
- first.which = "first";
- second.which = "second";
+ first.which = _("first");
+ second.which = _("second");
first.maybeThinking = second.maybeThinking = FALSE;
first.pr = second.pr = NoProc;
first.isr = second.isr = NULL;
case VariantGothic: /* [HGM] should work */
case VariantCapablanca: /* [HGM] should work */
case VariantCourier: /* [HGM] initial forced moves not implemented */
- case VariantShogi: /* [HGM] drops not tested for legality */
+ case VariantShogi: /* [HGM] could still mate with pawn drop */
case VariantKnightmate: /* [HGM] should work */
case VariantCylinder: /* [HGM] untested */
case VariantFalcon: /* [HGM] untested */
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()
+{
+ int i;
+ if(!seekGraphUp) return FALSE;
+ 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;
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: ");
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), TRUE);
pvInfoList[forwardMostMove-1].score = 100*score;
}
OutputKibitz(suppressKibitz, parse);
- next_out = i+1; // [HGM] suppress printing in ICS window
} else {
char tmp[MSG_SIZ];
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 {
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 *\n") && atoi(star_match[0])) {
+ 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]));
- looking_at(buf, &i, "*% "); // eat prompt
+ 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;
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
ZippyGameStart(whitename, blackname);
}
#endif /*ZIPPY*/
+ partnerBoardValid = FALSE; // [HGM] bughouse
continue;
}
Reset(TRUE, TRUE);
}
#endif /*ZIPPY*/
+ if(appData.bgObserve && partnerBoardValid) DrawPosition(TRUE, partnerBoard);
continue;
}
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, "*% ")) {
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);
}
}
-
+
/* 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)) ||
/* Send 'go' if we are in a mode where machine should play. */
if( (moveNum == 0 && setboardSpoiledMachineBlack && cps == &first) &&
(gameMode == TwoMachinesPlay ||
-#ifdef ZIPPY
+#if ZIPPY
gameMode == IcsPlayingBlack || gameMode == IcsPlayingWhite ||
#endif
gameMode == MachinePlaysBlack || gameMode == MachinePlaysWhite) ) {
/* POP Fabien */
sprintf(user_move, "o-o-o\n");
break;
+ case WhiteNonPromotion:
+ case BlackNonPromotion:
+ sprintf(user_move, "%c%c%c%c=\n", AAA + fromX, ONE + fromY, AAA + toX, ONE + toY);
+ break;
case WhitePromotionQueen:
case BlackPromotionQueen:
case WhitePromotionRook:
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:
case BlackPromotionKnight:
case WhitePromotionKing:
case BlackPromotionKing:
+ case WhiteNonPromotion:
+ case BlackNonPromotion:
case NormalMove:
case WhiteCapturesEnPassant:
case BlackCapturesEnPassant:
void
-ParsePV(char *pv)
+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;
endPV = forwardMostMove;
do {
- while(*pv == ' ') pv++;
- if(*pv == '(') pv++; // first (ponder) move can be in parentheses
+ 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 '%s'\n", valid, fromX+AAA, fromY+ONE, toX+AAA, toY+ONE, pv);
+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;
}
- while(*pv && *pv++ != ' '); // skip what we parsed; assume space separators
- if(moveType == Comment) { valid++; continue; } // allow comments in PV
nr++;
if(endPV+1 > framePtr) break; // no space, truncate
if(!valid) break;
moveList[endPV-1][1] = fromY + ONE;
moveList[endPV-1][2] = toX + AAA;
moveList[endPV-1][3] = toY + ONE;
- parseList[endPV-1][0] = NULLCHAR;
+ 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
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;
- index = startPV;
- while(buf[index] && buf[index] != '\n') 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);
+ ParsePV(buf+startPV, FALSE);
*start = startPV; *end = index-1;
return TRUE;
}
{ // 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]); // load the PV of the thinking engine in the boards array.
+ ParsePV(lastPV[which], FALSE); // load the PV of the thinking engine in the boards array.
return TRUE;
}
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;
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);
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;
char lastLoadGameTitle[MSG_SIZ], lastLoadPositionTitle[MSG_SIZ];
ChessMove lastLoadGameStart = (ChessMove) 0;
-ChessMove
-UserMoveTest(fromX, fromY, toX, toY, promoChar, captureOwn)
+void
+UserMoveEvent(fromX, fromY, toX, toY, promoChar)
int fromX, fromY, toX, toY;
int promoChar;
- Boolean captureOwn;
{
ChessMove moveType;
ChessSquare pdown, pup;
case IcsIdle:
/* We switched into a game mode where moves are not accepted,
perhaps while the mouse button was down. */
- return ImpossibleMove;
+ return;
case MachinePlaysWhite:
/* User is moving for Black */
if (WhiteOnMove(currentMove)) {
DisplayMoveError(_("It is White's turn"));
- return ImpossibleMove;
+ return;
}
break;
/* User is moving for White */
if (!WhiteOnMove(currentMove)) {
DisplayMoveError(_("It is Black's turn"));
- return ImpossibleMove;
+ return;
}
break;
/* User is moving for Black */
if (WhiteOnMove(currentMove)) {
DisplayMoveError(_("It is White's turn"));
- return ImpossibleMove;
+ return;
}
} else {
/* User is moving for White */
if (!WhiteOnMove(currentMove)) {
DisplayMoveError(_("It is Black's turn"));
- return ImpossibleMove;
+ return;
}
}
break;
"fromY %d, toX %d, toY %d\n",
fromX, fromY, toX, toY);
}
- return ImpossibleMove;
+ return;
}
break;
"fromY %d, toX %d, toY %d\n",
fromX, fromY, toX, toY);
}
- return ImpossibleMove;
+ return;
}
break;
click-click move is possible */
if (toX == -2 || toY == -2) {
boards[0][fromY][fromX] = EmptySquare;
- return AmbiguousMove;
+ DrawPosition(FALSE, boards[currentMove]);
+ return;
} else if (toX >= 0 && toY >= 0) {
boards[0][toY][toX] = boards[0][fromY][fromX];
if(fromX == BOARD_LEFT-2) { // handle 'moves' out of holdings
}
} else
boards[0][fromY][fromX] = EmptySquare;
- return AmbiguousMove;
+ DrawPosition(FALSE, boards[currentMove]);
+ return;
}
- return ImpossibleMove;
+ return;
}
- if(toX < 0 || toY < 0) return ImpossibleMove;
+ if(toX < 0 || toY < 0) return;
pdown = boards[currentMove][fromY][fromX];
pup = boards[currentMove][toY][toX];
- /* [HGM] If move started in holdings, it means a drop */
+ /* [HGM] If move started in holdings, it means a drop. Convert to standard form */
if( fromX == BOARD_LEFT-2 || fromX == BOARD_RGHT+1) {
- if( pup != EmptySquare ) return ImpossibleMove;
- if(appData.testLegality) {
- /* it would be more logical if LegalityTest() also figured out
- * which drops are legal. For now we forbid pawns on back rank.
- * Shogi is on its own here...
- */
- if( (pdown == WhitePawn || pdown == BlackPawn) &&
- (toY == 0 || toY == BOARD_HEIGHT -1 ) )
- return(ImpossibleMove); /* no pawn drops on 1st/8th */
- }
- return WhiteDrop; /* Not needed to specify white or black yet */
+ if( pup != EmptySquare ) return;
+ 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]);
+ // 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
+ while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
+ fromY = DROP_RANK;
}
- userOfferedDraw = FALSE;
-
/* [HGM] always test for legality, to get promotion info */
moveType = LegalityTest(boards[currentMove], PosFlags(currentMove),
fromY, fromX, toY, toX, promoChar);
if (appData.testLegality) {
if (moveType == IllegalMove || moveType == ImpossibleMove) {
DisplayMoveError(_("Illegal move"));
- return ImpossibleMove;
+ return;
}
}
- return moveType;
- /* [HGM] <popupFix> in stead of calling FinishMove directly, this
- function is made into one that returns an OK move type if FinishMove
- should be called. This to give the calling driver routine the
- opportunity to finish the userMove input with a promotion popup,
- without bothering the user with this for invalid or illegal moves */
-
-/* FinishMove(moveType, fromX, fromY, toX, toY, promoChar); */
+ FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
}
/* Common tail of UserMoveEvent and DropMenuEvent */
if(moveType == NormalMove && promoChar)
moveType = PromoCharToMoveType(WhiteOnMove(currentMove), promoChar);
- /* [HGM] convert drag-and-drop piece drops to standard form */
- 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]);
- // 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
- while(PieceToChar(fromX) == '.' || PieceToNumber(fromX) != fromY && fromX != (int) EmptySquare) fromX++;
- fromY = DROP_RANK;
- }
-
/* [HGM] <popupFix> The following if has been moved here from
UserMoveEvent(). Because it seemed to belong here (why not allow
piece drops in training games?), and because it can only be
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;
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?
}
void
-UserMoveEvent(fromX, fromY, toX, toY, promoChar)
- int fromX, fromY, toX, toY;
- int promoChar;
-{
- /* [HGM] This routine was added to allow calling of its two logical
- parts from other modules in the old way. Before, UserMoveEvent()
- automatically called FinishMove() if the move was OK, and returned
- otherwise. I separated the two, in order to make it possible to
- slip a promotion popup in between. But that it always needs two
- calls, to the first part, (now called UserMoveTest() ), and to
- FinishMove if the first part succeeded. Calls that do not need
- to do anything in between, can call this routine the old way.
- */
- ChessMove moveType = UserMoveTest(fromX, fromY, toX, toY, promoChar, FALSE);
-if(appData.debugMode) fprintf(debugFP, "moveType 4 = %d, promochar = %x\n", moveType, promoChar);
- if(moveType == AmbiguousMove)
- DrawPosition(FALSE, boards[currentMove]);
- else if(moveType != ImpossibleMove && moveType != Comment)
- FinishMove(moveType, fromX, fromY, toX, toY, promoChar);
-}
-
-void
Mark(board, flags, kind, rf, ff, rt, ft, closure)
Board board;
int flags;
{
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 == 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)) {
fromY = y;
second = 0;
MarkTargetSquares(0);
- DragPieceBegin(xPix, yPix);
+ 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;
}
if(x == BOARD_LEFT-2 && piece >= BlackPawn) {
n = PieceToNumber(piece - (int)BlackPawn);
- if(n > gameInfo.holdingsSize) { n = 0; piece = 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; }
+ if(n >= gameInfo.holdingsSize) { n = 0; piece = WhitePawn; }
boards[currentMove][n][BOARD_WIDTH-1] = piece;
boards[currentMove][n][BOARD_WIDTH-2]++;
}
}
}
+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;
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];
+ pCnt[p]++;
+ if(p == WhitePawn && r == BOARD_HEIGHT-1) (*wStale)++; else
+ if(p == BlackPawn && r == 0) (*bStale)++; // 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
+SufficientDefence(int pCnt[], int side, int nMine, int nHis)
+{
+ int myPawns = pCnt[WhitePawn+side]; // my total Pawn count;
+ int majorDefense = pCnt[BlackRook-side] + pCnt[BlackCannon-side] + pCnt[BlackKnight-side];
+
+ nMine -= pCnt[WhiteFerz+side] + pCnt[WhiteAlfil+side]; // discount defenders
+ if(nMine - myPawns > 2) return FALSE; // no trivial draws with more than 1 major
+ if(myPawns == 2 && nMine == 3) // KPP
+ return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 3;
+ if(myPawns == 1 && nMine == 2) // KP
+ return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
+ if(myPawns == 1 && nMine == 3 && pCnt[WhiteKnight+side]) // KHP
+ return majorDefense || pCnt[BlackFerz-side] + pCnt[BlackAlfil-side]*2 >= 5;
+ if(myPawns) return FALSE;
+ if(pCnt[WhiteRook+side])
+ return pCnt[BlackRook-side] ||
+ pCnt[BlackCannon-side] && (pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] >= 2) ||
+ pCnt[BlackKnight-side] && pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] > 2 ||
+ pCnt[BlackFerz-side] + pCnt[BlackAlfil-side] >= 4;
+ if(pCnt[WhiteCannon+side]) {
+ if(pCnt[WhiteFerz+side] + myPawns == 0) return TRUE; // Cannon needs platform
+ return majorDefense || pCnt[BlackAlfil-side] >= 2;
+ }
+ if(pCnt[WhiteKnight+side])
+ return majorDefense || pCnt[BlackFerz-side] >= 2 || pCnt[BlackAlfil-side] + pCnt[BlackPawn-side] >= 1;
+ return FALSE;
+}
+
+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(gameInfo.variant == VariantXiangqi ?
+ SufficientDefence(nr, WhitePawn, nrW, nrB) && SufficientDefence(nr, BlackPawn, nrB, nrW)
+ : 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
/* to make sure an illegal e.p. capture does not slip through, */
/* to cause a forfeit on a justified illegal-move complaint */
/* of the opponent. */
- if( gameMode==TwoMachinesPlay && appData.testLegality
- && fromY != DROP_RANK /* [HGM] temporary; should still add legality test for drops */
- ) {
+ if( gameMode==TwoMachinesPlay && appData.testLegality ) {
ChessMove moveType;
moveType = LegalityTest(boards[forwardMostMove], PosFlags(forwardMostMove),
fromY, fromX, toY, toX, promoChar);
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;
-
+ if(Adjudicate(cps)) return; // [HGM] adjudicate: for all automatic game ends
- // 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 == 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 = (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(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;
- }
- }
-
- /* 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, (signed char)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 &&
- (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
- && 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( (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( 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((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 ) {
- 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) {
}
- 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
case BlackPromotionKnight:
case WhitePromotionKing:
case BlackPromotionKing:
+ case WhiteNonPromotion:
+ case BlackNonPromotion:
case NormalMove:
case WhiteCapturesEnPassant:
case BlackCapturesEnPassant:
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 = (signed char)board[EP_STATUS];
if( board[toY][toX] != EmptySquare )
board[EP_STATUS] = EP_CAPTURE;
+ /* [HGM] In Shatranj and Courier all promotions are to Ferz */
+ if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier || gameInfo.variant == VariantMakruk)
+ && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
+
+ if (fromY == DROP_RANK) {
+ /* must be first */
+ piece = board[toY][toX] = (ChessSquare) fromX;
+ } else {
+ int i;
+
if( board[fromY][fromX] == WhitePawn ) {
if(fromY != toY) // [HGM] Xiangqi sideway Pawn moves should not count as 50-move breakers
board[EP_STATUS] = EP_PAWN_MOVE;
) board[CASTLING][i] = NoRights; // revoke for moved or captured piece
}
- }
-
- /* [HGM] In Shatranj and Courier all promotions are to Ferz */
- if((gameInfo.variant==VariantShatranj || gameInfo.variant==VariantCourier)
- && promoChar != 0) promoChar = PieceToChar(WhiteFerz);
-
- if (fromX == toX && fromY == toY) return;
+ if (fromX == toX && fromY == toY) return;
- if (fromY == DROP_RANK) {
- /* must be first */
- piece = board[toY][toX] = (ChessSquare) fromX;
- } else {
piece = board[fromY][fromX]; /* [HGM] remember, for Shogi promotion */
king = piece < (int) BlackPawn ? WhiteKing : BlackKing; /* [HGM] Knightmate simplify testing for castling */
if(gameInfo.variant == VariantKnightmate)
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)
}
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
endingGame = 0; /* [HGM] crash */
return;
} else {
- char buf[MSG_SIZ];
gameMode = nextGameMode;
sprintf(buf, _("Match %s vs. %s: final score %d-%d-%d"),
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) { // [HGM] crash: this calls GameEnds recursively through ExitEvent! Make it a harmless tail recursion.
+ if(matchMode == TRUE) DisplayFatalError(buf, 0, 0); else {
+ matchMode = FALSE; appData.matchGames = matchGame = 0;
+ DisplayNote(buf);
+ }
+ }
}
/* Assumes program was just initialized (initString sent).
case BlackPromotionKnight:
case WhitePromotionKing:
case BlackPromotionKing:
+ case WhiteNonPromotion:
+ case BlackNonPromotion:
case NormalMove:
case WhiteKingSideCastle:
case WhiteQueenSideCastle:
/* [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 (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 ) {
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;
int n;
if(x == BOARD_LEFT-2 && selection >= BlackPawn) {
n = PieceToNumber(selection - BlackPawn);
- if(n > gameInfo.holdingsSize) { n = 0; 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; }
+ if(n >= gameInfo.holdingsSize) { n = 0; selection = WhitePawn; }
boards[0][n][BOARD_WIDTH-1] = selection;
boards[0][n][BOARD_WIDTH-2]++;
}
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 &&
}
void
-RevertEvent()
+RevertEvent(Boolean annotate)
{
- if(PopTail(TRUE)) { // [HGM] vari: restore old game tail
+ if(PopTail(annotate)) { // [HGM] vari: restore old game tail
return;
}
if (gameMode != IcsExamining) {
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) {
}
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);
}
}
sprintf(res, " %s", PGNResult(gameInfo.result));
} else {
sprintf(res, " {%s} %s",
- gameInfo.resultDetails, PGNResult(gameInfo.result));
+ T_(gameInfo.resultDetails), PGNResult(gameInfo.result));
}
} else {
res[0] = NULLCHAR;
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;
}
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;
char *fen;
{
int i, j;
- char *p;
+ char *p, c;
int emptycount;
ChessSquare piece;
while(*p == ' ') p++;
/* Active color */
- switch (*p++) {
+ c = *p++;
+ if(appData.colorNickNames) {
+ if( c == appData.colorNickNames[0] ) c = 'w'; else
+ if( c == appData.colorNickNames[1] ) c = 'b';
+ }
+ switch (c) {
case 'w':
*blackPlaysFirst = FALSE;
break;
} /* assume possible unless obviously impossible */
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]] != WhiteKing) board[CASTLING][2] = 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]] != BlackKing) board[CASTLING][5] = 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 {
}
storedGames++;
- forwardMostMove = currentMove; // truncte game so we can start variation
+ forwardMostMove = firstMove; // truncate game so we can start variation
if(storedGames == 1) GreyRevert(FALSE);
}
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
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
+ 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];
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);
+}
+