DroidFish: Better handling of transpositions in ECO classification.

This commit is contained in:
Peter Osterlund 2016-11-15 20:54:25 +01:00
parent 4a9bcb8a21
commit 3292bec55c
8 changed files with 178 additions and 100 deletions

View File

@ -1954,7 +1954,7 @@ public class DroidFish extends Activity
private String thinkingStr2 = ""; private String thinkingStr2 = "";
private String bookInfoStr = ""; private String bookInfoStr = "";
private String ecoInfoStr = ""; private String ecoInfoStr = "";
private boolean ecoInTree = false; private int distToEcoTree = 0;
private String variantStr = ""; private String variantStr = "";
private ArrayList<ArrayList<Move>> pvMoves = new ArrayList<ArrayList<Move>>(); private ArrayList<ArrayList<Move>> pvMoves = new ArrayList<ArrayList<Move>>();
private ArrayList<Move> bookMoves = null; private ArrayList<Move> bookMoves = null;
@ -1966,7 +1966,7 @@ public class DroidFish extends Activity
thinkingStr2 = ti.statStr; thinkingStr2 = ti.statStr;
bookInfoStr = ti.bookInfo; bookInfoStr = ti.bookInfo;
ecoInfoStr = ti.eco; ecoInfoStr = ti.eco;
ecoInTree = ti.ecoInTree; distToEcoTree = ti.distToEcoTree;
pvMoves = ti.pvMoves; pvMoves = ti.pvMoves;
bookMoves = ti.bookMoves; bookMoves = ti.bookMoves;
updateThinkingInfo(); updateThinkingInfo();
@ -1995,7 +1995,9 @@ public class DroidFish extends Activity
} }
thinking.setText(s, TextView.BufferType.SPANNABLE); thinking.setText(s, TextView.BufferType.SPANNABLE);
} }
if ((mEcoHints == ECO_HINTS_ALWAYS || (mEcoHints == ECO_HINTS_AUTO && ecoInTree)) && int maxDistToEcoTree = 10;
if ((mEcoHints == ECO_HINTS_ALWAYS ||
(mEcoHints == ECO_HINTS_AUTO && distToEcoTree <= maxDistToEcoTree)) &&
!ecoInfoStr.isEmpty()) { !ecoInfoStr.isEmpty()) {
String s = thinkingEmpty ? "" : "<br>"; String s = thinkingEmpty ? "" : "<br>";
s += ecoInfoStr; s += ecoInfoStr;

View File

@ -60,7 +60,7 @@ public interface GUIInterface {
public ArrayList<ArrayList<Move>> pvMoves; public ArrayList<ArrayList<Move>> pvMoves;
public ArrayList<Move> bookMoves; public ArrayList<Move> bookMoves;
public String eco; public String eco;
public boolean ecoInTree; public int distToEcoTree;
} }
/** Update the computer thinking information. */ /** Update the computer thinking information. */

View File

@ -49,55 +49,103 @@ public class EcoDb {
return instance; return instance;
} }
/** Get ECO classification for a given tree node. */ /** Get ECO classification for a given tree node. Also returns distance in plies to "ECO tree". */
public Pair<String,Boolean> getEco(GameTree gt, GameTree.Node node) { public Pair<String,Integer> getEco(GameTree gt) {
ArrayList<GameTree.Node> gtNodePath = new ArrayList<GameTree.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>>();
int nodeIdx = -1; int nodeIdx = -1;
boolean inEcoTree = true; int distToEcoTree = 0;
while (node != null) {
// Find matching node furtherest from root in the ECO tree
boolean checkForDup = true;
while (true) {
GameTree.Node node = gt.currentNode;
CacheEntry e = findNode(node); CacheEntry e = findNode(node);
if (e != null) { if (e != null) {
nodeIdx = e.nodeIdx; nodeIdx = e.nodeIdx;
inEcoTree = e.inEcoTree; distToEcoTree = e.distToEcoTree;
checkForDup = false;
break; break;
} }
if (node == gt.rootNode) { Short idx = posHashToNodeIdx.get(gt.currentPos.zobristHash());
Short idx = posHashToNodeIdx.get(gt.startPos.zobristHash()); boolean inEcoTree = idx != null;
if (idx != null) { toCache.add(new Pair<GameTree.Node,Boolean>(node, inEcoTree));
if (idx != null) {
Node ecoNode = readNode(idx);
if (ecoNode.nameIdx != -1) {
nodeIdx = idx; nodeIdx = idx;
break; break;
} }
} }
gtNodePath.add(node);
node = node.getParent(); if (node == gt.rootNode)
break;
treePath.add(node.getChildNo());
gt.goBack();
} }
if (nodeIdx != -1) {
Node ecoNode = readNode(nodeIdx); // Handle duplicates in ECO tree (same position reachable from more than one path)
for (int i = gtNodePath.size() - 1; i >= 0; i--) { if (nodeIdx != -1 && checkForDup && gt.startPos.zobristHash() == startPosHash) {
GameTree.Node gtNode = gtNodePath.get(i); ArrayList<Short> dups = posHashToNodeIdx2.get(gt.currentPos.zobristHash());
int m = gtNode.move.getCompressedMove(); if (dups != null) {
int child = inEcoTree ? ecoNode.firstChild : -1; while (gt.currentNode != gt.rootNode) {
while (child != -1) { treePath.add(gt.currentNode.getChildNo());
Node cNode = readNode(child); gt.goBack();
if (cNode.move == m) }
break;
child = cNode.nextSibling; int currEcoNode = 0;
boolean foundDup = false;
while (!treePath.isEmpty()) {
gt.goForward(treePath.get(treePath.size() - 1));
treePath.remove(treePath.size() - 1);
int m = gt.currentNode.move.getCompressedMove();
Node ecoNode = readNode(currEcoNode);
boolean foundChild = false;
int child = ecoNode.firstChild;
while (child != -1) {
ecoNode = readNode(child);
if (ecoNode.move == m) {
foundChild = true;
break;
}
child = ecoNode.nextSibling;
}
if (!foundChild)
break;
currEcoNode = child;
for (Short dup : dups) {
if (dup == currEcoNode) {
nodeIdx = currEcoNode;
foundDup = true;
break;
}
}
if (foundDup)
break;
} }
if (child != -1) {
nodeIdx = child;
ecoNode = readNode(nodeIdx);
} else
inEcoTree = false;
cacheNode(gtNode, nodeIdx, inEcoTree);
} }
} }
for (int i = treePath.size() - 1; i >= 0; i--)
gt.goForward(treePath.get(i));
for (int i = toCache.size() - 1; i >= 0; i--) {
Pair<GameTree.Node,Boolean> p = toCache.get(i);
distToEcoTree++;
if (p.second)
distToEcoTree = 0;
cacheNode(p.first, nodeIdx, distToEcoTree);
}
if (nodeIdx != -1) { if (nodeIdx != -1) {
Node n = readNode(nodeIdx); Node n = readNode(nodeIdx);
if (n.nameIdx >= 0) if (n.nameIdx >= 0)
return new Pair<String, Boolean>(ecoNames[n.nameIdx], inEcoTree); return new Pair<String, Integer>(ecoNames[n.nameIdx], distToEcoTree);
} }
return new Pair<String, Boolean>("", false); return new Pair<String, Integer>("", 0);
} }
@ -111,13 +159,15 @@ public class EcoDb {
private byte[] nodesBuffer; private byte[] nodesBuffer;
private String[] ecoNames; private String[] ecoNames;
private HashMap<Long, Short> posHashToNodeIdx; private HashMap<Long, Short> posHashToNodeIdx;
private HashMap<Long, ArrayList<Short>> posHashToNodeIdx2; // Handles collisions
private final long startPosHash; // Zobrist hash for standard starting position
private static class CacheEntry { private static class CacheEntry {
final int nodeIdx; final int nodeIdx;
final boolean inEcoTree; final int distToEcoTree;
CacheEntry(int n, boolean i) { CacheEntry(int n, int d) {
nodeIdx = n; nodeIdx = n;
inEcoTree = i; distToEcoTree = d;
} }
} }
private WeakLRUCache<GameTree.Node, CacheEntry> gtNodeToIdx; private WeakLRUCache<GameTree.Node, CacheEntry> gtNodeToIdx;
@ -128,13 +178,14 @@ public class EcoDb {
} }
/** Store GameTree.Node to Node index in cache. */ /** Store GameTree.Node to Node index in cache. */
private void cacheNode(GameTree.Node node, int nodeIdx, boolean inTree) { private void cacheNode(GameTree.Node node, int nodeIdx, int distToEcoTree) {
gtNodeToIdx.put(node, new CacheEntry(nodeIdx, inTree)); gtNodeToIdx.put(node, new CacheEntry(nodeIdx, distToEcoTree));
} }
/** Constructor. */ /** Constructor. */
private EcoDb(Context context) { private EcoDb(Context context) {
posHashToNodeIdx = new HashMap<Long, Short>(); posHashToNodeIdx = new HashMap<Long, Short>();
posHashToNodeIdx2 = new HashMap<Long, ArrayList<Short>>();
gtNodeToIdx = new WeakLRUCache<GameTree.Node, CacheEntry>(50); gtNodeToIdx = new WeakLRUCache<GameTree.Node, CacheEntry>(50);
try { try {
ByteArrayOutputStream bufStream = new ByteArrayOutputStream(); ByteArrayOutputStream bufStream = new ByteArrayOutputStream();
@ -174,19 +225,32 @@ public class EcoDb {
throw new RuntimeException("Can't read ECO database"); throw new RuntimeException("Can't read ECO database");
} }
try { try {
Position pos = TextIO.readFEN(TextIO.startPosFEN);
startPosHash = pos.zobristHash();
if (nodesBuffer.length > 0) { if (nodesBuffer.length > 0) {
Position pos = TextIO.readFEN(TextIO.startPosFEN);
populateCache(pos, 0); populateCache(pos, 0);
} }
} catch (ChessParseError e) { } catch (ChessParseError e) {
throw new RuntimeException("Internal error");
} }
} }
/** Initialize popHashToNodeIdx. */ /** Initialize posHashToNodeIdx. */
private void populateCache(Position pos, int nodeIdx) { private void populateCache(Position pos, int nodeIdx) {
if (posHashToNodeIdx.get(pos.zobristHash()) == null)
posHashToNodeIdx.put(pos.zobristHash(), (short)nodeIdx);
Node node = readNode(nodeIdx); Node node = readNode(nodeIdx);
long hash = pos.zobristHash();
if (posHashToNodeIdx.get(hash) == null) {
posHashToNodeIdx.put(hash, (short)nodeIdx);
} else if (node.nameIdx != -1) {
ArrayList<Short> lst = null;
if (posHashToNodeIdx2.get(hash) == null) {
lst = new ArrayList<Short>();
posHashToNodeIdx2.put(hash, lst);
} else {
lst = posHashToNodeIdx2.get(hash);
}
lst.add((short)nodeIdx);
}
int child = node.firstChild; int child = node.firstChild;
UndoInfo ui = new UndoInfo(); UndoInfo ui = new UndoInfo();
while (child != -1) { while (child != -1) {

View File

@ -78,22 +78,10 @@ public class EcoBuilder {
gotMoves |= !isHeader; gotMoves |= !isHeader;
} }
readGame(pgn.toString()); readGame(pgn.toString());
setNameIndices(0);
writeDataFile(ecoDatFile); writeDataFile(ecoDatFile);
} }
/** For all tree nodes, if nameIndex not already set,
* set it from parent node nameIndex. */
private void setNameIndices(int nodeIdx) {
Node n = nodes.get(nodeIdx);
for (Node c : n.children) {
if (c.nameIdx == -1)
c.nameIdx = n.nameIdx;
setNameIndices(c.index);
}
}
/** Read and process one game. */ /** Read and process one game. */
private void readGame(String pgn) throws Throwable { private void readGame(String pgn) throws Throwable {
if (pgn.isEmpty()) if (pgn.isEmpty())
@ -142,7 +130,7 @@ 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 = parent.nameIdx; node.nameIdx = -1;
node.parent = parent; node.parent = parent;
nodes.add(node); nodes.add(node);
parent.children.add(node); parent.children.add(node);

View File

@ -684,7 +684,7 @@ public class DroidChessController {
private String bookInfo = ""; private String bookInfo = "";
private ArrayList<Move> bookMoves = null; private ArrayList<Move> bookMoves = null;
private String eco = ""; // ECO classification private String eco = ""; // ECO classification
private boolean ecoInTree = false; // True if current position is inside the EcoDB tree private int distToEcoTree = 0; // Number of plies since game was in the "ECO tree".
private Move ponderMove = null; private Move ponderMove = null;
private ArrayList<PvInfo> pvInfoV = new ArrayList<PvInfo>(); private ArrayList<PvInfo> pvInfoV = new ArrayList<PvInfo>();
@ -698,7 +698,7 @@ public class DroidChessController {
bookInfo = ""; bookInfo = "";
bookMoves = null; bookMoves = null;
eco = ""; eco = "";
ecoInTree = false; distToEcoTree = 0;
setSearchInfo(id); setSearchInfo(id);
} }
@ -789,7 +789,7 @@ public class DroidChessController {
ti.statStr = statStr; ti.statStr = statStr;
ti.bookInfo = bookInfo; ti.bookInfo = bookInfo;
ti.eco = eco; ti.eco = eco;
ti.ecoInTree = ecoInTree; ti.distToEcoTree = distToEcoTree;
ti.pvMoves = pvMoves; ti.pvMoves = pvMoves;
ti.bookMoves = bookMoves; ti.bookMoves = bookMoves;
latestThinkingInfo = ti; latestThinkingInfo = ti;
@ -863,11 +863,11 @@ public class DroidChessController {
@Override @Override
public void notifyBookInfo(int id, String bookInfo, ArrayList<Move> moveList, public void notifyBookInfo(int id, String bookInfo, ArrayList<Move> moveList,
String eco, boolean ecoInTree) { String eco, int distToEcoTree) {
this.bookInfo = bookInfo; this.bookInfo = bookInfo;
bookMoves = moveList; bookMoves = moveList;
this.eco = eco; this.eco = eco;
this.ecoInTree = ecoInTree; this.distToEcoTree = distToEcoTree;
setSearchInfo(id); setSearchInfo(id);
} }
@ -931,11 +931,10 @@ 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, Boolean> ecoData = Pair<String, Integer> ecoData =
EcoDb.getInstance(gui.getContext()).getEco(game.tree, game.tree.currentNode); EcoDb.getInstance(gui.getContext()).getEco(game.tree);
String eco = ecoData.first; String eco = ecoData.first;
boolean ecoInTree = ecoData.second; listener.notifyBookInfo(searchId, bi.first, bi.second, eco, ecoData.second);
listener.notifyBookInfo(searchId, bi.first, bi.second, eco, ecoInTree);
} }
} }
@ -975,11 +974,10 @@ 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, Boolean> ecoData = Pair<String, Integer> ecoData =
EcoDb.getInstance(gui.getContext()).getEco(game.tree, game.tree.currentNode); EcoDb.getInstance(gui.getContext()).getEco(game.tree);
String eco = ecoData.first; String eco = ecoData.first;
boolean ecoInTree = ecoData.second; listener.notifyBookInfo(searchId, "", null, eco, ecoData.second);
listener.notifyBookInfo(searchId, "", null, eco, ecoInTree);
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

@ -51,7 +51,7 @@ public class GameTree {
public Node rootNode; public Node rootNode;
public Node currentNode; public Node currentNode;
Position currentPos; // Cached value. Computable from "currentNode". public Position currentPos; // Cached value. Computable from "currentNode".
private final PgnToken.PgnTokenReceiver gameStateListener; private final PgnToken.PgnTokenReceiver gameStateListener;
@ -1086,21 +1086,22 @@ public class GameTree {
ArrayList<Integer> ret = new ArrayList<Integer>(64); ArrayList<Integer> ret = new ArrayList<Integer>(64);
Node node = this; Node node = this;
while (node.parent != null) { while (node.parent != null) {
Node p = node.parent; ret.add(node.getChildNo());
int childNo = -1;
for (int i = 0; i < p.children.size(); i++)
if (p.children.get(i) == node) {
childNo = i;
break;
}
if (childNo == -1) throw new RuntimeException();
ret.add(childNo);
node = node.parent; node = node.parent;
} }
Collections.reverse(ret); Collections.reverse(ret);
return ret; return ret;
} }
/** Return this node's position in the parent node child list. */
public final int getChildNo() {
Node p = parent;
for (int i = 0; i < p.children.size(); i++)
if (p.children.get(i) == this)
return i;
throw new RuntimeException();
}
static final void writeToStream(DataOutputStream dos, Node node) throws IOException { static final void writeToStream(DataOutputStream dos, Node node) throws IOException {
while (true) { while (true) {
dos.writeUTF(node.moveStr); dos.writeUTF(node.moveStr);

View File

@ -72,7 +72,7 @@ public interface SearchListener {
/** Report opening book information. */ /** Report opening book information. */
public void notifyBookInfo(int id, String bookInfo, ArrayList<Move> moveList, public void notifyBookInfo(int id, String bookInfo, ArrayList<Move> moveList,
String eco, boolean ecoInTree); String eco, int distToEcoTree);
/** Report move (or command, such as "resign") played by the engine. */ /** Report move (or command, such as "resign") played by the engine. */
public void notifySearchResult(int id, String cmd, Move ponder); public void notifySearchResult(int id, String cmd, Move ponder);

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, gt.currentNode).first; String eco = ecoDb.getEco(gt).first;
assertEquals("", eco); assertEquals("", eco);
gt.goForward(0); gt.goForward(0);
eco = ecoDb.getEco(gt, gt.currentNode).first; eco = ecoDb.getEco(gt).first;
assertEquals("B00: King's pawn opening", eco); assertEquals("B00: King's pawn opening", eco);
gt.goForward(0); gt.goForward(0);
eco = ecoDb.getEco(gt, gt.currentNode).first; eco = ecoDb.getEco(gt).first;
assertEquals("C20: King's pawn game", eco); assertEquals("C20: King's pawn game", eco);
gt.goForward(0); gt.goForward(0);
eco = ecoDb.getEco(gt, gt.currentNode).first; eco = ecoDb.getEco(gt).first;
assertEquals("C40: King's knight opening", eco); assertEquals("C40: King's knight opening", eco);
gt.goForward(0); gt.goForward(0);
eco = ecoDb.getEco(gt, gt.currentNode).first; eco = ecoDb.getEco(gt).first;
assertEquals("C44: King's pawn game", eco); assertEquals("C44: King's pawn game", eco);
gt.goForward(0); gt.goForward(0);
eco = ecoDb.getEco(gt, gt.currentNode).first; eco = ecoDb.getEco(gt).first;
assertEquals("C60: Ruy Lopez (Spanish opening)", eco); assertEquals("C60: Ruy Lopez (Spanish opening)", eco);
} }
{ {
@ -65,60 +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, game.tree.currentNode).first; String eco = ecoDb.getEco(game.tree).first;
assertEquals("C42: Petrov's defence", eco); assertEquals("C42: Petrov's defence", eco);
game.processString("Nxe5"); game.processString("Nxe5");
eco = ecoDb.getEco(game.tree, game.tree.currentNode).first; eco = ecoDb.getEco(game.tree).first;
assertEquals("C42: Petrov's defence", eco); assertEquals("C42: Petrov's defence", eco);
game.processString("d6"); game.processString("d6");
eco = ecoDb.getEco(game.tree, game.tree.currentNode).first; eco = ecoDb.getEco(game.tree).first;
assertEquals("C42: Petrov's defence", eco); assertEquals("C42: Petrov's defence", eco);
game.processString("Nxf7"); game.processString("Nxf7");
eco = ecoDb.getEco(game.tree, game.tree.currentNode).first; eco = ecoDb.getEco(game.tree).first;
assertEquals("C42: Petrov, Cochrane gambit", eco); assertEquals("C42: Petrov, Cochrane gambit", eco);
game.undoMove(); game.undoMove();
eco = ecoDb.getEco(game.tree, game.tree.currentNode).first; eco = ecoDb.getEco(game.tree).first;
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, game.tree.currentNode).first; eco = ecoDb.getEco(game.tree).first;
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, game.tree.currentNode).first; String eco = ecoDb.getEco(game.tree).first;
assertEquals("B20: Sicilian defence", eco); assertEquals("B20: Sicilian defence", eco);
game.processString("h3"); game.processString("h3");
eco = ecoDb.getEco(game.tree, game.tree.currentNode).first; eco = ecoDb.getEco(game.tree).first;
assertEquals("B20: Sicilian defence", eco); assertEquals("B20: Sicilian defence", eco);
game.processString("Nc6"); game.processString("Nc6");
eco = ecoDb.getEco(game.tree, game.tree.currentNode).first; eco = ecoDb.getEco(game.tree).first;
assertEquals("B20: Sicilian defence", eco); assertEquals("B20: Sicilian defence", eco);
game.processString("g3"); game.processString("g3");
eco = ecoDb.getEco(game.tree, game.tree.currentNode).first; eco = ecoDb.getEco(game.tree).first;
assertEquals("B20: Sicilian defence", eco); 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;
assertEquals("D15: QGD Slav, Schlechter variation", eco);
assertEquals(0, ecoDb.getEco(game.tree).second.intValue());
game.processString("a4");
assertEquals("D15: QGD Slav, Schlechter variation", eco);
assertEquals(1, ecoDb.getEco(game.tree).second.intValue());
}
{
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;
assertEquals("D90: Gruenfeld, Schlechter variation", eco);
assertEquals(0, ecoDb.getEco(game.tree).second.intValue());
game.processString("h4");
assertEquals("D90: Gruenfeld, Schlechter variation", eco);
assertEquals(1, ecoDb.getEco(game.tree).second.intValue());
game.processString("h5");
assertEquals("D90: Gruenfeld, Schlechter variation", eco);
assertEquals(2, ecoDb.getEco(game.tree).second.intValue());
}
} }
public void testEcoFromFEN() throws Throwable { public void testEcoFromFEN() throws Throwable {
EcoDb ecoDb = EcoDb.getInstance(getContext()); EcoDb ecoDb = EcoDb.getInstance(getContext());
GameTree gt = gtFromFEN("rnbqkbnr/ppp2ppp/8/3p4/3P4/8/PPP2PPP/RNBQKBNR w KQkq - 0 4"); GameTree gt = gtFromFEN("rnbqkbnr/ppp2ppp/4p3/3P4/3P4/8/PPP2PPP/RNBQKBNR b KQkq - 0 3");
String eco = ecoDb.getEco(gt, gt.currentNode).first; String eco = ecoDb.getEco(gt).first;
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, gt.currentNode).first; eco = ecoDb.getEco(gt).first;
assertEquals("B06: Robatsch (modern) defence", eco); assertEquals("B06: Robatsch (modern) defence", eco);
} }