MouseHunt - Eggsweeper Helper

Tool to help with SEH Eggsweeper puzzle boards

目前为 2019-04-25 提交的版本。查看 最新版本

  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.0
  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 'X' 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. let highScore = [0, []];
  141. for (let i = 0; i < 6; i++) {
  142. for (let j = 0; j < 9; j++) {
  143. const dataVal = getValue(i, j);
  144. const score = scoredBoard[i][j];
  145. scoreArray.push(score);
  146. if (score > highScore[0]) {
  147. highScore[0] = score;
  148. highScore[1] = [dataVal];
  149. } else if (score === highScore[0]) {
  150. highScore[1].push(dataVal);
  151. }
  152. }
  153. }
  154. console.log(scoreArray);
  155. console.log(highScore);
  156.  
  157. // Second pass: apply bonuses to tiles that can give the most info
  158. if (highScore[1].length > 1) {
  159. const newScores = [];
  160. for (let val of highScore[1]) {
  161. const pos = getArrayIndex(val);
  162. let score = highScore[0];
  163. for (let el of getBordering(pos.y, pos.x)) {
  164. if (scoredBoard[el[0]][el[1]] >= 0) score += 1;
  165. }
  166. newScores.push(score);
  167. }
  168.  
  169. const newArr = [0, []];
  170. for (let i = 0; i < newScores.length; i++) {
  171. if (newScores[i] > newArr[0]) {
  172. newArr[0] = newScores[i];
  173. newArr[1] = [highScore[1][i]];
  174. } else if (newScores[i] === newArr[0]) {
  175. newArr[1].push(highScore[1][i]);
  176. }
  177. }
  178. highScore = newArr;
  179. }
  180.  
  181. // Place suggestion(s) onto UI
  182. displayStats(board);
  183.  
  184. // Inject tile titles with "Score: #"
  185. for (let i = 1; i < 54; i++) {
  186. const tile = document.querySelector(
  187. `.eggSweeper-board-row-cell[data-index="${i}"]`
  188. );
  189. tile.setAttribute("title", `Score: ${scoreArray[i - 1]}`);
  190. }
  191.  
  192. // Add targeting overlay for high scores (>= 0 is okay)
  193. if (!isSilent) {
  194. for (let el of highScore[1]) {
  195. const tile = document.querySelector(
  196. `.eggSweeper-board-row-cell[data-index="${el}"]`
  197. );
  198.  
  199. if (tile) {
  200. // Inject "X" target(s) into UI
  201. const textSpan = document.createElement("span");
  202. textSpan.className = "egg-tile-target";
  203. textSpan.textContent = "X";
  204. textSpan.setAttribute(
  205. "style",
  206. `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;`
  207. );
  208. tile.appendChild(textSpan);
  209.  
  210. // Remove existing targets and score titles when a tile is clicked
  211. tile.addEventListener("click", function() {
  212. for (let node of document.querySelectorAll(".egg-tile-target")) {
  213. node.remove();
  214. }
  215. for (let i = 1; i < 55; i++) {
  216. const tile = document.querySelector(
  217. `.eggSweeper-board-row-cell[data-index="${i}"]`
  218. );
  219. tile.removeAttribute("title");
  220. }
  221. });
  222. }
  223. }
  224. }
  225.  
  226. console.timeEnd("Duration");
  227. }
  228.  
  229. /**
  230. * Counts hits/misses/total and render to top-left of UI
  231. * @param {object} board From game["board_rows"]
  232. */
  233. function displayStats(board) {
  234. let countMiss = 0;
  235. let countHit = 0;
  236. for (let row of board) {
  237. for (let tile of row.data) {
  238. if (tile.status === "miss") {
  239. countMiss++;
  240. } else if (tile.status.indexOf("complete") >= 0) {
  241. countHit++;
  242. }
  243. }
  244. }
  245.  
  246. // Reset "title-span-data" node
  247. const titleSpanData = document.querySelector("#title-span-data-egg");
  248. if (titleSpanData) {
  249. titleSpanData.remove();
  250. }
  251.  
  252. // Shovel stats on top left of game UI
  253. const mainTitle = document.querySelector(".eggSweeper-title");
  254. const leftSpan = document.createElement("span");
  255. leftSpan.id = "title-span-data-egg";
  256. leftSpan.textContent = `Hits: ${countHit}\r\nMisses: ${countMiss}\r\nTotal: ${countMiss +
  257. countHit}`;
  258. leftSpan.setAttribute(
  259. "style",
  260. "text-shadow: none; white-space: pre; z-index: 100; position: absolute; color: white; font-size: 12px; left: 10px; top: 0px; text-align: left;"
  261. );
  262. mainTitle.appendChild(leftSpan);
  263. }
  264.  
  265. /**
  266. * Generates a 6-row x 9-column pre-filled with a default value
  267. * @param {*} defaultValue
  268. */
  269. function generateEmptyGameBoard(defaultValue) {
  270. const returnBoard = [];
  271. for (let i = 0; i < 6; i++) {
  272. const arr = [];
  273. arr.length = 9;
  274. arr.fill(defaultValue);
  275. returnBoard.push(arr);
  276. }
  277.  
  278. return returnBoard;
  279. }
  280.  
  281. /**
  282. * Get bordering tile coordinates
  283. * Sample input: [1,1]
  284. * Return: [0,0] [0,1] [0,2] [1,0] [1,2] [2,0] [2,1] [2,2]
  285. * @param {number} row Integer from 0-5
  286. * @param {number} col Integer from 0-8
  287. * @return {number[]} Array of bordering [row, col] pairs
  288. */
  289. function getBordering(row, col) {
  290. const retArr = [];
  291. const rowM = row - 1;
  292. const colM = col - 1;
  293. const rowP = row + 1;
  294. const colP = col + 1;
  295.  
  296. function validRow(val) {
  297. return val >= 0 && val <= 5;
  298. }
  299.  
  300. function validCol(val) {
  301. return val >= 0 && val <= 8;
  302. }
  303.  
  304. if (validRow(rowM) && validCol(colM)) retArr.push([rowM, colM]);
  305. if (validRow(rowM) && validCol(col)) retArr.push([rowM, col]);
  306. if (validRow(rowM) && validCol(colP)) retArr.push([rowM, colP]);
  307. if (validRow(row) && validCol(colM)) retArr.push([row, colM]);
  308. if (validRow(row) && validCol(colP)) retArr.push([row, colP]);
  309. if (validRow(rowP) && validCol(colM)) retArr.push([rowP, colM]);
  310. if (validRow(rowP) && validCol(col)) retArr.push([rowP, col]);
  311. if (validRow(rowP) && validCol(colP)) retArr.push([rowP, colP]);
  312.  
  313. return retArr;
  314. }
  315.  
  316. /**
  317. * Convert array indices to an integer data-index value
  318. * @param {number} row Integer from 0-5
  319. * @param {number} col Integer from 0-8
  320. * @return {number} Integer from 1-54
  321. */
  322. function getValue(row, col) {
  323. return row * 9 + (col + 1);
  324. }
  325.  
  326. /**
  327. * Convert an integer data-index value to proper boardState array indices
  328. * @param {number} value Integer from 1-54
  329. * @return {object}
  330. */
  331. function getArrayIndex(value) {
  332. let posX = (value % 9) - 1;
  333. let posY = Math.floor(value / 9);
  334.  
  335. // Right-most column is a special case
  336. if (value % 9 === 0) {
  337. posX = 8;
  338. posY = Math.floor(value / 9) - 1;
  339. }
  340.  
  341. return {
  342. x: posX,
  343. y: posY
  344. };
  345. }
  346. })();

QingJ © 2025

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