/*
Texel - A UCI chess engine.
Copyright (C) 2012-2013 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/>.
*/
/*
* treeLogger.cpp
*
* Created on: Feb 25, 2012
* Author: petero
*/
#include "treeLogger.hpp"
#include "transpositionTable.hpp"
#include "textio.hpp"
#include "position.hpp"
#include "constants.hpp"
#include <iostream>
#include <iomanip>
#include <cassert>
void
TreeLoggerWriter::open(const std::string& filename,
ParallelData& pd0, int threadNo0) {
auto fn = filename + std::string(".") + num2Str(threadNo0);
os.open(fn.c_str(), std::ios_base::out |
std::ios_base::binary |
std::ios_base::trunc);
opened = true;
pd = &pd0;
threadNo = threadNo0;
}
void
TreeLoggerWriter::close() {
if (opened) {
if (nInWriteCache > 0) {
os.write((const char*)writeCache, Entry::bufSize * nInWriteCache);
nInWriteCache = 0;
}
opened = false;
os.close();
}
}
void
TreeLoggerWriter::writePosition(const Position& pos, int owningThread, U64 parentIndex, int moveNo) {
Position::SerializeData data;
pos.serialize(data);
entry.type = (nextIndex == 0) ? EntryType::POSITION_INCOMPLETE : EntryType::POSITION_PART0;
entry.p0.nextIndex = endMark;
entry.p0.word0 = data.v[0];
entry.p0.word1 = data.v[1];
appendEntry(entry);
nextIndex++;
entry.type = EntryType::POSITION_PART1;
entry.p1.t0Index = pd->t0Index;
entry.p1.word2 = data.v[2];
entry.p1.word3 = data.v[3];
appendEntry(entry);
nextIndex++;
entry.type = EntryType::POSITION_PART2;
entry.p2.word4 = data.v[4];
entry.p2.owningThread = owningThread;
entry.p2.parentIndex = (U32)parentIndex; // Pierre-Marie Baty -- added type cast
entry.p2.moveNo = moveNo;
appendEntry(entry);
nextIndex++;
}
void
TreeLoggerWriter::appendEntry(const Entry& entry) {
entry.serialize(entryBuffer);
memcpy(&writeCache[Entry::bufSize * nInWriteCache], entryBuffer, Entry::bufSize);
nInWriteCache++;
if (nInWriteCache == writeCacheSize) {
os.write((const char*)writeCache, Entry::bufSize * nInWriteCache);
nInWriteCache = 0;
}
}
TreeLoggerReader::TreeLoggerReader(const std::string& filename)
: fs(filename.c_str(), std::ios_base::out |
std::ios_base::in |
std::ios_base::binary),
filePos(-1), numEntries(0) {
fs.exceptions(std::ifstream::failbit | std::ifstream::badbit);
fs.seekg(0, std::ios_base::end);
S64 fileLen = fs.tellg();
numEntries = fileLen / Entry::bufSize;
computeForwardPointers();
}
void
TreeLoggerReader::close() {
fs.close();
}
void
TreeLoggerReader::main(const std::string& filename) {
try {
TreeLoggerReader an(filename);
an.mainLoop();
an.close();
} catch (const std::exception& ex) {
std::cout << "Error: " << ex.what() << std::endl;
}
}
void
TreeLoggerReader::computeForwardPointers() {
readEntry(0, entry);
if (entry.type != EntryType::POSITION_INCOMPLETE)
return;
std::cout << "Computing forward pointers..." << std::endl;
const size_t batchSize = 1000000;
std::vector<std::pair<U64,U64>> toWrite;
toWrite.reserve(batchSize);
U64 prevPosIdx = 0;
for (U64 i = 0; i < numEntries; i++) {
readEntry(i, entry);
if (entry.type == EntryType::NODE_END) {
U64 idx = entry.ee.startIndex;
toWrite.push_back(std::make_pair(idx, i));
} else if (entry.type == EntryType::POSITION_PART0) {
if (i > prevPosIdx)
toWrite.push_back(std::make_pair(prevPosIdx, i));
prevPosIdx = i;
}
if (toWrite.size() >= batchSize) {
flushForwardPointerData(toWrite);
toWrite.clear();
toWrite.reserve(batchSize);
}
}
flushForwardPointerData(toWrite);
readEntry(0, entry);
assert(entry.type == EntryType::POSITION_INCOMPLETE);
entry.type = EntryType::POSITION_PART0;
writeEntry(0, entry);
fs.flush();
std::cout << "Computing forward pointers... done" << std::endl;
}
void
TreeLoggerReader::flushForwardPointerData(std::vector<std::pair<U64,U64>>& toWrite) {
if (toWrite.empty())
return;
std::sort(toWrite.begin(), toWrite.end());
const U64 eSize = Entry::bufSize;
const U64 cacheSize = 512;
U8 cacheBuf[eSize * cacheSize];
const U64 emptyMark = std::numeric_limits<U64>::max();
U64 cacheStart = emptyMark; // Index for first entry in cache
for (auto p : toWrite) {
const U64 startIdx = p.first;
const U64 endIdx = p.second;
if ((cacheStart != emptyMark) &&
((startIdx < cacheStart) || (startIdx >= cacheStart + cacheSize))) { // flush
int nWrite = (int)std::min(cacheSize, numEntries - cacheStart); // Pierre-Marie Baty -- added type cast
fs.seekp(cacheStart * eSize, std::ios_base::beg);
fs.write((const char*)cacheBuf, nWrite * eSize);
cacheStart = emptyMark;
}
if (cacheStart == emptyMark) {
cacheStart = startIdx;
int nRead = (int)std::min(cacheSize, numEntries - cacheStart); // Pierre-Marie Baty -- added type cast
fs.seekg(cacheStart * eSize, std::ios_base::beg);
fs.read((char*)cacheBuf, nRead * eSize);
}
U8* entAddr = &cacheBuf[(startIdx - cacheStart) * eSize];
entry.deSerialize(entAddr);
if (entry.type == EntryType::NODE_START) {
entry.se.endIndex = (U32)endIdx; // Pierre-Marie Baty -- added type cast
} else if ((entry.type == EntryType::POSITION_PART0) ||
(entry.type == EntryType::POSITION_INCOMPLETE)) {
entry.p0.nextIndex = (U32)endIdx; // Pierre-Marie Baty -- added type cast
} else
assert(false);
entry.serialize(entAddr);
}
if (cacheStart != emptyMark) { // flush
int nWrite = (int)std::min(cacheSize, numEntries - cacheStart); // Pierre-Marie Baty -- added type cast
fs.seekp(cacheStart * eSize, std::ios_base::beg);
fs.write((const char*)cacheBuf, nWrite * eSize);
}
filePos = -1;
}
void
TreeLoggerReader::getRootNode(U64 index, Position& pos, int& owningThread,
U64& parentIndex, int& moveNo, U64& t0Index) {
readEntry(index, entry);
if (entry.type == EntryType::POSITION_PART1) {
index--;
readEntry(index, entry);
} else if (entry.type == EntryType::POSITION_PART2) {
index -= 2;
readEntry(index, entry);
}
assert((entry.type == EntryType::POSITION_INCOMPLETE) ||
(entry.type == EntryType::POSITION_PART0));
Position::SerializeData data;
data.v[0] = entry.p0.word0;
data.v[1] = entry.p0.word1;
readEntry(index + 1, entry);
assert(entry.type == EntryType::POSITION_PART1);
t0Index = entry.p1.t0Index;
data.v[2] = entry.p1.word2;
data.v[3] = entry.p1.word3;
readEntry(index + 2, entry);
assert(entry.type == EntryType::POSITION_PART2);
data.v[4] = entry.p2.word4;
owningThread = entry.p2.owningThread;
parentIndex = entry.p2.parentIndex;
moveNo = entry.p2.moveNo;
pos.deSerialize(data);
}
void
TreeLoggerReader::readEntry(U64 index, Entry& entry) {
S64 offs = index * Entry::bufSize;
if (offs != filePos)
fs.seekg(offs, std::ios_base::beg);
fs.read((char*)entryBuffer, sizeof(entryBuffer));
filePos = offs + sizeof(entryBuffer);
entry.deSerialize(entryBuffer);
}
void
TreeLoggerReader::writeEntry(U64 index, const Entry& entry) {
entry.serialize(entryBuffer);
S64 offs = index * Entry::bufSize;
fs.seekp(offs, std::ios_base::beg);
fs.write((const char*)entryBuffer, sizeof(entryBuffer));
}
static bool isNoMove(const Move& m) {
return (m.from() == 1) && (m.to() == 1);
}
std::string moveToStr(const Move& m) {
if (m.isEmpty())
return "null";
else if (isNoMove(m))
return "----";
else
return TextIO::moveToUCIString(m);
}
void
TreeLoggerReader::mainLoop() {
S64 currIndex = -1;
std::string prevStr = "";
bool doPrint = true;
while (true) {
if (doPrint) {
if (currIndex >= 0) {
std::vector<Move> moves;
U64 rootIdx = getMoveSequence(currIndex, moves);
if (currIndex != (S64)rootIdx) {
printNodeInfo(rootIdx);
for (size_t i = 0; i < moves.size(); i++) {
const Move& m = moves[i];
std::cout << " " << moveToStr(m);
}
std::cout << std::endl;
}
printNodeInfo(currIndex);
Position pos = getPosition(currIndex);
std::cout << TextIO::asciiBoard(pos);
std::cout << TextIO::toFEN(pos) << std::endl;
std::cout << num2Hex(pos.historyHash()) << std::endl;
if (currIndex != (S64)rootIdx) {
std::vector<U64> children;
findChildren(currIndex, children);
for (size_t i = 0; i < children.size(); i++)
printNodeInfo(children[i], i);
}
} else {
std::cout << std::setw(8) << currIndex << " entries:" << numEntries << std::endl;
}
}
doPrint = true;
std::cout << "Command:" << std::flush;
std::string cmdStr;
std::getline(std::cin, cmdStr);
if (!std::cin)
return;
if (cmdStr.length() == 0)
cmdStr = prevStr;
if (startsWith(cmdStr, "q")) {
return;
} else if (startsWith(cmdStr, "?")) {
printHelp();
doPrint = false;
} else if (isMove(cmdStr)) {
if (currIndex >= 0) {
std::vector<U64> children;
findChildren(currIndex, children);
std::string m = cmdStr;
StartEntry se {};
EndEntry ee {};
std::vector<U64> found;
for (size_t i = 0; i < children.size(); i++) {
readEntries((int)children[i], se, ee); // Pierre-Marie Baty -- added type cast
if (moveToStr(se.getMove()) == m)
found.push_back(children[i]);
}
if (found.empty()) {
std::cout << "No such move" << std::endl;
doPrint = false;
} else if (found.size() > 1) {
std::cout << "Ambiguous move" << std::endl;
for (size_t i = 0; i < found.size(); i++)
printNodeInfo(found[i]);
doPrint = false;
} else {
currIndex = found[0];
}
} else
doPrint = false;
} else if (startsWith(cmdStr, "u")) {
int n = getArg(cmdStr, 1);
for (int i = 0; i < n; i++)
currIndex = findParent(currIndex);
} else if (startsWith(cmdStr, "lp")) {
std::vector<int> args;
getArgs(cmdStr, 0, args);
if (args.size() == 2) {
int th = args[0];
U64 idx = args[1];
std::vector<U64> positions;
findChildren(-1, positions);
for (size_t p = 0; p < positions.size(); p++) {
U64 posIdx = positions[p];
Position pos;
int owningThread, moveNo;
U64 parentIndex;
U64 t0Index;
getRootNode(posIdx, pos, owningThread, parentIndex, moveNo, t0Index);
if ((owningThread == th) && (parentIndex == idx)) {
printNodeInfo(posIdx, p);
std::vector<U64> children;
findChildren(posIdx, children);
for (size_t c = 0; c < children.size(); c++)
printNodeInfo(children[c], c);
}
}
}
doPrint = false;
} else if (startsWith(cmdStr, "l")) {
std::vector<U64> children;
findChildren(currIndex, children);
if (currIndex >= 0) {
bool onlyBest = startsWith(cmdStr, "lb");
std::string m = getArgStr(cmdStr, "");
if (onlyBest) {
int bestDepth = -1;
int bestScore = std::numeric_limits<int>::min();
for (size_t i = 0; i < children.size(); i++) {
StartEntry se {};
EndEntry ee {};
bool haveEE = readEntries((int)children[i], se, ee); // Pierre-Marie Baty -- added type cast
if (!haveEE || (ee.scoreType == TType::T_GE) || isNoMove(se.getMove()))
continue;
int d = se.depth;
if ((ee.scoreType == TType::T_EXACT) && (ee.score > se.beta))
continue;
if ((d > bestDepth) || ((d == bestDepth) && (-ee.score > bestScore))) {
bool falseFH = false;
if (ee.scoreType == TType::T_LE) {
for (size_t ci = i+1; ci < children.size(); ci++) {
StartEntry se2 {};
EndEntry ee2 {};
bool haveEE2 = readEntries((int)children[ci], se2, ee2); // Pierre-Marie Baty -- added type cast
if (!haveEE2 || !(se2.move == se.move))
break;
if ((ee2.scoreType == TType::T_GE) ||
((ee2.scoreType == TType::T_EXACT) && (ee2.score > ee.score))) {
falseFH = true;
break;
}
if (ee2.scoreType == TType::T_EXACT)
break;
}
}
if (!falseFH) {
printNodeInfo(children[i], i, m);
bestDepth = d;
bestScore = -ee.score;
}
}
}
} else {
for (size_t i = 0; i < children.size(); i++)
printNodeInfo(children[i], i, m);
}
} else {
for (size_t i = 0; i < children.size(); i++)
printNodeInfo(children[i], i);
}
doPrint = false;
} else if (startsWith(cmdStr, "n")) {
if (currIndex >= 0) {
std::vector<U64> nodes;
getNodeSequence(currIndex, nodes);
for (size_t i = 0; i < nodes.size(); i++)
printNodeInfo(nodes[i]);
}
doPrint = false;
} else if (startsWith(cmdStr, "d")) {
std::vector<int> nVec;
getArgs(cmdStr, 0, nVec);
for (int n : nVec) {
std::vector<U64> children;
findChildren(currIndex, children);
if ((n >= 0) && (n < (int)children.size())) {
currIndex = children[n];
} else
break;
}
} else if (startsWith(cmdStr, "p")) {
if (currIndex >= 0) {
std::vector<Move> moves;
getMoveSequence(currIndex, moves);
for (size_t i = 0; i < moves.size(); i++)
std::cout << ' ' << moveToStr(moves[i]);
std::cout << std::endl;
}
doPrint = false;
} else if (startsWith(cmdStr, "h")) {
bool onlyPrev = startsWith(cmdStr, "hp");
U64 hashKey = currIndex >= 0 ? getPosition(currIndex).historyHash() : (U64)-1;
hashKey = getHashKey(cmdStr, hashKey);
std::vector<U64> nodes;
getNodesForHashKey(hashKey, nodes, onlyPrev ? currIndex+1 : numEntries);
for (size_t i = 0; i < nodes.size(); i++)
printNodeInfo(nodes[i]);
doPrint = false;
} else {
S64 i;
if (str2Num(cmdStr, i))
if ((i >= -1) && (i < (S64)numEntries))
currIndex = i;
}
prevStr = cmdStr;
}
}
bool
TreeLoggerReader::isMove(std::string cmdStr) const {
if (cmdStr.length() != 4)
return false;
cmdStr = toLowerCase(cmdStr);
for (int i = 0; i < 4; i++) {
int c = cmdStr[i];
if ((i == 0) || (i == 2)) {
if ((c < 'a') || (c > 'h'))
return false;
} else {
if ((c < '1') || (c > '8'))
return false;
}
}
return true;
}
void
TreeLoggerReader::getNodesForHashKey(U64 hashKey, std::vector<U64>& nodes, U64 maxEntry) {
Position pos;
for (U64 index = 0; index < maxEntry; index++) {
readEntry(index, entry);
if (entry.type == EntryType::NODE_END) {
if (entry.ee.hashKey == hashKey)
nodes.push_back(entry.ee.startIndex);
} else if (entry.type == EntryType::POSITION_PART0) {
getRootNode(index, pos);
if (pos.historyHash() == hashKey)
nodes.push_back(index);
}
}
std::sort(nodes.begin(), nodes.end());
}
U64
TreeLoggerReader::getHashKey(std::string& s, U64 defKey) const {
U64 key = defKey;
size_t idx = s.find_first_of(' ');
if (idx != s.npos) {
s = s.substr(idx + 1);
if (startsWith(s, "0x"))
s = s.substr(2);
hexStr2Num(s, key);
}
return key;
}
int
TreeLoggerReader::getArg(const std::string& s, int defVal) {
size_t idx = s.find_first_of(' ');
if (idx != s.npos) {
int tmp;
if (str2Num(s.substr(idx+1), tmp))
return tmp;
}
return defVal;
}
void
TreeLoggerReader::getArgs(const std::string& s, int defVal, std::vector<int>& args) {
std::vector<std::string> split;
splitString(s, split);
for (size_t i = 1; i < split.size(); i++) {
int tmp;
if (!str2Num(split[i], tmp)) {
args.clear();
break;
}
args.push_back(tmp);
}
if (args.empty())
args.push_back(defVal);
}
std::string
TreeLoggerReader::getArgStr(const std::string& s, const std::string& defVal) {
size_t idx = s.find_first_of(' ');
if (idx != s.npos)
return s.substr(idx+1);
return defVal;
}
void
TreeLoggerReader::printHelp() {
std::cout << " p - Print move sequence" << std::endl;
std::cout << " n - Print node info corresponding to move sequence" << std::endl;
std::cout << " l [move] - List child nodes, optionally only for one move" << std::endl;
std::cout << " lb [move] - List best child nodes, optionally only for one move" << std::endl;
std::cout << " lp th idx - List positions requested by thread th at index idx" << std::endl;
std::cout << " d [n1 [n2...]] - Go to child \"n\"" << std::endl;
std::cout << " move - Go to child \"move\", if unique" << std::endl;
std::cout << " u [levels] - Move up" << std::endl;
std::cout << " h [key] - Find nodes with current or given hash key" << std::endl;
std::cout << " hp [key] - Find nodes with current or given hash key before current node" << std::endl;
std::cout << " num - Go to node \"num\"" << std::endl;
std::cout << " q - Quit" << std::endl;
std::cout << " ? - Print this help" << std::endl;
}
bool
TreeLoggerReader::readEntries(int index, StartEntry& se, EndEntry& ee) {
readEntry(index, entry);
if (entry.type == EntryType::NODE_START) {
se = entry.se;
U32 eIdx = se.endIndex;
if (eIdx == endMark)
return false;
readEntry(eIdx, entry);
assert(entry.type == EntryType::NODE_END);
ee = entry.ee;
} else {
assert(entry.type == EntryType::NODE_END);
ee = entry.ee;
readEntry(ee.startIndex, entry);
assert(entry.type == EntryType::NODE_START);
se = entry.se;
}
return true;
}
S64
TreeLoggerReader::findParent(S64 index) {
if (index < 0)
return -1;
readEntry(index, entry);
if (entry.type == EntryType::NODE_END) {
index = entry.ee.startIndex;
readEntry(index, entry);
}
if (entry.type == EntryType::NODE_START) {
return entry.se.parentIndex;
} else if ((entry.type == EntryType::POSITION_PART0) ||
(entry.type == EntryType::POSITION_PART1) ||
(entry.type == EntryType::POSITION_PART2)) {
return -1;
} else {
assert(false);
return -1;
}
}
void
TreeLoggerReader::findChildren(S64 index, std::vector<U64>& childs) {
if (index < 0) {
if (numEntries == 0)
return;
U64 child = 0;
while (child < numEntries - 1) {
readEntry(child, entry);
assert(entry.type == EntryType::POSITION_PART0);
childs.push_back(child);
child = entry.p0.nextIndex;
}
} else {
readEntry(index, entry);
U64 child;
switch (entry.type) {
case EntryType::NODE_END:
index = entry.ee.startIndex;
// Fall through
case EntryType::NODE_START:
child = index + 1;
break;
case EntryType::POSITION_PART2:
index--;
// Fall through
case EntryType::POSITION_PART1:
index--;
// Fall through
case EntryType::POSITION_PART0:
child = index + 3;
break;
default:
assert(false);
}
StartEntry se {};
EndEntry ee {};
while (child < numEntries) {
readEntry(child, entry);
if (entry.type != EntryType::NODE_START)
break;
bool haveEE = readEntries((int)child, se, ee); // Pierre-Marie Baty -- added type cast
if (se.parentIndex == index)
childs.push_back(child);
if (!haveEE)
break;
if (se.endIndex == endMark)
break;
child = se.endIndex + 1;
}
}
}
int
TreeLoggerReader::getChildNo(U64 index) {
readEntry(index, entry);
if (entry.type == EntryType::NODE_END) {
index = entry.ee.startIndex;
readEntry(index, entry);
} else if (entry.type == EntryType::POSITION_PART1) {
index--;
readEntry(index, entry);
} else if (entry.type == EntryType::POSITION_PART2) {
index -= 2;
readEntry(index, entry);
}
std::vector<U64> childs;
if (entry.type == EntryType::NODE_START) {
findChildren(entry.se.parentIndex, childs);
} else if (entry.type == EntryType::POSITION_PART0) {
findChildren(-1, childs);
} else
assert(false);
for (int i = 0; i < (int)childs.size(); i++)
if (childs[i] == index)
return i;
return -1;
}
void
TreeLoggerReader::getNodeSequence(U64 index, std::vector<U64>& nodes) {
nodes.push_back(index);
while (true) {
readEntry(index, entry);
if (entry.type == EntryType::NODE_END) {
index = entry.ee.startIndex;
readEntry(index, entry);
}
if (entry.type == EntryType::NODE_START) {
index = entry.se.parentIndex;
nodes.push_back(index);
} else
break;
}
std::reverse(nodes.begin(), nodes.end());
}
U64
TreeLoggerReader::getMoveSequence(U64 index, std::vector<Move>& moves) {
StartEntry se {};
EndEntry ee {};
while (true) {
readEntry(index, entry);
if ((entry.type == EntryType::NODE_START) ||
(entry.type == EntryType::NODE_END)) {
readEntries((int)index, se, ee); // Pierre-Marie Baty -- added type cast
moves.push_back(se.getMove());
index = findParent(index);
} else
break;
}
std::reverse(moves.begin(), moves.end());
return index;
}
Position
TreeLoggerReader::getPosition(U64 index) {
std::vector<Move> moves;
U64 rootPosIdx = getMoveSequence(index, moves);
Position pos;
getRootNode(rootPosIdx, pos);
UndoInfo ui;
for (size_t i = 0; i < moves.size(); i++)
if (!isNoMove(moves[i]))
pos.makeMove(moves[i], ui);
return pos;
}
void
TreeLoggerReader::printNodeInfo(U64 index, int childNo, const std::string& filterMove) {
if (childNo == -1)
childNo = getChildNo(index);
readEntry(index, entry);
if ((entry.type == EntryType::NODE_START) ||
(entry.type == EntryType::NODE_END)) {
StartEntry se {};
EndEntry ee {};
bool haveEE = readEntries((int)index, se, ee); // Pierre-Marie Baty -- added type cast
std::string m = moveToStr(se.getMove());
if ((filterMove.length() > 0) && (m != filterMove))
return;
using SearchConst::plyScale;
std::cout << std::setw(3) << childNo
<< ' ' << std::setw(8) << index
<< ' ' << std::setw(8) << se.t0Index
<< ' ' << std::setw(8) << ee.t0Index
<< ' ' << m
<< " a:" << std::setw(6) << se.alpha
<< " b:" << std::setw(6) << se.beta
<< " p:" << std::setw(2) << (int)se.ply
<< " d:" << std::setw(2) << (int)se.depth/plyScale
<< '.' << ((int)se.depth % plyScale);
if (haveEE) {
int subTreeNodes = (se.endIndex - ee.startIndex - 1) / 2;
std::string type;
switch (ee.scoreType) {
case TType::T_EXACT: type = "= "; break;
case TType::T_GE : type = ">="; break;
case TType::T_LE : type = "<="; break;
default : type = " "; break;
}
std::cout << " s:" << type << std::setw(6) << ee.score
<< " e:" << std::setw(6) << ee.evalScore
<< " sub:" << subTreeNodes;
}
std::cout << std::endl;
} else if ((entry.type == EntryType::POSITION_PART0) ||
(entry.type == EntryType::POSITION_PART1) ||
(entry.type == EntryType::POSITION_PART2)) {
Position pos;
int owningThread, moveNo;
U64 parentIndex;
U64 t0Index;
getRootNode(index, pos, owningThread, parentIndex, moveNo, t0Index);
std::cout << std::setw(3) << childNo
<< ' ' << std::setw(8) << index
<< ' ' << owningThread
<< ' ' << std::setw(8) << parentIndex
<< ' ' << std::setw(2) << moveNo
<< ' ' << std::setw(8) << t0Index
<< ' ' << TextIO::toFEN(pos) << std::endl;
} else
assert(false);
}