telegram Mineroobot solver

resolve telegram mineroobot automatically

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==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);