DroidFish: Added support for Gaviota endgame tablebases.

This commit is contained in:
Peter Osterlund
2012-01-20 19:14:25 +00:00
parent e6f63f59e9
commit 6b7c844df0
87 changed files with 28407 additions and 61 deletions

View File

@@ -53,6 +53,17 @@ public class ChessBoard extends View {
List<Move> moveHints;
/** Decoration for a square. Currently the only possible decoration is a number. */
public final static class SquareDecoration {
int sq;
int number;
public SquareDecoration(int sq, int number) {
this.sq = sq;
this.number = number;
}
}
private ArrayList<SquareDecoration> decorations;
protected Paint darkPaint;
protected Paint brightPaint;
private Paint selectedSquarePaint;
@@ -60,6 +71,7 @@ public class ChessBoard extends View {
private Paint whitePiecePaint;
private Paint blackPiecePaint;
private Paint labelPaint;
private Paint decorationPaint;
private ArrayList<Paint> moveMarkPaint;
public ChessBoard(Context context, AttributeSet attrs) {
@@ -93,6 +105,9 @@ public class ChessBoard extends View {
labelPaint = new Paint();
labelPaint.setAntiAlias(true);
decorationPaint = new Paint();
decorationPaint.setAntiAlias(true);
moveMarkPaint = new ArrayList<Paint>();
for (int i = 0; i < 6; i++) {
@@ -122,6 +137,7 @@ public class ChessBoard extends View {
whitePiecePaint.setColor(ct.getColor(ColorTheme.BRIGHT_PIECE));
blackPiecePaint.setColor(ct.getColor(ColorTheme.DARK_PIECE));
labelPaint.setColor(ct.getColor(ColorTheme.SQUARE_LABEL));
decorationPaint.setColor(ct.getColor(ColorTheme.DECORATION));
for (int i = 0; i < 6; i++)
moveMarkPaint.get(i).setColor(ct.getColor(ColorTheme.ARROW_0 + i));
@@ -130,7 +146,7 @@ public class ChessBoard extends View {
private Handler handlerTimer = new Handler();
final class AnimInfo {
private final class AnimInfo {
AnimInfo() { startTime = -1; }
boolean paused;
long posHash; // Position the animation is valid for
@@ -183,7 +199,7 @@ public class ChessBoard extends View {
drawPiece(canvas, xCrd, yCrd, piece);
}
}
AnimInfo anim = new AnimInfo();
private AnimInfo anim = new AnimInfo();
/**
* Set up move animation. The animation will start the next time setPosition is called.
@@ -191,7 +207,7 @@ public class ChessBoard extends View {
* @param move The move leading to the target position.
* @param forward True if forward direction, false for undo move.
*/
public void setAnimMove(Position sourcePos, Move move, boolean forward) {
public final void setAnimMove(Position sourcePos, Move move, boolean forward) {
anim.startTime = -1;
anim.paused = true; // Animation starts at next position update
if (forward) {
@@ -366,6 +382,7 @@ public class ChessBoard extends View {
blackPiecePaint.setTextSize(sqSize);
whitePiecePaint.setTextSize(sqSize);
labelPaint.setTextSize(sqSize/4.0f);
decorationPaint.setTextSize(sqSize/3.0f);
computeOrigin(width, height);
for (int x = 0; x < 8; x++) {
for (int y = 0; y < 8; y++) {
@@ -406,8 +423,10 @@ public class ChessBoard extends View {
cursorSquarePaint.setStrokeWidth(sqSize/(float)16);
canvas.drawRect(x0, y0, x0 + sqSize, y0 + sqSize, cursorSquarePaint);
}
if (!animActive)
if (!animActive) {
drawMoveHints(canvas);
drawDecorations(canvas);
}
anim.draw(canvas);
// long t1 = System.currentTimeMillis();
@@ -511,8 +530,7 @@ public class ChessBoard extends View {
private final void drawLabel(Canvas canvas, int xCrd, int yCrd, boolean right,
boolean bottom, char c) {
String s = "";
s += c;
String s = Character.toString(c);
if (labelBounds == null) {
labelBounds = new Rect();
labelPaint.getTextBounds("f", 0, 1, labelBounds);
@@ -556,7 +574,7 @@ public class ChessBoard extends View {
return sq;
}
final private boolean myColor(int piece) {
private final boolean myColor(int piece) {
return (piece != Piece.EMPTY) && (Piece.isWhite(piece) == pos.whiteMove);
}
@@ -667,4 +685,48 @@ public class ChessBoard extends View {
invalidate();
}
}
public final void setSquareDecorations(ArrayList<SquareDecoration> decorations) {
boolean equal = false;
if ((this.decorations == null) || (decorations == null)) {
equal = this.decorations == decorations;
} else {
equal = this.decorations.equals(decorations);
}
if (!equal) {
this.decorations = decorations;
invalidate();
}
}
private final void drawDecorations(Canvas canvas) {
if (decorations == null)
return;
for (SquareDecoration sd : decorations) {
int sq = sd.sq;
if ((sd.sq < 0) || (sd.sq >= 64))
continue;
int xCrd = getXCrd(Position.getX(sq));
int yCrd = getYCrd(Position.getY(sq));
int num = sd.number;
String s;
if (num > 0)
s = "+" + String.valueOf(num);
else if (num < 0)
s = String.valueOf(num);
else
s = "0";
Rect bounds = new Rect();
decorationPaint.getTextBounds(s, 0, s.length(), bounds);
xCrd += (sqSize - (bounds.left + bounds.right)) / 2;
yCrd += (sqSize - (bounds.top + bounds.bottom)) / 2;
canvas.drawText(s, xCrd, yCrd, decorationPaint);
}
}
public final int getSelectedSquare() {
return selectedSquare;
}
}

View File

@@ -46,13 +46,14 @@ public class ColorTheme {
final static int ARROW_4 = 11;
final static int ARROW_5 = 12;
final static int SQUARE_LABEL = 13;
private final static int numColors = 14;
final static int DECORATION = 14;
private final static int numColors = 15;
private int colorTable[] = new int[numColors];
private static final String[] prefNames = {
"darkSquare", "brightSquare", "selectedSquare", "cursorSquare", "darkPiece", "brightPiece", "currentMove",
"arrow0", "arrow1", "arrow2", "arrow3", "arrow4", "arrow5", "squareLabel"
"arrow0", "arrow1", "arrow2", "arrow3", "arrow4", "arrow5", "squareLabel", "decoration"
};
private static final String prefPrefix = "color_";
@@ -61,19 +62,23 @@ public class ColorTheme {
private final static String themeColors[][] = {
{
"#FF808080", "#FFBEBE5A", "#FFFF0000", "#FF00FF00", "#FF000000", "#FFFFFFFF", "#FF888888",
"#A01F1FFF", "#A0FF1F1F", "#501F1FFF", "#50FF1F1F", "#1E1F1FFF", "#28FF1F1F", "#FFFF0000"
"#A01F1FFF", "#A0FF1F1F", "#501F1FFF", "#50FF1F1F", "#1E1F1FFF", "#28FF1F1F", "#FFFF0000",
"#FF9F9F66"
},
{
"#FF77A26D", "#FFC8C365", "#FFFFFF00", "#FF00FF00", "#FF202020", "#FFFFFFCC", "#FF6B9262",
"#A01F1FFF", "#A0FF1F1F", "#501F1FFF", "#50FF1F1F", "#1E1F1FFF", "#28FF1F1F", "#FFFF0000"
"#A01F1FFF", "#A0FF1F1F", "#501F1FFF", "#50FF1F1F", "#1E1F1FFF", "#28FF1F1F", "#FFFF0000",
"#FF808080"
},
{
"#FF83A5D2", "#FFFFFFFA", "#FF3232D1", "#FF5F5FFD", "#FF282828", "#FFF0F0F0", "#FF3333FF",
"#A01F1FFF", "#A01FFF1F", "#501F1FFF", "#501FFF1F", "#1E1F1FFF", "#281FFF1F", "#FFFF0000"
"#A01F1FFF", "#A01FFF1F", "#501F1FFF", "#501FFF1F", "#1E1F1FFF", "#281FFF1F", "#FFFF0000",
"#FF808080"
},
{
"#FF666666", "#FFDDDDDD", "#FFFF0000", "#FF0000FF", "#FF000000", "#FFFFFFFF", "#FF888888",
"#A01F1FFF", "#A0FF1F1F", "#501F1FFF", "#50FF1F1F", "#1E1F1FFF", "#28FF1F1F", "#FFFF0000"
"#A01F1FFF", "#A0FF1F1F", "#501F1FFF", "#50FF1F1F", "#1E1F1FFF", "#28FF1F1F", "#FFFF0000",
"#FF909090"
}
};

View File

@@ -28,6 +28,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.TreeMap;
import org.petero.droidfish.ChessBoard.SquareDecoration;
import org.petero.droidfish.activities.CPUWarning;
import org.petero.droidfish.activities.EditBoard;
import org.petero.droidfish.activities.EditPGNLoad;
@@ -39,10 +40,12 @@ import org.petero.droidfish.engine.EngineUtil;
import org.petero.droidfish.gamelogic.DroidChessController;
import org.petero.droidfish.gamelogic.ChessParseError;
import org.petero.droidfish.gamelogic.Move;
import org.petero.droidfish.gamelogic.Pair;
import org.petero.droidfish.gamelogic.Position;
import org.petero.droidfish.gamelogic.TextIO;
import org.petero.droidfish.gamelogic.PgnToken;
import org.petero.droidfish.gamelogic.GameTree.Node;
import org.petero.droidfish.gtb.Probe;
import android.app.Activity;
import android.app.AlertDialog;
@@ -163,8 +166,10 @@ public class DroidFish extends Activity implements GUIInterface {
private final static String bookDir = "DroidFish";
private final static String pgnDir = "DroidFish" + File.separator + "pgn";
private final static String engineDir = "DroidFish" + File.separator + "uci";
private final static String gtbDefaultDir = "DroidFish" + File.separator + "gtb";
private BookOptions bookOptions = new BookOptions();
private PGNOptions pgnOptions = new PGNOptions();
private EGTBOptions egtbOptions = new EGTBOptions();
private long lastVisibleMillis; // Time when GUI became invisible. 0 if currently visible.
private long lastComputationMillis; // Time when engine last showed that it was computing.
@@ -235,6 +240,7 @@ public class DroidFish extends Activity implements GUIInterface {
new File(extDir + sep + bookDir).mkdirs();
new File(extDir + sep + pgnDir).mkdirs();
new File(extDir + sep + engineDir).mkdirs();
new File(extDir + sep + gtbDefaultDir).mkdirs();
}
private String getPgnIntent() {
@@ -404,6 +410,7 @@ public class DroidFish extends Activity implements GUIInterface {
Move m = cb.mousePressed(sq);
if (m != null)
ctrl.makeHumanMove(m);
setEgtbHints(cb.getSelectedSquare());
}
}
});
@@ -416,9 +423,9 @@ public class DroidFish extends Activity implements GUIInterface {
public void onTrackballEvent(MotionEvent event) {
if (ctrl.humansTurn()) {
Move m = cb.handleTrackballEvent(event);
if (m != null) {
if (m != null)
ctrl.makeHumanMove(m);
}
setEgtbHints(cb.getSelectedSquare());
}
}
});
@@ -609,6 +616,20 @@ public class DroidFish extends Activity implements GUIInterface {
bookOptions.random = (settings.getInt("bookRandom", 500) - 500) * (3.0 / 500);
setBookOptions();
egtbOptions.hints = settings.getBoolean("tbHints", false);
egtbOptions.hintsEdit = settings.getBoolean("tbHintsEdit", false);
egtbOptions.rootProbe = settings.getBoolean("tbRootProbe", false);
egtbOptions.engineProbe = settings.getBoolean("tbEngineProbe", true);
String gtbPath = settings.getString("gtbPath", "");
if (gtbPath.length() == 0) {
File extDir = Environment.getExternalStorageDirectory();
String sep = File.separator;
gtbPath = extDir.getAbsolutePath() + sep + gtbDefaultDir;
}
egtbOptions.gtbPath = gtbPath;
setEgtbOptions();
setEgtbHints(cb.getSelectedSquare());
updateThinkingInfo();
pgnOptions.view.variations = settings.getBoolean("viewVariations", true);
@@ -679,6 +700,29 @@ public class DroidFish extends Activity implements GUIInterface {
ctrl.setBookOptions(options);
}
private final void setEgtbOptions() {
ctrl.setEgtbOptions(new EGTBOptions(egtbOptions));
}
private final void setEgtbHints(int sq) {
if (!egtbOptions.hints || (sq < 0)) {
cb.setSquareDecorations(null);
return;
}
Probe gtbProbe = Probe.getInstance();
ArrayList<Pair<Integer, Integer>> x = gtbProbe.movePieceProbe(cb.pos, sq);
if (x == null) {
cb.setSquareDecorations(null);
return;
}
ArrayList<SquareDecoration> sd = new ArrayList<SquareDecoration>();
for (Pair<Integer,Integer> p : x)
sd.add(new SquareDecoration(p.first, p.second));
cb.setSquareDecorations(sd);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.options_menu, menu);
@@ -841,6 +885,7 @@ public class DroidFish extends Activity implements GUIInterface {
@Override
public void setSelection(int sq) {
cb.setSelection(sq);
setEgtbHints(sq);
}
@Override
@@ -940,12 +985,13 @@ public class DroidFish extends Activity implements GUIInterface {
}
@Override
public void setPosition(Position pos, String variantInfo, List<Move> variantMoves) {
public void setPosition(Position pos, String variantInfo, ArrayList<Move> variantMoves) {
variantStr = variantInfo;
this.variantMoves = variantMoves;
cb.setPosition(pos);
setBoardFlip();
updateThinkingInfo();
setEgtbHints(cb.getSelectedSquare());
}
private String thinkingStr1 = "";
@@ -953,12 +999,12 @@ public class DroidFish extends Activity implements GUIInterface {
private String bookInfoStr = "";
private String variantStr = "";
private ArrayList<ArrayList<Move>> pvMoves = new ArrayList<ArrayList<Move>>();
private List<Move> bookMoves = null;
private List<Move> variantMoves = null;
private ArrayList<Move> bookMoves = null;
private ArrayList<Move> variantMoves = null;
@Override
public void setThinkingInfo(String pvStr, String statStr, String bookInfo,
ArrayList<ArrayList<Move>> pvMoves, List<Move> bookMoves) {
ArrayList<ArrayList<Move>> pvMoves, ArrayList<Move> bookMoves) {
thinkingStr1 = pvStr;
thinkingStr2 = statStr;
bookInfoStr = bookInfo;

View File

@@ -0,0 +1,44 @@
package org.petero.droidfish;
/** Endgame tablebase probing options. */
public final class EGTBOptions {
public boolean hints; // Hints when playing/analyzing
public boolean hintsEdit; // Hints in "edit board" mode
public boolean rootProbe; // Only search optimal moves at root
public boolean engineProbe; // Let engine use EGTB
public String gtbPath; // GTB directory path
public EGTBOptions() {
hints = false;
hintsEdit = false;
rootProbe = false;
engineProbe = false;
gtbPath = "";
}
public EGTBOptions(EGTBOptions other) {
hints = other.hints;
hintsEdit = other.hintsEdit;
rootProbe = other.rootProbe;
engineProbe = other.engineProbe;
gtbPath = other.gtbPath;
}
@Override
public boolean equals(Object o) {
if ((o == null) || (o.getClass() != this.getClass()))
return false;
EGTBOptions other = (EGTBOptions)o;
return ((hints == other.hints) &&
(hintsEdit == other.hintsEdit) &&
(rootProbe == other.rootProbe) &&
(engineProbe == other.engineProbe) &&
gtbPath.equals(other.gtbPath));
}
@Override
public int hashCode() {
return 0;
}
}

View File

@@ -19,7 +19,6 @@
package org.petero.droidfish;
import java.util.ArrayList;
import java.util.List;
import org.petero.droidfish.gamelogic.Game;
import org.petero.droidfish.gamelogic.Move;
@@ -31,7 +30,7 @@ import android.content.Context;
public interface GUIInterface {
/** Update the displayed board position. */
public void setPosition(Position pos, String variantInfo, List<Move> variantMoves);
public void setPosition(Position pos, String variantInfo, ArrayList<Move> variantMoves);
/** Mark square sq as selected. Set to -1 to clear selection. */
public void setSelection(int sq);
@@ -55,7 +54,7 @@ public interface GUIInterface {
/** Update the computer thinking information. */
public void setThinkingInfo(String pvStr, String statStr, String bookInfo,
ArrayList<ArrayList<Move>> pvMoves, List<Move> bookMoves);
ArrayList<ArrayList<Move>> pvMoves, ArrayList<Move> bookMoves);
/** Ask what to promote a pawn to. Should call reportPromotePiece() when done. */
public void requestPromotePiece();

View File

@@ -58,7 +58,7 @@ public class ChessBoardEdit extends ChessBoard {
y0 = (height - (sqSize * 10 + gap)) / 2;
}
int extraPieces(int x, int y) {
private final int extraPieces(int x, int y) {
if (y == -1) { // White pieces
switch (x) {
case 0: return Piece.WKING;

View File

@@ -18,21 +18,28 @@
package org.petero.droidfish.activities;
import java.util.ArrayList;
import org.petero.droidfish.ChessBoard;
import org.petero.droidfish.R;
import org.petero.droidfish.ChessBoard.SquareDecoration;
import org.petero.droidfish.gamelogic.ChessParseError;
import org.petero.droidfish.gamelogic.Move;
import org.petero.droidfish.gamelogic.Pair;
import org.petero.droidfish.gamelogic.Piece;
import org.petero.droidfish.gamelogic.Position;
import org.petero.droidfish.gamelogic.TextIO;
import org.petero.droidfish.gtb.Probe;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.res.Configuration;
import android.os.Bundle;
import android.preference.PreferenceManager;
import android.text.ClipboardManager;
import android.view.GestureDetector;
import android.view.KeyEvent;
@@ -53,12 +60,17 @@ public class EditBoard extends Activity {
private Button okButton;
private Button cancelButton;
boolean egtbHints;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
initUI();
SharedPreferences settings = PreferenceManager.getDefaultSharedPreferences(this);
egtbHints = settings.getBoolean("tbHintsEdit", false);
Intent i = getIntent();
Position pos;
try {
@@ -78,7 +90,7 @@ public class EditBoard extends Activity {
cb.cursorY = oldCB.cursorY;
cb.cursorVisible = oldCB.cursorVisible;
cb.setPosition(oldCB.pos);
cb.setSelection(oldCB.selectedSquare);
setSelection(oldCB.selectedSquare);
status.setText(statusStr);
}
@@ -128,6 +140,7 @@ public class EditBoard extends Activity {
Move m = cb.mousePressed(sq);
if (m != null)
doMove(m);
setEgtbHints(cb.getSelectedSquare());
}
});
cb.setOnTouchListener(new OnTouchListener() {
@@ -138,9 +151,9 @@ public class EditBoard extends Activity {
cb.setOnTrackballListener(new ChessBoard.OnTrackballListener() {
public void onTrackballEvent(MotionEvent event) {
Move m = cb.handleTrackballEvent(event);
if (m != null) {
if (m != null)
doMove(m);
}
setEgtbHints(cb.getSelectedSquare());
}
});
cb.setOnLongClickListener(new OnLongClickListener() {
@@ -152,10 +165,34 @@ public class EditBoard extends Activity {
});
}
private final void setSelection(int sq) {
cb.setSelection(sq);
setEgtbHints(sq);
}
private final void setEgtbHints(int sq) {
if (!egtbHints || (sq < 0)) {
cb.setSquareDecorations(null);
return;
}
Probe gtbProbe = Probe.getInstance();
ArrayList<Pair<Integer, Integer>> x = gtbProbe.relocatePieceProbe(cb.pos, sq);
if (x == null) {
cb.setSquareDecorations(null);
return;
}
ArrayList<SquareDecoration> sd = new ArrayList<SquareDecoration>();
for (Pair<Integer,Integer> p : x)
sd.add(new SquareDecoration(p.first, p.second));
cb.setSquareDecorations(sd);
}
private void doMove(Move m) {
if (m.to < 0) {
if ((m.from < 0) || (cb.pos.getPiece(m.from) == Piece.EMPTY)) {
cb.setSelection(m.to);
setSelection(m.to);
return;
}
}
@@ -172,9 +209,9 @@ public class EditBoard extends Activity {
pos.setPiece(m.from, Piece.EMPTY);
cb.setPosition(pos);
if (m.from >= 0)
cb.setSelection(-1);
setSelection(-1);
else
cb.setSelection(m.from);
setSelection(m.from);
checkValid();
}
@@ -263,13 +300,13 @@ public class EditBoard extends Activity {
switch (item) {
case 0: // Edit side to move
showDialog(SIDE_DIALOG);
cb.setSelection(-1);
setSelection(-1);
checkValid();
break;
case 1: { // Clear board
Position pos = new Position();
cb.setPosition(pos);
cb.setSelection(-1);
setSelection(-1);
checkValid();
break;
}
@@ -277,7 +314,7 @@ public class EditBoard extends Activity {
try {
Position pos = TextIO.readFEN(TextIO.startPosFEN);
cb.setPosition(pos);
cb.setSelection(-1);
setSelection(-1);
checkValid();
} catch (ChessParseError e) {
}
@@ -286,19 +323,19 @@ public class EditBoard extends Activity {
case 3: // Edit castling flags
removeDialog(CASTLE_DIALOG);
showDialog(CASTLE_DIALOG);
cb.setSelection(-1);
setSelection(-1);
checkValid();
break;
case 4: // Edit en passant file
removeDialog(EP_DIALOG);
showDialog(EP_DIALOG);
cb.setSelection(-1);
setSelection(-1);
checkValid();
break;
case 5: // Edit move counters
removeDialog(MOVCNT_DIALOG);
showDialog(MOVCNT_DIALOG);
cb.setSelection(-1);
setSelection(-1);
checkValid();
break;
case 6: { // Copy position
@@ -306,7 +343,7 @@ public class EditBoard extends Activity {
String fen = TextIO.toFEN(cb.pos) + "\n";
ClipboardManager clipboard = (ClipboardManager)getSystemService(CLIPBOARD_SERVICE);
clipboard.setText(fen);
cb.setSelection(-1);
setSelection(-1);
break;
}
case 7: { // Paste position
@@ -321,7 +358,7 @@ public class EditBoard extends Activity {
cb.setPosition(e.pos);
Toast.makeText(getApplicationContext(), getParseErrString(e), Toast.LENGTH_SHORT).show();
}
cb.setSelection(-1);
setSelection(-1);
checkValid();
}
break;

View File

@@ -19,7 +19,7 @@
package org.petero.droidfish.book;
/** Settings controlling opening book usage */
public class BookOptions {
public final class BookOptions {
public String filename = "";
public int maxLength = 1000000;

View File

@@ -23,6 +23,7 @@ import java.io.FileReader;
import java.io.IOException;
import java.util.ArrayList;
import org.petero.droidfish.EGTBOptions;
import org.petero.droidfish.book.BookOptions;
import org.petero.droidfish.book.DroidBook;
import org.petero.droidfish.gamelogic.Move;
@@ -33,6 +34,8 @@ import org.petero.droidfish.gamelogic.SearchListener;
import org.petero.droidfish.gamelogic.TextIO;
import org.petero.droidfish.gamelogic.UndoInfo;
import org.petero.droidfish.gamelogic.SearchListener.PvInfo;
import org.petero.droidfish.gtb.Probe;
import org.petero.droidfish.gtb.Probe.ProbeResult;
import android.content.Context;
@@ -45,6 +48,8 @@ public class DroidComputerPlayer {
private final Context context;
private final SearchListener listener;
private final DroidBook book;
private EGTBOptions egtbOptions;
private final Probe gtbProbe;
/** Set when "ucinewgame" needs to be sent. */
private boolean newGame = false;
@@ -118,6 +123,7 @@ public class DroidComputerPlayer {
long[] posHashList; // For draw decision after completed search
int posHashListSize; // For draw decision after completed search
ArrayList<Move> searchMoves; // Moves to search, or null to search all moves
/**
* Create a request to start an engine.
@@ -238,6 +244,8 @@ public class DroidComputerPlayer {
this.context = context;
this.listener = listener;
book = DroidBook.getInstance();
egtbOptions = new EGTBOptions();
gtbProbe = Probe.getInstance();
engineState = new EngineState();
searchRequest = null;
}
@@ -264,12 +272,21 @@ public class DroidComputerPlayer {
public final void setBookOptions(BookOptions options) {
book.setOptions(options);
}
public final void setEgtbOptions(EGTBOptions options) {
egtbOptions = options;
gtbProbe.setPath(options.gtbPath);
}
/** Return all book moves, both as a formatted string and as a list of moves. */
public final Pair<String, ArrayList<Move>> getBookHints(Position pos) {
return book.getAllBookMoves(pos);
}
public final ProbeResult egtbProbe(Position pos) {
return gtbProbe.probeHard(pos);
}
/** Get engine reported name. */
public final synchronized String getEngineName() {
return engineName;
@@ -319,6 +336,22 @@ public class DroidComputerPlayer {
handleQueue();
}
/** Decide what moves to search. Filters out non-optimal moves if tablebases are used. */
private final ArrayList<Move> movesToSearch(SearchRequest sr) {
ArrayList<Move> moves = null;
if (egtbOptions.rootProbe) {
moves = gtbProbe.findOptimal(sr.currPos);
}
if (moves != null) {
sr.searchMoves = moves;
} else {
moves = new MoveGen().pseudoLegalMoves(sr.currPos);
moves = MoveGen.removeIllegal(sr.currPos, moves);
sr.searchMoves = null;
}
return moves;
}
/**
* Start a search. Search result is returned to the search listener object.
* The result can be a valid move string, in which case the move is played
@@ -352,9 +385,8 @@ public class DroidComputerPlayer {
}
}
// If only one legal move, play it without searching
ArrayList<Move> moves = new MoveGen().pseudoLegalMoves(sr.currPos);
moves = MoveGen.removeIllegal(sr.currPos, moves);
// If only one move to search, play it without searching
ArrayList<Move> moves = movesToSearch(sr);
if (moves.size() == 0) {
listener.notifySearchResult(sr.searchId, "", null); // User set up a position where computer has no valid moves.
return;
@@ -381,8 +413,7 @@ public class DroidComputerPlayer {
stopSearch();
// If no legal moves, there is nothing to analyze
ArrayList<Move> moves = new MoveGen().pseudoLegalMoves(sr.currPos);
moves = MoveGen.removeIllegal(sr.currPos, moves);
ArrayList<Move> moves = movesToSearch(sr);
if (moves.size() == 0)
return;
@@ -485,7 +516,7 @@ public class DroidComputerPlayer {
engineState.searchId = searchRequest.searchId;
// Reduce remaining time there was an engine delay
// Reduce remaining time if there was an engine delay
if (isSearch) {
long now = System.currentTimeMillis();
int delay = (int)(now - searchRequest.startTime);
@@ -530,6 +561,13 @@ public class DroidComputerPlayer {
goStr.append(String.format(" movestogo %d", sr.movesToGo));
if (sr.ponderMove != null)
goStr.append(" ponder");
if (sr.searchMoves != null) {
goStr.append(" searchmoves");
for (Move m : sr.searchMoves) {
goStr.append(' ');
goStr.append(TextIO.moveToUCIString(m));
}
}
uciEngine.writeLineToEngine(goStr.toString());
engineState.setState((sr.ponderMove == null) ? MainState.SEARCH : MainState.PONDER);
} else { // Analyze
@@ -547,7 +585,16 @@ public class DroidComputerPlayer {
uciEngine.writeLineToEngine(posStr.toString());
uciEngine.setOption("UCI_AnalyseMode", true);
uciEngine.setOption("Threads", sr.engineThreads > 0 ? sr.engineThreads : numCPUs);
uciEngine.writeLineToEngine("go infinite");
StringBuilder goStr = new StringBuilder(96);
goStr.append("go infinite");
if (sr.searchMoves != null) {
goStr.append(" searchmoves");
for (Move m : sr.searchMoves) {
goStr.append(' ');
goStr.append(TextIO.moveToUCIString(m));
}
}
uciEngine.writeLineToEngine(goStr.toString());
engineState.setState(MainState.ANALYZE);
}
}
@@ -624,7 +671,7 @@ public class DroidComputerPlayer {
switch (engineState.state) {
case READ_OPTIONS: {
if (readUCIOption(uci, s)) {
uci.initOptions();
uci.initOptions(egtbOptions);
uci.writeLineToEngine("ucinewgame");
uci.writeLineToEngine("isready");
engineState.setState(MainState.WAIT_READY);

View File

@@ -27,6 +27,7 @@ import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.channels.FileChannel;
import org.petero.droidfish.EGTBOptions;
import org.petero.droidfish.R;
import android.content.Context;
@@ -160,9 +161,13 @@ public class ExternalEngine extends UCIEngineBase {
/** @inheritDoc */
@Override
public void initOptions() {
super.initOptions();
public void initOptions(EGTBOptions egtbOptions) {
super.initOptions(egtbOptions);
setOption("Hash", 16);
if (egtbOptions.engineProbe) {
setOption("GaviotaTbPath", egtbOptions.gtbPath);
setOption("GaviotaTbCache", 8);
}
}
/** @inheritDoc */

View File

@@ -18,6 +18,8 @@
package org.petero.droidfish.engine;
import org.petero.droidfish.EGTBOptions;
public interface UCIEngine {
/** For reporting engine error messages. */
@@ -30,7 +32,7 @@ public interface UCIEngine {
public void initialize();
/** Initialize default options. */
public void initOptions();
public void initOptions(EGTBOptions egtbOptions);
/** Shut down engine. */
public void shutDown();

View File

@@ -21,6 +21,7 @@ package org.petero.droidfish.engine;
import java.util.HashMap;
import java.util.HashSet;
import org.petero.droidfish.EGTBOptions;
import org.petero.droidfish.engine.cuckoochess.CuckooChessEngine;
import android.content.Context;
@@ -61,7 +62,7 @@ public abstract class UCIEngineBase implements UCIEngine {
}
@Override
public void initOptions() {
public void initOptions(EGTBOptions egtbOptions) {
isUCI = true;
}

View File

@@ -25,6 +25,7 @@ import chess.Position;
import chess.TextIO;
import java.util.ArrayList;
import org.petero.droidfish.EGTBOptions;
import org.petero.droidfish.engine.LocalPipe;
import org.petero.droidfish.engine.UCIEngineBase;
@@ -73,8 +74,8 @@ public class CuckooChessEngine extends UCIEngineBase {
/** @inheritDoc */
@Override
public final void initOptions() {
super.initOptions();
public final void initOptions(EGTBOptions egtbOptions) {
super.initOptions(egtbOptions);
}
/** @inheritDoc */

View File

@@ -22,6 +22,7 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import org.petero.droidfish.EGTBOptions;
import org.petero.droidfish.GUIInterface;
import org.petero.droidfish.GameMode;
import org.petero.droidfish.PGNOptions;
@@ -40,6 +41,7 @@ public class DroidChessController {
private DroidComputerPlayer computerPlayer = null;
private PgnToken.PgnTokenReceiver gameTextListener = null;
private BookOptions bookOptions = new BookOptions();
private EGTBOptions egtbOptions = new EGTBOptions();
private Game game = null;
private Move ponderMove = null;
private GUIInterface gui;
@@ -81,6 +83,7 @@ public class DroidChessController {
if (computerPlayer == null) {
computerPlayer = new DroidComputerPlayer(gui.getContext(), listener);
computerPlayer.setBookOptions(bookOptions);
computerPlayer.setEgtbOptions(egtbOptions);
}
computerPlayer.queueStartEngine(searchId, engine);
searchId++;
@@ -139,6 +142,14 @@ public class DroidChessController {
}
}
public final synchronized void setEgtbOptions(EGTBOptions options) {
if (!egtbOptions.equals(options)) {
egtbOptions = options;
if (computerPlayer != null)
computerPlayer.setEgtbOptions(egtbOptions);
}
}
/** Set engine and engine strength. Restart computer thinking if appropriate.
* @param engine Name of engine.
* @param strength Engine strength, 0 - 1000. */
@@ -560,7 +571,7 @@ public class DroidChessController {
private boolean whiteMove = true;
private String bookInfo = "";
private List<Move> bookMoves = null;
private ArrayList<Move> bookMoves = null;
private Move ponderMove = null;
private ArrayList<PvInfo> pvInfoV = new ArrayList<PvInfo>();
@@ -676,7 +687,7 @@ public class DroidChessController {
}
@Override
public void notifyBookInfo(int id, String bookInfo, List<Move> moveList) {
public void notifyBookInfo(int id, String bookInfo, ArrayList<Move> moveList) {
this.bookInfo = bookInfo;
bookMoves = moveList;
setSearchInfo(id);
@@ -944,7 +955,7 @@ public class DroidChessController {
}
private final synchronized void setThinkingInfo(int id, ArrayList<ArrayList<Move>> pvMoves, String pvStr,
String statStr, String bookInfo, List<Move> bookMoves) {
String statStr, String bookInfo, ArrayList<Move> bookMoves) {
if (id == searchId)
gui.setThinkingInfo(pvStr, statStr, bookInfo, pvMoves, bookMoves);
}

View File

@@ -674,10 +674,10 @@ public class GameTree {
}
/** List of possible continuation moves. */
public final List<Move> variations() {
public final ArrayList<Move> variations() {
if (currentNode.verifyChildren(currentPos))
updateListener();
List<Move> ret = new ArrayList<Move>();
ArrayList<Move> ret = new ArrayList<Move>();
for (Node child : currentNode.children)
ret.add(child.move);
return ret;

View File

@@ -19,7 +19,6 @@
package org.petero.droidfish.gamelogic;
import java.util.ArrayList;
import java.util.List;
/**
@@ -83,7 +82,7 @@ public interface SearchListener {
public void notifyStats(int id, int nodes, int nps, int time);
/** Report opening book information. */
public void notifyBookInfo(int id, String bookInfo, List<Move> moveList);
public void notifyBookInfo(int id, String bookInfo, ArrayList<Move> moveList);
/** Report move (or command, such as "resign") played by the engine. */
public void notifySearchResult(int id, String cmd, Move ponder);

View File

@@ -0,0 +1,83 @@
/*
GtbProbe - Java interface to Gaviota endgame tablebases.
Copyright (C) 2011-2012 Peter Österlund, peterosterlund2@gmail.com
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.petero.droidfish.gtb;
/** Interface to native gtb probing code. */
class GtbProbe {
static {
System.loadLibrary("gtb");
}
private String currTbPath = "";
GtbProbe() {
}
final synchronized void setPath(String tbPath) {
if (!currTbPath.equals(tbPath)) {
currTbPath = tbPath;
init(tbPath);
}
}
final static int NOPIECE = 0;
final static int PAWN = 1;
final static int KNIGHT = 2;
final static int BISHOP = 3;
final static int ROOK = 4;
final static int QUEEN = 5;
final static int KING = 6;
final static int NOSQUARE = 64;
// Castle masks
final static int H1_CASTLE = 8;
final static int A1_CASTLE = 4;
final static int H8_CASTLE = 2;
final static int A8_CASTLE = 1;
// tbinfo values
final static int DRAW = 0;
final static int WMATE = 1;
final static int BMATE = 2;
final static int FORBID = 3;
final static int UNKNOWN = 7;
/**
* Probe table bases.
* @param wtm True if white to move.
* @param epSq En passant square, or NOSQUARE.
* @param castleMask Castle mask.
* @param whiteSquares Array of squares occupied by white pieces, terminated with NOSQUARE.
* @param blackSquares Array of squares occupied by black pieces, terminated with NOSQUARE.
* @param whitePieces Array of white pieces, terminated with NOPIECE.
* @param blackPieces Array of black pieces, terminated with NOPIECE.
* @param result Two element array. Set to [tbinfo, plies].
* @return True if success.
*/
public final native boolean probeHard(boolean wtm, int epSq,
int castleMask,
int[] whiteSquares,
int[] blackSquares,
byte[] whitePieces,
byte[] blackPieces,
int[] result);
private final native static boolean init(String tbPath);
}

View File

@@ -0,0 +1,278 @@
/*
GtbCuckoo - Interface to Gaviota endgame tablebases.
Copyright (C) 2011-2012 Peter Österlund, peterosterlund2@gmail.com
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.petero.droidfish.gtb;
import java.util.ArrayList;
import org.petero.droidfish.gamelogic.Move;
import org.petero.droidfish.gamelogic.MoveGen;
import org.petero.droidfish.gamelogic.Pair;
import org.petero.droidfish.gamelogic.Piece;
import org.petero.droidfish.gamelogic.Position;
import org.petero.droidfish.gamelogic.UndoInfo;
/** Interface between Position class and GTB probing code. */
public class Probe {
private final GtbProbe gtb;
private final int whiteSquares[];
private final int blackSquares[];
private final byte whitePieces[];
private final byte blackPieces[];
private static final Probe INSTANCE = new Probe();
/** Get singleton instance. */
public static Probe getInstance() {
return INSTANCE;
}
/** Constructor. */
private Probe() {
gtb = new GtbProbe();
whiteSquares = new int[65];
blackSquares = new int[65];
whitePieces = new byte[65];
blackPieces = new byte[65];
}
public void setPath(String tbPath) {
gtb.setPath(tbPath);
}
public static final class ProbeResult {
public final static int DRAW = 0;
public final static int WMATE = 1;
public final static int BMATE = 2;
public final static int UNKNOWN = 3;
public int result;
public int movesToMate; // Full moves to mate, or 0 if DRAW or UNKNOWN.
}
/**
* Probe table bases.
* @param pos The position to probe.
* @param result Two element array. Set to [tbinfo, plies].
* @return True if success.
*/
public final ProbeResult probeHard(Position pos) {
int castleMask = 0;
if (pos.a1Castle()) castleMask |= GtbProbe.A1_CASTLE;
if (pos.h1Castle()) castleMask |= GtbProbe.H1_CASTLE;
if (pos.a8Castle()) castleMask |= GtbProbe.A8_CASTLE;
if (pos.h8Castle()) castleMask |= GtbProbe.H8_CASTLE;
int nWhite = 0;
int nBlack = 0;
for (int sq = 0; sq < 64; sq++) {
int p = pos.getPiece(sq);
switch (p) {
case Piece.WKING:
whiteSquares[nWhite] = sq;
whitePieces[nWhite++] = GtbProbe.KING;
break;
case Piece.WQUEEN:
whiteSquares[nWhite] = sq;
whitePieces[nWhite++] = GtbProbe.QUEEN;
break;
case Piece.WROOK:
whiteSquares[nWhite] = sq;
whitePieces[nWhite++] = GtbProbe.ROOK;
break;
case Piece.WBISHOP:
whiteSquares[nWhite] = sq;
whitePieces[nWhite++] = GtbProbe.BISHOP;
break;
case Piece.WKNIGHT:
whiteSquares[nWhite] = sq;
whitePieces[nWhite++] = GtbProbe.KNIGHT;
break;
case Piece.WPAWN:
whiteSquares[nWhite] = sq;
whitePieces[nWhite++] = GtbProbe.PAWN;
break;
case Piece.BKING:
blackSquares[nBlack] = sq;
blackPieces[nBlack++] = GtbProbe.KING;
break;
case Piece.BQUEEN:
blackSquares[nBlack] = sq;
blackPieces[nBlack++] = GtbProbe.QUEEN;
break;
case Piece.BROOK:
blackSquares[nBlack] = sq;
blackPieces[nBlack++] = GtbProbe.ROOK;
break;
case Piece.BBISHOP:
blackSquares[nBlack] = sq;
blackPieces[nBlack++] = GtbProbe.BISHOP;
break;
case Piece.BKNIGHT:
blackSquares[nBlack] = sq;
blackPieces[nBlack++] = GtbProbe.KNIGHT;
break;
case Piece.BPAWN:
blackSquares[nBlack] = sq;
blackPieces[nBlack++] = GtbProbe.PAWN;
break;
}
}
whiteSquares[nWhite] = GtbProbe.NOSQUARE;
blackSquares[nBlack] = GtbProbe.NOSQUARE;
whitePieces[nWhite] = GtbProbe.NOPIECE;
blackPieces[nBlack] = GtbProbe.NOPIECE;
int epSquare = pos.getEpSquare();
if (epSquare == -1)
epSquare = GtbProbe.NOSQUARE;
int[] result = new int[2];
boolean res = gtb.probeHard(pos.whiteMove, epSquare, castleMask,
whiteSquares, blackSquares, whitePieces, blackPieces,
result);
ProbeResult ret = new ProbeResult();
if (res) {
switch (result[0]) {
case GtbProbe.DRAW:
ret.result = ProbeResult.DRAW;
ret.movesToMate = 0;
break;
case GtbProbe.WMATE:
ret.result = ProbeResult.WMATE;
ret.movesToMate = (result[1] + 1) / 2;
break;
case GtbProbe.BMATE:
ret.result = ProbeResult.BMATE;
ret.movesToMate = (result[1] + 1) / 2;
break;
default:
ret.result = ProbeResult.UNKNOWN;
ret.movesToMate = 0;
break;
}
} else {
ret.result = ProbeResult.UNKNOWN;
ret.movesToMate = 0;
}
return ret;
}
/** Return a list of all legal moves that are not known to be non-optimal.
* Returns null if no legal move could be excluded. */
public final ArrayList<Move> findOptimal(Position pos) {
ArrayList<Move> moveList = new MoveGen().pseudoLegalMoves(pos);
moveList = MoveGen.removeIllegal(pos, moveList);
ArrayList<Move> optimalMoves = new ArrayList<Move>();
ArrayList<Move> unknownMoves = new ArrayList<Move>();
final int MATE0 = 100000;
int bestScore = -1000000;
UndoInfo ui = new UndoInfo();
for (Move m : moveList) {
pos.makeMove(m, ui);
ProbeResult res = probeHard(pos);
pos.unMakeMove(m, ui);
if (res.result == ProbeResult.UNKNOWN) {
unknownMoves.add(m);
} else {
int wScore;
if (res.result == ProbeResult.WMATE)
wScore = MATE0 - res.movesToMate;
else if (res.result == ProbeResult.BMATE)
wScore = -(MATE0 - res.movesToMate);
else
wScore = 0;
int score = pos.whiteMove ? wScore : -wScore;
if (score > bestScore) {
optimalMoves.clear();
optimalMoves.add(m);
bestScore = score;
} else if (score == bestScore) {
optimalMoves.add(m);
} else {
// Ignore move
}
}
}
for (Move m : unknownMoves)
optimalMoves.add(m);
return (optimalMoves.size() < moveList.size()) ? optimalMoves : null;
}
/** For a given position and from square, return EGTB information
* about all legal destination squares. Return null if no information available. */
public final ArrayList<Pair<Integer,Integer>> movePieceProbe(Position pos, int fromSq) {
int p = pos.getPiece(fromSq);
if ((p == Piece.EMPTY) || (pos.whiteMove != Piece.isWhite(p)))
return null;
ArrayList<Pair<Integer,Integer>> ret = new ArrayList<Pair<Integer,Integer>>();
ArrayList<Move> moveList = new MoveGen().pseudoLegalMoves(pos);
moveList = MoveGen.removeIllegal(pos, moveList);
UndoInfo ui = new UndoInfo();
for (Move m : moveList) {
if (m.from != fromSq)
continue;
pos.makeMove(m, ui);
ProbeResult res = probeHard(pos);
pos.unMakeMove(m, ui);
if (res.result == ProbeResult.UNKNOWN)
continue;
int score = 0;
if (res.result == ProbeResult.WMATE) {
score = pos.whiteMove ? res.movesToMate + 1 : -res.movesToMate;
} else if (res.result == ProbeResult.BMATE) {
score = pos.whiteMove ? -res.movesToMate : res.movesToMate + 1;
}
ret.add(new Pair<Integer,Integer>(m.to, score));
}
return ret;
}
/** For a given position and from square, return EGTB information
* about all legal alternative positions for the piece on from square.
* Return null if no information is available. */
public final ArrayList<Pair<Integer, Integer>> relocatePieceProbe(Position pos, int fromSq) {
int p = pos.getPiece(fromSq);
if (p == Piece.EMPTY)
return null;
boolean isPawn = (Piece.makeWhite(p) == Piece.WPAWN);
ArrayList<Pair<Integer,Integer>> ret = new ArrayList<Pair<Integer,Integer>>();
for (int sq = 0; sq < 64; sq++) {
if ((sq != fromSq) && (pos.getPiece(sq) != Piece.EMPTY))
continue;
if (isPawn && ((sq < 8) || (sq >= 56)))
continue;
pos.setPiece(fromSq, Piece.EMPTY);
pos.setPiece(sq, p);
ProbeResult res = probeHard(pos);
pos.setPiece(sq, Piece.EMPTY);
pos.setPiece(fromSq, p);
if (res.result == ProbeResult.UNKNOWN)
continue;
int score = 0;
if (res.result == ProbeResult.WMATE) {
score = res.movesToMate;
} else if (res.result == ProbeResult.BMATE) {
score = -res.movesToMate;
}
ret.add(new Pair<Integer,Integer>(sq, score));
}
return ret;
}
}