/* CuckooChess - A java chess program. Copyright (C) 2011 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 chess; import java.io.BufferedOutputStream; import java.io.BufferedReader; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.RandomAccessFile; import java.nio.ByteBuffer; import java.nio.MappedByteBuffer; import java.nio.channels.FileChannel; import java.nio.channels.FileChannel.MapMode; import java.util.ArrayList; import java.util.Collections; import chess.TranspositionTable.TTEntry; public final class TreeLogger { private byte[] entryBuffer = new byte[16]; private ByteBuffer bb = ByteBuffer.wrap(entryBuffer); // Used in write mode private FileOutputStream os = null; private BufferedOutputStream bos = null; private long nextIndex = 0; // Used in analyze mode private MappedByteBuffer mapBuf = null; private FileChannel fc = null; private int numEntries = 0; private TreeLogger() { } /** Get a logger object set up for writing to a log file. */ public static final TreeLogger getWriter(String filename, Position pos) { try { TreeLogger log = new TreeLogger(); log.os = new FileOutputStream(filename); log.bos = new BufferedOutputStream(log.os, 65536); log.writeHeader(pos); log.nextIndex = 0; return log; } catch (FileNotFoundException e) { throw new RuntimeException(); } } private final void writeHeader(Position pos) { try { byte[] fen = TextIO.toFEN(pos).getBytes(); bos.write((byte)(fen.length)); bos.write(fen); byte[] pad = new byte[128-1-fen.length]; for (int i = 0; i < pad.length; i++) pad[i] = 0; bos.write(pad); } catch (IOException e) { throw new RuntimeException(); } } /** Get a logger object set up for analyzing a log file. */ public static final TreeLogger getAnalyzer(String filename) { try { TreeLogger log = new TreeLogger(); RandomAccessFile raf; raf = new RandomAccessFile(filename, "rw"); log.fc = raf.getChannel(); long len = raf.length(); log.numEntries = (int) ((len - 128) / 16); log.mapBuf = log.fc.map(MapMode.READ_WRITE, 0, len); log.computeForwardPointers(); return log; } catch (FileNotFoundException e) { throw new RuntimeException(); } catch (IOException e) { throw new RuntimeException(); } } public final void close() { try { if (bos != null) bos.close(); if (fc != null) fc.close(); } catch (IOException e) { } } /* This is the on-disk format. Big-endian byte-order is used. * First there is one header entry. Then there is a set of start/end entries. * A StartEntry can be identified by its first 4 bytes (endIndex/startIndex) * being either -1 (endIndex not computed), or > the entry index. * * private static final class Header { * byte fenLen; // Used length of fen array * byte[] fen; // 126 bytes, 0-padded * byte flags; // bit 0: 1 if endIndex has been computed for all StartEntries. * } * * private static final class StartEntry { * int endIndex; * int parentIndex; // -1 for root node * short move; * short alpha; * short beta; * byte ply; * byte depth; * } * * private static final class EndEntry { * int startIndex; * short score; * short scoreType; * short evalScore; * byte[] hashKey; // lower 6 bytes of position hash key * } */ // ---------------------------------------------------------------------------- // Functions used for tree logging /** * Log information when entering a search node. * @param parentId Index of parent node. * @param m Move made to go from parent node to this node * @param alpha Search parameter * @param beta Search parameter * @param ply Search parameter * @param depth Search parameter * @return node index */ final long logNodeStart(long parentIndex, Move m, int alpha, int beta, int ply, int depth) { bb.putInt ( 0, (int)-1); bb.putInt ( 4, (int)parentIndex); bb.putShort( 8, (short)(m.from + (m.to << 6) + (m.promoteTo << 12))); bb.putShort(10, (short)alpha); bb.putShort(12, (short)beta); bb.put (14, (byte)ply); bb.put (15, (byte)depth); try { bos.write(bb.array()); } catch (IOException e) { throw new RuntimeException(); } return nextIndex++; } /** * @param startIndex Pointer to corresponding start node entry. * @param score Computed score for this node. * @param scoreType See TranspositionTable, T_EXACT, T_GE, T_LE. * @param evalScore Score returned by evaluation function at this node, if known. * @return node index */ final long logNodeEnd(long startIndex, int score, int scoreType, int evalScore, long hashKey) { bb.putInt ( 0, (int)startIndex); bb.putShort( 4, (short)score); bb.putShort( 6, (short)scoreType); bb.putLong( 8, hashKey); bb.putShort( 8, (short)evalScore); // Overwrites first two byte of hashKey try { bos.write(bb.array()); } catch (IOException e) { throw new RuntimeException(); } return nextIndex++; } // ---------------------------------------------------------------------------- // Functions used for tree analyzing private static final int indexToFileOffs(int index) { return 128 + index * 16; } /** Compute endIndex for all StartNode entries. */ private final void computeForwardPointers() { if ((mapBuf.get(127) & (1<<7)) != 0) return; System.out.printf("Computing forward pointers...\n"); StartEntry se = new StartEntry(); EndEntry ee = new EndEntry(); for (int i = 0; i < numEntries; i++) { boolean isStart = readEntry(i, se, ee); if (!isStart) { int offs = indexToFileOffs(ee.startIndex); mapBuf.putInt(offs, i); } } mapBuf.put(127, (byte)(1 << 7)); mapBuf.force(); System.out.printf("Computing forward pointers... done\n"); } /** Get FEN string for root node position. */ private final String getRootNodeFEN() { int len = mapBuf.get(0); byte[] fenB = new byte[len]; for (int i = 0; i < len; i++) fenB[i] = mapBuf.get(1+i); String ret = new String(fenB); return ret; } static final class StartEntry { int endIndex; int parentIndex; // -1 for root node Move move; short alpha; short beta; byte ply; byte depth; } static final class EndEntry { int startIndex; short score; short scoreType; short evalScore; long hashKey; // Note! Upper 2 bytes are not valid (ie 0) } /** Read a start/end entry. * @return True if entry was a start entry, false if it was an end entry. */ private final boolean readEntry(int index, StartEntry se, EndEntry ee) { int offs = indexToFileOffs(index); for (int i = 0; i < 16; i++) bb.put(i, mapBuf.get(offs + i)); int otherIndex = bb.getInt(0); boolean isStartEntry = (otherIndex == -1) || (otherIndex > index); if (isStartEntry) { se.endIndex = otherIndex; se.parentIndex = bb.getInt(4); int m = bb.getShort(8); se.move = new Move(m & 63, (m >> 6) & 63, (m >> 12) & 15); se.alpha = bb.getShort(10); se.beta = bb.getShort(12); se.ply = bb.get(14); se.depth = bb.get(15); } else { ee.startIndex = otherIndex; ee.score = bb.getShort(4); ee.scoreType = bb.getShort(6); ee.evalScore = bb.getShort(8); ee.hashKey = bb.getLong(8) & 0x0000ffffffffffffL; } return isStartEntry; } // ---------------------------------------------------------------------------- // Functions used for the interactive tree browser public static final void main(String[] args) throws IOException { if (args.length != 1) { System.out.printf("Usage: progname filename\n"); System.exit(1); } TreeLogger an = getAnalyzer(args[0]); try { Position rootPos = TextIO.readFEN(an.getRootNodeFEN()); an.mainLoop(rootPos); } catch (ChessParseError e) { throw new RuntimeException(); } an.close(); } private final void mainLoop(Position rootPos) throws IOException { int currIndex = -1; BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); String prevStr = ""; boolean doPrint = true; while (true) { if (doPrint) { ArrayList<Move> moves = getMoveSequence(currIndex); for (Move m : moves) System.out.printf(" %s", TextIO.moveToUCIString(m)); System.out.printf("\n"); printNodeInfo(rootPos, currIndex); Position pos = getPosition(rootPos, currIndex); System.out.print(TextIO.asciiBoard(pos)); System.out.printf("%s\n", TextIO.toFEN(pos)); System.out.printf("%16x\n", pos.historyHash()); if (currIndex >= 0) { ArrayList<Integer> children = findChildren(currIndex); for (Integer c : children) printNodeInfo(rootPos, c); } } doPrint = true; System.out.printf("Command:"); String cmdStr = in.readLine(); if (cmdStr == null) return; if (cmdStr.length() == 0) cmdStr = prevStr; if (cmdStr.startsWith("q")) { return; } else if (cmdStr.startsWith("?")) { printHelp(); doPrint = false; } else if (isMove(cmdStr)) { ArrayList<Integer> children = findChildren(currIndex); String m = cmdStr; StartEntry se = new StartEntry(); EndEntry ee = new EndEntry(); ArrayList<Integer> found = new ArrayList<Integer>(); for (Integer c : children) { readEntries(c, se, ee); if (TextIO.moveToUCIString(se.move).equals(m)) found.add(c); } if (found.size() == 0) { System.out.printf("No such move\n"); doPrint = false; } else if (found.size() > 1) { System.out.printf("Ambiguous move\n"); for (Integer c : found) printNodeInfo(rootPos, c); doPrint = false; } else { currIndex = found.get(0); } } else if (cmdStr.startsWith("u")) { int n = getArg(cmdStr, 1); for (int i = 0; i < n; i++) currIndex = findParent(currIndex); } else if (cmdStr.startsWith("l")) { ArrayList<Integer> children = findChildren(currIndex); String m = getArgStr(cmdStr, ""); for (Integer c : children) printNodeInfo(rootPos, c, m); doPrint = false; } else if (cmdStr.startsWith("n")) { ArrayList<Integer> nodes = getNodeSequence(currIndex); for (int node : nodes) printNodeInfo(rootPos, node); doPrint = false; } else if (cmdStr.startsWith("d")) { ArrayList<Integer> nVec = getArgs(cmdStr, 0); for (int n : nVec) { ArrayList<Integer> children = findChildren(currIndex); if ((n >= 0) && (n < children.size())) { currIndex = children.get(n); } else break; } } else if (cmdStr.startsWith("p")) { ArrayList<Move> moves = getMoveSequence(currIndex); for (Move m : moves) System.out.printf(" %s", TextIO.moveToUCIString(m)); System.out.printf("\n"); doPrint = false; } else if (cmdStr.startsWith("h")) { long hashKey = getPosition(rootPos, currIndex).historyHash(); hashKey = getHashKey(cmdStr, hashKey); ArrayList<Integer> nodes = getNodeForHashKey(hashKey); for (int node : nodes) printNodeInfo(rootPos, node); doPrint = false; } else { try { int i = Integer.parseInt(cmdStr); if ((i >= -1) && (i < numEntries)) currIndex = i; } catch (NumberFormatException e) { } } prevStr = cmdStr; } } private final boolean isMove(String cmdStr) { if (cmdStr.length() != 4) return false; cmdStr = cmdStr.toLowerCase(); for (int i = 0; i < 4; i++) { int c = cmdStr.charAt(i); if ((i == 0) || (i == 2)) { if ((c < 'a') || (c > 'h')) return false; } else { if ((c < '1') || (c > '8')) return false; } } return true; } /** Return all nodes with a given hash key. */ private final ArrayList<Integer> getNodeForHashKey(long hashKey) { hashKey &= 0x0000ffffffffffffL; ArrayList<Integer> ret = new ArrayList<Integer>(); StartEntry se = new StartEntry(); EndEntry ee = new EndEntry(); for (int index = 0; index < numEntries; index++) { boolean isStart = readEntry(index, se, ee); if (!isStart) { if (ee.hashKey == hashKey) { int sIdx = ee.startIndex; ret.add(sIdx); } } } Collections.sort(ret); return ret; } /** Get hash key from an input string. */ private final long getHashKey(String s, long defKey) { long key = defKey; int idx = s.indexOf(' '); if (idx > 0) { s = s.substring(idx + 1); if (s.startsWith("0x")) s = s.substring(2); try { key = Long.parseLong(s, 16); } catch (NumberFormatException e) { } } return key; } /** Get integer parameter from an input string. */ private static final int getArg(String s, int defVal) { try { int idx = s.indexOf(' '); if (idx > 0) { return Integer.parseInt(s.substring(idx+1)); } } catch (NumberFormatException e) { } return defVal; } /** Get a list of integer parameters from an input string. */ final ArrayList<Integer> getArgs(String s, int defVal) { ArrayList<Integer> ret = new ArrayList<Integer>(); String[] split = s.split(" "); try { for (int i = 1; i < split.length; i++) ret.add(Integer.parseInt(split[i])); } catch (NumberFormatException e) { ret.clear(); } if (ret.size() == 0) ret.add(defVal); return ret; } /** Get a string parameter from an input string. */ private static final String getArgStr(String s, String defVal) { int idx = s.indexOf(' '); if (idx > 0) return s.substring(idx+1); return defVal; } private final void printHelp() { System.out.printf(" p - Print move sequence\n"); System.out.printf(" n - Print node info corresponding to move sequence\n"); System.out.printf(" l [move] - List child nodes, optionally only for one move\n"); System.out.printf(" d [n1 [n2...]] - Go to child \"n\"\n"); System.out.printf(" move - Go to child \"move\", if unique\n"); System.out.printf(" u [levels] - Move up\n"); System.out.printf(" h [key] - Find nodes with current (or given) hash key\n"); System.out.printf(" num - Go to node \"num\"\n"); System.out.printf(" q - Quit\n"); System.out.printf(" ? - Print this help\n"); } /** Read start/end entries for a tree node. Return true if the end entry exists. */ private final boolean readEntries(int index, StartEntry se, EndEntry ee) { boolean isStart = readEntry(index, se, ee); if (isStart) { int eIdx = se.endIndex; if (eIdx >= 0) { readEntry(eIdx, null, ee); } else { return false; } } else { int sIdx = ee.startIndex; readEntry(sIdx, se, null); } return true; } /** Find the parent node to a node. */ private final int findParent(int index) { if (index >= 0) { StartEntry se = new StartEntry(); EndEntry ee = new EndEntry(); readEntries(index, se, ee); index = se.parentIndex; } return index; } /** Find all children of a node. */ private final ArrayList<Integer> findChildren(int index) { ArrayList<Integer> ret = new ArrayList<Integer>(); StartEntry se = new StartEntry(); EndEntry ee = new EndEntry(); int child = index + 1; while ((child >= 0) && (child < numEntries)) { boolean haveEE = readEntries(child, se, ee); if (se.parentIndex == index) ret.add(child); if (!haveEE) break; if (child != ee.startIndex) break; // two end entries in a row, no more children // if (se.parentIndex != index) // break; child = se.endIndex + 1; } return ret; } /** Get node position in parents children list. */ private final int getChildNo(int index) { ArrayList<Integer> childs = findChildren(findParent(index)); for (int i = 0; i < childs.size(); i++) if (childs.get(i) == index) return i; return -1; } /** Get list of nodes from root position to a node. */ private final ArrayList<Integer> getNodeSequence(int index) { ArrayList<Integer> nodes = new ArrayList<Integer>(); nodes.add(index); while (index >= 0) { index = findParent(index); nodes.add(index); } Collections.reverse(nodes); return nodes; } /** Find list of moves from root node to a node. */ private final ArrayList<Move> getMoveSequence(int index) { ArrayList<Move> moves = new ArrayList<Move>(); StartEntry se = new StartEntry(); EndEntry ee = new EndEntry(); while (index >= 0) { readEntries(index, se, ee); moves.add(se.move); index = findParent(index); } Collections.reverse(moves); return moves; } /** Find the position corresponding to a node. */ private final Position getPosition(Position rootPos, int index) { ArrayList<Move> moves = getMoveSequence(index); Position ret = new Position(rootPos); UndoInfo ui = new UndoInfo(); for (Move m : moves) ret.makeMove(m, ui); return ret; } private final void printNodeInfo(Position rootPos, int index) { printNodeInfo(rootPos, index, ""); } private final void printNodeInfo(Position rootPos, int index, String filterMove) { if (index < 0) { // Root node System.out.printf("%8d entries:%d\n", index, numEntries); } else { StartEntry se = new StartEntry(); EndEntry ee = new EndEntry(); boolean haveEE = readEntries(index, se, ee); String m = TextIO.moveToUCIString(se.move); if ((filterMove.length() > 0) && !m.equals(filterMove)) return; System.out.printf("%3d %8d %s a:%6d b:%6d p:%2d d:%2d", getChildNo(index), index, m, se.alpha, se.beta, se.ply, se.depth); if (haveEE) { int subTreeNodes = (se.endIndex - ee.startIndex - 1) / 2; String type; switch (ee.scoreType) { case TTEntry.T_EXACT: type = "= "; break; case TTEntry.T_GE : type = ">="; break; case TTEntry.T_LE : type = "<="; break; default : type = " "; break; } System.out.printf(" s:%s%6d e:%6d sub:%d", type, ee.score, ee.evalScore, subTreeNodes); } System.out.printf("\n"); } } }