analyze-sgf
Version:
Analyze SGF file by KataGo Parallel Analysis Engine to produce Reviewed SGF file
129 lines (104 loc) • 3.98 kB
JavaScript
#!/usr/bin/env node
/**
* @fileOverview Command line interface for analyze-sgf.
*/
const fs = require('fs');
const chalk = require('chalk');
const jschardet = require('jschardet');
const iconv = require('iconv-lite');
const getopts = require('./getopts');
const sgfconv = require('./sgfconv');
const katagoconv = require('./katagoconv');
const katago = require('./katago');
const GameTree = require('./gametree');
const toSGF = require('./gib2sgf');
const { httpgetSGF, isValidURL } = require('./httpget-sgf');
const log = (message) => console.error(chalk.grey(message));
const getExt = (path) =>
path.substring(1 + path.lastIndexOf('.'), path.length).toLowerCase();
// Parses args and merges them with yaml config.
const opts = getopts();
// Starts communication with KataGo.
(async () => {
for (const path of opts.paths) { // eslint-disable-line
try {
const { newPath, sgf, responses } = opts.jsonGiven
? processJSON(path)
: await processSGF(path); // eslint-disable-line
saveAnalyzed(newPath, sgf, responses, opts.saveGiven, opts.sgf);
} catch (error) {
log(`${error.message}, while processing: ${path}`);
}
}
})();
// Analyzes a SGF by KataGo Analysis JSON, not by KataGo Analysis Engine.
//
// Simply returns SGF and KataGo responses from JSON.
// JSON file format: tailless SGF + '\n' + KataGo responses.
function processJSON(path) {
if (getExt(path) !== 'json') {
log(`skipped: ${path}`);
return {};
}
const sgfAndResponses = fs.readFileSync(path).toString();
const index = sgfAndResponses.indexOf('\n');
const sgf = sgfconv.correctSGFDialects(sgfAndResponses.substring(0, index));
const responses = sgfAndResponses.substring(index + 1);
return { newPath: path, responses, sgf };
}
// Analyzes a SGF by KataGo Analysis Engine.
async function processSGF(path) {
const ext = getExt(path);
const isURL = isValidURL(path);
if (ext !== 'sgf' && ext !== 'gib' && !isURL) {
log(`skipped: ${path}`);
return {};
}
// Gets SGF file name and contents from Web, GIB, or SGF.
const { newPath, sgf } = getNewPathAndSGF(isURL, path, ext);
// Sends query to KataGo.
const responses = await katago.analyze(
katagoconv.sgfToKataGoAnalysisQuery(sgf, opts.analysis),
opts.revisit,
opts.sgf.minWinrateDropForVariations / 100,
opts.katago,
);
return { newPath, responses, sgf };
}
// Gets SGF file name and contents from Web, GIB, or SGF.
function getNewPathAndSGF(isURL, path, ext) {
// Gets SGF from web server and generates file.
if (isURL) {
const sgf = httpgetSGF(path);
const newPath = sgfconv.prettyPathFromSGF(sgf);
fs.writeFileSync(newPath, sgf);
log(`downloaded: ${newPath}`);
return { sgf, newPath };
}
// Reads a SGF/GIB file.
const content = fs.readFileSync(path);
const detected = jschardet.detect(content);
const sgf = iconv.decode(content, detected.encoding).toString();
const newSGF = ext === 'gib' ? toSGF(sgf) : sgfconv.correctSGFDialects(sgf);
return { sgf: newSGF, newPath: path };
}
// Saves SGF file and JSON responses from KataGo.
function saveAnalyzed(targetPath, sgf, responses, saveResponse, gopts) {
if (!targetPath || !sgf || !responses) return;
if (responses.search('{"error"') === 0) throw Error(responses.slice(0, -1));
const targetName = targetPath.substring(0, targetPath.lastIndexOf('.'));
// Saves analysis responses to JSON.
if (saveResponse) {
const jsonPath = `${targetName}.json`;
// JSON file format: tailless SGF + '\n' + KataGo responses.
fs.writeFileSync(jsonPath, `${sgfconv.removeTails(sgf)}\n${responses}`);
log(`generated: ${jsonPath}`);
}
// Saves analyzed SGF.
const gametree = new GameTree(sgf, responses, gopts);
const sgfPath = `${targetName}${gopts.fileSuffix}.sgf`;
fs.writeFileSync(sgfPath, gametree.getSGF());
log(`generated: ${sgfPath}`);
const report = gametree.getReport();
if (report) console.log(report);
}