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