// ==UserScript==
// @name telegram Mineroobot solver
// @version 0.0.1
// @include https://web.telegram.org/*
// @description resolve telegram mineroobot automatically
// @namespace mineroobot-solver.mmis1000.me
// @run-at document-start
// @grant none
// ==/UserScript==
window.name = 'NG_ENABLE_DEBUG_INFO!' + window.name;
/**
* Converts an HSL color value to RGB. Conversion formula
* adapted from http://en.wikipedia.org/wiki/HSL_color_space.
* Assumes h, s, and l are contained in the set [0, 1] and
* returns r, g, and b in the set [0, 255].
*
* @param Number h The hue
* @param Number s The saturation
* @param Number l The lightness
* @return Array The RGB representation
*/
function hslToRgb(h, s, l) {
var r, g, b;
if (s == 0) {
r = g = b = l; // achromatic
}
else {
var hue2rgb = function hue2rgb(p, q, t) {
if (t < 0) t += 1;
if (t > 1) t -= 1;
if (t < 1 / 6) return p + (q - p) * 6 * t;
if (t < 1 / 2) return q;
if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
return p;
};
var q = l < 0.5 ? l * (1 + s) : l + s - l * s;
var p = 2 * l - q;
r = hue2rgb(p, q, h + 1 / 3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1 / 3);
}
return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
}
/**
* @param {number} r range between 0-256
* @param {number} g range between 0-256
* @param {number} b range between 0-256
* @return {string}
*/
function rgbColor(r, g, b) {
// return `\u001b[${bg ? 48 : 38};2;${r};${g};${b}m`;
var str = ((r << 16) + (g << 8) + b).toString(16);
while (str.length < 6) str = '0' + str;
return '#' + str;
}
/**
* @param {number} h range between 0-1
* @param {number} s range between 0-1
* @param {number} l range between 0-1
* @return {string}
*/
function hslColor(h, s, l) {
var [r, g, b] = hslToRgb(h, s, l);
return rgbColor(r, g, b);
}
/**
* @param {number} m total
* @param {number} n selected
* @return {number}
*/
function c(m, n) {
var val = 1;
for (let temp = m; temp > m - n; temp--) {
val = val * temp / (temp - m + n);
}
return val;
}
/**
* @param {number} w width
* @param {number} h height
* @return {function}
*/
function p(w, h) {
return function ptr(x, y) {
var pos = {x, y};
pos.next = function () {
if (x < 0 || x >= w || y < 0 && y >= h) {
return null;
}
// highest valid pointer
if (x >= w - 1 && y >= h - 1) {
return null;
}
if (x < w - 1) {
return ptr(x + 1, y);
} else {
return ptr(0, y + 1);
}
};
if (x >= 0 && x < w && y >= 0 && y < h) {
pos.offset = x + y * w;
} else {
pos.offset = null;
}
pos.neighbors = function() {
return [
ptr(x - 1, y - 1),
ptr(x, y - 1),
ptr(x + 1, y - 1),
ptr(x - 1, y),
ptr(x + 1, y),
ptr(x - 1, y + 1),
ptr(x, y + 1),
ptr(x + 1, y + 1)
];
};
return pos;
};
}
/**
* @param {number} w width
* @param {number} h height
* @param (any} init init value
* @param {any} outBoundVal value access out of table bound
* @return {function}
*/
function t(w, h, init, outBoundVal) {
var data = [];
var Ptr = p(w, h);
function table(ptr, val) {
if (val != null) {
if (ptr.offset != null) {
data[ptr.offset] = val;
}
} else {
if (ptr.offset != null) {
return data[ptr.offset];
} else {
return outBoundVal;
}
}
}
table.w = w;
table.h = h;
table.data = data;
for (let ptr = Ptr(0, 0); ptr; ptr = ptr.next()) {
data[ptr.offset] = init;
}
table.clone = function () {
var newTable = t(w, h, init, outBoundVal);
var ptr = p(w, h)(0, 0);
for (let ptr = Ptr(0, 0); ptr; ptr = ptr.next()) {
newTable(ptr, table(ptr));
}
return newTable;
};
//regex must has global flag
table.countRegex = function(regex) {
var counter = 0;
for (let ptr = Ptr(0, 0); ptr; ptr = ptr.next()) {
if (regex.test(table(ptr))) {
counter += 1;
}
}
return counter;
};
table.count = function (val) {
var counter = 0;
for (let ptr = Ptr(0, 0); ptr; ptr = ptr.next()) {
if (table(ptr) === val) {
counter += 1;
}
}
return counter;
};
table.toString = function (seperator) {
var res = [];
seperator = seperator == null ? ' ,' : seperator;
for (var i = 0; i < data.length; i += w) {
res.push(data.slice(i, i + w).join(seperator));
}
return res.join('\r\n');
};
return table;
}
/**
* @param {number} w width
* @param {number} h height
* @param (number} totalMines total mines
* @param {string} str the game board
* @return {function}
*/
function resolve(w, h, totalMines, str) {
/*
'-': unknown
'x': empty
'o': bomb
'0' - '9': count near by
*/
var table = t(w, h, '-', 'x');
// init
var Ptr = p(w, h);
var ptr = Ptr(0, 0);
var temp = str.replace(/[\r\n]/g, '');
for (let ptr = Ptr(0, 0); ptr; ptr = ptr.next()) {
table(ptr, temp.slice(ptr.offset, ptr.offset + 1));
}
// found slot with no no number neighbors
// true: has neighbor
// false: no neighbor
var mask = t(w, h, false, false);
for (let ptr = Ptr(0, 0); ptr; ptr = ptr.next()) {
let neightborPtrs = ptr.neighbors();
mask(ptr, neightborPtrs.reduce(function (prev, ptr) {
var val = table(ptr);
return prev || !!val.match(/[0-9]/);
}, false));
}
var unPredicableSlots = mask.count(false);
var totalFoundMines = table.count('o');
var minesLeft = totalMines - totalFoundMines;
// situation when there are n mines drops in unprediactable area;
var unPredicableMultiplier = {};
var combinationCounts = {};
var guessTables = [];
for (let unPredicableCount = minesLeft; unPredicableCount >= 0; unPredicableCount--) {
unPredicableMultiplier[unPredicableCount] = c(unPredicableSlots, unPredicableCount);
combinationCounts[unPredicableCount] = 0;
guessTables[unPredicableCount] = t(w, h, 0, 0);
}
// solve until there is no other possible combination
function solve(ptr, currentTable, minesLeft) {
// move until guessable Slot
while (ptr && (!mask(ptr) || currentTable(ptr) !== '-')) {
ptr = ptr.next();
}
if (!ptr && minesLeft >= 0) {
// found a possible solution;
// console.log('hit #' + combinationCounts[minesLeft] + '\r\n' + currentTable.toString());
combinationCounts[minesLeft] += 1;
for (let ptr = Ptr(0, 0); ptr; ptr = ptr.next()) {
if (mask(ptr)) {
var guessTable = guessTables[minesLeft];
if (!guessTable) {
console.log(minesLeft);
return;
}
if (currentTable(ptr) === 'o') {
guessTable(ptr, guessTable(ptr) + 1);
}
}
}
return;
}
// check if this slot can be mine or not be mine
// and abort the branch if that branch failed;
var neightbors = ptr.neighbors();
var canBeMine = true;
var canBeEmpty = true;
neightbors.forEach(function (neightbor) {
if (currentTable(neightbor).match(/[0-9\s]/)) {
var neightborsOfNeighbor = neightbor.neighbors();
var number = currentTable(neightbor);
number = number === ' ' ? 0 : parseInt(number, 10);
var minesNearBy = neightborsOfNeighbor.reduce(function (prev, curr) {
if (currentTable(curr) === 'o') {
return prev + 1;
} else {
return prev;
}
}, 0);
var emptyNearBy = neightborsOfNeighbor.reduce(function (prev, curr) {
if (currentTable(curr).match(/[0-9x\s]/)) {
return prev + 1;
} else {
return prev;
}
}, 0);
var unknownNearBy = 8 - minesNearBy - emptyNearBy;
var minesToPlace = number - minesNearBy;
if (minesToPlace === unknownNearBy) canBeEmpty = false;
if (minesToPlace === 0) canBeMine = false;
}
});
if (canBeMine && minesLeft !== 0) {
let newTable = currentTable.clone();
newTable(ptr, 'o');
solve(ptr.next(), newTable, minesLeft - 1);
// we still have mine, and we can put mine here
}
if (canBeEmpty) {
let newTable = currentTable.clone();
newTable(ptr, 'x');
solve(ptr.next(), newTable, minesLeft);
}
// if (!canBeMine && !canBeEmpty) {
// console.log('badGuess\r\n' + currentTable)
// }
}
solve(Ptr(0, 0), table.clone(), minesLeft);
// console.log(unPredicableMultiplier)
// console.log(combinationCounts)
// console.log(guessTables.map(function (table, i) {
// return '# guessTable ' + i + '\r\n' + table.toString();
// }).join('\r\n'))
// sum up the possibility
var finalBoardCount = 0;
var finalBoard = t(w, h, 0, 0);
for (let unPredicableCount = minesLeft; unPredicableCount >= 0; unPredicableCount--) {
finalBoardCount += (unPredicableMultiplier[unPredicableCount] * combinationCounts[unPredicableCount]);
// init possibility for unPredicable slot;
for (let ptr = Ptr(0, 0); ptr; ptr = ptr.next()) {
if (!mask(ptr)) {
guessTables[unPredicableCount](ptr, combinationCounts[unPredicableCount] * (unPredicableCount / unPredicableSlots));
}
}
for (let ptr = Ptr(0, 0); ptr; ptr = ptr.next()) {
finalBoard(ptr, finalBoard(ptr) + guessTables[unPredicableCount](ptr) * unPredicableMultiplier[unPredicableCount]);
}
}
if (finalBoardCount === 0) {
throw new Error('invalid board');
}
function formatToPercent(num, small) {
small = small || 2;
var val = parseFloat(num * 100).toFixed(small) + "%";
// xxx.<small>
var totalLength = small + 5;
while (val.length < totalLength) {
val = ' ' + val;
}
return val;
}
var finalBoardPercent = t(w, h, "", "");
for (let ptr = Ptr(0, 0); ptr; ptr = ptr.next()) {
finalBoard(ptr, finalBoard(ptr) / finalBoardCount);
finalBoardPercent(ptr, formatToPercent(finalBoard(ptr)));
if (table(ptr) !== '-') {
finalBoard(ptr, -1);
finalBoardPercent(ptr, ' <N/A>');
}
}
var map = {
'-8': '░',
'0': '□',
'1': '▁',
'2': '▂',
'3': '▃',
'4': '▄',
'5': '▅',
'6': '▆',
'7': '▇',
'8': '█'
};
var visualBoard = t(w, h, " ", " ");
// var visualColorBoard = t(w, h, " ", " ");
for (let ptr = Ptr(0, 0); ptr; ptr = ptr.next()) {
visualBoard(ptr, map[Math.floor(finalBoard(ptr) * 8)]);
// if(finalBoard(ptr) >= 0) {
// visualColorBoard(ptr, hslColor(finalBoard(ptr) / 3, 1, 0.5, true) + ' ' + table(ptr) + '\u001b[0m');
// } else {
// visualColorBoard(ptr, hslColor(2 / 3, 1, 0.5, true) + ' ' + table(ptr) + '\u001b[0m' );
// }
}
console.log('board with ' + totalMines + ' mines');
console.log(table.toString());
console.log('result is');
console.log(finalBoardPercent.toString());
console.log(visualBoard.toString(' '));
// console.log(visualColorBoard.toString(''));
return finalBoard;
}
var map = {
'?': 'o',
'?️': 'o',
'1\u20E3': '1',
'2\u20E3': '2',
'3\u20E3': '3',
'4\u20E3': '4',
'5\u20E3': '5',
'6\u20E3': '6',
'7\u20E3': '7',
'8\u20E3': '8',
'9\u20E3': '9',
' ': '0',
'⬜️': '-',
'?': 'o',
};
var ended = /^? Winner\:/g;
function check() {
var historyRoot = $('.im_history_messages_peer:visible');
var topScope = historyRoot.scope();
var messages = topScope.peerHistory.messages;
var boardMesssages = messages.filter((m)=> m.viaBotID === 223493268);
// interate through messages to find board
// and find board element by id
var messageEls = historyRoot.find('.im_content_message_wrap');
boardMesssages.forEach((message)=>{
var messageEl = messageEls.filter((i, el)=>{
return $(el).scope().historyMessage.$$hashKey === message.$$hashKey;
});
if (messageEl.length > 0 && !messageEl.get(0).watching) {
track(messageEl.scope(), messageEl);
}
});
}
if (typeof unsafeWindow !== 'undefined') {
window.check = unsafeWindow.check = check;
} else {
window.check = check;
}
function track($scope, $el) {
var reply_markup = $scope.historyMessage.reply_markup.rows.map((i)=>i.buttons);
var buttonEls = $el.find('.reply_markup_button_wrap');
var reply_markup_els = reply_markup.map((row)=>row.map((button)=>
buttonEls.filter((i,el)=>$(el).scope().button.$$hashKey === button.$$hashKey)
));
console.log(reply_markup_els);
if(reply_markup_els.length < 8) {
// not yet started
return;
}
//$el.get(0).watching = true;
function compute() {
var reply_markup = $scope.historyMessage.reply_markup.rows.map((i)=>i.buttons);
var buttonEls = $el.find('.reply_markup_button_wrap');
var reply_markup_els = reply_markup.map((row)=>row.map((button)=>
buttonEls.filter((i,el)=>$(el).scope().button.$$hashKey === button.$$hashKey)
));
console.log(reply_markup_els);
reply_markup = reply_markup.slice(0, 8);
reply_markup_els = reply_markup_els.slice(0, 8);
var board = reply_markup.map((row)=>{
return row.map((button)=>{
var mapped = map[button.text];
if (mapped == null) {
throw new Error('cannot map ' + button.text);
}
return mapped;
}).join('');
}).join('\r\n');
console.log(board);
try {
var result = resolve(7, 8, 15, board);
var ptr = p(7, 8)(0, 0);
for (let ptr = p(7, 8)(0, 0); ptr; ptr = ptr.next()) {
if (result(ptr) >= 0) {
$(reply_markup_els[ptr.y][ptr.x]).find('button').css('background', hslColor(result(ptr) / 3, 1, 0.5));
} else {
console.log(hslColor(2 / 3, 1, 0.5));
$(reply_markup_els[ptr.y][ptr.x]).find('button').css('background', hslColor(2 / 3, 1, 0.5));
}
}
} catch(e){}
}
compute();
$scope.$watch('historyMessage.message', compute);
}
setInterval(check, 10000);