diff --git a/DroidFish/src/org/petero/droidfish/book/EcoDb.java b/DroidFish/src/org/petero/droidfish/book/EcoDb.java index 2138fbe..5906686 100644 --- a/DroidFish/src/org/petero/droidfish/book/EcoDb.java +++ b/DroidFish/src/org/petero/droidfish/book/EcoDb.java @@ -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 getEco(GameTree gt) { + public Result getEco(GameTree gt) { ArrayList treePath = new ArrayList(); // Path to restore gt to original node ArrayList> toCache = new ArrayList>(); @@ -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(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("", 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 posHashToNodeIdx; private HashMap> 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 names = new ArrayList(); - 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 lst = null; if (posHashToNodeIdx2.get(hash) == null) { lst = new ArrayList(); @@ -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; } diff --git a/DroidFish/src/org/petero/droidfish/buildtools/EcoBuilder.java b/DroidFish/src/org/petero/droidfish/buildtools/EcoBuilder.java index 75ed272..1e2dc5d 100644 --- a/DroidFish/src/org/petero/droidfish/buildtools/EcoBuilder.java +++ b/DroidFish/src/org/petero/droidfish/buildtools/EcoBuilder.java @@ -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 children = new ArrayList(); Node parent; } private ArrayList nodes; - private ArrayList names; - private HashMap nameToIndex; + private ArrayList strs; + private HashMap strToIndex; + /** Constructor. */ private EcoBuilder() { nodes = new ArrayList(); - names = new ArrayList(); - nameToIndex = new HashMap(); + strs = new ArrayList(); + strToIndex = new HashMap(); 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 headers = new HashMap(); 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 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 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 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); } diff --git a/DroidFish/src/org/petero/droidfish/gamelogic/DroidChessController.java b/DroidFish/src/org/petero/droidfish/gamelogic/DroidChessController.java index 2827c2c..79eaa77 100644 --- a/DroidFish/src/org/petero/droidfish/gamelogic/DroidChessController.java +++ b/DroidFish/src/org/petero/droidfish/gamelogic/DroidChessController.java @@ -933,10 +933,9 @@ public class DroidChessController { private final void updateBookHints() { if (game != null) { Pair> bi = computerPlayer.getBookHints(game.currPos(), localPt()); - Pair 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 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> ph = game.getUCIHistory(); Position currPos = new Position(game.currPos()); long now = System.currentTimeMillis(); diff --git a/DroidFishTest/src/org/petero/droidfish/book/EcoTest.java b/DroidFishTest/src/org/petero/droidfish/book/EcoTest.java index 9bead3f..cd286a1 100644 --- a/DroidFishTest/src/org/petero/droidfish/book/EcoTest.java +++ b/DroidFishTest/src/org/petero/droidfish/book/EcoTest.java @@ -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); }