<commit_before>'use strict';

var CONST = require('../../../constants');
var main = require('../../../main');
var Grid = require('../../../Grid');
var GroupInfo = require('./GroupInfo');
var Void = require('./Void');
var ZoneFiller = require('./ZoneFiller');

var EMPTY = CONST.EMPTY, GRID_BORDER = CONST.GRID_BORDER;
var BLACK = CONST.BLACK, WHITE = CONST.WHITE;
var ALIVE = GroupInfo.ALIVE;
var FAILS = GroupInfo.FAILS, LIVES = GroupInfo.LIVES, UNDECIDED = GroupInfo.UNDECIDED;


/** @class Our main board analyser / score counter etc.
 */
function BoardAnalyser() {
    this.version = 'chuckie';
    this.mode = null;
    this.goban = null;
    this.analyseGrid = null;
    this.scoreGrid = null;
    this.allVoids = [];
    this.allGroupInfos = null;
    this.scores = [0, 0];
    this.prisoners = [0, 0];
    this.filler = null;
}
module.exports = BoardAnalyser;


BoardAnalyser.prototype.countScore = function (goban) {
    if (main.debug) main.log.debug('Counting score...');
    this.scores[BLACK] = this.scores[WHITE] = 0;
    this.prisoners = goban.countPrisoners();

    if (!this.scoreGrid) this.scoreGrid = new Grid(goban.gsize, GRID_BORDER);
    if (!this._initAnalysis('SCORE', goban, this.scoreGrid)) return;
    this._runAnalysis();
};

BoardAnalyser.prototype.getScoringGrid = function () {
    return this.scoreGrid;
};

BoardAnalyser.prototype.getVoidAt = function (stone) {
    return this.analyseGrid.yx[stone.j][stone.i];
};

BoardAnalyser.prototype.startMoveAnalysis = function (goban, grid) {
    this._initAnalysis('MOVE', goban, grid);
};

BoardAnalyser.prototype.continueMoveAnalysis = function () {
    this._runAnalysis();
};

BoardAnalyser.prototype.analyseTerritory = function (goban, grid, first2play) {
    if (!this._initAnalysis('TERRITORY', goban, grid)) return;
    this._runAnalysis(first2play);
};

BoardAnalyser.prototype.image = function () {
    return this.analyseGrid.image();
};

BoardAnalyser.prototype.debugDump = function () {
    var res = 'Grid:\n' + this.analyseGrid.toText() + 'Voids:\n';
    for (var v, v_array = this.allVoids, v_ndx = 0; v=v_array[v_ndx], v_ndx < v_array.length; v_ndx++) {
        res += v.toString() + '\n';
    }
    res += 'Groups:\n';
    for (var ndx in this.allGroupInfos) {
        res += this.allGroupInfos[~~ndx].toString() + '\n';
    }
    if (this.scores) {
        res += 'Score:' + this.scores.map(function (s, i) {
            return ' player ' + i + ': ' + s + ' points';
        });
    }
    return res;
};

BoardAnalyser.prototype._initAnalysis = function (mode, goban, grid) {
    this.mode = mode;
    this.goban = goban;
    this.analyseGrid = grid;
    this.filler = new ZoneFiller(goban, grid);
    if (goban.moveNumber() === 0) return false;

    grid.initFromGoban(goban);
    this._initVoidsAndGroups();
    this._findBrothers();
    this._findEyeOwners();
    return true;
};

BoardAnalyser.prototype._runAnalysis = function (first2play) {
    if (this.mode === 'MOVE') {
        this._lifeOrDeathLoop();
    } else {
        this._findBattleWinners();
        this._lifeOrDeathLoop(first2play);
        this._findDameVoids();
        this._finalColoring();
    }
};

BoardAnalyser.prototype._addGroup = function (g, v) {
    var gi = this.allGroupInfos[g.ndx];
    if (!gi) {
        if (!g._info || g._info.boan !== this) {
            g._info = new GroupInfo(g, this);
        } else {
            g._info.resetAnalysis();
        }
        gi = this.allGroupInfos[g.ndx] = g._info;
    }
    gi.nearVoids.push(v);
};

/** Create the list of voids and groups.
 * Voids know which groups are around them, but groups do not own any void yet.
 */
BoardAnalyser.prototype._initVoidsAndGroups = function () {
    if (main.debug) main.log.debug('---Initialising voids & groups...');
    var voidCode = Grid.ZONE_CODE;
    this.allGroupInfos = {};
    this.allVoids.clear();
    var n, groups, goban = this.goban;
    var v = new Void(goban, voidCode++);
    for (var j = 1; j <= goban.gsize; j++) {
        for (var i = 1; i <= goban.gsize; i++) {
            var vcount = this.filler.fillWithColor(i, j, EMPTY, v);
            if (vcount === 0) continue;

            // 1 new void created
            this.allVoids.push(v);
            // keep all the groups
            for (var color = BLACK; color <= WHITE; color++) {
                groups = v.groups[color];
                for (n = groups.length - 1; n >= 0; n--) this._addGroup(groups[n], v);
            }

            v = new Void(goban, voidCode++);
        }
    }
};

BoardAnalyser.prototype._findBrothers = function () {
    for (var ndx in this.allGroupInfos) {
        this.allGroupInfos[~~ndx].findBrothers();
    }
};

// Find voids surrounded by a single color -> eyes
BoardAnalyser.prototype._findEyeOwners = function () {
    if (main.debug) main.log.debug('---Finding eye owners...');
    for (var n = this.allVoids.length - 1; n >= 0; n--) {
        this.allVoids[n].findOwner();
    }
};

function normalizeLiveliness(life) {
    // Remove 1 if we have only 1 eye - a single-eye group is not more "resistant"
    if (life > 1 && life < 2) {
        return life - 1;
    }
    return life;
}

function compareLiveliness(life) {
    // make sure we have a winner, not a tie
    if (life[BLACK] === life[WHITE] || (life[BLACK] >= ALIVE && life[WHITE] >= ALIVE)) {
        return undefined;
    }
    life[BLACK] = normalizeLiveliness(life[BLACK]);
    life[WHITE] = normalizeLiveliness(life[WHITE]);
    return life[BLACK] > life[WHITE] ? BLACK : WHITE;
}

BoardAnalyser.prototype._findBattleWinners = function () {
    var life = [0, 0];
    for (;;) {
        var foundOne = false;
        for (var i = this.allVoids.length - 1; i >= 0; i--) {
            var v = this.allVoids[i];
            if (v.color !== undefined) continue;
            life[BLACK] = life[WHITE] = 0;
            for (var color = BLACK; color <= WHITE; color++) {
                // NB: we don't check for brothers' liveliness counted twice.
                // No issue noticed so far - see testUnconnectedBrothers / b4
                for (var n = v.groups[color].length - 1; n >= 0; n--) {
                    var gi = v.groups[color][n]._info;
                    life[color] += gi.liveliness() + gi.group.lives / 10000; // is gi.group.lives still necessary?
                }
            }
            var winner = compareLiveliness(life);
            // make sure we have a winner, not a tie
            if (winner === undefined) {
                if (main.debug) main.log.debug('BATTLED VOID in dispute: ' + v + ' with ' + life[0]);
                continue;
            }<commit_msg>BoardAnalyser: small improv group "battle"<commit_after>'use strict';

var CONST = require('../../../constants');
var main = require('../../../main');
var Grid = require('../../../Grid');
var GroupInfo = require('./GroupInfo');
var Void = require('./Void');
var ZoneFiller = require('./ZoneFiller');

var EMPTY = CONST.EMPTY, GRID_BORDER = CONST.GRID_BORDER;
var BLACK = CONST.BLACK, WHITE = CONST.WHITE;
var ALIVE = GroupInfo.ALIVE;
var FAILS = GroupInfo.FAILS, LIVES = GroupInfo.LIVES, UNDECIDED = GroupInfo.UNDECIDED;


/** @class Our main board analyser / score counter etc.
 */
function BoardAnalyser() {
    this.version = 'chuckie';
    this.mode = null;
    this.goban = null;
    this.analyseGrid = null;
    this.scoreGrid = null;
    this.allVoids = [];
    this.allGroupInfos = null;
    this.scores = [0, 0];
    this.prisoners = [0, 0];
    this.filler = null;
}
module.exports = BoardAnalyser;


BoardAnalyser.prototype.countScore = function (goban) {
    if (main.debug) main.log.debug('Counting score...');
    this.scores[BLACK] = this.scores[WHITE] = 0;
    this.prisoners = goban.countPrisoners();

    if (!this.scoreGrid) this.scoreGrid = new Grid(goban.gsize, GRID_BORDER);
    if (!this._initAnalysis('SCORE', goban, this.scoreGrid)) return;
    this._runAnalysis();
};

BoardAnalyser.prototype.getScoringGrid = function () {
    return this.scoreGrid;
};

BoardAnalyser.prototype.getVoidAt = function (stone) {
    return this.analyseGrid.yx[stone.j][stone.i];
};

BoardAnalyser.prototype.startMoveAnalysis = function (goban, grid) {
    this._initAnalysis('MOVE', goban, grid);
};

BoardAnalyser.prototype.continueMoveAnalysis = function () {
    this._runAnalysis();
};

BoardAnalyser.prototype.analyseTerritory = function (goban, grid, first2play) {
    if (!this._initAnalysis('TERRITORY', goban, grid)) return;
    this._runAnalysis(first2play);
};

BoardAnalyser.prototype.image = function () {
    return this.analyseGrid.image();
};

BoardAnalyser.prototype.debugDump = function () {
    var res = 'Grid:\n' + this.analyseGrid.toText() + 'Voids:\n';
    for (var v, v_array = this.allVoids, v_ndx = 0; v=v_array[v_ndx], v_ndx < v_array.length; v_ndx++) {
        res += v.toString() + '\n';
    }
    res += 'Groups:\n';
    for (var ndx in this.allGroupInfos) {
        res += this.allGroupInfos[~~ndx].toString() + '\n';
    }
    if (this.scores) {
        res += 'Score:' + this.scores.map(function (s, i) {
            return ' player ' + i + ': ' + s + ' points';
        });
    }
    return res;
};

BoardAnalyser.prototype._initAnalysis = function (mode, goban, grid) {
    this.mode = mode;
    this.goban = goban;
    this.analyseGrid = grid;
    this.filler = new ZoneFiller(goban, grid);
    if (goban.moveNumber() === 0) return false;

    grid.initFromGoban(goban);
    this._initVoidsAndGroups();
    this._findBrothers();
    this._findEyeOwners();
    return true;
};

BoardAnalyser.prototype._runAnalysis = function (first2play) {
    if (this.mode === 'MOVE') {
        this._lifeOrDeathLoop();
    } else {
        this._findBattleWinners();
        this._lifeOrDeathLoop(first2play);
        this._findDameVoids();
        this._finalColoring();
    }
};

BoardAnalyser.prototype._addGroup = function (g, v) {
    var gi = this.allGroupInfos[g.ndx];
    if (!gi) {
        if (!g._info || g._info.boan !== this) {
            g._info = new GroupInfo(g, this);
        } else {
            g._info.resetAnalysis();
        }
        gi = this.allGroupInfos[g.ndx] = g._info;
    }
    gi.nearVoids.push(v);
};

/** Create the list of voids and groups.
 * Voids know which groups are around them, but groups do not own any void yet.
 */
BoardAnalyser.prototype._initVoidsAndGroups = function () {
    if (main.debug) main.log.debug('---Initialising voids & groups...');
    var voidCode = Grid.ZONE_CODE;
    this.allGroupInfos = {};
    this.allVoids.clear();
    var n, groups, goban = this.goban;
    var v = new Void(goban, voidCode++);
    for (var j = 1; j <= goban.gsize; j++) {
        for (var i = 1; i <= goban.gsize; i++) {
            var vcount = this.filler.fillWithColor(i, j, EMPTY, v);
            if (vcount === 0) continue;

            // 1 new void created
            this.allVoids.push(v);
            // keep all the groups
            for (var color = BLACK; color <= WHITE; color++) {
                groups = v.groups[color];
                for (n = groups.length - 1; n >= 0; n--) this._addGroup(groups[n], v);
            }

            v = new Void(goban, voidCode++);
        }
    }
};

BoardAnalyser.prototype._findBrothers = function () {
    for (var ndx in this.allGroupInfos) {
        this.allGroupInfos[~~ndx].findBrothers();
    }
};

// Find voids surrounded by a single color -> eyes
BoardAnalyser.prototype._findEyeOwners = function () {
    if (main.debug) main.log.debug('---Finding eye owners...');
    for (var n = this.allVoids.length - 1; n >= 0; n--) {
        this.allVoids[n].findOwner();
    }
};

function normalizeLiveliness(life) {
    // Remove 1 if we have only 1 eye - a single-eye group is not more "resistant"
    if (life > 1 && life < 2) {
        return life - 1;
    }
    return life;
}

function compareLiveliness(life) {
    // make sure we have a winner, not a tie
    if (life[BLACK] === life[WHITE] || (life[BLACK] >= ALIVE && life[WHITE] >= ALIVE)) {
        return undefined;
    }
    life[BLACK] = normalizeLiveliness(life[BLACK]);
    life[WHITE] = normalizeLiveliness(life[WHITE]);
    return life[BLACK] > life[WHITE] ? BLACK : WHITE;
}

BoardAnalyser.prototype._findBattleWinners = function () {
    var goban = this.goban;
    if (goban.moveNumber() < 2 * goban.gsize) return; // no battle before enough moves were played
    var life = [0, 0], size = [0, 0];
    for (;;) {
        var foundOne = false;
        for (var i = this.allVoids.length - 1; i >= 0; i--) {
            var v = this.allVoids[i];
            if (v.color !== undefined) continue;
            if (v.vcount > 2 * goban.gsize) continue; // no battle for big voids
            life[BLACK] = life[WHITE] = size[BLACK] = size[WHITE] = 0;
            for (var color = BLACK; color <= WHITE; color++) {
                // NB: we don't check for brothers' liveliness counted twice.
                // No issue noticed so far - see testUnconnectedBrothers / b4
                for (var n = v.groups[color].length - 1; n >= 0; n--) {
                    var gi = v.groups[color][n]._info;
                    life[color] += gi.liveliness();
                    size[color] += gi.countBandSize();
                }
            }
            // no battle if 2 big groups around
            if (size[BLACK] > 4 && size[WHITE] > 4) continue;

            var winner = compareLiveliness(life);
            // make sure we have a winner, not a tie
            if (winner === undefined) {
                if (main.debug) main.log.debug('BATTLED VOID in dispute: ' + v + ' with ' + life[0]);
                continue;
            }
{'fraction_non_alphanumeric': 0.1525225159258988, 'fraction_numerical': 0.005711356813355788, 'mean_word_length': 2.4472488642099948, 'pattern_counts': {'":': 0, '<': 16, '<?xml version=': 0, '>': 24, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1347694', 'n_tokens_mistral': 4648, 'n_tokens_neox': 4556, 'n_words': 1083}