Skip to content

Commit 6f5a19d

Browse files
committed
xchenard: Fixes for displaying analysis on xboard/WinBoard hosts.
Fixed a bug where xchenard was not including the final ply in the principle variation sent to the host. When the host wants to display analysis, do not immediately stop analysis when there is only one legal move. Just like in the blunder alert case, keep thinking to show the analysis. Display best path even on the most shallow search level. Provide configuration options in xchenard.ini to work around quirks of "SCID vs PC" host: sdponder=49 stponder=3600 These suppress making/printing the move if the analysis ends early (due to finding a forced mate). This prevents SCID from replacing the nice SAN formatted move with an algebraic longmove.
1 parent 3997323 commit 6f5a19d

File tree

4 files changed

+81
-53
lines changed

4 files changed

+81
-53
lines changed

src/chess.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -930,6 +930,9 @@ class ComputerChessPlayer: public ChessPlayer
930930
void blunderAlert_SetInstanceFlag() { blunderAlertInstance = true; }
931931
bool blunderAlert_QueryInstanceFlag() const { return blunderAlertInstance; }
932932

933+
bool isEnabledImmediateSingularMove() const { return immediateSingularMove; }
934+
void setEnabledImmediateSingularMove(bool enable) { immediateSingularMove = enable; }
935+
933936
bool isBackgroundThinker() const { return oppTimeInstance || blunderAlertInstance; }
934937

935938
Move getPredictedOpponentMove() const { return predictedOppMove; }
@@ -1129,6 +1132,7 @@ class ComputerChessPlayer: public ChessPlayer
11291132
Move predictedOppMove;
11301133

11311134
bool blunderAlertInstance; // is this a blunder alert thinker?
1135+
bool immediateSingularMove; // when there is a single legal move, make it without analysis
11321136

11331137
public:
11341138
// Since it uses so much memory, all ComputerChessPlayer

src/search.cpp

Lines changed: 42 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,8 @@ ComputerChessPlayer::ComputerChessPlayer ( ChessUI &ui ):
6767
KingPosTable ( KingPosTableQR ),
6868
oppTimeInstance ( false ),
6969
oppTimeEnable ( false ),
70-
blunderAlertInstance(false)
70+
blunderAlertInstance(false),
71+
immediateSingularMove(true)
7172
{
7273
rootml.num = 0;
7374
ResetHistoryBuffers();
@@ -308,7 +309,7 @@ bool ComputerChessPlayer::GetMove (
308309

309310
if ( oppTimeEnable && !oppTimeInstance && searchType == CCPST_TIMED_SEARCH )
310311
{
311-
bool oppTime = userInterface.oppTime_finishThinking (
312+
bool oppTime = userInterface.oppTime_finishThinking (
312313
board,
313314
timeLimit,
314315
timeSpent,
@@ -451,7 +452,7 @@ void ComputerChessPlayer::GetWhiteMove (
451452
blackHist[i] /= 2;
452453
}
453454

454-
if ( rootml.num == 1 )
455+
if ( rootml.num == 1 && isEnabledImmediateSingularMove() )
455456
{
456457
// If we are doing a blunder alert analysis,
457458
// we really need to know the score for the position
@@ -501,7 +502,7 @@ void ComputerChessPlayer::GetWhiteMove (
501502
}
502503

503504
LearnTree tree;
504-
if ( (searchType == CCPST_TIMED_SEARCH) && trainingEnabled &&
505+
if ( (searchType == CCPST_TIMED_SEARCH) && trainingEnabled &&
505506
tree.familiarPosition ( board, bestmove, timeLimit, rootml ) )
506507
{
507508
sprintf ( buffer, "experience (%6d)", int(bestmove.score) );
@@ -517,7 +518,7 @@ void ComputerChessPlayer::GetWhiteMove (
517518
// See if we can recycle best path info from the previous move's search.
518519
UINT32 hash = board.Hash();
519520
int startLevel = minlevel;
520-
if ( hash==expectedNextBoardHash &&
521+
if ( hash==expectedNextBoardHash &&
521522
currentBestPath.depth>=2 && prevCompletedLevel>1 )
522523
{
523524
// Strip the top two plies because they are in the past!
@@ -606,7 +607,7 @@ void ComputerChessPlayer::GetBlackMove (
606607
blackHist[i] /= 2;
607608
}
608609

609-
if ( rootml.num == 1 )
610+
if ( rootml.num == 1 && isEnabledImmediateSingularMove() )
610611
{
611612
// If we are doing a blunder alert analysis,
612613
// we really need to know the score for the position
@@ -655,7 +656,7 @@ void ComputerChessPlayer::GetBlackMove (
655656
}
656657

657658
LearnTree tree;
658-
if ( searchType==CCPST_TIMED_SEARCH && trainingEnabled &&
659+
if ( searchType==CCPST_TIMED_SEARCH && trainingEnabled &&
659660
tree.familiarPosition ( board, bestmove, timeLimit, rootml ) )
660661
{
661662
sprintf ( buffer, "experience (%6d)", int(bestmove.score) );
@@ -671,7 +672,7 @@ void ComputerChessPlayer::GetBlackMove (
671672
// See if we can recycle best path info from the previous move's search.
672673
UINT32 hash = board.Hash();
673674
int startLevel = minlevel;
674-
if ( hash==expectedNextBoardHash &&
675+
if ( hash==expectedNextBoardHash &&
675676
currentBestPath.depth>=2 && prevCompletedLevel>1 )
676677
{
677678
// Strip the top two plies because they are in the past!
@@ -761,29 +762,28 @@ BestPath *ComputerChessPlayer::SaveTLMBestPath ( Move move )
761762
{
762763
// Try to find move in existing TLM BestPaths...
763764

765+
bool found = false;
764766
int i;
765-
for ( i=0; i < eachBestPathCount; i++ )
766-
{
767+
for ( i=0; !found && i < eachBestPathCount; i++ )
767768
if ( eachBestPath[i].m[0] == move )
769+
found = true;
770+
771+
if (!found)
772+
{
773+
// Need to add a new one!
774+
775+
if ( eachBestPathCount >= MAX_MOVES )
768776
{
769-
eachBestPath[i] = nextBestPath[1];
770-
eachBestPath[i].m[0] = move;
771-
return & ( eachBestPath[i] );
777+
ChessFatal ( "BestPath top-level-move overflow!" );
778+
return 0;
772779
}
773-
}
774-
775-
// Need to add a new one!
776780

777-
if ( eachBestPathCount >= MAX_MOVES )
778-
{
779-
ChessFatal ( "BestPath top-level-move overflow!" );
780-
return 0;
781+
i = eachBestPathCount++;
781782
}
782783

783-
i = eachBestPathCount++;
784784
eachBestPath[i] = nextBestPath[1];
785785
eachBestPath[i].m[0] = move;
786-
return & ( eachBestPath[i] );
786+
return &eachBestPath[i];
787787
}
788788

789789

@@ -903,16 +903,13 @@ SCORE ComputerChessPlayer::WhiteSearchRoot (
903903
{
904904
bestmove = *move;
905905
expectedScoreNow = bestscore = score;
906-
if ( level > 1 )
906+
userInterface.DisplayBestMoveSoFar ( board, bestmove, level );
907+
if ( path )
907908
{
908-
userInterface.DisplayBestMoveSoFar ( board, bestmove, level );
909-
if ( path )
910-
{
911-
if ( oppTimeInstance )
912-
InsertPrediction ( board, *path, userInterface );
913-
else
914-
userInterface.DisplayBestPath ( board, *path );
915-
}
909+
if ( oppTimeInstance )
910+
InsertPrediction ( board, *path, userInterface );
911+
else
912+
userInterface.DisplayBestPath ( board, *path );
916913
}
917914
}
918915
}
@@ -1012,16 +1009,13 @@ SCORE ComputerChessPlayer::BlackSearchRoot (
10121009
{
10131010
bestmove = *move;
10141011
expectedScoreNow = bestscore = score;
1015-
if ( level > 1 )
1012+
userInterface.DisplayBestMoveSoFar ( board, bestmove, level );
1013+
if ( path )
10161014
{
1017-
userInterface.DisplayBestMoveSoFar ( board, bestmove, level );
1018-
if ( path )
1019-
{
1020-
if ( oppTimeInstance )
1021-
InsertPrediction ( board, *path, userInterface );
1022-
else
1023-
userInterface.DisplayBestPath ( board, *path );
1024-
}
1015+
if ( oppTimeInstance )
1016+
InsertPrediction ( board, *path, userInterface );
1017+
else
1018+
userInterface.DisplayBestPath ( board, *path );
10251019
}
10261020
}
10271021
}
@@ -1125,8 +1119,8 @@ SCORE ComputerChessPlayer::WhiteSearch (
11251119
if ( ml.num == 0 )
11261120
{
11271121
// This is the end of the game!
1128-
bestscore = (board.flags & SF_WCHECK)
1129-
? (BLACK_WINS + WIN_POSTPONEMENT(depth))
1122+
bestscore = (board.flags & SF_WCHECK)
1123+
? (BLACK_WINS + WIN_POSTPONEMENT(depth))
11301124
: DRAW;
11311125

11321126
userInterface.DebugExit ( depth, board, bestscore );
@@ -1143,8 +1137,8 @@ SCORE ComputerChessPlayer::WhiteSearch (
11431137
// But we make sure to use transposition *only* after checking for
11441138
// draw by repetition.
11451139

1146-
if ( xpos
1147-
&& xpos->searchedDepth >= level-depth
1140+
if ( xpos
1141+
&& xpos->searchedDepth >= level-depth
11481142
&& numReps < 2
11491143
&& ml.IsLegal(xpos->bestReply) )
11501144
{
@@ -1280,8 +1274,8 @@ SCORE ComputerChessPlayer::BlackSearch (
12801274
if ( ml.num == 0 )
12811275
{
12821276
// This is the end of the game!
1283-
bestscore = (board.flags & SF_BCHECK)
1284-
? (WHITE_WINS - WIN_POSTPONEMENT(depth))
1277+
bestscore = (board.flags & SF_BCHECK)
1278+
? (WHITE_WINS - WIN_POSTPONEMENT(depth))
12851279
: DRAW;
12861280

12871281
userInterface.DebugExit ( depth, board, bestscore );
@@ -1298,8 +1292,8 @@ SCORE ComputerChessPlayer::BlackSearch (
12981292
// But we make sure to use transposition *only* after checking for
12991293
// draw by repetition.
13001294

1301-
if ( xpos
1302-
&& xpos->searchedDepth >= level-depth
1295+
if ( xpos
1296+
&& xpos->searchedDepth >= level-depth
13031297
&& numReps < 2
13041298
&& ml.IsLegal(xpos->bestReply) )
13051299
{

src/uixboard.cpp

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,6 @@ void ChessUI_xboard::DisplayCurrentMove ( const ChessBoard &, Move, int /*level*
284284

285285
void ChessUI_xboard::DisplayBestPath (const ChessBoard &_board, const BestPath &path)
286286
{
287-
288287
if (thinkingDisplayEnabled)
289288
{
290289
static const int MAX_PATH = 20;
@@ -325,9 +324,12 @@ void ChessUI_xboard::DisplayBestPath (const ChessBoard &_board, const BestPath &
325324

326325
// We change the board here, but we put it back the way we find it!
327326
ChessBoard &board = (ChessBoard &) _board;
328-
for (i=0; i < path.depth; ++i)
327+
for (i=0; i <= path.depth; ++i)
329328
{
330329
Move move = path.m[i];
330+
if (!board.isLegal(move))
331+
break;
332+
331333
FormatChessMove (board, move, string);
332334
if (col >= 70)
333335
{

src/xchenard.cpp

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,11 @@ bool PonderingAllowed = false;
5353
int MyRemainingTime = 0; // when in time management mode, this stores the most recently reported number of centiseconds remaining in the time period
5454
int MemoryAllotmentInMegabytes = 0; // remains 0 unless overridden by "memory" command. reset to 0 after used.
5555

56+
// The SuppressPrintMove... stuff is a hack to provide better analysis output for "SCID vs PC" as a host program.
57+
int SuppressPrintMoveDepth = -1;
58+
int SuppressPrintMoveTime = -1;
59+
bool SuppressPrintMove = false;
60+
5661
void dprintf (const char *format, ...)
5762
{
5863
if (DebugPrintAllowed)
@@ -643,7 +648,12 @@ void ThinkIfChenardsTurn()
643648
{
644649
// This is a little weird: we may have received and processed a 'force' command.
645650
// If this happens, it means it is no longer our turn!
646-
if (IsComputersTurn())
651+
// [2021-11-06] Also weird: SuppressPrintMove==true when the user has configured "sdponder"
652+
// to work around our lack of supporting the "analyze" command.
653+
// Hosts like "SCID vs PC" work around xchenard not supporting "analyze" by
654+
// setting a very high search depth: "sd 50". But then it gets confused in its
655+
// analysis window when we actually print a move!
656+
if (IsComputersTurn() && !SuppressPrintMove)
647657
{
648658
ponder_again:
649659
char moveString [6];
@@ -768,6 +778,7 @@ void SetDepthCommand (const char *rest)
768778
TheComputerPlayer.SetSearchDepth (depth);
769779
}
770780
TimeManagement = false;
781+
SuppressPrintMove = (SuppressPrintMoveDepth >= 0) && (depth > SuppressPrintMoveDepth);
771782
}
772783

773784

@@ -779,6 +790,7 @@ void SetTimeCommand (const char *rest)
779790
TheComputerPlayer.SetTimeLimit (100 * numberOfSeconds);
780791
}
781792
TimeManagement = false;
793+
SuppressPrintMove = (SuppressPrintMoveTime >= 0) && (numberOfSeconds > SuppressPrintMoveTime);
782794
}
783795

784796

@@ -819,6 +831,7 @@ void SetLevelCommand (const char *rest)
819831
TotalMovesPerPeriod = moves;
820832
IncrementSecondsPerMove = increment;
821833
TimeManagement = true;
834+
SuppressPrintMove = false;
822835
}
823836
}
824837

@@ -1025,6 +1038,14 @@ void ParseOption (const char *text)
10251038
// I am just providing a back door for someone to send a memory command via xchenard.ini if needed.
10261039
MemoryAllotmentInMegabytes = valueInt;
10271040
}
1041+
else if (0 == strcmp(name, "sdponder"))
1042+
{
1043+
SuppressPrintMoveDepth = valueInt;
1044+
}
1045+
else if (0 == strcmp(name, "stponder"))
1046+
{
1047+
SuppressPrintMoveTime = valueInt;
1048+
}
10281049
else
10291050
{
10301051
goto unknown_option;
@@ -1073,6 +1094,7 @@ bool ExecuteCommand (const char *verb, const char *rest) // returns true
10731094
printf ("feature myname=\"Chenard %s\"\n", CHENARD_VERSION);
10741095

10751096
// Send feature requests...
1097+
// See: https://www.gnu.org/software/xboard/engine-intf.html#9
10761098
printf ("feature sigint=0\n");
10771099
printf ("feature sigterm=0\n");
10781100
printf ("feature ping=1\n");
@@ -1170,11 +1192,17 @@ bool ExecuteCommand (const char *verb, const char *rest) // returns true
11701192
}
11711193
else if (0 == strcmp(verb,"post"))
11721194
{
1173-
TheUserInterface.EnableThinkingDisplay (true);
1195+
TheUserInterface.EnableThinkingDisplay(true);
1196+
1197+
// Always force some analysis to take place, so xboard/WinBoard can display the analysis.
1198+
TheComputerPlayer.setEnabledImmediateSingularMove(false);
11741199
}
11751200
else if (0 == strcmp(verb,"nopost"))
11761201
{
1177-
TheUserInterface.EnableThinkingDisplay (true);
1202+
TheUserInterface.EnableThinkingDisplay(false);
1203+
1204+
// Allow the computer to move immediately when there is only one legal move.
1205+
TheComputerPlayer.setEnabledImmediateSingularMove(true);
11781206
}
11791207
else if (0 == strcmp(verb,"hard"))
11801208
{

0 commit comments

Comments
 (0)