MouseHunt - Eggsweeper Helper

Tool to help with SEH Eggsweeper puzzle boards

  1. // ==UserScript==
  2. // @name MouseHunt - Eggsweeper Helper
  3. // @author Tran Situ (tsitu)
  4. // @namespace https://gf.qytechs.cn/en/users/232363-tsitu
  5. // @version 1.2
  6. // @description Tool to help with SEH Eggsweeper puzzle boards
  7. // @match http://www.mousehuntgame.com/*
  8. // @match https://www.mousehuntgame.com/*
  9. // ==/UserScript==
  10.  
  11. (function () {
  12. const originalOpen = XMLHttpRequest.prototype.open;
  13. XMLHttpRequest.prototype.open = function () {
  14. this.addEventListener("load", function () {
  15. if (
  16. this.responseURL ===
  17. "https://www.mousehuntgame.com/managers/ajax/events/eggstreme_eggscavation.php"
  18. ) {
  19. console.group("Eggsweeper Helper");
  20. let game;
  21. try {
  22. game = JSON.parse(this.responseText)["game"];
  23. if (game["is_active"] && !game["is_complete"]) {
  24. if (game["num_snowballs"] > 0) {
  25. parseBoard(game, false);
  26. } else {
  27. parseBoard(game, true);
  28. console.log("You are out of Shovels!");
  29. }
  30. } else if (game["is_active"] && game["is_complete"]) {
  31. displayStats(game.board_rows);
  32. } else {
  33. console.log("Board is inactive");
  34. }
  35. } catch (error) {
  36. console.log("Failed to process server response");
  37. console.error(error.stack);
  38. }
  39. console.groupEnd("Eggsweeper Helper");
  40. }
  41. });
  42. originalOpen.apply(this, arguments);
  43. };
  44.  
  45. /**
  46. * Main function to process overall game state
  47. * @param {object} game Parsed response from eggstreme_eggscavation.php
  48. * @param {boolean} isSilent True = inject "o" targeting overlay
  49. */
  50. function parseBoard(game, isSilent) {
  51. const board = game.board_rows;
  52. console.time("Duration");
  53.  
  54. console.log(board);
  55.  
  56. // Build an empty initial board
  57. const boardState = generateEmptyGameBoard("available");
  58.  
  59. // Parse current game state and populate boardState with hits and misses
  60. for (let row of board) {
  61. for (let tile of row.data) {
  62. const loc = getArrayIndex(tile.value);
  63. if (tile.status === "miss") {
  64. boardState[loc.y][loc.x] = tile.num_clues;
  65. } else if (tile.status.indexOf("complete") >= 0) {
  66. boardState[loc.y][loc.x] = "hit";
  67. } else if (tile.status === "no_egg") {
  68. boardState[loc.y][loc.x] = "none";
  69. }
  70. }
  71. }
  72. console.table(boardState);
  73.  
  74. /**
  75. * Calculate intermediate ENE board
  76. * 0 and up: Tile value after subtracting surrounding hits
  77. * -1: Initial value, corresponds to 0 in final scored board
  78. * -2: Guaranteed no egg here, corresponds to -1 in final scored board
  79. */
  80. const intBoard = generateEmptyGameBoard(-1);
  81. for (let i = 0; i < 6; i++) {
  82. for (let j = 0; j < 9; j++) {
  83. const tile = boardState[i][j];
  84. if (typeof tile === "number") {
  85. let score = tile;
  86. if (tile > 0) {
  87. for (let el of getBordering(i, j)) {
  88. if (boardState[el[0]][el[1]] === "hit") {
  89. score -= 1;
  90. }
  91. }
  92. }
  93. intBoard[i][j] = score;
  94.  
  95. if (score === 0) {
  96. for (let el of getBordering(i, j)) {
  97. if (intBoard[el[0]][el[1]] === -1) {
  98. intBoard[el[0]][el[1]] = -2;
  99. }
  100. }
  101. }
  102. } else if (tile === "hit") {
  103. intBoard[i][j] = -2;
  104. }
  105. }
  106. }
  107. console.table(intBoard);
  108.  
  109. /**
  110. * Calculate final scored board
  111. * 1 and up: Positive score, there is a chance an egg is underneath this tile
  112. * 0: Initial score, no extra info to include/exclude chance of egg
  113. * -1: Guaranteed no egg here
  114. */
  115. const scoredBoard = generateEmptyGameBoard(0);
  116. for (let i = 0; i < 6; i++) {
  117. for (let j = 0; j < 9; j++) {
  118. const int = intBoard[i][j];
  119. if (int > 0) {
  120. for (let el of getBordering(i, j)) {
  121. if (
  122. boardState[el[0]][el[1]] === "available" &&
  123. intBoard[el[0]][el[1]] !== -2
  124. ) {
  125. scoredBoard[el[0]][el[1]] += int;
  126. }
  127. }
  128. } else if (int === -2) {
  129. scoredBoard[i][j] = -1;
  130. }
  131. if (typeof boardState[i][j] === "number") {
  132. scoredBoard[i][j] = -1;
  133. }
  134. }
  135. }
  136. console.table(scoredBoard);
  137.  
  138. // Calculate scores and ideal position(s)
  139. const scoreArray = [];
  140. const noHits = [];
  141. let highScore = [0, []];
  142. for (let i = 0; i < 6; i++) {
  143. for (let j = 0; j < 9; j++) {
  144. const dataVal = getValue(i, j);
  145. const score = scoredBoard[i][j];
  146. scoreArray.push(score);
  147. if (score > highScore[0]) {
  148. highScore[0] = score;
  149. highScore[1] = [dataVal];
  150. } else if (score === highScore[0]) {
  151. highScore[1].push(dataVal);
  152. } else if (score === -1) {
  153. noHits.push(dataVal); // Accumulate guaranteed no hit tiles
  154. }
  155. }
  156. }
  157. console.log(scoreArray);
  158. console.log(highScore);
  159.  
  160. // Second pass: apply bonuses to tiles that can give the most info
  161. if (highScore[1].length > 1) {
  162. const newScores = [];
  163. for (let val of highScore[1]) {
  164. const pos = getArrayIndex(val);
  165. let score = highScore[0];
  166. for (let el of getBordering(pos.y, pos.x)) {
  167. if (scoredBoard[el[0]][el[1]] >= 0) score += 1;
  168. }
  169. newScores.push(score);
  170. }
  171.  
  172. const newArr = [0, []];
  173. for (let i = 0; i < newScores.length; i++) {
  174. if (newScores[i] > newArr[0]) {
  175. newArr[0] = newScores[i];
  176. newArr[1] = [highScore[1][i]];
  177. } else if (newScores[i] === newArr[0]) {
  178. newArr[1].push(highScore[1][i]);
  179. }
  180. }
  181. highScore = newArr;
  182. }
  183. console.log(highScore);
  184.  
  185. // Place suggestion(s) onto UI
  186. for (let i = 1; i < 54; i++) {
  187. const tile = document.querySelector(
  188. `.eggSweeper-board-row-cell[data-index="${i}"]`
  189. );
  190. // Inject tile titles with "Score: #"
  191. tile.setAttribute("title", `Score: ${scoreArray[i - 1]}`);
  192.  
  193. // Remove existing targets and score titles when an "available" tile is clicked
  194. if (tile.getAttribute("tsitu-click-listener") !== "true") {
  195. tile.addEventListener("click", function () {
  196. if (tile.className.indexOf("available") >= 0) {
  197. document.querySelectorAll(".egg-tile-target").forEach(node => {
  198. node.remove();
  199. });
  200. document
  201. .querySelectorAll(
  202. ".eggSweeper-layer.tiles .eggSweeper-board-row-cell"
  203. )
  204. .forEach(node => {
  205. node.removeAttribute("title");
  206. });
  207. }
  208. });
  209. tile.setAttribute("tsitu-click-listener", "true");
  210. }
  211. }
  212.  
  213. // Add targeting overlay for high scores and mark guaranteed no hit tiles
  214. if (!isSilent) {
  215. for (let el of highScore[1]) {
  216. const tile = document.querySelector(
  217. `.eggSweeper-board-row-cell[data-index="${el}"]`
  218. );
  219.  
  220. if (tile) {
  221. // Inject "o" target(s) into UI
  222. const textSpan = document.createElement("span");
  223. textSpan.className = "egg-tile-target";
  224. textSpan.textContent = "o";
  225. textSpan.setAttribute(
  226. "style",
  227. `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;`
  228. );
  229. tile.appendChild(textSpan);
  230. }
  231. }
  232.  
  233. for (let el of noHits) {
  234. const tile = document.querySelector(
  235. `.eggSweeper-board-row-cell[data-index="${el}"]`
  236. );
  237.  
  238. if (tile && tile.className.indexOf("available") >= 0) {
  239. // Auto-mark as a no-egg tile
  240. if (!tile.classList.contains("no_egg")) {
  241. tile.classList.toggle("no_egg");
  242. tile.style.pointerEvents = "auto";
  243. }
  244. }
  245. }
  246. }
  247.  
  248. displayStats(board);
  249. console.timeEnd("Duration");
  250. }
  251.  
  252. /**
  253. * Counts hits/misses/total and render to top-left of UI
  254. * @param {object} board From game["board_rows"]
  255. */
  256. function displayStats(board) {
  257. let countMiss = 0;
  258. let countHit = 0;
  259. for (let row of board) {
  260. for (let tile of row.data) {
  261. if (tile.status === "miss") {
  262. countMiss++;
  263. } else if (tile.status.indexOf("complete") >= 0) {
  264. countHit++;
  265. }
  266. }
  267. }
  268.  
  269. // Reset "title-span-data" node
  270. const titleSpanData = document.querySelector("#title-span-data-egg");
  271. if (titleSpanData) {
  272. titleSpanData.remove();
  273. }
  274.  
  275. // Shovel stats on top left of game UI
  276. const mainTitle = document.querySelector(".eggSweeper-title");
  277. const leftSpan = document.createElement("span");
  278. leftSpan.id = "title-span-data-egg";
  279. leftSpan.textContent = `Hits: ${countHit}\r\nMisses: ${countMiss}\r\nTotal: ${
  280. countMiss + countHit
  281. }`;
  282. leftSpan.setAttribute(
  283. "style",
  284. "text-shadow: none; white-space: pre; z-index: 100; position: absolute; color: white; font-size: 12px; left: 10px; top: 0px; text-align: left;"
  285. );
  286. mainTitle.appendChild(leftSpan);
  287. }
  288.  
  289. /**
  290. * Generates a 6-row x 9-column pre-filled with a default value
  291. * @param {*} defaultValue
  292. */
  293. function generateEmptyGameBoard(defaultValue) {
  294. const returnBoard = [];
  295. for (let i = 0; i < 6; i++) {
  296. const arr = [];
  297. arr.length = 9;
  298. arr.fill(defaultValue);
  299. returnBoard.push(arr);
  300. }
  301.  
  302. return returnBoard;
  303. }
  304.  
  305. /**
  306. * Get bordering tile coordinates
  307. * Sample input: [1,1]
  308. * Return: [0,0] [0,1] [0,2] [1,0] [1,2] [2,0] [2,1] [2,2]
  309. * @param {number} row Integer from 0-5
  310. * @param {number} col Integer from 0-8
  311. * @return {number[]} Array of bordering [row, col] pairs
  312. */
  313. function getBordering(row, col) {
  314. const retArr = [];
  315. const rowM = row - 1;
  316. const colM = col - 1;
  317. const rowP = row + 1;
  318. const colP = col + 1;
  319.  
  320. function validRow(val) {
  321. return val >= 0 && val <= 5;
  322. }
  323.  
  324. function validCol(val) {
  325. return val >= 0 && val <= 8;
  326. }
  327.  
  328. if (validRow(rowM) && validCol(colM)) retArr.push([rowM, colM]);
  329. if (validRow(rowM) && validCol(col)) retArr.push([rowM, col]);
  330. if (validRow(rowM) && validCol(colP)) retArr.push([rowM, colP]);
  331. if (validRow(row) && validCol(colM)) retArr.push([row, colM]);
  332. if (validRow(row) && validCol(colP)) retArr.push([row, colP]);
  333. if (validRow(rowP) && validCol(colM)) retArr.push([rowP, colM]);
  334. if (validRow(rowP) && validCol(col)) retArr.push([rowP, col]);
  335. if (validRow(rowP) && validCol(colP)) retArr.push([rowP, colP]);
  336.  
  337. return retArr;
  338. }
  339.  
  340. /**
  341. * Convert array indices to an integer data-index value
  342. * @param {number} row Integer from 0-5
  343. * @param {number} col Integer from 0-8
  344. * @return {number} Integer from 1-54
  345. */
  346. function getValue(row, col) {
  347. return row * 9 + (col + 1);
  348. }
  349.  
  350. /**
  351. * Convert an integer data-index value to proper boardState array indices
  352. * @param {number} value Integer from 1-54
  353. * @return {object}
  354. */
  355. function getArrayIndex(value) {
  356. let posX = (value % 9) - 1;
  357. let posY = Math.floor(value / 9);
  358.  
  359. // Right-most column is a special case
  360. if (value % 9 === 0) {
  361. posX = 8;
  362. posY = Math.floor(value / 9) - 1;
  363. }
  364.  
  365. return {
  366. x: posX,
  367. y: posY
  368. };
  369. }
  370. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址