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; +}