diff --git a/classes.js b/classes.js
new file mode 100644
index 0000000..922a7da
--- /dev/null
+++ b/classes.js
@@ -0,0 +1,29 @@
+function Sweep(width, height, mines) {
+ this.mines = +mines;
+ this.field = create_matrix(height, width,
+ function() { return new Cell('q') });
+}
+
+function Cell(type) {
+ function is_of_type (type) {
+ return function () {
+ return this.type === type
+ }
+ }
+
+ this.type = type;
+ this.isUnknown = is_of_type('q');
+ this.isEmpty = is_of_type('0');
+ this.isFree = is_of_type('f');
+ this.isBomb = is_of_type('b');
+ this.isNumber = function () {
+ var eln = parseInt(this.type, 10);
+ return !isNaN(eln) && (eln !== 0)
+ };
+}
+
+function Position2D(row, col) {
+ this.row = +row;
+ this.col = +col;
+ this.toString = function () { return '(' + row + ', ' + col + ')' };
+}
diff --git a/generator.js b/generator.js
new file mode 100644
index 0000000..70851b4
--- /dev/null
+++ b/generator.js
@@ -0,0 +1,23 @@
+function generate (width, height, mines, seed) {
+ if (mines > elems || width <= 0 || height <= 0) {
+ return null;
+ }
+ var elems = width * height;
+
+ var coeffs_arr = [elems - mines];
+ for (var i = 1; i <= mines; ++i) {
+ coeffs_arr.push(coeffs_arr[i-1] * (elems - mines + i));
+ }
+
+ var field = create_matrix(height, width, function() { return false; });
+
+ var rest = create_map(elems, function(i) { return i; });
+ for (var i = mines - 1; i >= 0; --i) {
+ var r_idx = Math.floor(seed / coeffs_arr[i]) % rest.length;
+ var idx = rest.splice(r_idx, 1)[0];
+ field[Math.floor(idx / width)][idx % width] = true;
+ seed %= coeffs_arr[i];
+ }
+
+ return field;
+}
diff --git a/index.html b/index.html
index 857f895..9aff4aa 100644
--- a/index.html
+++ b/index.html
@@ -4,8 +4,11 @@
Antisweep
+
+
+
diff --git a/solver.js b/solver.js
index 48b98b2..cc10e33 100644
--- a/solver.js
+++ b/solver.js
@@ -1,399 +1,306 @@
-function create_map(n, func) {
- return Array.apply(null, new Array(+n)).map(function(x, index, arr) {
- return func(index);
- });
+solver = {
+ debug: false,
+ log: function () {
+ if (this.debug) {
+ console.log.apply(console, arguments);
+ }
+ }
}
-function foreach_obj(obj, func) {
- for (var key in obj) {
- if (obj.hasOwnProperty(key)) {
- func(obj[key], key);
- }
- }
+solver.test = function test () {
+
+ function display(name, results) {
+ console.log(name + ": " + results.map(
+ function(c) { if (c) {
+ return "."
+ } else {
+ return "x"
+ }}).join('')
+ );
+ }
+
+ function check_results(solution, correct) {
+ return solution.map(function(x) {
+ return x.toString()
+ }).sort().toString() === correct.map(function(x) {
+ return x.toString()
+ }).sort().toString();
+ }
+
+ /* AX = B :
+ * x 1 2 1 x x
+ * x x x x x
+ */
+ var A = create_matrix(3, 8, function(){return false});
+ A[0][0] = A[0][1] = A[0][2] = A[0][3] = true;
+ A[1][2] = A[1][3] = A[1][4] = true;
+ A[2][3] = A[2][4] = A[2][5] = A[2][6] = true;
+
+ var B = create_map(3, function(){return 1});
+ B[1] = 2;
+
+ var solve_ab = this.solve_for_rules(A, B, 2, 3);
+ var solve_ab_cond = check_results(solve_ab,
+ [[0, 0, 1, 0, 1, 0, 0, 1],
+ [0, 0, 1, 0, 1, 0, 0, 0]]
+ );
+
+ /* CX = D :
+ * 2 x x
+ * x x 2
+ */
+ var C = create_matrix(2, 4, function(){return true});
+ C[0][1] = false;
+ C[1][2] = false;
+
+ var D = create_map(2, function(){return 2});
+
+ var solve_cd = this.solve_for_rules(C, D, 3, 3);
+ var solve_cd_cond = check_results(solve_cd,
+ [[1, 1, 1, 0],
+ [0, 1, 1, 1]]);
+
+ /* EX = F :
+ * x 1 x
+ */
+ var E = create_matrix(1, 2, function(){return true});
+ var F = create_map(1, function(){return 1});
+
+ var solve_ef = this.solve_for_rules(E, F, 1, 1);
+ var solve_ef_cond = check_results(solve_ef,
+ [[1, 0], [0, 1]]);
+
+ display("Simple tests", [solve_ab_cond, solve_cd_cond, solve_ef_cond]);
+
+ /* CX = B, slightly artificial example:
+ * 1 _ 1
+ * x 2 x
+ * _ x _
+ */
+ var C = create_matrix(3, 3, function(){return false});
+ C[0][0] = true;
+ C[1][0] = C[1][1] = C[1][2] = true;
+ C[2][1] = true;
+
+ var elim_cb = this.eliminate_obvious_rules(C, B);
+ var elim_cb_cond = elim_cb.A.height === 0 && elim_cb.B.length === 0 &&
+ elim_cb.res.toString() === "1,1,0";
+
+ display("Elimination of obvious rules", [elim_cb_cond]);
+
+ /* DX = B:
+ * 1 2 x x 1
+ * _ x x x x
+ */
+ var D = create_matrix(3, 6, function(){return false});
+ D[0][0] = true;
+ D[1][0] = D[1][1] = D[1][2] = true;
+ D[2][3] = D[2][4] = D[2][5] = true;
+
+ var split_db = this.split_at_components(D, B);
+
+ return {
+ solve_ab : solve_ab,
+ elim_cb : elim_cb,
+ split : split_db
+ };
}
-function create_matrix(n, m, func) {
- var result = create_map(n, function(i) {
- return create_map(m, function (j) {
- return func(i, j);
- })
- });
- result.width = m;
- result.height = n;
- result.matrForEach = function (callback, thisArg) {
- var that = thisArg ? thisArg : this;
- for (var i = 0; i < result.height; ++i) {
- for (var j = 0; j < result.width; ++j) {
- callback(that[i][j], i, j, that);
- }
- }
- };
- return result;
+solver.eliminate_obvious_rules = function eliminate_obvious_rules (A, B) {
+ var res = [];
+ var aWork = copy_matrix(A);
+ var bWork = B.slice();
+
+ var idxs = create_map(aWork.width, function(i) { return i });
+
+ var obvious;
+ do {
+ obvious = false;
+
+ for (var i = 0; i < aWork.height; ++i) {
+ var rowCnt = 0;
+ for (var j = 0; j < aWork.width; ++j) {
+ rowCnt += aWork[i][j];
+ }
+ if ((bWork[i] > rowCnt) || (bWork[i] < 0)) {
+ return null;
+ }
+ if (bWork[i] && (bWork[i] !== rowCnt)) {
+ continue;
+ }
+
+ obvious = true;
+ for (var j = 0; j < aWork.width; ++j){
+ if (!aWork[i][j]) {
+ continue;
+ }
+ res[idxs[j]] = (bWork[i] !== 0) * aWork[i][j];
+ if (res[idxs[j]]) {
+ for (var z = 0; z < aWork.height; ++z) {
+ if (aWork[z][j]) {
+ --bWork[z];
+ }
+ }
+ }
+ aWork = copy_matrix_wo_col(aWork, j);
+ idxs = create_map(aWork.width, function(i) {
+ return idxs[i + (i >= j)];
+ });
+ --j;
+ }
+ bWork.splice(i, 1);
+ aWork = copy_matrix_wo_row(aWork, i--);
+ }
+ } while (obvious);
+
+ return {
+ A : aWork,
+ B : bWork,
+ res : res
+ }
}
-function copy_matrix(matr) {
- var result = [];
- result.width = matr.width;
- result.height = matr.height;
- for (var i = 0; i < matr.height; ++i) {
- result[i] = [];
- for (var j = 0; j < matr.width; ++j) {
- result[i][j] = matr[i][j];
- }
- }
- result.matrForEach = matr.matrForEach;
- return result;
+solver.split_at_components = function split_at_components (A, B) {
+ var res = [];
+ var assigned = Object.create(null);
+ for (var i = 0; i < A.height; ++i) {
+ if (i in assigned)
+ continue;
+
+ var comp = [];
+ var curr = Object.create(null);
+ for (var j = 0; j < A.width; ++j) {
+ if (A[i][j]) {
+ curr[j] = true;
+ }
+ }
+
+ for (var k = i; k < A.height; ++k) {
+ if (k in assigned)
+ continue;
+
+ for (var j = 0; j < A.width; ++j) {
+ if (curr[j] && A[k][j]) {
+ comp.push(k);
+ assigned[k] = true;
+ break;
+ }
+ }
+ }
+
+ var a = create_matrix(comp.length, A.width, function (i, j) {
+ return A[comp[i]][j];
+ });
+
+ var b = create_map(comp.length, function(i) {
+ return B[comp[i]];
+ });
+
+ var ixs = comp;
+
+ res.push({a: a, b: b, ixs: ixs});
+ }
+ return res;
}
-function Sweep(width, height, mines) {
- this.mines = +mines;
- this.field = create_matrix(height, width,
- function() { return new Cell('q') });
+solver.partition_split_idx = function partition_split_idx (A, B) {
+ for (var idx = 0; idx < A.width; ++idx) {
+ for (var r = 0; r < A.height; ++r) {
+ if (A[r][idx])
+ return idx;
+ }
+ }
+ return null;
}
-function Cell(type) {
- function is_of_type (type) {
- return function () {
- return this.type === type
- }
- }
-
- this.type = type;
- this.isUnknown = is_of_type('q');
- this.isEmpty = is_of_type('0');
- this.isFree = is_of_type('f');
- this.isBomb = is_of_type('b');
- this.isNumber = function () {
- var eln = parseInt(this.type, 10);
- return !isNaN(eln) && (eln !== 0)
- };
-}
-
-function Position2D(row, col) {
- this.row = +row;
- this.col = +col;
- this.toString = function () { return '(' + row + ', ' + col + ')' };
-}
-
-var solver = {};
-
-solver.solve = function (swp) {
- try {
- var calc = this.calculate(swp);
- } catch (e) {
- alert(e.message);
- return
- }
- calc.new_field.matrForEach(function (curr, i, j) {
- if (!curr.isFree()) {
- swp.field[i][j] = curr;
- }
- });
- return calc.probs;
-}
-
-function fold_around (mi, mj, Mi, Mj, i, j, a, accFunc) {
- if ((mi != i) && (mj != j)) { a = accFunc(a, i-1, j-1) }
- if ((mi != i) && (Mj != j)) { a = accFunc(a, i-1, j+1) }
- if ((Mi != i) && (mj != j)) { a = accFunc(a, i+1, j-1) }
- if ((Mi != i) && (Mj != j)) { a = accFunc(a, i+1, j+1) }
- if ((mi != i) ) { a = accFunc(a, i-1, j ) }
- if ((Mi != i) ) { a = accFunc(a, i+1, j ) }
- if ( (mj != j)) { a = accFunc(a, i , j-1) }
- if ( (Mj != j)) { a = accFunc(a, i , j+1) }
- return a;
-}
-
-function Lead (required, unknowns) {
- this.required = +required;
- this.unknowns = unknowns;
-}
-
-solver.get_rules = function (field) {
- var w = field.width;
- var h = field.height;
-
- function cell_name (i, j) {
- return [i, j].join(';');
- }
-
- function acc_f (el, cell) {
- if (!el.isBomb() && !el.isUnknown()) {
- return function (a) { return a }
- }
-
- return function (a, i, j) {
- var e = field[i][j];
- if (!e.isNumber() && !e.isEmpty()) {
- return a;
- }
-
- var name = cell_name(i, j);
- if (!a[name]) {
- a[name] = new Lead(e.type, []);
- a[name].pos = new Position2D(i, j);
- }
-
- if (el.isBomb()) {
- --a[name].required;
- } else {
- a[name].unknowns.push(cell);
- cell.used = true;
- }
- return a;
- }
- }
-
- var unknowns = [];
- var leads = {};
- var free_unknowns = [];
- field.matrForEach(function (el, i, j) {
- if (!el.isUnknown() && !el.isBomb()) {
- return
- }
- var cell = new Position2D(i, j);
- cell.used = false;
- leads = fold_around(0, 0, h-1, w-1, i, j, leads,
- acc_f(el, cell));
- if (el.isUnknown()) {
- (cell.used ? unknowns : free_unknowns).push(cell);
- }
- delete cell.used;
- });
-
- this.delete_empty_rules();
-
- return {
- unknowns : unknowns,
- leads : leads,
- free_unknowns : free_unknowns
- }
-}
-
-solver.delete_empty_rules = function (leads) {
- foreach_obj(leads, function (lead, key) {
- // Check for validity
- if ((lead.required > lead.unknowns.length) ||
- (lead.required < 0)) {
- throw {
- name : 'unexpected_cell_type',
- pos : lead.pos,
- message : 'The vicinity of the cell ' +
- lead.pos + ' conflicts with its type'
- }
- }
- // Remove the rules that can't provide any new information
- if (!lead.required && !lead.unknowns.length) {
- delete leads[key];
- }
- });
-}
-
-solver.guess_cell_types = function(field) {
- var h = field.height;
- var w = field.width;
-
- function near_unknown_f (a, i, j) {
- var el = field[i][j];
- a.near_unknown = a.near_unknown || el.isUnknown();
- if (el.isBomb()) {
- ++a.mines_around;
- }
- return a
- }
-
- field.matrForEach(function (el, i, j) {
- var el = field[i][j];
- if (!el.isFree()) {
- return
- }
-
- var info = {
- near_unknown : false,
- mines_around : 0
- };
-
- info = fold_around(0, 0, h-1, w-1, i, j, info,
- near_unknown_f);
-
- field[i][j].type = info.near_unknown ? 'q' :
- info.mines_around + "";
- });
-
- return field;
-}
-
-solver.calculate = function(swp) {
- var field = copy_matrix(swp.field);
- var totalUnknown = 0;
-
- var obvious;
- var rules = this.get_rules(field);
- do {
- obvious = 0;
- foreach_obj(rules.leads, function(lead) {
- if (lead.required && (lead.required !==
- lead.unknowns.length))
- { return }
-
- var type = lead.required ? 'b' : 'f';
- lead.unknowns.forEach(function (e) {
- field[e.row][e.col] = new Cell(type);
- });
- obvious += lead.unknowns.length;
- });
- rules = this.get_rules(field);
- } while (obvious);
-
- var bombs_found = 0;
-
- var probs = create_matrix(field.height, field.width, function (i, j) {
- var el = field[i][j];
- if (el.isUnknown()) {
- return -2;
- } else if (el.isBomb()) {
- ++bombs_found;
- return 100;
- } else if (el.isFree()) {
- return 0;
- } else {
- return -1;
- }
- });
- var res = this.calculate_probabilities(rules, swp.mines - bombs_found);
- var norm = this.normalized(res, rules.free_unknowns.length);
- var update_probs = function (is_bound) {
- return function (val, index) {
- var curr_prob = is_bound ? norm.bound[index] :
- norm.free;
- probs[val.row][val.col] = curr_prob;
- if (curr_prob === 0) {
- field[val.row][val.col].type = 'f';
- } else if (curr_prob === 100) {
- field[val.row][val.col].type = 'b';
- }
- }
- };
- rules.unknowns.forEach(update_probs(true));
- rules.free_unknowns.forEach(update_probs(false));
-
- return {
- probs : probs,
- new_field : this.guess_cell_types(field)
- }
-}
-
-solver.count_mines = function (field) {
- var result = 0;
- field.matrForEach(function (el) {
- if (el.isBomb()) {
- ++result;
- }
- });
- return result;
-}
-
-solver.is_correct = function (rules, mines_left) {
- var result = true;
- foreach_obj(rules.leads, function(lead) {
- var consider = true;
- var current = (lead.unknowns.filter(function (val) {
- consider = consider && val.traversed;
- return val.set
- }).length === lead.required);
- if (consider) {
- result = result && current;
- }
- });
- return result;
-}
+/* Find all possible solutions for A * X = B assuming that both a_ij and x_i
+ * are in {true, false}.
+ *
+ * A, B -- matrices to be solved;
+ * {min|max} -- if a number, sum of all x_i must be {more|less} than the value
+ * of this parameter.
+ * */
+
+solver.solve_for_rules = function solve_for_rules (A, B, min, max) {
+ var min = isInt(min) ? min : -1;
+ var max = isInt(max) ? max : Infinity;
+
+ if (B.length != A.height) {
+ throw {
+ name : 'extended_matrix_incorrect',
+ text : B + ' can\'t be an extension of ' + A +
+ ' as their dimensions don\'t match'
+ }
+ }
+
+ this.log({A : A, B : B, min : min, max : max});
+
+ var obvious = this.eliminate_obvious_rules(A, B);
+ this.log({After_elimination : obvious});
+ if (obvious === null) {
+ return [];
+ }
+ var aWork = obvious.A;
+ var bWork = obvious.B;
+ var resTempl = obvious.res;
+
+ var foundMines = 0;
+ for (var i = 0; i < resTempl.length; ++i) {
+ foundMines += i in resTempl && resTempl[i];
+ }
+ min -= foundMines;
+ max -= foundMines;
+ this.log({min: min, max: max});
+
+ if (max < 0 || aWork.width < min) {
+ return [];
+ }
+
+ if (aWork.width === 0 || aWork.height === 0) {
+
+ for (var i = 0; i < bWork.length; ++i) {
+ if (bWork[i] !== 0) {
+ return [];
+ }
+ }
+
+ this.log('Success!');
+ var combs = combinations_arr(aWork.width, min, max);
+ return combs.map(function(el) {
+ return merge_in(resTempl, el); });
+ }
+
+ var res = [];
+
+ var splitIdx = this.partition_split_idx(aWork, bWork);
+ var maxAtIdx = Math.max.apply(null, create_map(aWork.height,
+ function(j) { return aWork[j][splitIdx]; }));
+
+ var aWoSplit = copy_matrix_wo_col(aWork, splitIdx);
+
+ for (var i = maxAtIdx; i >= 0; --i) {
+ var newB = create_map(bWork.length, function(j) {
+ return bWork[j] - (aWork[j][splitIdx] ? i : 0);
+ });
+ this.log({i: i, newB: newB, splitIdx: splitIdx});
+ var r = this.solve_for_rules (aWoSplit, newB, min - i, max - i);
+ if (r.length > 0) {
+ res = res.concat(r.map(function(el) {
+ el.splice(splitIdx, 0, i); return el;}));
+ }
+ this.log({Out: i, res: res});
+ }
+
+ res = res.map(function(el) {
+ for (var i = 0; i < resTempl.length; ++i) {
+ if (i in resTempl) {
+ el.splice(i, 0, resTempl[i]);
+ }
+ }
+ return el;
+ });
+
+ return res;
-function combinations (n, k) {
- if (n < k) {
- return 0;
- }
-
- var i;
- var r = 1;
- for (i = k + 1; i <= n; ++i) {
- r *= i;
- }
- for (i = 1; i <= (n - k); ++i) {
- r /= i;
- }
- return r;
}
-solver.calculate_probabilities = function (rules, mines_left, index) {
- index = index ? index : 0;
- var result = {
- probs : create_map(rules.unknowns.length, function () {
- return 0
- }),
- prob_free : 0,
- total : 0
- }
-
- if (index === rules.unknowns.length) {
- if ((rules.free_unknowns.length < mines_left) ||
- (!this.is_correct(rules, mines_left)))
- { return result }
-
- var modifier = combinations(rules.free_unknowns.length,
- mines_left);
-
- var probs = [];
- rules.unknowns.forEach(function (val, index) {
- if (val.set) {
- result.probs[index] += modifier;
- }
- });
-
- result.prob_free += mines_left * modifier;
- result.total += modifier;
- return result;
- }
-
- var s;
- var curr = rules.unknowns[index];
-
- if (!this.is_correct(rules, mines_left)) {
- return result;
- }
-
- curr.traversed = true;
-
- if (mines_left) {
- curr.set = true;
- --mines_left;
- s = this.calculate_probabilities(rules, mines_left,
- index + 1);
- result.total += s.total;
- s.probs.forEach(function (val, index) {
- result.probs[index] += val;
- });
- result.prob_free += s.prob_free;
- curr.set = false;
- ++mines_left;
- }
-
- s = this.calculate_probabilities(rules, mines_left,
- index + 1);
- result.total += s.total;
- result.prob_free += s.prob_free;
- s.probs.forEach(function (val, index) {
- result.probs[index] += val;
- });
-
- curr.traversed = false;
- return result;
-}
-
-solver.normalized = function (s, free_unknowns_cnt) {
- return {
- bound : s.probs.map(function (val) {
- return val * 100 / s.total;
- }),
- free : s.prob_free * 100 / (s.total * free_unknowns_cnt)
- }
-}
diff --git a/sweep.js b/sweep.js
index e02776c..f24187a 100644
--- a/sweep.js
+++ b/sweep.js
@@ -1,210 +1,262 @@
var csweep;
function td_id(name, i, j) {
- return [name, 'row', i, 'col', j].join('_');
+ return [name, 'row', i, 'col', j].join('_');
}
function remove_children(node) {
- while (node.firstChild) {
- node.removeChild(node.firstChild);
- }
+ while (node.firstChild) {
+ node.removeChild(node.firstChild);
+ }
}
function sweep_to_table(swp, table, cellFunc, probs) {
- remove_children(table);
- for (var i = 0; i < swp.field.height; ++i) {
- var newTr = document.createElement('tr');
- for (var j = 0; j < swp.field.width; ++j) {
- newTr.appendChild(cellFunc(swp, i, j, probs));
- }
- table.appendChild(newTr);
- }
- return table;
+ remove_children(table);
+ for (var i = 0; i < swp.field.height; ++i) {
+ var newTr = document.createElement('tr');
+ for (var j = 0; j < swp.field.width; ++j) {
+ newTr.appendChild(cellFunc(swp, i, j, probs));
+ }
+ table.appendChild(newTr);
+ }
+ return table;
}
function update_field(probs) {
- sweep_to_table(csweep, document.getElementById('sweep_field'),
- create_cell, probs);
+ sweep_to_table(csweep, document.getElementById('sweep_field'),
+ create_cell, probs);
}
function update_popup (e) {
- var popup = document.getElementById('popup');
- if (!popup) {
- return
- }
- popup.style.left = (e.clientX) + 'px';
- popup.style.top = (e.clientY) + 'px';
+ var popup = document.getElementById('popup');
+ if (!popup) {
+ return
+ }
+ popup.style.left = (e.clientX) + 'px';
+ popup.style.top = (e.clientY) + 'px';
}
function create_cell(swp, i, j, probs) {
- var callbacks = {};
- if (probs) {
- callbacks.mouseover = function (e) {
- var prob = probs[i][j];
- if (prob === -1) {
- return;
- }
- var popup = document.createElement('div');
- popup.id = 'popup';
- var text = prob.toPrecision(3) + '%';
- var textNode = document.createTextNode(text);
- popup.appendChild(textNode);
- document.getElementsByTagName('body'
- )[0].appendChild(popup);
- update_popup (e);
- }
- callbacks.mouseout = function (e) {
- var popup = document.getElementById('popup');
- if (popup) {
- popup.remove();
- }
- }
- }
- var newCell = cell_to_dom(swp.field[i][j], callbacks);
- newCell.id = td_id('sweep_field', i, j);
- if (probs) {
- var color = get_color(probs[i][j]);
- newCell.style.backgroundColor = color;
- }
- pos = new Position2D (i, j);
- newCell.addEventListener("click", get_dialog(pos));
- return newCell;
+ var callbacks = {};
+ if (probs) {
+ callbacks.mouseover = function (e) {
+ var prob = probs[i][j];
+ if (prob === -1) {
+ return;
+ }
+ var popup = document.createElement('div');
+ popup.id = 'popup';
+ var text = (prob * 100).toPrecision(3) + '%';
+ var textNode = document.createTextNode(text);
+ popup.appendChild(textNode);
+ document.getElementsByTagName('body'
+ )[0].appendChild(popup);
+ update_popup (e);
+ }
+ callbacks.mouseout = function (e) {
+ var popup = document.getElementById('popup');
+ if (popup) {
+ popup.remove();
+ }
+ }
+ }
+ var newCell = cell_to_dom(swp.field[i][j], callbacks);
+ newCell.id = td_id('sweep_field', i, j);
+ if (probs) {
+ var color = get_color(probs[i][j]);
+ newCell.style.backgroundColor = color;
+ }
+ pos = new Position2D (i, j);
+ newCell.addEventListener("click", get_dialog(pos));
+ return newCell;
}
function cell_to_dom (cell, callbacks) {
- var type = cell.type;
- var res = document.createElement ('td');
- res.className += ['cell', type].join('_');
- var text;
- if (cell.isBomb()) {
- text = '⚑';
- } else if (cell.isUnknown() || cell.isEmpty()) {
- text = '';
- } else {
- text = type;
- }
- var textNode = document.createTextNode(text);
- var boldText = document.createElement("b");
- boldText.appendChild(textNode);
- res.appendChild(boldText);
-
- for (var name in callbacks) {
- if (callbacks.hasOwnProperty(name)) {
- res.addEventListener(name, callbacks[name]);
- }
- }
-
- return res;
+ var type = cell.type;
+ var res = document.createElement ('td');
+ res.className += ['cell', type].join('_');
+ var text;
+ if (cell.isBomb()) {
+ text = '⚑';
+ } else if (cell.isUnknown() || cell.isEmpty()) {
+ text = '';
+ } else {
+ text = type;
+ }
+ var textNode = document.createTextNode(text);
+ var boldText = document.createElement("b");
+ boldText.appendChild(textNode);
+ res.appendChild(boldText);
+
+ for (var name in callbacks) {
+ if (callbacks.hasOwnProperty(name)) {
+ res.addEventListener(name, callbacks[name]);
+ }
+ }
+
+ return res;
}
function close_dialog() {
- var old_dialog = document.getElementById('dialog');
- if (old_dialog) {
- old_dialog.remove();
- }
+ var old_dialog = document.getElementById('dialog');
+ if (old_dialog) {
+ old_dialog.remove();
+ }
}
function get_dialog(pos) {
- return function (e) {
- close_dialog();
- var dialog = document.createElement('table');
- dialog.className = 'dialog';
- dialog.id = 'dialog';
- dialog.style['position'] = 'absolute';
- dialog.style['left'] = e.clientX + 'px';
- dialog.style['top'] = e.clientY + 'px';
- var types = [ '1', '2', '3', '4', '5', '6', '7', '8',
- 'q', 'b', '0' ];
- function get_click_callback(type) {
- return function () {
- csweep.field[pos.row][pos.col].type = type;
- close_dialog();
- update_field();
- }
- }
- var width = 4;
- for (var i = 0; i < Math.ceil(types.length / width); ++i) {
- var row = document.createElement ('tr');
- for (var j = 0; j < width; ++j) {
- var type = types[i * width + j];
- if (!type) {
- break;
- }
- var callbacks = {
- click : get_click_callback(type)
- };
- var cell = cell_to_dom(new Cell(type),
- callbacks);
- row.appendChild(cell);
- }
- dialog.appendChild(row);
- }
- var body = document.getElementsByTagName('body')[0];
- body.appendChild(dialog);
- }
+ return function (e) {
+ close_dialog();
+ var dialog = document.createElement('table');
+ dialog.className = 'dialog';
+ dialog.id = 'dialog';
+ dialog.style['position'] = 'absolute';
+ dialog.style['left'] = e.clientX + 'px';
+ dialog.style['top'] = e.clientY + 'px';
+ var types = [ '1', '2', '3', '4', '5', '6', '7', '8',
+ 'q', 'b', '0' ];
+ function get_click_callback(type) {
+ return function () {
+ csweep.field[pos.row][pos.col].type = type;
+ close_dialog();
+ update_field();
+ }
+ }
+ var width = 4;
+ for (var i = 0; i < Math.ceil(types.length / width); ++i) {
+ var row = document.createElement ('tr');
+ for (var j = 0; j < width; ++j) {
+ var type = types[i * width + j];
+ if (!type) {
+ break;
+ }
+ var callbacks = {
+ click : get_click_callback(type)
+ };
+ var cell = cell_to_dom(new Cell(type),
+ callbacks);
+ row.appendChild(cell);
+ }
+ dialog.appendChild(row);
+ }
+ var body = document.getElementsByTagName('body')[0];
+ body.appendChild(dialog);
+ }
}
function apply_form() {
- var width = document.getElementById('field_width').value;
- var height = document.getElementById('field_height').value;
- var mines = document.getElementById('mines_count').value;
- csweep = new Sweep(width, height, mines);
- update_field();
- return false;
+ var width = document.getElementById('field_width').value;
+ var height = document.getElementById('field_height').value;
+ var mines = document.getElementById('mines_count').value;
+ csweep = new Sweep(width, height, mines);
+ update_field();
+ return false;
}
function restore_form() {
- document.getElementById('field_width' ).value = csweep.field.width;
- document.getElementById('field_height').value = csweep.field.height;
- document.getElementById('mines_count' ).value = csweep.mines;
+ document.getElementById('field_width' ).value = csweep.field.width;
+ document.getElementById('field_height').value = csweep.field.height;
+ document.getElementById('mines_count' ).value = csweep.mines;
}
function set_initial_page() {
- csweep = new Sweep(4, 4, 6);
- restore_form();
- apply_form();
- document.addEventListener('mousemove', update_popup);
- document.addEventListener('click', function(e) {
- var target = e.target;
- while (target !== null) {
- if (/sweep_field_/.test(target.id)) {
- return;
- }
- target = target.parentElement;
- }
- close_dialog();
- });
+ csweep = new Sweep(4, 4, 6);
+ restore_form();
+ apply_form();
+ document.addEventListener('mousemove', update_popup);
+ document.addEventListener('click', function(e) {
+ var target = e.target;
+ while (target !== null) {
+ if (/sweep_field_/.test(target.id)) {
+ return;
+ }
+ target = target.parentElement;
+ }
+ close_dialog();
+ });
}
function solve() {
- var probs = solver.solve(csweep);
- update_field(probs);
+ var number_idxs = [];
+ var bomb_idxs = [];
+ var free_idxs = [];
+ csweep.field.forEach(function (c, i, j) {
+ var idx = i * csweep.field.width + j
+ if (c.isNumber() || c.isEmpty()) {
+ number_idxs.push(idx)
+ } else if (c.isUnknown()) {
+ free_idxs.push(idx)
+ } else if (c.isBomb()) {
+ bomb_idxs.push(idx);
+ }
+ });
+
+ var w = Number(csweep.field.width);
+
+ var A = create_matrix(number_idxs.length, free_idxs.length,
+ function () { return false; });
+ var B = create_map(number_idxs.length, function (idx) {
+ var nidx = number_idxs[idx];
+ return Number(csweep.field[
+ Math.floor(nidx / w)][nidx % w].type); });
+
+ var near = function (i, j) {
+ return Math.abs(Math.floor(i / w) - Math.floor(j / w)) <= 1 &&
+ Math.abs((i % w) - (j % w)) <= 1
+ };
+
+ for (var i = 0; i < number_idxs.length; ++i) {
+ var nidx = number_idxs[i];
+ for (var j = 0; j < free_idxs.length; ++j) {
+ if (near(nidx, free_idxs[j])) {
+ A[i][j] = true;
+ }
+ }
+ for (var j = 0; j < bomb_idxs.length; ++j) {
+ if (near(nidx, bomb_idxs[j])) {
+ --B[i];
+ }
+ }
+ }
+
+ var mines = csweep.mines - bomb_idxs.length;
+
+ var res = solver.solve_for_rules(A, B, mines, mines);
+
+ if (res) {
+ var probs = create_matrix(csweep.field.height, w,
+ function() { return 0; });
+
+ for (var i = 0; i < bomb_idxs.length; ++i) {
+ var bidx = bomb_idxs[i];
+ probs[Math.floor(bidx / w)][bidx % w] = -1;
+ }
+
+ for (var i = 0; i < number_idxs.length; ++i) {
+ var nidx = number_idxs[i];
+ probs[Math.floor(nidx / w)][nidx % w] = -1;
+ }
+
+ for (var i = 0; i < free_idxs.length; ++i) {
+ var fidx = free_idxs[i];
+ for (var j = 0; j < res.length; ++j) {
+ probs[Math.floor(fidx / w)][fidx % w]
+ += res[j][i];
+ }
+ probs[Math.floor(fidx / w)][fidx % w] /= res.length;
+ }
+ update_field(probs);
+ }
}
function to_hex(c) {
- var hex = Math.floor(c).toString(16);
- return hex.length == 1 ? "0" + hex : hex;
+ var hex = Math.floor(c).toString(16);
+ return hex.length == 1 ? "0" + hex : hex;
}
function get_color(prob) {
- var red, green, blue;
- switch (prob) {
- case 100 :
- red = 255;
- green = 0;
- break;
- case 0 :
- red = 0;
- green = 255;
- break;
- case -1 :
- return undefined
- default :
- red = 255 * prob / 100;
- green = 255 * (100 - prob) / 200;
- }
- var red = to_hex(red);
- var green = to_hex(green);
- var blue = to_hex(0);
- return "#" + red + green + blue;
+ return prob === -1 ? undefined : ("#" +
+ to_hex(255 * Math.pow(prob, 1.1)) +
+ to_hex(255 * (1 - Math.pow(prob, 0.4))) +
+ to_hex(0));
}
diff --git a/util.js b/util.js
new file mode 100644
index 0000000..db82807
--- /dev/null
+++ b/util.js
@@ -0,0 +1,126 @@
+function isInt(n) {
+ return Number(n) === n && n % 1 === 0;
+}
+
+var fact_arr = [1, 1];
+function factorial(n) {
+ for (var i = 2; i <= n; ++i) {
+ fact_arr[i] = i * fact_arr[i-1];
+ }
+ return fact_arr[n];
+}
+
+function permutation(arr, n) {
+ var perm = [];
+ var rest = arr.slice();
+ for (var i = 0; i < arr.length; ++i) {
+ var f = factorial(arr.length - i - 1);
+ var pos = Math.floor(n / f);
+ n = n % f;
+ perm.push(rest.splice(pos, 1)[0]);
+ }
+
+ return perm;
+}
+
+function create_map(n, func) {
+ var result = [];
+ var i;
+ for (i = 0; i < n; ++i) {
+ result.push(func(i));
+ }
+ return result;
+}
+
+function create_matrix(n, m, func) {
+ var i, j;
+ var result = create_map(n, function(i) {
+ return create_map(m, function (j) {
+ return func(i, j);
+ });
+ });
+ result.width = m;
+ result.height = n;
+ result.forEach = function (callback, thisArg) {
+ var that = thisArg || this;
+ for (i = 0; i < result.height; ++i) {
+ for (j = 0; j < result.width; ++j) {
+ callback(that[i][j], i, j, that);
+ }
+ }
+ };
+ return result;
+}
+
+function copy_matrix(matr) {
+ var i, j;
+ var result = [];
+ result.width = matr.width;
+ result.height = matr.height;
+ for (i = 0; i < matr.height; ++i) {
+ result[i] = [];
+ for (j = 0; j < matr.width; ++j) {
+ result[i][j] = matr[i][j];
+ }
+ }
+ result.matrForEach = matr.matrForEach;
+ return result;
+}
+
+function copy_matrix_wo_col(matr, col) {
+ return create_matrix(matr.height, matr.width - 1, function(i, j) {
+ return j < col ? matr[i][j] : matr[i][j+1];
+ });
+}
+
+function copy_matrix_wo_row(matr, row) {
+ return create_matrix(matr.height - 1, matr.width, function(i, j) {
+ return i < row ? matr[i][j] : matr[i+1][j];
+ });
+}
+
+function cr_const(n) {
+ return function() {
+ return n;
+ };
+}
+
+function combinations_arr (n, kmin, kmax) {
+ if (kmax < 0 || n < kmin) {
+ return [];
+ }
+
+ if (n === 0) {
+ return [[]];
+ }
+
+ if (n === 1) {
+ var res = [];
+ if (kmax > 0) {
+ res.push([1]);
+ }
+ if (kmin <= 0) {
+ res.push([0]);
+ }
+ return res;
+ }
+
+ var combsPlus = combinations_arr (n-1, kmin-1, kmax-1);
+ var combsMin = combinations_arr (n-1, kmin, kmax);
+
+ combsPlus.map(function (el) { el.splice(0, 0, 1); return el; });
+ combsMin.map( function (el) { el.splice(0, 0, 0); return el; });
+ return combsPlus.concat(combsMin);
+}
+
+function merge_in (arr1, arr2) {
+ var s = arr1.slice();
+ var lp = 0;
+ for (var i = 0; i < arr2.length; ++i) {
+ while (lp in s) {
+ ++lp;
+ }
+ s[lp] = arr2[i];
+ }
+ return s;
+}