- // ==UserScript==
- // @name MouseHunt - Eggsweeper Helper
- // @author Tran Situ (tsitu)
- // @namespace https://gf.qytechs.cn/en/users/232363-tsitu
- // @version 1.0
- // @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 'X' 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 = [];
- 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);
- }
- }
- }
- 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;
- }
-
- // Place suggestion(s) onto UI
- displayStats(board);
-
- // Inject tile titles with "Score: #"
- for (let i = 1; i < 54; i++) {
- const tile = document.querySelector(
- `.eggSweeper-board-row-cell[data-index="${i}"]`
- );
- tile.setAttribute("title", `Score: ${scoreArray[i - 1]}`);
- }
-
- // Add targeting overlay for high scores (>= 0 is okay)
- if (!isSilent) {
- for (let el of highScore[1]) {
- const tile = document.querySelector(
- `.eggSweeper-board-row-cell[data-index="${el}"]`
- );
-
- if (tile) {
- // Inject "X" target(s) into UI
- const textSpan = document.createElement("span");
- textSpan.className = "egg-tile-target";
- textSpan.textContent = "X";
- textSpan.setAttribute(
- "style",
- `z-index: 100; position: absolute; color: firebrick; font-size: 36px; font-weight: bold; left: 18px; top: 8px; text-align: center; pointer-events: none; text-shadow: -1px 0 white, 0 1px white, 1px 0 white, 0 -1px white;`
- );
- tile.appendChild(textSpan);
-
- // Remove existing targets and score titles when a tile is clicked
- tile.addEventListener("click", function() {
- for (let node of document.querySelectorAll(".egg-tile-target")) {
- node.remove();
- }
- for (let i = 1; i < 55; i++) {
- const tile = document.querySelector(
- `.eggSweeper-board-row-cell[data-index="${i}"]`
- );
- tile.removeAttribute("title");
- }
- });
- }
- }
- }
-
- 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
- };
- }
- })();