resolve telegram mineroobot automatically
// ==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('r###lt 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 r###lt = 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 (r###lt(ptr) >= 0) { $(reply_markup_els[ptr.y][ptr.x]).find('button').css('background', hslColor(r###lt(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);