Greasy Fork

Greasy Fork is available in English.

MouseHunt - Eggsweeper Helper

Tool to help with SEH Eggsweeper puzzle boards

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         MouseHunt - Eggsweeper Helper
// @author       Tran Situ (tsitu)
// @namespace    http://greasyfork.icu/en/users/232363-tsitu
// @version      1.2
// @description  Tool to help with SEH Eggsweeper puzzle boards
// @match        http://www.mousehuntgame.com/*
// @match        https://www.mousehuntgame.com/*
// ==/UserScript==

(function () {
  const originalOpen = XMLHttpRequest.prototype.open;
  XMLHttpRequest.prototype.open = function () {
    this.addEventListener("load", function () {
      if (
        this.responseURL ===
        "https://www.mousehuntgame.com/managers/ajax/events/eggstreme_eggscavation.php"
      ) {
        console.group("Eggsweeper Helper");
        let game;
        try {
          game = JSON.parse(this.responseText)["game"];
          if (game["is_active"] && !game["is_complete"]) {
            if (game["num_snowballs"] > 0) {
              parseBoard(game, false);
            } else {
              parseBoard(game, true);
              console.log("You are out of Shovels!");
            }
          } else if (game["is_active"] && game["is_complete"]) {
            displayStats(game.board_rows);
          } else {
            console.log("Board is inactive");
          }
        } catch (error) {
          console.log("Failed to process server response");
          console.error(error.stack);
        }
        console.groupEnd("Eggsweeper Helper");
      }
    });
    originalOpen.apply(this, arguments);
  };

  /**
   * Main function to process overall game state
   * @param {object} game Parsed response from eggstreme_eggscavation.php
   * @param {boolean} isSilent True = inject "o" targeting overlay
   */
  function parseBoard(game, isSilent) {
    const board = game.board_rows;
    console.time("Duration");

    console.log(board);

    // Build an empty initial board
    const boardState = generateEmptyGameBoard("available");

    // Parse current game state and populate boardState with hits and misses
    for (let row of board) {
      for (let tile of row.data) {
        const loc = getArrayIndex(tile.value);
        if (tile.status === "miss") {
          boardState[loc.y][loc.x] = tile.num_clues;
        } else if (tile.status.indexOf("complete") >= 0) {
          boardState[loc.y][loc.x] = "hit";
        } else if (tile.status === "no_egg") {
          boardState[loc.y][loc.x] = "none";
        }
      }
    }
    console.table(boardState);

    /**
     * Calculate intermediate ENE board
     * 0 and up: Tile value after subtracting surrounding hits
     * -1: Initial value, corresponds to 0 in final scored board
     * -2: Guaranteed no egg here, corresponds to -1 in final scored board
     */
    const intBoard = generateEmptyGameBoard(-1);
    for (let i = 0; i < 6; i++) {
      for (let j = 0; j < 9; j++) {
        const tile = boardState[i][j];
        if (typeof tile === "number") {
          let score = tile;
          if (tile > 0) {
            for (let el of getBordering(i, j)) {
              if (boardState[el[0]][el[1]] === "hit") {
                score -= 1;
              }
            }
          }
          intBoard[i][j] = score;

          if (score === 0) {
            for (let el of getBordering(i, j)) {
              if (intBoard[el[0]][el[1]] === -1) {
                intBoard[el[0]][el[1]] = -2;
              }
            }
          }
        } else if (tile === "hit") {
          intBoard[i][j] = -2;
        }
      }
    }
    console.table(intBoard);

    /**
     * Calculate final scored board
     * 1 and up: Positive score, there is a chance an egg is underneath this tile
     * 0: Initial score, no extra info to include/exclude chance of egg
     * -1: Guaranteed no egg here
     */
    const scoredBoard = generateEmptyGameBoard(0);
    for (let i = 0; i < 6; i++) {
      for (let j = 0; j < 9; j++) {
        const int = intBoard[i][j];
        if (int > 0) {
          for (let el of getBordering(i, j)) {
            if (
              boardState[el[0]][el[1]] === "available" &&
              intBoard[el[0]][el[1]] !== -2
            ) {
              scoredBoard[el[0]][el[1]] += int;
            }
          }
        } else if (int === -2) {
          scoredBoard[i][j] = -1;
        }
        if (typeof boardState[i][j] === "number") {
          scoredBoard[i][j] = -1;
        }
      }
    }
    console.table(scoredBoard);

    // Calculate scores and ideal position(s)
    const scoreArray = [];
    const noHits = [];
    let highScore = [0, []];
    for (let i = 0; i < 6; i++) {
      for (let j = 0; j < 9; j++) {
        const dataVal = getValue(i, j);
        const score = scoredBoard[i][j];
        scoreArray.push(score);
        if (score > highScore[0]) {
          highScore[0] = score;
          highScore[1] = [dataVal];
        } else if (score === highScore[0]) {
          highScore[1].push(dataVal);
        } else if (score === -1) {
          noHits.push(dataVal); // Accumulate guaranteed no hit tiles
        }
      }
    }
    console.log(scoreArray);
    console.log(highScore);

    // Second pass: apply bonuses to tiles that can give the most info
    if (highScore[1].length > 1) {
      const newScores = [];
      for (let val of highScore[1]) {
        const pos = getArrayIndex(val);
        let score = highScore[0];
        for (let el of getBordering(pos.y, pos.x)) {
          if (scoredBoard[el[0]][el[1]] >= 0) score += 1;
        }
        newScores.push(score);
      }

      const newArr = [0, []];
      for (let i = 0; i < newScores.length; i++) {
        if (newScores[i] > newArr[0]) {
          newArr[0] = newScores[i];
          newArr[1] = [highScore[1][i]];
        } else if (newScores[i] === newArr[0]) {
          newArr[1].push(highScore[1][i]);
        }
      }
      highScore = newArr;
    }
    console.log(highScore);

    // Place suggestion(s) onto UI
    for (let i = 1; i < 54; i++) {
      const tile = document.querySelector(
        `.eggSweeper-board-row-cell[data-index="${i}"]`
      );
      // Inject tile titles with "Score: #"
      tile.setAttribute("title", `Score: ${scoreArray[i - 1]}`);

      // Remove existing targets and score titles when an "available" tile is clicked
      if (tile.getAttribute("tsitu-click-listener") !== "true") {
        tile.addEventListener("click", function () {
          if (tile.className.indexOf("available") >= 0) {
            document.querySelectorAll(".egg-tile-target").forEach(node => {
              node.remove();
            });
            document
              .querySelectorAll(
                ".eggSweeper-layer.tiles .eggSweeper-board-row-cell"
              )
              .forEach(node => {
                node.removeAttribute("title");
              });
          }
        });
        tile.setAttribute("tsitu-click-listener", "true");
      }
    }

    // Add targeting overlay for high scores and mark guaranteed no hit tiles
    if (!isSilent) {
      for (let el of highScore[1]) {
        const tile = document.querySelector(
          `.eggSweeper-board-row-cell[data-index="${el}"]`
        );

        if (tile) {
          // Inject "o" target(s) into UI
          const textSpan = document.createElement("span");
          textSpan.className = "egg-tile-target";
          textSpan.textContent = "o";
          textSpan.setAttribute(
            "style",
            `z-index: 100; position: absolute; color: seagreen; font-size: 70px; font-weight: bold; left: 8px; top: -21px; text-align: center; pointer-events: none; text-shadow: -1px 0 white, 0 1px white, 1px 0 white, 0 -1px white;`
          );
          tile.appendChild(textSpan);
        }
      }

      for (let el of noHits) {
        const tile = document.querySelector(
          `.eggSweeper-board-row-cell[data-index="${el}"]`
        );

        if (tile && tile.className.indexOf("available") >= 0) {
          // Auto-mark as a no-egg tile
          if (!tile.classList.contains("no_egg")) {
            tile.classList.toggle("no_egg");
            tile.style.pointerEvents = "auto";
          }
        }
      }
    }

    displayStats(board);
    console.timeEnd("Duration");
  }

  /**
   * Counts hits/misses/total and render to top-left of UI
   * @param {object} board From game["board_rows"]
   */
  function displayStats(board) {
    let countMiss = 0;
    let countHit = 0;
    for (let row of board) {
      for (let tile of row.data) {
        if (tile.status === "miss") {
          countMiss++;
        } else if (tile.status.indexOf("complete") >= 0) {
          countHit++;
        }
      }
    }

    // Reset "title-span-data" node
    const titleSpanData = document.querySelector("#title-span-data-egg");
    if (titleSpanData) {
      titleSpanData.remove();
    }

    // Shovel stats on top left of game UI
    const mainTitle = document.querySelector(".eggSweeper-title");
    const leftSpan = document.createElement("span");
    leftSpan.id = "title-span-data-egg";
    leftSpan.textContent = `Hits:        ${countHit}\r\nMisses:   ${countMiss}\r\nTotal:      ${
      countMiss + countHit
    }`;
    leftSpan.setAttribute(
      "style",
      "text-shadow: none; white-space: pre; z-index: 100; position: absolute; color: white; font-size: 12px; left: 10px; top: 0px; text-align: left;"
    );
    mainTitle.appendChild(leftSpan);
  }

  /**
   * Generates a 6-row x 9-column pre-filled with a default value
   * @param {*} defaultValue
   */
  function generateEmptyGameBoard(defaultValue) {
    const returnBoard = [];
    for (let i = 0; i < 6; i++) {
      const arr = [];
      arr.length = 9;
      arr.fill(defaultValue);
      returnBoard.push(arr);
    }

    return returnBoard;
  }

  /**
   * Get bordering tile coordinates
   * Sample input: [1,1]
   * Return: [0,0] [0,1] [0,2] [1,0] [1,2] [2,0] [2,1] [2,2]
   * @param {number} row Integer from 0-5
   * @param {number} col Integer from 0-8
   * @return {number[]} Array of bordering [row, col] pairs
   */
  function getBordering(row, col) {
    const retArr = [];
    const rowM = row - 1;
    const colM = col - 1;
    const rowP = row + 1;
    const colP = col + 1;

    function validRow(val) {
      return val >= 0 && val <= 5;
    }

    function validCol(val) {
      return val >= 0 && val <= 8;
    }

    if (validRow(rowM) && validCol(colM)) retArr.push([rowM, colM]);
    if (validRow(rowM) && validCol(col)) retArr.push([rowM, col]);
    if (validRow(rowM) && validCol(colP)) retArr.push([rowM, colP]);
    if (validRow(row) && validCol(colM)) retArr.push([row, colM]);
    if (validRow(row) && validCol(colP)) retArr.push([row, colP]);
    if (validRow(rowP) && validCol(colM)) retArr.push([rowP, colM]);
    if (validRow(rowP) && validCol(col)) retArr.push([rowP, col]);
    if (validRow(rowP) && validCol(colP)) retArr.push([rowP, colP]);

    return retArr;
  }

  /**
   * Convert array indices to an integer data-index value
   * @param {number} row Integer from 0-5
   * @param {number} col Integer from 0-8
   * @return {number} Integer from 1-54
   */
  function getValue(row, col) {
    return row * 9 + (col + 1);
  }

  /**
   * Convert an integer data-index value to proper boardState array indices
   * @param {number} value Integer from 1-54
   * @return {object}
   */
  function getArrayIndex(value) {
    let posX = (value % 9) - 1;
    let posY = Math.floor(value / 9);

    // Right-most column is a special case
    if (value % 9 === 0) {
      posX = 8;
      posY = Math.floor(value / 9) - 1;
    }

    return {
      x: posX,
      y: posY
    };
  }
})();