UNPKG

analyze-sgf

Version:

Analyze SGF file by KataGo Parallel Analysis Engine to produce Reviewed SGF file

140 lines (114 loc) 3.83 kB
/** * @fileOverview SGF Node data structure. * Please see <https://homepages.cwi.nl/~aeb/go/misc/sgf.html>. */ /* eslint no-param-reassign: ["error", { "props": false }] */ const sgfconv = require('./sgfconv'); // Carries a SGF Node and its win rate. class Node { constructor(node, title) { // e.g., ';B[aa]', ';W[cc]'. this.node = node; this.info = title ? `${title}\n` : ''; this.report = ''; const index = node.search(/\b[BW]\[/); if (index === -1) throw Error(`Invalid NodeSequece: ${node}`); // 'B' or 'W' this.pl = node.substring(index, index + 1); } // Sets comment. setReport(report) { this.report = report; } // Gets SGF node with comments. getSGF() { if (this.sgf) return this.sgf; const comment = [this.info, this.report].filter((v) => v).join('\n'); this.sgf = comment ? sgfconv.addComment(this.node, comment) : this.node; return this.sgf; } // Calculates scoreDrop, winrateDrop, ... and sets them to this.info and // the properties of this.node. setWinrate(prevInfo, curInfo, opts) { calcWinrate(this, prevInfo, curInfo); setProperties(this, opts); } // e.g., 'BC9 B17 F16 L3 F14 R7 (B 54.61%, B 0.19)' formatPV(boardYSize) { return ( `${sgfconv.seqToPV(this.node, boardYSize)} (` + `${formatWinrate(this.winrate)}, ${formatScoreLead(this.scoreLead)}, ` + `${this.visits} visits)` ); } } // Calculates scoreDrop, winrateDrop, winrate, ... function calcWinrate(that, prevInfo, curInfo) { if (prevInfo) { that.winrateDrop = prevInfo.winrate - curInfo.winrate; that.scoreDrop = prevInfo.scoreLead - curInfo.scoreLead; if (that.pl === 'W') { that.winrateDrop = -that.winrateDrop; that.scoreDrop = -that.scoreDrop; } } if (that.pl === 'W') { that.myWinrate = 1 - curInfo.winrate; that.myScoreLead = -curInfo.scoreLead; } else { that.myWinrate = curInfo.winrate; that.myScoreLead = curInfo.scoreLead; } that.winrate = curInfo.winrate; that.scoreLead = curInfo.scoreLead; that.visits = curInfo.visits; } const float = (f) => parseFloat(f).toFixed(2); // e.g., // * Win rate: B 51.74% // * Score lead: W 0.20 // * Win rate drop: B ⇣30.29% // * Score drop: B ⇣4.31 // * Visits: 1015 const getWinratesInfo = (that) => [ `* Win rate: ${formatWinrate(that.winrate)}\n` + `* Score lead: ${formatScoreLead(that.scoreLead)}\n`, that.winrateDrop !== undefined ? `* Win rate drop: ${that.pl}${float(that.winrateDrop * 100)}%\n` + `* Score drop: ${that.pl}${float(that.scoreDrop)}\n` : '', `* Visits: ${that.visits}\n`, ].join(''); // Sets winrate, scoreDrop, winrateDrop, ... to that.info and the properties // of that.node. function setProperties(that, opts) { if (that.propertiesGot === true) return; that.propertiesGot = true; if (that.winrate != null) { // Does not add winrate report to SGF comment property. Adds it when // Node.getSGF() is called. that.info += `\n${getWinratesInfo(that)}`; // RSGF win rate. that.node = sgfconv.addProperty( that.node, `SBKV[${float(that.winrate * 100)}]`, 0, ); } if (that.winrateDrop < opts.maxWinrateDropForGoodMove / 100) that.node = sgfconv.toGoodNode(that.node); else if (that.winrateDrop > opts.minWinrateDropForBadHotSpot / 100) that.node = sgfconv.toBadHotSpot(that.node); else if (that.winrateDrop > opts.minWinrateDropForBadMove / 100) that.node = sgfconv.toBadNode(that.node); } function formatWinrate(winrate) { const v = float(winrate * 100); return v > 50 ? `B ${v}%` : `W ${float(100 - v)}%`; } function formatScoreLead(scoreLead) { const v = float(scoreLead); return v > 0 ? `B ${v}` : `W ${float(-v)}`; } module.exports = Node;