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

View File

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

View File

@@ -933,10 +933,9 @@ public class DroidChessController {
private final void updateBookHints() { private final void updateBookHints() {
if (game != null) { if (game != null) {
Pair<String, ArrayList<Move>> bi = computerPlayer.getBookHints(game.currPos(), localPt()); Pair<String, ArrayList<Move>> bi = computerPlayer.getBookHints(game.currPos(), localPt());
Pair<String, Integer> ecoData = EcoDb.Result ecoData = EcoDb.getInstance().getEco(game.tree);
EcoDb.getInstance().getEco(game.tree); String eco = ecoData.getName();
String eco = ecoData.first; listener.notifyBookInfo(searchId, bi.first, bi.second, eco, ecoData.distToEcoTree);
listener.notifyBookInfo(searchId, bi.first, bi.second, eco, ecoData.second);
} }
} }
@@ -976,10 +975,9 @@ public class DroidChessController {
computerPlayer.queueAnalyzeRequest(sr); computerPlayer.queueAnalyzeRequest(sr);
} else if (computersTurn || ponder) { } else if (computersTurn || ponder) {
listener.clearSearchInfo(searchId); listener.clearSearchInfo(searchId);
Pair<String, Integer> ecoData = EcoDb.Result ecoData = EcoDb.getInstance().getEco(game.tree);
EcoDb.getInstance().getEco(game.tree); String eco = ecoData.getName();
String eco = ecoData.first; listener.notifyBookInfo(searchId, "", null, eco, ecoData.distToEcoTree);
listener.notifyBookInfo(searchId, "", null, eco, ecoData.second);
final Pair<Position, ArrayList<Move>> ph = game.getUCIHistory(); final Pair<Position, ArrayList<Move>> ph = game.getUCIHistory();
Position currPos = new Position(game.currPos()); Position currPos = new Position(game.currPos());
long now = System.currentTimeMillis(); 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"; String pgn = "e4 e5 Nf3 Nc6 Bb5 a6 Ba4 Nf6 O-O Be7 Re1";
GameTree gt = readPGN(pgn); GameTree gt = readPGN(pgn);
String eco = ecoDb.getEco(gt).first; String eco = ecoDb.getEco(gt).getName();
assertEquals("", eco); assertEquals("", eco);
gt.goForward(0); gt.goForward(0);
eco = ecoDb.getEco(gt).first; eco = ecoDb.getEco(gt).getName();
assertEquals("B00: King's pawn opening", eco); assertEquals("B00: King's pawn opening", eco);
gt.goForward(0); gt.goForward(0);
eco = ecoDb.getEco(gt).first; eco = ecoDb.getEco(gt).getName();
assertEquals("C20: King's pawn game", eco); assertEquals("C20: King's pawn game", eco);
gt.goForward(0); gt.goForward(0);
eco = ecoDb.getEco(gt).first; eco = ecoDb.getEco(gt).getName();
assertEquals("C40: King's knight opening", eco); assertEquals("C40: King's knight opening", eco);
gt.goForward(0); gt.goForward(0);
eco = ecoDb.getEco(gt).first; eco = ecoDb.getEco(gt).getName();
assertEquals("C44: King's pawn game", eco); assertEquals("C44: King's pawn game", eco);
gt.goForward(0); gt.goForward(0);
eco = ecoDb.getEco(gt).first; eco = ecoDb.getEco(gt).getName();
assertEquals("C60: Ruy Lopez (Spanish opening)", eco); assertEquals("C60: Ruy Lopez (Spanish opening)", eco);
} }
{ {
@@ -65,85 +65,85 @@ public class EcoTest extends AndroidTestCase {
game.processString("e5"); game.processString("e5");
game.processString("Nf3"); game.processString("Nf3");
game.processString("Nf6"); game.processString("Nf6");
String eco = ecoDb.getEco(game.tree).first; String eco = ecoDb.getEco(game.tree).getName();
assertEquals("C42: Petrov's defence", eco); assertEquals("C42: Petrov's defence", eco);
game.processString("Nxe5"); game.processString("Nxe5");
eco = ecoDb.getEco(game.tree).first; eco = ecoDb.getEco(game.tree).getName();
assertEquals("C42: Petrov's defence", eco); assertEquals("C42: Petrov's defence", eco);
game.processString("d6"); game.processString("d6");
eco = ecoDb.getEco(game.tree).first; eco = ecoDb.getEco(game.tree).getName();
assertEquals("C42: Petrov's defence", eco); assertEquals("C42: Petrov's defence", eco);
game.processString("Nxf7"); game.processString("Nxf7");
eco = ecoDb.getEco(game.tree).first; eco = ecoDb.getEco(game.tree).getName();
assertEquals("C42: Petrov, Cochrane gambit", eco); assertEquals("C42: Petrov, Cochrane gambit", eco);
game.undoMove(); game.undoMove();
eco = ecoDb.getEco(game.tree).first; eco = ecoDb.getEco(game.tree).getName();
assertEquals("C42: Petrov's defence", eco); assertEquals("C42: Petrov's defence", eco);
game.processString("Nf3"); game.processString("Nf3");
game.processString("Nxe4"); game.processString("Nxe4");
game.processString("d4"); game.processString("d4");
eco = ecoDb.getEco(game.tree).first; eco = ecoDb.getEco(game.tree).getName();
assertEquals("C42: Petrov, classical attack", eco); assertEquals("C42: Petrov, classical attack", eco);
} }
{ {
Game game = new Game(null, new TimeControlData()); Game game = new Game(null, new TimeControlData());
game.processString("e4"); game.processString("e4");
game.processString("c5"); game.processString("c5");
String eco = ecoDb.getEco(game.tree).first; String eco = ecoDb.getEco(game.tree).getName();
assertEquals("B20: Sicilian defence", eco); assertEquals("B20: Sicilian defence", eco);
game.processString("h3"); game.processString("h3");
eco = ecoDb.getEco(game.tree).first; eco = ecoDb.getEco(game.tree).getName();
assertEquals("B20: Sicilian defence", eco); assertEquals("B20: Sicilian defence", eco);
game.processString("Nc6"); game.processString("Nc6");
eco = ecoDb.getEco(game.tree).first; eco = ecoDb.getEco(game.tree).getName();
assertEquals("B20: Sicilian defence", eco); assertEquals("B20: Sicilian defence", eco);
game.processString("g3"); game.processString("g3");
eco = ecoDb.getEco(game.tree).first; eco = ecoDb.getEco(game.tree).getName();
assertEquals("B20: Sicilian defence", eco); assertEquals("B20: Sicilian defence", eco);
} }
{ {
Game game = new Game(null, new TimeControlData()); Game game = new Game(null, new TimeControlData());
for (String m : new String[]{"d4", "d5", "c4", "c6", "Nf3", "Nf6", "Nc3", "g6"}) for (String m : new String[]{"d4", "d5", "c4", "c6", "Nf3", "Nf6", "Nc3", "g6"})
game.processString(m); 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("D15: QGD Slav, Schlechter variation", eco);
assertEquals(0, ecoDb.getEco(game.tree).second.intValue()); assertEquals(0, ecoDb.getEco(game.tree).distToEcoTree);
game.processString("a4"); game.processString("a4");
assertEquals("D15: QGD Slav, Schlechter variation", eco); 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()); Game game = new Game(null, new TimeControlData());
for (String m : new String[]{"d4", "Nf6", "c4", "g6", "Nc3", "d5", "Nf3", "c6"}) for (String m : new String[]{"d4", "Nf6", "c4", "g6", "Nc3", "d5", "Nf3", "c6"})
game.processString(m); game.processString(m);
String eco = ecoDb.getEco(game.tree).first; String eco = ecoDb.getEco(game.tree).getName();
assertEquals("D90: Gruenfeld, Schlechter variation", eco); assertEquals("D90: Gruenfeld, Schlechter variation", eco);
assertEquals(0, ecoDb.getEco(game.tree).second.intValue()); assertEquals(0, ecoDb.getEco(game.tree).distToEcoTree);
game.processString("h4"); game.processString("h4");
assertEquals("D90: Gruenfeld, Schlechter variation", eco); assertEquals("D90: Gruenfeld, Schlechter variation", eco);
assertEquals(1, ecoDb.getEco(game.tree).second.intValue()); assertEquals(1, ecoDb.getEco(game.tree).distToEcoTree);
game.processString("h5"); game.processString("h5");
assertEquals("D90: Gruenfeld, Schlechter variation", eco); 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 { public void testEcoFromFEN() throws Throwable {
EcoDb ecoDb = EcoDb.getInstance(); EcoDb ecoDb = EcoDb.getInstance();
GameTree gt = gtFromFEN("rnbqkbnr/ppp2ppp/4p3/3P4/3P4/8/PPP2PPP/RNBQKBNR b KQkq - 0 3"); 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); assertEquals("C01: French, exchange variation", eco);
gt = gtFromFEN("rnbqk1nr/ppppppbp/6p1/8/3PP3/8/PPP2PPP/RNBQKBNR w KQkq - 1 3"); 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); assertEquals("B06: Robatsch (modern) defence", eco);
} }