Greasy Fork is available in English.

MouseHunt - Eggsweeper Helper

Tool to help with SEH Eggsweeper puzzle boards

Version vom 13.04.2019. Aktuellste Version

  1. // ==UserScript==
  2. // @name MouseHunt - Eggsweeper Helper
  3. // @author Tran Situ (tsitu)
  4. // @namespace https://greasyfork.org/en/users/232363-tsitu
  5. // @version 0.1 (beta)
  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);
  26. // } else {
  27. // console.log("You are out of Shovels!");
  28. // }
  29. parseBoard(game);
  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. */
  49. function parseBoard(game) {
  50. const board = game.board_rows;
  51. console.time("Duration");
  52.  
  53. console.log(board);
  54.  
  55. // Build an empty initial board
  56. const boardState = generateEmptyGameBoard("available");
  57.  
  58. // Parse current game state and populate boardState with hits and misses
  59. for (let row of board) {
  60. for (let tile of row.data) {
  61. const loc = getArrayIndex(tile.value);
  62. if (tile.status === "miss") {
  63. boardState[loc.y][loc.x] = tile.num_clues;
  64. } else if (tile.status.indexOf("complete") >= 0) {
  65. boardState[loc.y][loc.x] = "hit";
  66. } else if (tile.status === "no_egg") {
  67. boardState[loc.y][loc.x] = "none";
  68. }
  69. }
  70. }
  71. console.table(boardState);
  72.  
  73. // Calculate intermediate ENE board
  74. const intBoard = generateEmptyGameBoard(-1);
  75. for (let i = 0; i < 6; i++) {
  76. for (let j = 0; j < 9; j++) {
  77. const tile = boardState[i][j];
  78. if (typeof tile === "number" && tile > 0) {
  79. let score = tile;
  80. for (let el of getBordering(i, j)) {
  81. if (boardState[el[0]][el[1]] === "hit") {
  82. score -= 1;
  83. }
  84. }
  85. intBoard[i][j] = score;
  86. }
  87. }
  88. }
  89. console.table(intBoard);
  90.  
  91. // Calculate final scored board
  92. const scoredBoard = generateEmptyGameBoard(0);
  93. for (let i = 0; i < 6; i++) {
  94. for (let j = 0; j < 9; j++) {
  95. const int = intBoard[i][j];
  96. if (int > 0) {
  97. for (let el of getBordering(i, j)) {
  98. if (boardState[el[0]][el[1]] === "available") {
  99. scoredBoard[el[0]][el[1]] += int;
  100. }
  101. }
  102. }
  103. }
  104. }
  105.  
  106. // Reset deduced implicit no egg tiles
  107. for (let i = 0; i < 6; i++) {
  108. for (let j = 0; j < 9; j++) {
  109. const int = intBoard[i][j];
  110. if (int === 0) {
  111. for (let el of getBordering(i, j)) {
  112. if (boardState[el[0]][el[1]] === "available") {
  113. scoredBoard[el[0]][el[1]] = 0;
  114. }
  115. }
  116. }
  117. }
  118. }
  119.  
  120. console.table(scoredBoard);
  121.  
  122. // Calculate scores and ideal position(s)
  123. const scoreArray = [];
  124. const highScore = [0, []];
  125. for (let i = 0; i < 6; i++) {
  126. for (let j = 0; j < 9; j++) {
  127. const dataVal = getValue(i, j);
  128. const score = scoredBoard[i][j];
  129. scoreArray.push(score);
  130. if (score > highScore[0]) {
  131. highScore[0] = score;
  132. highScore[1] = [dataVal];
  133. } else if (score === highScore[0]) {
  134. highScore[1].push(dataVal);
  135. }
  136. }
  137. }
  138. console.log(scoreArray);
  139. console.log(highScore);
  140.  
  141. // Place suggestion(s) onto UI
  142. displayStats(board);
  143.  
  144. // Inject tile titles with "Score: #"
  145. for (let i = 1; i < 54; i++) {
  146. const tile = document.querySelector(
  147. `.eggSweeper-board-row-cell[data-index="${i}"]`
  148. );
  149. tile.setAttribute("title", `Score: ${scoreArray[i - 1]}`);
  150. }
  151.  
  152. // Add targeting overlay for high scores (only if score > 0)
  153. if (highScore[0] > 0) {
  154. for (let i = 0; i < highScore[1].length; i++) {
  155. const tile = document.querySelector(
  156. `.eggSweeper-board-row-cell[data-index="${highScore[1][i]}"]`
  157. );
  158.  
  159. if (tile) {
  160. // Inject "X" target(s) into UI
  161. const textSpan = document.createElement("span");
  162. textSpan.className = "egg-tile-target";
  163. textSpan.textContent = "X";
  164. textSpan.setAttribute(
  165. "style",
  166. `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;`
  167. );
  168. tile.appendChild(textSpan);
  169.  
  170. // Remove existing targets and score titles when a tile is clicked
  171. tile.addEventListener("click", function() {
  172. for (let node of document.querySelectorAll(".egg-tile-target")) {
  173. node.remove();
  174. }
  175. for (let i = 1; i < 55; i++) {
  176. const tile = document.querySelector(
  177. `.eggSweeper-board-row-cell[data-index="${i}"]`
  178. );
  179. tile.removeAttribute("title");
  180. }
  181. });
  182. }
  183. }
  184. }
  185.  
  186. console.timeEnd("Duration");
  187. }
  188.  
  189. /**
  190. * Counts hits/misses/total and render to top-left of UI
  191. * @param {object} board From game["board_rows"]
  192. */
  193. function displayStats(board) {
  194. let countMiss = 0;
  195. let countHit = 0;
  196. for (let row of board) {
  197. for (let tile of row.data) {
  198. if (tile.status === "miss") {
  199. countMiss++;
  200. } else if (tile.status.indexOf("complete") >= 0) {
  201. countHit++;
  202. }
  203. }
  204. }
  205.  
  206. // Reset "title-span-data" node
  207. const titleSpanData = document.querySelector("#title-span-data-egg");
  208. if (titleSpanData) {
  209. titleSpanData.remove();
  210. }
  211.  
  212. // Shovel stats on top left of game UI
  213. const mainTitle = document.querySelector(".eggSweeper-title");
  214. const leftSpan = document.createElement("span");
  215. leftSpan.id = "title-span-data-egg";
  216. leftSpan.textContent = `Hits: ${countHit}\r\nMisses: ${countMiss}\r\nTotal: ${countMiss +
  217. countHit}`;
  218. leftSpan.setAttribute(
  219. "style",
  220. "text-shadow: none; white-space: pre; z-index: 100; position: absolute; color: white; font-size: 12px; left: 10px; top: 0px; text-align: left;"
  221. );
  222. mainTitle.appendChild(leftSpan);
  223. }
  224.  
  225. /**
  226. * Generates a 6-row x 9-column pre-filled with a default value
  227. * @param {*} defaultValue
  228. */
  229. function generateEmptyGameBoard(defaultValue) {
  230. const returnBoard = [];
  231. for (let i = 0; i < 6; i++) {
  232. const arr = [];
  233. arr.length = 9;
  234. arr.fill(defaultValue);
  235. returnBoard.push(arr);
  236. }
  237.  
  238. return returnBoard;
  239. }
  240.  
  241. /**
  242. * Get bordering tile coordinates
  243. * Sample input: [1,1]
  244. * Return: [0,0] [0,1] [0,2] [1,0] [1,2] [2,0] [2,1] [2,2]
  245. * @param {number} row Integer from 0-5
  246. * @param {number} col Integer from 0-8
  247. * @return {number[]} Array of bordering [row, col] pairs
  248. */
  249. function getBordering(row, col) {
  250. const retArr = [];
  251. const rowM = row - 1;
  252. const colM = col - 1;
  253. const rowP = row + 1;
  254. const colP = col + 1;
  255.  
  256. function validRow(val) {
  257. return val >= 0 && val <= 5;
  258. }
  259.  
  260. function validCol(val) {
  261. return val >= 0 && val <= 8;
  262. }
  263.  
  264. if (validRow(rowM) && validCol(colM)) retArr.push([rowM, colM]);
  265. if (validRow(rowM) && validCol(col)) retArr.push([rowM, col]);
  266. if (validRow(rowM) && validCol(colP)) retArr.push([rowM, colP]);
  267. if (validRow(row) && validCol(colM)) retArr.push([row, colM]);
  268. if (validRow(row) && validCol(colP)) retArr.push([row, colP]);
  269. if (validRow(rowP) && validCol(colM)) retArr.push([rowP, colM]);
  270. if (validRow(rowP) && validCol(col)) retArr.push([rowP, col]);
  271. if (validRow(rowP) && validCol(colP)) retArr.push([rowP, colP]);
  272.  
  273. return retArr;
  274. }
  275.  
  276. /**
  277. * Convert array indices to an integer data-index value
  278. * @param {number} row Integer from 0-5
  279. * @param {number} col Integer from 0-8
  280. * @return {number} Integer from 1-54
  281. */
  282. function getValue(row, col) {
  283. return row * 9 + (col + 1);
  284. }
  285.  
  286. /**
  287. * Convert an integer data-index value to proper boardState array indices
  288. * @param {number} value Integer from 1-54
  289. * @return {object}
  290. */
  291. function getArrayIndex(value) {
  292. let posX = (value % 9) - 1;
  293. let posY = Math.floor(value / 9);
  294.  
  295. // Right-most column is a special case
  296. if (value % 9 === 0) {
  297. posX = 8;
  298. posY = Math.floor(value / 9) - 1;
  299. }
  300.  
  301. return {
  302. x: posX,
  303. y: posY
  304. };
  305. }
  306. })();