DroidFish: Changed representation of the ECO database.

This makes it around 10% smaller.
This commit is contained in:
Peter Osterlund 2016-12-16 23:09:30 +01:00
parent 4408669833
commit 7dd20129ee
4 changed files with 129 additions and 80 deletions

View File

@ -48,9 +48,32 @@ public class EcoDb {
}
return instance;
}
public static class Result {
public final String eco; // The ECO code
public final String opn; // The opening name, or null
public final String var; // The variation name, or null
public final int distToEcoTree;
Result(String eco, String opn, String var, int d) {
this.eco = eco;
this.opn = opn;
this.var = var;
distToEcoTree = d;
}
/** Return string formatted as "eco: opn, var". */
public String getName() {
String s = eco;
if (!opn.isEmpty()) {
s = s + ": " + opn;
if (!var.isEmpty())
s = s + ", " + var;
}
return s;
}
}
/** Get ECO classification for a given tree node. Also returns distance in plies to "ECO tree". */
public Pair<String,Integer> getEco(GameTree gt) {
public Result getEco(GameTree gt) {
ArrayList<Integer> treePath = new ArrayList<Integer>(); // Path to restore gt to original node
ArrayList<Pair<GameTree.Node,Boolean>> toCache = new ArrayList<Pair<GameTree.Node,Boolean>>();
@ -74,7 +97,7 @@ public class EcoDb {
if (idx != null) {
Node ecoNode = readNode(idx);
if (ecoNode.nameIdx != -1) {
if (ecoNode.ecoIdx != -1) {
nodeIdx = idx;
break;
}
@ -142,10 +165,18 @@ public class EcoDb {
if (nodeIdx != -1) {
Node n = readNode(nodeIdx);
if (n.nameIdx >= 0)
return new Pair<String, Integer>(ecoNames[n.nameIdx], distToEcoTree);
String eco = "", opn = "", var = "";
if (n.ecoIdx >= 0) {
eco = strPool[n.ecoIdx];
if (n.opnIdx >= 0) {
opn = strPool[n.opnIdx];
if (n.varIdx >= 0)
var = strPool[n.varIdx];
}
return new Result(eco, opn, var, distToEcoTree);
}
}
return new Pair<String, Integer>("", 0);
return new Result("", "", "", 0);
}
/** Get all moves in the ECO tree from a given position. */
@ -182,13 +213,15 @@ public class EcoDb {
private static class Node {
int move; // Move (compressed) leading to the position corresponding to this node
int nameIdx; // Index in names array, or -1
int ecoIdx; // Index in string array, or -1
int opnIdx; // Index in string array, or -1
int varIdx; // Index in string array, or -1
int firstChild;
int nextSibling;
}
private byte[] nodesBuffer;
private String[] ecoNames;
private String[] strPool;
private HashMap<Long, Short> posHashToNodeIdx;
private HashMap<Long, ArrayList<Short>> posHashToNodeIdx2; // Handles collisions
private final long startPosHash; // Zobrist hash for standard starting position
@ -239,11 +272,11 @@ public class EcoDb {
break;
nNodes++;
}
nodesBuffer = new byte[nNodes * 8];
System.arraycopy(buf, 0, nodesBuffer, 0, nNodes * 8);
nodesBuffer = new byte[nNodes * 12];
System.arraycopy(buf, 0, nodesBuffer, 0, nNodes * 12);
ArrayList<String> names = new ArrayList<String>();
int idx = (nNodes + 1) * 8;
int idx = (nNodes + 1) * 12;
int start = idx;
for (int i = idx; i < buf.length; i++) {
if (buf[i] == 0) {
@ -251,7 +284,7 @@ public class EcoDb {
start = i + 1;
}
}
ecoNames = names.toArray(new String[names.size()]);
strPool = names.toArray(new String[names.size()]);
} catch (IOException ex) {
throw new RuntimeException("Can't read ECO database");
}
@ -272,7 +305,7 @@ public class EcoDb {
long hash = pos.zobristHash();
if (posHashToNodeIdx.get(hash) == null) {
posHashToNodeIdx.put(hash, (short)nodeIdx);
} else if (node.nameIdx != -1) {
} else if (node.ecoIdx != -1) {
ArrayList<Short> lst = null;
if (posHashToNodeIdx2.get(hash) == null) {
lst = new ArrayList<Short>();
@ -300,11 +333,13 @@ public class EcoDb {
private static Node readNode(int index, byte[] buf) {
Node n = new Node();
int o = index * 8;
int o = index * 12;
n.move = getU16(buf, o);
n.nameIdx = getS16(buf, o + 2);
n.firstChild = getS16(buf, o + 4);
n.nextSibling = getS16(buf, o + 6);
n.ecoIdx = getS16(buf, o + 2);
n.opnIdx = getS16(buf, o + 4);
n.varIdx = getS16(buf, o + 6);
n.firstChild = getS16(buf, o + 8);
n.nextSibling = getS16(buf, o + 10);
return n;
}

View File

@ -41,23 +41,28 @@ public class EcoBuilder {
private static class Node {
int index; // Index in nodes array
Move move; // Move leading to the position corresponding to this node
int nameIdx; // Index in names array, or -1
int ecoIdx; // Index in string array, or -1
int opnIdx; // Index in string array, or -1
int varIdx; // Index in string array, or -1
ArrayList<Node> children = new ArrayList<Node>();
Node parent;
}
private ArrayList<Node> nodes;
private ArrayList<String> names;
private HashMap<String, Integer> nameToIndex;
private ArrayList<String> strs;
private HashMap<String, Integer> strToIndex;
/** Constructor. */
private EcoBuilder() {
nodes = new ArrayList<Node>();
names = new ArrayList<String>();
nameToIndex = new HashMap<String, Integer>();
strs = new ArrayList<String>();
strToIndex = new HashMap<String, Integer>();
Node rootNode = new Node();
rootNode.index = 0;
rootNode.move = new Move(0, 0, 0);
rootNode.nameIdx = -1;
rootNode.ecoIdx = -1;
rootNode.opnIdx = -1;
rootNode.varIdx = -1;
nodes.add(rootNode);
}
@ -94,27 +99,18 @@ public class EcoBuilder {
HashMap<String,String> headers = new HashMap<String,String>();
GameTree tree = game.tree;
tree.getHeaders(headers);
String eco = headers.get("ECO");
String opening = headers.get("Opening");
String variation = headers.get("Variation");
String name = eco + ": " + opening;
if (variation != null)
name = name + ", " + variation;
// Add name to data structures
Integer nameIdx = nameToIndex.get(name);
if (nameIdx == null) {
nameIdx = nameToIndex.size();
nameToIndex.put(name, nameIdx);
names.add(name);
}
int ecoIdx = addData(headers, "ECO");
int opnIdx = addData(headers, "Opening");
int varIdx = addData(headers, "Variation");
// Add corresponding moves to data structures
Node parent = nodes.get(0);
while (true) {
ArrayList<Move> moves = tree.variations();
if (moves.isEmpty()) {
parent.nameIdx = nameIdx;
parent.ecoIdx = ecoIdx;
parent.opnIdx = opnIdx;
parent.varIdx = varIdx;
break;
}
Move m = moves.get(0);
@ -130,7 +126,9 @@ public class EcoBuilder {
Node node = new Node();
node.index = nodes.size();
node.move = m;
node.nameIdx = -1;
node.ecoIdx = -1;
node.opnIdx = -1;
node.varIdx = -1;
node.parent = parent;
nodes.add(node);
parent.children.add(node);
@ -141,24 +139,42 @@ public class EcoBuilder {
}
}
/** Add ECO, opening or variation data to string pool. */
private int addData(HashMap<String, String> headers, String hdrName) {
String s = headers.get(hdrName);
if (s == null)
return -1;
Integer idx = strToIndex.get(s);
if (idx == null) {
idx = strToIndex.size();
strToIndex.put(s, idx);
strs.add(s);
}
return idx;
}
/** Write the binary ECO code data file. */
private void writeDataFile(String ecoDatFile) throws Throwable {
FileOutputStream out = new FileOutputStream(ecoDatFile);
// Write nodes
byte[] buf = new byte[8];
byte[] buf = new byte[12];
for (int i = 0; i < nodes.size(); i++) {
Node n = nodes.get(i);
int cm = n.move == null ? 0 : n.move.getCompressedMove();
buf[0] = (byte)(cm >> 8); // Move, high byte
buf[1] = (byte)(cm & 255); // Move, low byte
buf[2] = (byte)(n.nameIdx >> 8); // Index, high byte
buf[3] = (byte)(n.nameIdx & 255); // Index, low byte
buf[2] = (byte)(n.ecoIdx >> 8); // Index, high byte
buf[3] = (byte)(n.ecoIdx & 255); // Index, low byte
buf[4] = (byte)(n.opnIdx >> 8); // Index, high byte
buf[5] = (byte)(n.opnIdx & 255); // Index, low byte
buf[6] = (byte)(n.varIdx >> 8); // Index, high byte
buf[7] = (byte)(n.varIdx & 255); // Index, low byte
int firstChild = -1;
if (n.children.size() > 0)
firstChild = n.children.get(0).index;
buf[4] = (byte)(firstChild >> 8);
buf[5] = (byte)(firstChild & 255);
buf[8] = (byte)(firstChild >> 8);
buf[9] = (byte)(firstChild & 255);
int nextSibling = -1;
if (n.parent != null) {
ArrayList<Node> siblings = n.parent.children;
@ -169,17 +185,17 @@ public class EcoBuilder {
}
}
}
buf[6] = (byte)(nextSibling >> 8);
buf[7] = (byte)(nextSibling & 255);
buf[10] = (byte)(nextSibling >> 8);
buf[11] = (byte)(nextSibling & 255);
out.write(buf);
}
for (int i = 0; i < buf.length; i++)
buf[i] = -1;
out.write(buf);
// Write names
// Write strings
buf = new byte[]{0};
for (String name : names) {
for (String name : strs) {
out.write(name.getBytes("UTF-8"));
out.write(buf);
}

View File

@ -933,10 +933,9 @@ public class DroidChessController {
private final void updateBookHints() {
if (game != null) {
Pair<String, ArrayList<Move>> bi = computerPlayer.getBookHints(game.currPos(), localPt());
Pair<String, Integer> ecoData =
EcoDb.getInstance().getEco(game.tree);
String eco = ecoData.first;
listener.notifyBookInfo(searchId, bi.first, bi.second, eco, ecoData.second);
EcoDb.Result ecoData = EcoDb.getInstance().getEco(game.tree);
String eco = ecoData.getName();
listener.notifyBookInfo(searchId, bi.first, bi.second, eco, ecoData.distToEcoTree);
}
}
@ -976,10 +975,9 @@ public class DroidChessController {
computerPlayer.queueAnalyzeRequest(sr);
} else if (computersTurn || ponder) {
listener.clearSearchInfo(searchId);
Pair<String, Integer> ecoData =
EcoDb.getInstance().getEco(game.tree);
String eco = ecoData.first;
listener.notifyBookInfo(searchId, "", null, eco, ecoData.second);
EcoDb.Result ecoData = EcoDb.getInstance().getEco(game.tree);
String eco = ecoData.getName();
listener.notifyBookInfo(searchId, "", null, eco, ecoData.distToEcoTree);
final Pair<Position, ArrayList<Move>> ph = game.getUCIHistory();
Position currPos = new Position(game.currPos());
long now = System.currentTimeMillis();

View File

@ -36,27 +36,27 @@ public class EcoTest extends AndroidTestCase {
{
String pgn = "e4 e5 Nf3 Nc6 Bb5 a6 Ba4 Nf6 O-O Be7 Re1";
GameTree gt = readPGN(pgn);
String eco = ecoDb.getEco(gt).first;
String eco = ecoDb.getEco(gt).getName();
assertEquals("", eco);
gt.goForward(0);
eco = ecoDb.getEco(gt).first;
eco = ecoDb.getEco(gt).getName();
assertEquals("B00: King's pawn opening", eco);
gt.goForward(0);
eco = ecoDb.getEco(gt).first;
eco = ecoDb.getEco(gt).getName();
assertEquals("C20: King's pawn game", eco);
gt.goForward(0);
eco = ecoDb.getEco(gt).first;
eco = ecoDb.getEco(gt).getName();
assertEquals("C40: King's knight opening", eco);
gt.goForward(0);
eco = ecoDb.getEco(gt).first;
eco = ecoDb.getEco(gt).getName();
assertEquals("C44: King's pawn game", eco);
gt.goForward(0);
eco = ecoDb.getEco(gt).first;
eco = ecoDb.getEco(gt).getName();
assertEquals("C60: Ruy Lopez (Spanish opening)", eco);
}
{
@ -65,85 +65,85 @@ public class EcoTest extends AndroidTestCase {
game.processString("e5");
game.processString("Nf3");
game.processString("Nf6");
String eco = ecoDb.getEco(game.tree).first;
String eco = ecoDb.getEco(game.tree).getName();
assertEquals("C42: Petrov's defence", eco);
game.processString("Nxe5");
eco = ecoDb.getEco(game.tree).first;
eco = ecoDb.getEco(game.tree).getName();
assertEquals("C42: Petrov's defence", eco);
game.processString("d6");
eco = ecoDb.getEco(game.tree).first;
eco = ecoDb.getEco(game.tree).getName();
assertEquals("C42: Petrov's defence", eco);
game.processString("Nxf7");
eco = ecoDb.getEco(game.tree).first;
eco = ecoDb.getEco(game.tree).getName();
assertEquals("C42: Petrov, Cochrane gambit", eco);
game.undoMove();
eco = ecoDb.getEco(game.tree).first;
eco = ecoDb.getEco(game.tree).getName();
assertEquals("C42: Petrov's defence", eco);
game.processString("Nf3");
game.processString("Nxe4");
game.processString("d4");
eco = ecoDb.getEco(game.tree).first;
eco = ecoDb.getEco(game.tree).getName();
assertEquals("C42: Petrov, classical attack", eco);
}
{
Game game = new Game(null, new TimeControlData());
game.processString("e4");
game.processString("c5");
String eco = ecoDb.getEco(game.tree).first;
String eco = ecoDb.getEco(game.tree).getName();
assertEquals("B20: Sicilian defence", eco);
game.processString("h3");
eco = ecoDb.getEco(game.tree).first;
eco = ecoDb.getEco(game.tree).getName();
assertEquals("B20: Sicilian defence", eco);
game.processString("Nc6");
eco = ecoDb.getEco(game.tree).first;
eco = ecoDb.getEco(game.tree).getName();
assertEquals("B20: Sicilian defence", eco);
game.processString("g3");
eco = ecoDb.getEco(game.tree).first;
eco = ecoDb.getEco(game.tree).getName();
assertEquals("B20: Sicilian defence", eco);
}
{
Game game = new Game(null, new TimeControlData());
for (String m : new String[]{"d4", "d5", "c4", "c6", "Nf3", "Nf6", "Nc3", "g6"})
game.processString(m);
String eco = ecoDb.getEco(game.tree).first;
String eco = ecoDb.getEco(game.tree).getName();
assertEquals("D15: QGD Slav, Schlechter variation", eco);
assertEquals(0, ecoDb.getEco(game.tree).second.intValue());
assertEquals(0, ecoDb.getEco(game.tree).distToEcoTree);
game.processString("a4");
assertEquals("D15: QGD Slav, Schlechter variation", eco);
assertEquals(1, ecoDb.getEco(game.tree).second.intValue());
assertEquals(1, ecoDb.getEco(game.tree).distToEcoTree);
}
{
Game game = new Game(null, new TimeControlData());
for (String m : new String[]{"d4", "Nf6", "c4", "g6", "Nc3", "d5", "Nf3", "c6"})
game.processString(m);
String eco = ecoDb.getEco(game.tree).first;
String eco = ecoDb.getEco(game.tree).getName();
assertEquals("D90: Gruenfeld, Schlechter variation", eco);
assertEquals(0, ecoDb.getEco(game.tree).second.intValue());
assertEquals(0, ecoDb.getEco(game.tree).distToEcoTree);
game.processString("h4");
assertEquals("D90: Gruenfeld, Schlechter variation", eco);
assertEquals(1, ecoDb.getEco(game.tree).second.intValue());
assertEquals(1, ecoDb.getEco(game.tree).distToEcoTree);
game.processString("h5");
assertEquals("D90: Gruenfeld, Schlechter variation", eco);
assertEquals(2, ecoDb.getEco(game.tree).second.intValue());
assertEquals(2, ecoDb.getEco(game.tree).distToEcoTree);
}
}
public void testEcoFromFEN() throws Throwable {
EcoDb ecoDb = EcoDb.getInstance();
GameTree gt = gtFromFEN("rnbqkbnr/ppp2ppp/4p3/3P4/3P4/8/PPP2PPP/RNBQKBNR b KQkq - 0 3");
String eco = ecoDb.getEco(gt).first;
String eco = ecoDb.getEco(gt).getName();
assertEquals("C01: French, exchange variation", eco);
gt = gtFromFEN("rnbqk1nr/ppppppbp/6p1/8/3PP3/8/PPP2PPP/RNBQKBNR w KQkq - 1 3");
eco = ecoDb.getEco(gt).first;
eco = ecoDb.getEco(gt).getName();
assertEquals("B06: Robatsch (modern) defence", eco);
}