mirror of
https://github.com/peterosterlund2/droidfish.git
synced 2024-11-27 06:10:28 +01:00
Add support for ABK opening books
Move probabilities are not exactly the same as in the Arena Chess GUI because it is unknown how the computation in Arena works.
This commit is contained in:
parent
03375fc479
commit
06375cbf1b
|
@ -23,7 +23,7 @@ import java.util.ArrayList;
|
||||||
|
|
||||||
import junit.framework.TestCase;
|
import junit.framework.TestCase;
|
||||||
|
|
||||||
import org.petero.droidfish.book.DroidBook;
|
import org.petero.droidfish.book.IOpeningBook.BookPosInput;
|
||||||
import org.petero.droidfish.gamelogic.ChessParseError;
|
import org.petero.droidfish.gamelogic.ChessParseError;
|
||||||
import org.petero.droidfish.gamelogic.Move;
|
import org.petero.droidfish.gamelogic.Move;
|
||||||
import org.petero.droidfish.gamelogic.MoveGen;
|
import org.petero.droidfish.gamelogic.MoveGen;
|
||||||
|
@ -38,19 +38,21 @@ public class BookTest extends TestCase {
|
||||||
public void testGetBookMove() throws ChessParseError {
|
public void testGetBookMove() throws ChessParseError {
|
||||||
Position pos = TextIO.readFEN(TextIO.startPosFEN);
|
Position pos = TextIO.readFEN(TextIO.startPosFEN);
|
||||||
DroidBook book = DroidBook.getInstance();
|
DroidBook book = DroidBook.getInstance();
|
||||||
Move move = book.getBookMove(pos);
|
BookPosInput posInput = new BookPosInput(pos, null, null);
|
||||||
|
Move move = book.getBookMove(posInput);
|
||||||
checkValid(pos, move);
|
checkValid(pos, move);
|
||||||
|
|
||||||
// Test "out of book" condition
|
// Test "out of book" condition
|
||||||
pos.setCastleMask(0);
|
pos.setCastleMask(0);
|
||||||
move = book.getBookMove(pos);
|
move = book.getBookMove(posInput);
|
||||||
assertEquals(null, move);
|
assertEquals(null, move);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testGetAllBookMoves() throws ChessParseError {
|
public void testGetAllBookMoves() throws ChessParseError {
|
||||||
Position pos = TextIO.readFEN(TextIO.startPosFEN);
|
Position pos = TextIO.readFEN(TextIO.startPosFEN);
|
||||||
DroidBook book = DroidBook.getInstance();
|
DroidBook book = DroidBook.getInstance();
|
||||||
ArrayList<Move> moves = book.getAllBookMoves(pos, false).second;
|
BookPosInput posInput = new BookPosInput(pos, null, null);
|
||||||
|
ArrayList<Move> moves = book.getAllBookMoves(posInput, false).second;
|
||||||
assertTrue(moves.size() > 1);
|
assertTrue(moves.size() > 1);
|
||||||
for (Move m : moves) {
|
for (Move m : moves) {
|
||||||
checkValid(pos, m);
|
checkValid(pos, m);
|
||||||
|
|
|
@ -2500,7 +2500,7 @@ public class DroidFish extends Activity
|
||||||
if (dotIdx < 0)
|
if (dotIdx < 0)
|
||||||
return false;
|
return false;
|
||||||
String ext = filename.substring(dotIdx+1);
|
String ext = filename.substring(dotIdx+1);
|
||||||
return ("ctg".equals(ext) || "bin".equals(ext));
|
return ("ctg".equals(ext) || "bin".equals(ext) || "abk".equals(ext));
|
||||||
});
|
});
|
||||||
final int numFiles = fileNames.length;
|
final int numFiles = fileNames.length;
|
||||||
final String[] items = new String[numFiles + 3];
|
final String[] items = new String[numFiles + 3];
|
||||||
|
|
|
@ -0,0 +1,294 @@
|
||||||
|
/*
|
||||||
|
DroidFish - An Android chess program.
|
||||||
|
Copyright (C) 2020 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.book;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.RandomAccessFile;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
|
||||||
|
import org.petero.droidfish.book.DroidBook.BookEntry;
|
||||||
|
import org.petero.droidfish.gamelogic.ChessParseError;
|
||||||
|
import org.petero.droidfish.gamelogic.Move;
|
||||||
|
import org.petero.droidfish.gamelogic.Piece;
|
||||||
|
import org.petero.droidfish.gamelogic.Position;
|
||||||
|
import org.petero.droidfish.gamelogic.TextIO;
|
||||||
|
|
||||||
|
/** Handle Arena Chess GUI opening books. */
|
||||||
|
class AbkBook implements IOpeningBook {
|
||||||
|
private File abkFile; // The ".abk" file
|
||||||
|
private Position startPos;
|
||||||
|
|
||||||
|
/** Constructor. */
|
||||||
|
public AbkBook() {
|
||||||
|
try {
|
||||||
|
startPos = TextIO.readFEN(TextIO.startPosFEN);
|
||||||
|
} catch (ChessParseError ex) {
|
||||||
|
throw new RuntimeException(ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static boolean canHandle(BookOptions options) {
|
||||||
|
String filename = options.filename;
|
||||||
|
return filename.endsWith(".abk");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean enabled() {
|
||||||
|
return abkFile.canRead();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setOptions(BookOptions options) {
|
||||||
|
abkFile = new File(options.filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class MoveData {
|
||||||
|
Move move;
|
||||||
|
double weightPrio;
|
||||||
|
double weightNGames;
|
||||||
|
double weightScore;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ArrayList<BookEntry> getBookEntries(BookPosInput posInput) {
|
||||||
|
if (!startPos.equals(posInput.getPrevPos()))
|
||||||
|
return null;
|
||||||
|
|
||||||
|
try (RandomAccessFile abkF = new RandomAccessFile(abkFile, "r")) {
|
||||||
|
ArrayList<Move> gameMoves = posInput.getMoves();
|
||||||
|
|
||||||
|
BookSettings bs = new BookSettings(abkF);
|
||||||
|
if (gameMoves.size() >= bs.maxPly)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
AbkBookEntry ent = new AbkBookEntry();
|
||||||
|
int entNo = 900;
|
||||||
|
for (Move m : gameMoves) {
|
||||||
|
int iter = 0;
|
||||||
|
while (true) {
|
||||||
|
if (entNo < 0)
|
||||||
|
return null;
|
||||||
|
ent.read(abkF, entNo);
|
||||||
|
if (ent.getMove().equals(m) && ent.isValid()) {
|
||||||
|
entNo = ent.nextMove;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
entNo = ent.nextSibling;
|
||||||
|
iter++;
|
||||||
|
if (iter > 255)
|
||||||
|
return null; // Corrupt book
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (entNo < 0)
|
||||||
|
return null;
|
||||||
|
|
||||||
|
boolean wtm = (gameMoves.size() % 2) == 0;
|
||||||
|
ArrayList<MoveData> moves = new ArrayList<>();
|
||||||
|
while (entNo >= 0) {
|
||||||
|
ent.read(abkF, entNo);
|
||||||
|
MoveData md = new MoveData();
|
||||||
|
md.move = ent.getMove();
|
||||||
|
|
||||||
|
int nWon = wtm ? ent.nWon : ent.nLost;
|
||||||
|
int nLost = wtm ? ent.nLost : ent.nWon;
|
||||||
|
int nDraw = ent.nGames - nWon - nLost;
|
||||||
|
md.weightPrio = scaleWeight(ent.priority, bs.prioImportance);
|
||||||
|
md.weightNGames = scaleWeight(ent.nGames, bs.nGamesImportance);
|
||||||
|
double score = (nWon + nDraw * 0.5) / ent.nGames;
|
||||||
|
md.weightScore = scaleWeight(score, bs.scoreImportance);
|
||||||
|
|
||||||
|
if (ent.isValid() &&
|
||||||
|
(!bs.skipPrio0Moves || ent.priority > 0) &&
|
||||||
|
(ent.nGames >= bs.minGames) &&
|
||||||
|
(nWon >= bs.minWonGames) &&
|
||||||
|
(score * 100 >= (wtm ? bs.minWinPercentWhite : bs.minWinPercentBlack))) {
|
||||||
|
moves.add(md);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (moves.size() > 255)
|
||||||
|
return null; // Corrupt book
|
||||||
|
entNo = ent.nextSibling;
|
||||||
|
}
|
||||||
|
|
||||||
|
double sumWeightPrio = 0;
|
||||||
|
double sumWeightNGames = 0;
|
||||||
|
double sumWeightScore = 0;
|
||||||
|
for (MoveData md : moves) {
|
||||||
|
sumWeightPrio += md.weightPrio;
|
||||||
|
sumWeightNGames += md.weightNGames;
|
||||||
|
sumWeightScore += md.weightScore;
|
||||||
|
}
|
||||||
|
|
||||||
|
ArrayList<BookEntry> ret = new ArrayList<>();
|
||||||
|
boolean hasNonZeroWeight = false;
|
||||||
|
for (MoveData md : moves) {
|
||||||
|
BookEntry be = new BookEntry(md.move);
|
||||||
|
double wP = sumWeightPrio > 0 ? md.weightPrio / sumWeightPrio : 0.0;
|
||||||
|
double wN = sumWeightNGames > 0 ? md.weightNGames / sumWeightNGames : 0.0;
|
||||||
|
double wS = sumWeightScore > 0 ? md.weightScore / sumWeightScore : 0.0;
|
||||||
|
double a = 0.624;
|
||||||
|
double w = wP * Math.exp(a * bs.prioImportance) +
|
||||||
|
wN * Math.exp(a * bs.nGamesImportance) +
|
||||||
|
wS * Math.exp(a * bs.scoreImportance) * 1.4;
|
||||||
|
hasNonZeroWeight |= w > 0;
|
||||||
|
be.weight = (float)w;
|
||||||
|
ret.add(be);
|
||||||
|
}
|
||||||
|
if (!hasNonZeroWeight)
|
||||||
|
for (BookEntry be : ret)
|
||||||
|
be.weight = 1;
|
||||||
|
return ret;
|
||||||
|
} catch (IOException e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class AbkBookEntry {
|
||||||
|
private byte[] data = new byte[28];
|
||||||
|
|
||||||
|
private byte from; // From square, 0 = a1, 7 = h1, 8 = a2, 63 = h8
|
||||||
|
private byte to; // To square
|
||||||
|
private byte promotion; // 0 = none, +-1 = rook, +-2 = knight, +-3 = bishop, +-4 = queen
|
||||||
|
byte priority; // 0 = bad, >0 better, 9 best
|
||||||
|
int nGames; // Number of times games in which move was played
|
||||||
|
int nWon; // Number of won games for white
|
||||||
|
int nLost; // Number of lost games for white
|
||||||
|
int flags; // Value is 0x01000000 if move has been deleted
|
||||||
|
int nextMove; // First following move (by opposite color)
|
||||||
|
int nextSibling; // Next alternative move (by same color)
|
||||||
|
|
||||||
|
AbkBookEntry() {
|
||||||
|
}
|
||||||
|
|
||||||
|
void read(RandomAccessFile f, long entNo) throws IOException {
|
||||||
|
f.seek(entNo * 28);
|
||||||
|
f.readFully(data);
|
||||||
|
|
||||||
|
from = data[0];
|
||||||
|
to = data[1];
|
||||||
|
promotion = data[2];
|
||||||
|
priority = data[3];
|
||||||
|
nGames = extractInt(4);
|
||||||
|
nWon = extractInt(8);
|
||||||
|
nLost = extractInt(12);
|
||||||
|
flags = extractInt(16);
|
||||||
|
nextMove = extractInt(20);
|
||||||
|
nextSibling = extractInt(24);
|
||||||
|
}
|
||||||
|
|
||||||
|
Move getMove() {
|
||||||
|
int prom;
|
||||||
|
switch (promotion) {
|
||||||
|
case 0: prom = Piece.EMPTY; break;
|
||||||
|
case -1: prom = Piece.WROOK; break;
|
||||||
|
case -2: prom = Piece.WKNIGHT; break;
|
||||||
|
case -3: prom = Piece.WBISHOP; break;
|
||||||
|
case -4: prom = Piece.WQUEEN; break;
|
||||||
|
case 1: prom = Piece.BROOK; break;
|
||||||
|
case 2: prom = Piece.BKNIGHT; break;
|
||||||
|
case 3: prom = Piece.BBISHOP; break;
|
||||||
|
case 4: prom = Piece.BQUEEN; break;
|
||||||
|
default: prom = -1; break;
|
||||||
|
}
|
||||||
|
return new Move(from, to, prom);
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean isValid() {
|
||||||
|
return flags != 0x01000000;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int extractInt(int offs) {
|
||||||
|
return AbkBook.extractInt(data, offs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Convert 4 bytes starting at "offs" in buf[] to an integer. */
|
||||||
|
private static int extractInt(byte[] buf, int offs) {
|
||||||
|
int ret = 0;
|
||||||
|
for (int i = 3; i >= 0; i--) {
|
||||||
|
int b = buf[offs + i];
|
||||||
|
if (b < 0) b += 256;
|
||||||
|
ret = (ret << 8) + b;
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class BookSettings {
|
||||||
|
private byte[] buf = new byte[256];
|
||||||
|
|
||||||
|
int minGames;
|
||||||
|
int minWonGames;
|
||||||
|
int minWinPercentWhite; // 0 - 100
|
||||||
|
int minWinPercentBlack; // 0 - 100
|
||||||
|
|
||||||
|
int prioImportance; // 0 - 15
|
||||||
|
int nGamesImportance; // 0 - 15
|
||||||
|
int scoreImportance; // 0 - 15
|
||||||
|
|
||||||
|
int maxPly;
|
||||||
|
|
||||||
|
boolean skipPrio0Moves = false; // Not stored in abk file
|
||||||
|
|
||||||
|
public BookSettings(RandomAccessFile abkF) throws IOException {
|
||||||
|
abkF.seek(0);
|
||||||
|
abkF.readFully(buf);
|
||||||
|
|
||||||
|
minGames = getInt(0xde, Integer.MAX_VALUE);
|
||||||
|
minWonGames = getInt(0xe2, Integer.MAX_VALUE);
|
||||||
|
minWinPercentWhite = getInt(0xe6, 100);
|
||||||
|
minWinPercentBlack = getInt(0xea, 100);
|
||||||
|
|
||||||
|
prioImportance = getInt(0xee, 15);
|
||||||
|
nGamesImportance = getInt(0xf2, 15);
|
||||||
|
scoreImportance = getInt(0xf6, 15);
|
||||||
|
|
||||||
|
maxPly = getInt(0xfa, 9999);
|
||||||
|
|
||||||
|
if (prioImportance == 0 && nGamesImportance == 0 && scoreImportance == 0) {
|
||||||
|
minGames = 0;
|
||||||
|
minWonGames = 0;
|
||||||
|
minWinPercentWhite = 0;
|
||||||
|
minWinPercentBlack = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getInt(int offs, int maxVal) {
|
||||||
|
int val = extractInt(buf, offs);
|
||||||
|
return Math.min(Math.max(val, 0), maxVal);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static double scaleWeight(double w, int importance) {
|
||||||
|
double e;
|
||||||
|
switch (importance) {
|
||||||
|
case 0:
|
||||||
|
return 0;
|
||||||
|
case 1:
|
||||||
|
e = 0.66;
|
||||||
|
break;
|
||||||
|
case 2:
|
||||||
|
e = 0.86;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
e = 1 + ((double)importance - 3) / 6;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return Math.pow(w, e);
|
||||||
|
}
|
||||||
|
}
|
|
@ -62,7 +62,8 @@ class CtgBook implements IOpeningBook {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ArrayList<BookEntry> getBookEntries(Position pos) {
|
public ArrayList<BookEntry> getBookEntries(BookPosInput posInput) {
|
||||||
|
Position pos = posInput.getCurrPos();
|
||||||
try (RandomAccessFile ctgF = new RandomAccessFile(ctgFile, "r");
|
try (RandomAccessFile ctgF = new RandomAccessFile(ctgFile, "r");
|
||||||
RandomAccessFile ctbF = new RandomAccessFile(ctbFile, "r");
|
RandomAccessFile ctbF = new RandomAccessFile(ctbFile, "r");
|
||||||
RandomAccessFile ctoF = new RandomAccessFile(ctoFile, "r")) {
|
RandomAccessFile ctoF = new RandomAccessFile(ctoFile, "r")) {
|
||||||
|
|
|
@ -28,6 +28,7 @@ import java.util.List;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
|
||||||
import org.petero.droidfish.Util;
|
import org.petero.droidfish.Util;
|
||||||
|
import org.petero.droidfish.book.IOpeningBook.BookPosInput;
|
||||||
import org.petero.droidfish.gamelogic.Move;
|
import org.petero.droidfish.gamelogic.Move;
|
||||||
import org.petero.droidfish.gamelogic.MoveGen;
|
import org.petero.droidfish.gamelogic.MoveGen;
|
||||||
import org.petero.droidfish.gamelogic.Position;
|
import org.petero.droidfish.gamelogic.Position;
|
||||||
|
@ -64,7 +65,6 @@ public final class DroidBook {
|
||||||
}
|
}
|
||||||
|
|
||||||
private DroidBook() {
|
private DroidBook() {
|
||||||
rndGen.setSeed(System.currentTimeMillis());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Set opening book options. */
|
/** Set opening book options. */
|
||||||
|
@ -74,6 +74,8 @@ public final class DroidBook {
|
||||||
externalBook = new CtgBook();
|
externalBook = new CtgBook();
|
||||||
else if (PolyglotBook.canHandle(options))
|
else if (PolyglotBook.canHandle(options))
|
||||||
externalBook = new PolyglotBook();
|
externalBook = new PolyglotBook();
|
||||||
|
else if (AbkBook.canHandle(options))
|
||||||
|
externalBook = new AbkBook();
|
||||||
else
|
else
|
||||||
externalBook = new NullBook();
|
externalBook = new NullBook();
|
||||||
externalBook.setOptions(options);
|
externalBook.setOptions(options);
|
||||||
|
@ -83,10 +85,11 @@ public final class DroidBook {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Return a random book move for a position, or null if out of book. */
|
/** Return a random book move for a position, or null if out of book. */
|
||||||
public final synchronized Move getBookMove(Position pos) {
|
public final synchronized Move getBookMove(BookPosInput posInput) {
|
||||||
|
Position pos = posInput.getCurrPos();
|
||||||
if ((options != null) && (pos.fullMoveCounter > options.maxLength))
|
if ((options != null) && (pos.fullMoveCounter > options.maxLength))
|
||||||
return null;
|
return null;
|
||||||
List<BookEntry> bookMoves = getBook().getBookEntries(pos);
|
List<BookEntry> bookMoves = getBook().getBookEntries(posInput);
|
||||||
if (bookMoves == null || bookMoves.isEmpty())
|
if (bookMoves == null || bookMoves.isEmpty())
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
|
@ -116,11 +119,12 @@ public final class DroidBook {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Return all book moves, both as a formatted string and as a list of moves. */
|
/** Return all book moves, both as a formatted string and as a list of moves. */
|
||||||
public final synchronized Pair<String,ArrayList<Move>> getAllBookMoves(Position pos,
|
public final synchronized Pair<String,ArrayList<Move>> getAllBookMoves(BookPosInput posInput,
|
||||||
boolean localized) {
|
boolean localized) {
|
||||||
|
Position pos = posInput.getCurrPos();
|
||||||
StringBuilder ret = new StringBuilder();
|
StringBuilder ret = new StringBuilder();
|
||||||
ArrayList<Move> bookMoveList = new ArrayList<>();
|
ArrayList<Move> bookMoveList = new ArrayList<>();
|
||||||
ArrayList<BookEntry> bookMoves = getBook().getBookEntries(pos);
|
ArrayList<BookEntry> bookMoves = getBook().getBookEntries(posInput);
|
||||||
|
|
||||||
// Check legality
|
// Check legality
|
||||||
if (bookMoves != null) {
|
if (bookMoves != null) {
|
||||||
|
|
|
@ -43,7 +43,8 @@ public class EcoBook implements IOpeningBook {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ArrayList<BookEntry> getBookEntries(Position pos) {
|
public ArrayList<BookEntry> getBookEntries(BookPosInput posInput) {
|
||||||
|
Position pos = posInput.getCurrPos();
|
||||||
ArrayList<Move> moves = EcoDb.getInstance().getMoves(pos);
|
ArrayList<Move> moves = EcoDb.getInstance().getMoves(pos);
|
||||||
ArrayList<BookEntry> entries = new ArrayList<>();
|
ArrayList<BookEntry> entries = new ArrayList<>();
|
||||||
for (int i = 0; i < moves.size(); i++) {
|
for (int i = 0; i < moves.size(); i++) {
|
||||||
|
|
|
@ -18,18 +18,61 @@
|
||||||
|
|
||||||
package org.petero.droidfish.book;
|
package org.petero.droidfish.book;
|
||||||
|
|
||||||
|
import android.util.Pair;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
||||||
import org.petero.droidfish.book.DroidBook.BookEntry;
|
import org.petero.droidfish.book.DroidBook.BookEntry;
|
||||||
|
import org.petero.droidfish.gamelogic.Game;
|
||||||
|
import org.petero.droidfish.gamelogic.Move;
|
||||||
import org.petero.droidfish.gamelogic.Position;
|
import org.petero.droidfish.gamelogic.Position;
|
||||||
|
|
||||||
interface IOpeningBook {
|
public interface IOpeningBook {
|
||||||
/** Return true if book is currently enabled. */
|
/** Return true if book is currently enabled. */
|
||||||
boolean enabled();
|
boolean enabled();
|
||||||
|
|
||||||
/** Set book options, including filename. */
|
/** Set book options, including filename. */
|
||||||
void setOptions(BookOptions options);
|
void setOptions(BookOptions options);
|
||||||
|
|
||||||
/** Get all book entries for a position. */
|
/** Information required to query an opening book. */
|
||||||
ArrayList<BookEntry> getBookEntries(Position pos);
|
class BookPosInput {
|
||||||
|
private final Position currPos;
|
||||||
|
|
||||||
|
private Game game;
|
||||||
|
private Position prevPos;
|
||||||
|
private ArrayList<Move> moves;
|
||||||
|
|
||||||
|
public BookPosInput(Position currPos, Position prevPos, ArrayList<Move> moves) {
|
||||||
|
this.currPos = currPos;
|
||||||
|
this.prevPos = prevPos;
|
||||||
|
this.moves = moves;
|
||||||
|
}
|
||||||
|
|
||||||
|
public BookPosInput(Game game) {
|
||||||
|
currPos = game.currPos();
|
||||||
|
this.game = game;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Position getCurrPos() {
|
||||||
|
return currPos;
|
||||||
|
}
|
||||||
|
public Position getPrevPos() {
|
||||||
|
lazyInit();
|
||||||
|
return prevPos;
|
||||||
|
}
|
||||||
|
public ArrayList<Move> getMoves() {
|
||||||
|
lazyInit();
|
||||||
|
return moves;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void lazyInit() {
|
||||||
|
if (prevPos == null) {
|
||||||
|
Pair<Position, ArrayList<Move>> ph = game.getUCIHistory();
|
||||||
|
prevPos = ph.first;
|
||||||
|
moves = ph.second;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/** Get all book entries for a position. */
|
||||||
|
ArrayList<BookEntry> getBookEntries(BookPosInput posInput);
|
||||||
}
|
}
|
||||||
|
|
|
@ -52,7 +52,8 @@ final class InternalBook implements IOpeningBook {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ArrayList<BookEntry> getBookEntries(Position pos) {
|
public ArrayList<BookEntry> getBookEntries(BookPosInput posInput) {
|
||||||
|
Position pos = posInput.getCurrPos();
|
||||||
initInternalBook();
|
initInternalBook();
|
||||||
ArrayList<BookEntry> ents = bookMap.get(pos.zobristHash());
|
ArrayList<BookEntry> ents = bookMap.get(pos.zobristHash());
|
||||||
if (ents == null)
|
if (ents == null)
|
||||||
|
|
|
@ -37,7 +37,7 @@ class NoBook implements IOpeningBook {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ArrayList<BookEntry> getBookEntries(Position pos) {
|
public ArrayList<BookEntry> getBookEntries(BookPosInput posInput) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,7 +35,7 @@ class NullBook implements IOpeningBook {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ArrayList<BookEntry> getBookEntries(Position pos) {
|
public ArrayList<BookEntry> getBookEntries(BookPosInput posInput) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -370,7 +370,8 @@ class PolyglotBook implements IOpeningBook {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final ArrayList<BookEntry> getBookEntries(Position pos) {
|
public final ArrayList<BookEntry> getBookEntries(BookPosInput posInput) {
|
||||||
|
Position pos = posInput.getCurrPos();
|
||||||
try (RandomAccessFile f = new RandomAccessFile(bookFile, "r")) {
|
try (RandomAccessFile f = new RandomAccessFile(bookFile, "r")) {
|
||||||
long numEntries = f.length() / 16;
|
long numEntries = f.length() / 16;
|
||||||
long key = getHashKey(pos);
|
long key = getHashKey(pos);
|
||||||
|
|
|
@ -28,6 +28,7 @@ import java.util.TreeMap;
|
||||||
import org.petero.droidfish.EngineOptions;
|
import org.petero.droidfish.EngineOptions;
|
||||||
import org.petero.droidfish.book.BookOptions;
|
import org.petero.droidfish.book.BookOptions;
|
||||||
import org.petero.droidfish.book.DroidBook;
|
import org.petero.droidfish.book.DroidBook;
|
||||||
|
import org.petero.droidfish.book.IOpeningBook.BookPosInput;
|
||||||
import org.petero.droidfish.gamelogic.Move;
|
import org.petero.droidfish.gamelogic.Move;
|
||||||
import org.petero.droidfish.gamelogic.MoveGen;
|
import org.petero.droidfish.gamelogic.MoveGen;
|
||||||
import org.petero.droidfish.gamelogic.Position;
|
import org.petero.droidfish.gamelogic.Position;
|
||||||
|
@ -95,7 +96,7 @@ public class DroidComputerPlayer {
|
||||||
int searchId; // Unique identifier for this search request
|
int searchId; // Unique identifier for this search request
|
||||||
long startTime; // System time (milliseconds) when search request was created
|
long startTime; // System time (milliseconds) when search request was created
|
||||||
|
|
||||||
Position prevPos; // Position at last irreversible move
|
Position prevPos; // Position at last null move
|
||||||
ArrayList<Move> mList; // Moves after prevPos, including ponderMove
|
ArrayList<Move> mList; // Moves after prevPos, including ponderMove
|
||||||
Position currPos; // currPos = prevPos + mList - ponderMove
|
Position currPos; // currPos = prevPos + mList - ponderMove
|
||||||
boolean drawOffer; // True if other side made draw offer
|
boolean drawOffer; // True if other side made draw offer
|
||||||
|
@ -361,8 +362,9 @@ public class DroidComputerPlayer {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Return all book moves, both as a formatted string and as a list of moves. */
|
/** Return all book moves, both as a formatted string and as a list of moves. */
|
||||||
public final Pair<String, ArrayList<Move>> getBookHints(Position pos, boolean localized) {
|
public final Pair<String, ArrayList<Move>> getBookHints(BookPosInput posInput,
|
||||||
return book.getAllBookMoves(pos, localized);
|
boolean localized) {
|
||||||
|
return book.getAllBookMoves(posInput, localized);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get engine reported name. */
|
/** Get engine reported name. */
|
||||||
|
@ -454,7 +456,8 @@ public class DroidComputerPlayer {
|
||||||
|
|
||||||
if (sr.ponderMove == null) {
|
if (sr.ponderMove == null) {
|
||||||
// If we have a book move, play it.
|
// If we have a book move, play it.
|
||||||
Move bookMove = book.getBookMove(sr.currPos);
|
BookPosInput posInput = new BookPosInput(sr.currPos, sr.prevPos, sr.mList);
|
||||||
|
Move bookMove = book.getBookMove(posInput);
|
||||||
if (bookMove != null) {
|
if (bookMove != null) {
|
||||||
if (canClaimDraw(sr.currPos, posHashList, posHashListSize, bookMove).isEmpty()) {
|
if (canClaimDraw(sr.currPos, posHashList, posHashListSize, bookMove).isEmpty()) {
|
||||||
listener.notifySearchResult(sr.searchId,
|
listener.notifySearchResult(sr.searchId,
|
||||||
|
|
|
@ -39,6 +39,7 @@ import org.petero.droidfish.PGNOptions;
|
||||||
import org.petero.droidfish.Util;
|
import org.petero.droidfish.Util;
|
||||||
import org.petero.droidfish.book.BookOptions;
|
import org.petero.droidfish.book.BookOptions;
|
||||||
import org.petero.droidfish.book.EcoDb;
|
import org.petero.droidfish.book.EcoDb;
|
||||||
|
import org.petero.droidfish.book.IOpeningBook.BookPosInput;
|
||||||
import org.petero.droidfish.engine.DroidComputerPlayer;
|
import org.petero.droidfish.engine.DroidComputerPlayer;
|
||||||
import org.petero.droidfish.engine.UCIOptions;
|
import org.petero.droidfish.engine.UCIOptions;
|
||||||
import org.petero.droidfish.engine.DroidComputerPlayer.EloData;
|
import org.petero.droidfish.engine.DroidComputerPlayer.EloData;
|
||||||
|
@ -955,7 +956,8 @@ public class DroidChessController {
|
||||||
|
|
||||||
private void updateBookHints() {
|
private void updateBookHints() {
|
||||||
if (game != null) {
|
if (game != null) {
|
||||||
Pair<String, ArrayList<Move>> bi = computerPlayer.getBookHints(game.currPos(), localPt());
|
BookPosInput posInput = new BookPosInput(game);
|
||||||
|
Pair<String, ArrayList<Move>> bi = computerPlayer.getBookHints(posInput, localPt());
|
||||||
EcoDb.Result ecoData = EcoDb.getInstance().getEco(game.tree);
|
EcoDb.Result ecoData = EcoDb.getInstance().getEco(game.tree);
|
||||||
String eco = ecoData.getName();
|
String eco = ecoData.getName();
|
||||||
listener.notifyBookInfo(searchId, bi.first, bi.second, eco, ecoData.distToEcoTree);
|
listener.notifyBookInfo(searchId, bi.first, bi.second, eco, ecoData.distToEcoTree);
|
||||||
|
|
|
@ -112,7 +112,7 @@ public class Game {
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
final Position currPos() {
|
public final Position currPos() {
|
||||||
return tree.currentPos;
|
return tree.currentPos;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -389,7 +389,7 @@ If you are running on battery power, it is recommended that you change settings
|
||||||
<string name="prefs_bookTournamentMode_summary">Ignore moves marked as not for tournament play</string>
|
<string name="prefs_bookTournamentMode_summary">Ignore moves marked as not for tournament play</string>
|
||||||
<string name="prefs_bookRandom_title">Book Randomization</string>
|
<string name="prefs_bookRandom_title">Book Randomization</string>
|
||||||
<string name="prefs_bookFile_title">Book Filename</string>
|
<string name="prefs_bookFile_title">Book Filename</string>
|
||||||
<string name="prefs_bookFile_summary">Polyglot or CTG book file in DroidFish directory on SD Card</string>
|
<string name="prefs_bookFile_summary">Polyglot, ABK or CTG book file in DroidFish directory on SD Card</string>
|
||||||
<string name="prefs_pgnSettings_title">PGN Settings</string>
|
<string name="prefs_pgnSettings_title">PGN Settings</string>
|
||||||
<string name="prefs_pgnSettings_summary">Settings for import and export of portable game notation (PGN) data</string>
|
<string name="prefs_pgnSettings_summary">Settings for import and export of portable game notation (PGN) data</string>
|
||||||
<string name="prefs_pgn_viewer">PGN viewer</string>
|
<string name="prefs_pgn_viewer">PGN viewer</string>
|
||||||
|
|
31
README.md
31
README.md
|
@ -560,16 +560,19 @@ restored.
|
||||||
|
|
||||||
## Installing additional opening books
|
## Installing additional opening books
|
||||||
|
|
||||||
To use *polyglot* or *CTG* book files:
|
To use *polyglot*, *CTG* or *ABK* book files:
|
||||||
|
|
||||||
1. Copy one or more polyglot book files to the `DroidFish/book` directory on the
|
1. Copy one or more opening book files to the `DroidFish/book` directory on the
|
||||||
external storage. Polyglot books must have the file extension `.bin`.
|
external storage.
|
||||||
**Note!** The Android file system is case sensitive, so the extension must be
|
|
||||||
`.bin`, not `.Bin` or `.BIN`.
|
|
||||||
|
|
||||||
1. Copy one or more CTG book files to the `DroidFish/book` directory. A CTG
|
1. Polyglot books must have the file extension `.bin`.
|
||||||
book consists of three files with file extensions `.ctg`, `.ctb` and
|
**Note!** The Android file system may be case sensitive, in which case the
|
||||||
`.cto`. You must copy all three files.
|
extension must be `.bin`, not `.Bin` or `.BIN`.
|
||||||
|
|
||||||
|
1. A *CTG* book consists of three files with file extensions `.ctg`, `.ctb`
|
||||||
|
and `.cto`. You must copy all three files.
|
||||||
|
|
||||||
|
1. an *ABK* book must have the file extension `.abk`.
|
||||||
|
|
||||||
1. Go to *Left drawer menu* -> *Select opening book*.
|
1. Go to *Left drawer menu* -> *Select opening book*.
|
||||||
|
|
||||||
|
@ -862,11 +865,11 @@ You can change aspects of the opening book from *Left drawer menu* -> *Settings*
|
||||||
* *Prefer main lines*: When enabled, moves that are marked as main line moves in
|
* *Prefer main lines*: When enabled, moves that are marked as main line moves in
|
||||||
the book are given a higher weight so they will be played more often by the
|
the book are given a higher weight so they will be played more often by the
|
||||||
chess engine.
|
chess engine.
|
||||||
**Note!** This option only has an effect for CTG opening books.
|
**Note!** This option only has an effect for *CTG* opening books.
|
||||||
|
|
||||||
* *Tournament mode*: When enabled, only book moves that are marked for
|
* *Tournament mode*: When enabled, only book moves that are marked for
|
||||||
tournament play are played by the chess engine.
|
tournament play are played by the chess engine.
|
||||||
**Note!** This option only has an effect for CTG opening books.
|
**Note!** This option only has an effect for *CTG* opening books.
|
||||||
|
|
||||||
* *Book randomization*: Controls how often different book moves are played by
|
* *Book randomization*: Controls how often different book moves are played by
|
||||||
the engine. The default is 50% which means that the statistics from the
|
the engine. The default is 50% which means that the statistics from the
|
||||||
|
@ -882,6 +885,10 @@ You can change aspects of the opening book from *Left drawer menu* -> *Settings*
|
||||||
very big opening book stored somewhere on the device but it would be
|
very big opening book stored somewhere on the device but it would be
|
||||||
impractical to copy it to the `DroidFish/book` directory.
|
impractical to copy it to the `DroidFish/book` directory.
|
||||||
|
|
||||||
**Note!** The move percentages calculated by *DroidFish* for CTG books are
|
**Note!** The move percentages calculated by *DroidFish* for *CTG* books are
|
||||||
unlikely to agree with percentages calculated by other chess programs that can
|
unlikely to agree with percentages calculated by other chess programs that can
|
||||||
use CTG books.
|
use *CTG* books.
|
||||||
|
|
||||||
|
**Note!** The move percentages calculated by *DroidFish* for *ABK* books are not
|
||||||
|
always equal to percentages shown in the Arena Chess GUI, because the algorithm
|
||||||
|
used by Arena to compute the percentages is unknown.
|
||||||
|
|
Binary file not shown.
Loading…
Reference in New Issue
Block a user