(function (root) { root.createMatrix = function (rows, columns, init) { var result = new Array(rows); for (var i = 0; i < rows; i++) { result[i] = new Array(columns); if (init !== undefined) result[i].fill(init); } result.rows = rows; result.columns = columns; return result; }; root.Problem = function (distribution, attacker, defender) { this.distribution = distribution; this.attacker = attacker; this.defender = defender; }; root.DistributionBase = function (min, max) { this.min = min; this.max = max; }; root.DistributionBase.prototype.at = function (index) { return this[index] || 0; }; root.DistributionBase.prototype.toString = function () { if (this.min === undefined || this.max === undefined) { return 'no stats'; } else { var result = 'Min: ' + this.min + ', Max: ' + this.max + '. '; var sum = 0; result += '['; for (var i = Math.min(this.min, -1); i <= Math.max(this.max, 1); ++i) { if (i === 0) result += '| '; result += round(this.at(i), 3) + ' '; sum += this.at(i); if (i === 0) result += '| '; } result += ']'; result += ' ' + round(sum, 3); result += '. ' + round(this.downTo(-1), 3) + ':' + round(this.downTo(1), 3); return result; } function round(number, precision) { var factor = Math.pow(10, precision); return Math.round(number * factor) / factor; } }; root.DistributionBase.prototype.downTo = function (index) { if (index === 0) return this.at(index); var result = 0; if (index < 0) for (var i = this.min; i <= index; i++) result += this.at(i); else for (var i = index; i <= this.max; i++) result += this.at(i); return result; }; root.EmpiricalDistribution = function () { }; root.EmpiricalDistribution.prototype = Object.create(root.DistributionBase.prototype); /** Increment count at index */ root.EmpiricalDistribution.prototype.increment = function (index) { this[index] = this.at(index) + 1; if (this.min === undefined) this.min = index; else if (index < this.min) this.min = index; if (this.max === undefined) this.max = index; else if (this.max < index) this.max = index; }; /** Convert counts to probabilities */ root.EmpiricalDistribution.prototype.normalize = function () { var sum = 0; for (var i = this.min; i <= this.max; ++i) sum += this.at(i); if (sum !== 0) for (var i = this.min; i <= this.max; ++i) this[i] = this.at(i) / sum; }; /** Problem ensembles are needed when linear units dying order is violated. * E.g. * Barrage - only Fighters die * Assault Cannon - only non Fighters die * Graviton Lazer - preferably non Fighters die * * So original problem is split into several subproblems, depending on which units died. */ root.EnsembleSplit = function (parentProblem) { this.subproblems = {}; this.parentProblem = parentProblem; }; root.EnsembleSplit.prototype.increment = function (attackerVictims, defenderVictims, fromRow, fromColumn, value) { if (value === 0) return; attackerVictims = attackerVictims.collapseRanges(fromRow); defenderVictims = defenderVictims.collapseRanges(fromColumn); var subproblemKey = this._subproblemKey(attackerVictims, defenderVictims); if (!this.subproblems[subproblemKey]) this.subproblems[subproblemKey] = this._problemFactory(attackerVictims, defenderVictims); this.subproblems[subproblemKey].distribution[fromRow - attackerVictims.dead()][fromColumn - defenderVictims.dead()] += value; }; root.EnsembleSplit.prototype.getSubproblems = function () { var subproblems = this.subproblems; return Object.keys(this.subproblems).map(function (key) { return subproblems[key]; }); }; root.EnsembleSplit.prototype._problemFactory = function (attackerVictims, defenderVictims) { var attackerDeficit = attackerVictims.rangesLength(); var defenderDeficit = defenderVictims.rangesLength(); var distribution = root.createMatrix(this.parentProblem.distribution.rows - attackerDeficit, this.parentProblem.distribution.columns - defenderDeficit, 0); var attacker = splice(this.parentProblem.attacker, attackerVictims.ranges); var defender = splice(this.parentProblem.defender, defenderVictims.ranges); return new root.Problem(distribution, attacker, defender); function rangesLength(ranges) { var result = 0; for (var i = 0; i < ranges.length / 2; ++i) result += rangeLength(ranges, i); return result; function rangeLength(ranges, rangeIndex) { var rangeStart = rangeIndex * 2; if (isNaN(ranges[rangeStart])) return 0; return isNaN(ranges[rangeStart + 1]) ? 1 : ranges[rangeStart + 1] - ranges[rangeStart]; } } function splice(fleet, ranges) { var allRangesNull = true; for (var i = 0; i < ranges.length / 2; ++i) { if (!isNaN(ranges[i * 2])) { allRangesNull = false; break; } } if (allRangesNull) return fleet; else { var result = fleet.slice(); for (var i = ranges.length / 2 - 1; 0 <= i; --i) { if (!isNaN(ranges[i * 2])) { result.splice(ranges[i * 2], (ranges[i * 2 + 1] - ranges[i * 2]) || 1); } } return result; } } }; root.EnsembleSplit.prototype._subproblemKey = function (attackerVictims, defenderVictimms) { return 'a' + attackerVictims.rangesKey() + 'd' + defenderVictimms.rangesKey(); }; root.Victim = function () { this.ranges = []; } root.Victim.prototype.addRange = function (from, to) { var ranges = this.ranges; if (!isNaN(from) && from !== to) { if (ranges.length) { if (ranges[ranges.length - 1] === from) { ranges[ranges.length - 1] = to === undefined ? from + 1 : to; return; } if (ranges[ranges.length - 1] === undefined && ranges[ranges.length - 2] + 1 === from) { if (to === undefined) ranges[ranges.length - 1] = from + 1; else ranges[ranges.length - 1] = to; return; } } if (from + 1 === to) this.ranges.push(from, undefined); else this.ranges.push(from, to); } }; root.Victim.prototype.rangesLength = function () { var result = 0; for (var i = 0; i < this.ranges.length / 2; ++i) result += rangeLength(this.ranges, i); return result; function rangeLength(ranges, rangeIndex) { var rangeStart = rangeIndex * 2; if (isNaN(ranges[rangeStart])) return 0; return isNaN(ranges[rangeStart + 1]) ? 1 : (ranges[rangeStart + 1] - ranges[rangeStart]); } }; root.Victim.prototype.dead = function () { return this._dead !== undefined ? this._dead : this.rangesLength(); }; root.Victim.prototype.collapseRanges = function (fleetCeiling) { var ranges = this.ranges; if (ranges.length && ( ranges[ranges.length - 1] === fleetCeiling || ranges[ranges.length - 1] === undefined && ranges[ranges.length - 2] + 1 === fleetCeiling)) { var result; result = new root.Victim(); result.ranges = ranges.slice(0, ranges.length - 2); result._dead = this.rangesLength(); return result; } return this; }; root.Victim.prototype.rangesKey = function () { var rangeKeys = []; for (var i = 0; i < this.ranges.length / 2; ++i) { rangeKeys.push(this.ranges[i * 2] + (this.ranges[i * 2 + 1] === undefined ? '' : '-' + this.ranges[i * 2 + 1])); } return rangeKeys.join(','); }; root.Victim.Null = new root.Victim(); /** taken from https://stackoverflow.com/questions/22697936/binary-search-in-javascript * God only knows why javascript doesn't have it's own binary search */ root.binarySearch = function (ar, el, compare_fn) { var m = 0; var n = ar.length - 1; while (m <= n) { var k = (n + m) >> 1; var cmp = compare_fn(el, ar[k]); if (cmp > 0) { m = k + 1; } else if (cmp < 0) { n = k - 1; } else { return k; } } return -m - 1; }; })(typeof exports === 'undefined' ? window : exports);