MouseHunt - Snowball Showdown Helper

Tool to help with Snowball Showdown puzzle boards

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         MouseHunt - Snowball Showdown Helper
// @author       Tran Situ (tsitu)
// @namespace    https://greasyfork.org/en/users/232363-tsitu
// @version      1.3.2
// @description  Tool to help with Snowball Showdown 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.indexOf(
          "mousehuntgame.com/managers/ajax/events/snowball_showdown.php"
        ) >= 0
      ) {
        console.group("Snowball Showdown Helper");
        let game;
        try {
          game = JSON.parse(this.responseText)["snowball_showdown_game"];
          if (game["is_active"] == true && !game["is_complete"]) {
            if (game["num_snowballs"] > 0) {
              parseBoard(game);
            } else {
              console.log("You are out of Throwable Snowballs!");
            }
          } else if (game["is_active"] === true && 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("Snowball Showdown Helper");
      }
    });
    originalOpen.apply(this, arguments);
  };

  /**
   * Main function to process overall game state
   * @param {object} game Parsed response from snowball_showdown.php
   */
  function parseBoard(game) {
    const board = game.board_rows;
    const currentShapes = game.rewards
      .filter(el => {
        if (el.status !== "complete") {
          return el;
        }
      })
      .map(el => el.name);
    console.time("Duration");

    // console.log(board);
    console.log(currentShapes);

    // Build an empty initial board
    const boardState = generateEmptyGameBoard("available");

    /**
     * Parse current game state
     */
    // 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] = "miss";
        } else if (tile.status === "hit") {
          boardState[loc.y][loc.x] = "hit";
        } else if (tile.status === "complete") {
          boardState[loc.y][loc.x] = "miss";
        }
      }
    }
    console.table(boardState);

    /**
     * Superpose remaining shape possibility boards
     */
    const sumBoard = generateEmptyGameBoard(0);
    for (let shape of currentShapes) {
      const scored = generateScoredBoard(boardState, shape);
      for (let i = 0; i < 6; i++) {
        for (let j = 0; j < 11; j++) {
          sumBoard[i][j] += scored[i][j];
        }
      }
    }

    // Set all non-available tiles to 0
    for (let i = 0; i < 6; i++) {
      for (let j = 0; j < 11; j++) {
        if (boardState[i][j] !== "available") {
          sumBoard[i][j] = 0;
        }
      }
    }
    console.table(sumBoard);

    /**
     * Calculate scores and ideal position(s)
     */
    const scoreArray = [];
    const noHits = [];
    const highScore = [0, []];
    for (let i = 0; i < 6; i++) {
      for (let j = 0; j < 11; j++) {
        const dataVal = getValue(i, j);
        const score = sumBoard[i][j];
        scoreArray.push(score);
        if (score > highScore[0]) {
          highScore[0] = score;
          highScore[1] = [dataVal];
        } else if (score === highScore[0]) {
          highScore[1].push(dataVal);
        }

        if (score === 0) {
          noHits.push(dataVal); // Accumulate guaranteed no hit tiles
        }
      }
    }
    // console.log(scoreArray);
    // console.log(highScore);
    // console.log(noHits);

    /**
     * Place suggestion(s) onto UI
     */
    // Inject tile titles with "Score: #"
    for (let i = 1; i < 67; i++) {
      const tile = document
        .querySelector(".snowballShowdownView-layer.tiles")
        .querySelector(
          `.snowballShowdownView-board-row-cell[data-index="${i}"]`
        );
      tile.setAttribute(
        "title",
        `Score: ${parseFloat(scoreArray[i - 1].toFixed(2))}`
      );

      // Remove existing targets and score titles when an "available" tile is clicked
      if (tile.getAttribute("tsitu-click-listener") !== "true") {
        tile.addEventListener("click", function () {
          if (tile.className.indexOf("available") >= 0) {
            document.querySelectorAll(".snowball-tile-target").forEach(node => {
              node.remove();
            });
            document
              .querySelectorAll(
                ".snowballShowdownView-layer.tiles .snowballShowdownView-board-row-cell"
              )
              .forEach(node => {
                node.removeAttribute("title");
              });
          }

          // Reset disabled class if user overrode indicator and clicked anyway
          if (tile.className.indexOf("disabled") >= 0) {
            tile.classList.toggle("disabled");
          }
        });
        tile.setAttribute("tsitu-click-listener", "true");
      }
    }

    // Add targeting overlay for high scores
    for (let i = 0; i < highScore[1].length; i++) {
      const tile = document
        .querySelector(".snowballShowdownView-layer.tiles")
        .querySelector(
          `.snowballShowdownView-board-row-cell[data-index="${highScore[1][i]}"]`
        );

      if (tile) {
        // Inject "o" target(s) into UI
        const textSpan = document.createElement("span");
        textSpan.className = "snowball-tile-target";
        textSpan.textContent = "o";
        textSpan.setAttribute(
          "style",
          `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;`
        );
        tile.appendChild(textSpan);
      }
    }

    // Mark guaranteed no hit tiles
    for (let el of noHits) {
      const tile = document
        .querySelector(".snowballShowdownView-layer.tiles")
        .querySelector(
          `.snowballShowdownView-board-row-cell[data-index="${el}"]`
        );

      if (tile && tile.className.indexOf("available") >= 0) {
        if (!tile.classList.contains("disabled")) {
          tile.classList.toggle("disabled");
          tile.style.pointerEvents = "auto";
        }
      }
    }

    displayStats(board);
    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 === "hit" || tile.status === "complete") {
          countHit++;
        }
      }
    }

    // Reset "title-span-data" node
    const titleSpanData = document.querySelector("#title-span-data");
    if (titleSpanData) {
      titleSpanData.remove();
    }

    // Snowball stats on top left of game UI
    const mainTitle = document.querySelector(".snowballShowdownView-title");
    const leftSpan = document.createElement("span");
    leftSpan.id = "title-span-data";
    leftSpan.textContent = `Hits:        ${countHit}\r\nMisses:   ${countMiss}\r\nTotal:      ${
      countMiss + countHit
    }`;
    leftSpan.setAttribute(
      "style",
      "text-shadow: 1px 1px 10px #608bec, 1px 1px 2px #000; white-space: pre; z-index: 100; position: absolute; color: #fff; font-size: 16px; left: 35px; top: 15px; text-align: left;"
    );
    mainTitle.appendChild(leftSpan);
  }

  // row (left-right), col (up-down) from [0, 0] to [5, 10]
  // combos start at top-left corner for each shape
  // prettier-ignore
  const allShapes = {
    "Chamberstick Candle": {
      default: {
        map: [[0, 1], [0, 1], [1, 1, 1]],
        combos: [
          [[0, 0], [0, 3], [0, 6], [3, 0], [3, 3], [3, 6]],
          [[0, 1], [0, 4], [0, 7], [3, 1], [3, 4], [3, 7]],
          [[0, 2], [0, 5], [0, 8], [3, 2], [3, 5], [3, 8]],
          [[1, 0], [1, 3], [1, 6]],
          [[1, 1], [1, 4], [1, 7]],
          [[1, 2], [1, 5], [1, 8]],
          [[2, 0], [2, 3], [2, 6]],
          [[2, 1], [2, 4], [2, 7]],
          [[2, 2], [2, 5], [2, 8]]
        ]
      },
      tipped_left: {
        map: [[0, 0, 1], [1, 1, 1], [0, 0, 1]],
        combos: [
          [[0, 0], [0, 3], [0, 6], [3, 0], [3, 3], [3, 6]],
          [[0, 1], [0, 4], [0, 7], [3, 1], [3, 4], [3, 7]],
          [[0, 2], [0, 5], [0, 8], [3, 2], [3, 5], [3, 8]],
          [[1, 0], [1, 3], [1, 6]],
          [[1, 1], [1, 4], [1, 7]],
          [[1, 2], [1, 5], [1, 8]],
          [[2, 0], [2, 3], [2, 6]],
          [[2, 1], [2, 4], [2, 7]],
          [[2, 2], [2, 5], [2, 8]]
        ]
      },
      tipped_right: {
        map: [[1], [1, 1, 1], [1]],
        combos: [
          [[0, 0], [0, 3], [0, 6], [3, 0], [3, 3], [3, 6]],
          [[0, 1], [0, 4], [0, 7], [3, 1], [3, 4], [3, 7]],
          [[0, 2], [0, 5], [0, 8], [3, 2], [3, 5], [3, 8]],
          [[1, 0], [1, 3], [1, 6]],
          [[1, 1], [1, 4], [1, 7]],
          [[1, 2], [1, 5], [1, 8]],
          [[2, 0], [2, 3], [2, 6]],
          [[2, 1], [2, 4], [2, 7]],
          [[2, 2], [2, 5], [2, 8]]
        ]
      },
      upside_down: {
        map: [[1, 1, 1], [0, 1], [0, 1]],
        combos: [
          [[0, 0], [0, 3], [0, 6], [3, 0], [3, 3], [3, 6]],
          [[0, 1], [0, 4], [0, 7], [3, 1], [3, 4], [3, 7]],
          [[0, 2], [0, 5], [0, 8], [3, 2], [3, 5], [3, 8]],
          [[1, 0], [1, 3], [1, 6]],
          [[1, 1], [1, 4], [1, 7]],
          [[1, 2], [1, 5], [1, 8]],
          [[2, 0], [2, 3], [2, 6]],
          [[2, 1], [2, 4], [2, 7]],
          [[2, 2], [2, 5], [2, 8]]
        ]
      }
    },
    "Candy Cane": {
      default: {
        map: [[1, 1], [1, 1], [1], [1]],
        combos: [
          [[0, 0], [0, 2], [0, 4], [0, 6], [0, 8]],
          [[0, 1], [0, 3], [0, 5], [0, 7], [0, 9]],
          [[1, 0], [1, 2], [1, 4], [1, 6], [1, 8]],
          [[1, 1], [1, 3], [1, 5], [1, 7], [1, 9]],
          [[2, 0], [2, 2], [2, 4], [2, 6], [2, 8]],
          [[2, 1], [2, 3], [2, 5], [2, 7], [2, 9]]
        ]
      },
      left: {
        map: [[1, 1], [1, 1], [0, 1], [0, 1]],
        combos: [
          [[0, 0], [0, 2], [0, 4], [0, 6], [0, 8]],
          [[0, 1], [0, 3], [0, 5], [0, 7], [0, 9]],
          [[1, 0], [1, 2], [1, 4], [1, 6], [1, 8]],
          [[1, 1], [1, 3], [1, 5], [1, 7], [1, 9]],
          [[2, 0], [2, 2], [2, 4], [2, 6], [2, 8]],
          [[2, 1], [2, 3], [2, 5], [2, 7], [2, 9]]
        ]
      },
      side_up_right: {
        map: [[0, 0, 1, 1], [1, 1, 1, 1]],
        combos: [
          [[0, 0], [0, 4], [2, 0], [2, 4], [4, 0], [4, 4]],
          [[0, 1], [0, 5], [2, 1], [2, 5], [4, 1], [4, 5]],
          [[0, 2], [0, 6], [2, 2], [2, 6], [4, 2], [4, 6]],
          [[0, 3], [0, 7], [2, 3], [2, 7], [4, 3], [4, 7]],
          [[1, 0], [1, 4], [3, 0], [3, 4]],
          [[1, 1], [1, 5], [3, 1], [3, 5]],
          [[1, 2], [1, 6], [3, 2], [3, 6]],
          [[1, 3], [1, 7], [3, 3], [3, 7]]
        ]
      },
      side_up_left: {
        map: [[1, 1], [1, 1, 1, 1]],
        combos: [
          [[0, 0], [0, 4], [2, 0], [2, 4], [4, 0], [4, 4]],
          [[0, 1], [0, 5], [2, 1], [2, 5], [4, 1], [4, 5]],
          [[0, 2], [0, 6], [2, 2], [2, 6], [4, 2], [4, 6]],
          [[0, 3], [0, 7], [2, 3], [2, 7], [4, 3], [4, 7]],
          [[1, 0], [1, 4], [3, 0], [3, 4]],
          [[1, 1], [1, 5], [3, 1], [3, 5]],
          [[1, 2], [1, 6], [3, 2], [3, 6]],
          [[1, 3], [1, 7], [3, 3], [3, 7]]
        ]
      },
      side_down_right: {
        map: [[1, 1, 1, 1], [0, 0, 1, 1]],
        combos: [
          [[0, 0], [0, 4], [2, 0], [2, 4], [4, 0], [4, 4]],
          [[0, 1], [0, 5], [2, 1], [2, 5], [4, 1], [4, 5]],
          [[0, 2], [0, 6], [2, 2], [2, 6], [4, 2], [4, 6]],
          [[0, 3], [0, 7], [2, 3], [2, 7], [4, 3], [4, 7]],
          [[1, 0], [1, 4], [3, 0], [3, 4]],
          [[1, 1], [1, 5], [3, 1], [3, 5]],
          [[1, 2], [1, 6], [3, 2], [3, 6]],
          [[1, 3], [1, 7], [3, 3], [3, 7]]
        ]
      },
      side_down_left: {
        map: [[1, 1, 1, 1], [1, 1]],
        combos: [
          [[0, 0], [0, 4], [2, 0], [2, 4], [4, 0], [4, 4]],
          [[0, 1], [0, 5], [2, 1], [2, 5], [4, 1], [4, 5]],
          [[0, 2], [0, 6], [2, 2], [2, 6], [4, 2], [4, 6]],
          [[0, 3], [0, 7], [2, 3], [2, 7], [4, 3], [4, 7]],
          [[1, 0], [1, 4], [3, 0], [3, 4]],
          [[1, 1], [1, 5], [3, 1], [3, 5]],
          [[1, 2], [1, 6], [3, 2], [3, 6]],
          [[1, 3], [1, 7], [3, 3], [3, 7]]
        ]
      },
      down_right: {
        map: [[1], [1], [1, 1], [1, 1]],
        combos: [
          [[0, 0], [0, 2], [0, 4], [0, 6], [0, 8]],
          [[0, 1], [0, 3], [0, 5], [0, 7], [0, 9]],
          [[1, 0], [1, 2], [1, 4], [1, 6], [1, 8]],
          [[1, 1], [1, 3], [1, 5], [1, 7], [1, 9]],
          [[2, 0], [2, 2], [2, 4], [2, 6], [2, 8]],
          [[2, 1], [2, 3], [2, 5], [2, 7], [2, 9]]
        ]
      },
      down_left: {
        map: [[0, 1], [0, 1], [1, 1], [1, 1]],
        combos: [
          [[0, 0], [0, 2], [0, 4], [0, 6], [0, 8]],
          [[0, 1], [0, 3], [0, 5], [0, 7], [0, 9]],
          [[1, 0], [1, 2], [1, 4], [1, 6], [1, 8]],
          [[1, 1], [1, 3], [1, 5], [1, 7], [1, 9]],
          [[2, 0], [2, 2], [2, 4], [2, 6], [2, 8]],
          [[2, 1], [2, 3], [2, 5], [2, 7], [2, 9]]
        ]
      }
    },
    "Crescent Cookie": {
      default: {
        map: [[1, 1], [1], [1, 1]],
        combos: [
          [
            [0, 0],
            [0, 2],
            [0, 4],
            [0, 6],
            [0, 8],
            [3, 0],
            [3, 2],
            [3, 4],
            [3, 6],
            [3, 8]
          ],
          [
            [0, 1],
            [0, 3],
            [0, 5],
            [0, 7],
            [0, 9],
            [3, 1],
            [3, 3],
            [3, 5],
            [3, 7],
            [3, 9]
          ],
          [[1, 0], [1, 2], [1, 4], [1, 6], [1, 8]],
          [[1, 1], [1, 3], [1, 5], [1, 7], [1, 9]],
          [[2, 0], [2, 2], [2, 4], [2, 6], [2, 8]],
          [[2, 1], [2, 3], [2, 5], [2, 7], [2, 9]]
        ]
      },
      down: {
        map: [[1, 1, 1], [1, 0, 1]],
        combos: [
          [
            [0, 0],
            [0, 3],
            [0, 6],
            [2, 0],
            [2, 3],
            [2, 6],
            [4, 0],
            [4, 3],
            [4, 6]
          ],
          [
            [0, 1],
            [0, 4],
            [0, 7],
            [2, 1],
            [2, 4],
            [2, 7],
            [4, 1],
            [4, 4],
            [4, 7]
          ],
          [
            [0, 2],
            [0, 5],
            [0, 8],
            [2, 2],
            [2, 5],
            [2, 8],
            [4, 2],
            [4, 5],
            [4, 8]
          ],
          [[1, 0], [1, 3], [1, 6], [3, 0], [3, 3], [3, 6]],
          [[1, 1], [1, 4], [1, 7], [3, 1], [3, 4], [3, 7]],
          [[1, 2], [1, 5], [1, 8], [3, 2], [3, 5], [3, 8]]
        ]
      },
      left: {
        map: [[1, 1], [0, 1], [1, 1]],
        combos: [
          [
            [0, 0],
            [0, 2],
            [0, 4],
            [0, 6],
            [0, 8],
            [3, 0],
            [3, 2],
            [3, 4],
            [3, 6],
            [3, 8]
          ],
          [
            [0, 1],
            [0, 3],
            [0, 5],
            [0, 7],
            [0, 9],
            [3, 1],
            [3, 3],
            [3, 5],
            [3, 7],
            [3, 9]
          ],
          [[1, 0], [1, 2], [1, 4], [1, 6], [1, 8]],
          [[1, 1], [1, 3], [1, 5], [1, 7], [1, 9]],
          [[2, 0], [2, 2], [2, 4], [2, 6], [2, 8]],
          [[2, 1], [2, 3], [2, 5], [2, 7], [2, 9]]
        ]
      },
      up: {
        map: [[1, 0, 1], [1, 1, 1]],
        combos: [
          [
            [0, 0],
            [0, 3],
            [0, 6],
            [2, 0],
            [2, 3],
            [2, 6],
            [4, 0],
            [4, 3],
            [4, 6]
          ],
          [
            [0, 1],
            [0, 4],
            [0, 7],
            [2, 1],
            [2, 4],
            [2, 7],
            [4, 1],
            [4, 4],
            [4, 7]
          ],
          [
            [0, 2],
            [0, 5],
            [0, 8],
            [2, 2],
            [2, 5],
            [2, 8],
            [4, 2],
            [4, 5],
            [4, 8]
          ],
          [[1, 0], [1, 3], [1, 6], [3, 0], [3, 3], [3, 6]],
          [[1, 1], [1, 4], [1, 7], [3, 1], [3, 4], [3, 7]],
          [[1, 2], [1, 5], [1, 8], [3, 2], [3, 5], [3, 8]]
        ]
      }
    },
    "Large Gift": {
      default: {
        map: [[1, 1], [1, 1], [1, 1]],
        combos: [
          [
            [0, 0],
            [0, 2],
            [0, 4],
            [0, 6],
            [0, 8],
            [3, 0],
            [3, 2],
            [3, 4],
            [3, 6],
            [3, 8]
          ],
          [
            [0, 1],
            [0, 3],
            [0, 5],
            [0, 7],
            [0, 9],
            [3, 1],
            [3, 3],
            [3, 5],
            [3, 7],
            [3, 9]
          ],
          [[1, 0], [1, 2], [1, 4], [1, 6], [1, 8]],
          [[1, 1], [1, 3], [1, 5], [1, 7], [1, 9]],
          [[2, 0], [2, 2], [2, 4], [2, 6], [2, 8]],
          [[2, 1], [2, 3], [2, 5], [2, 7], [2, 9]]
        ]
      },
      side: {
        map: [[1, 1, 1], [1, 1, 1]],
        combos: [
          [
            [0, 0],
            [0, 3],
            [0, 6],
            [2, 0],
            [2, 3],
            [2, 6],
            [4, 0],
            [4, 3],
            [4, 6]
          ],
          [
            [0, 1],
            [0, 4],
            [0, 7],
            [2, 1],
            [2, 4],
            [2, 7],
            [4, 1],
            [4, 4],
            [4, 7]
          ],
          [
            [0, 2],
            [0, 5],
            [0, 8],
            [2, 2],
            [2, 5],
            [2, 8],
            [4, 2],
            [4, 5],
            [4, 8]
          ],
          [[1, 0], [1, 3], [1, 6], [3, 0], [3, 3], [3, 6]],
          [[1, 1], [1, 4], [1, 7], [3, 1], [3, 4], [3, 7]],
          [[1, 2], [1, 5], [1, 8], [3, 2], [3, 5], [3, 8]]
        ]
      }
    },
    "Cozy Scarf": {
      default: {
        map: [[0, 1, 1], [1, 1]],
        combos: [
          [
            [0, 0],
            [0, 2],
            [0, 4],
            [0, 6],
            [0, 8],
            [2, 0],
            [2, 2],
            [2, 4],
            [2, 6],
            [2, 8],
            [4, 0],
            [4, 2],
            [4, 4],
            [4, 6],
            [4, 8]
          ],
          [
            [0, 1],
            [0, 3],
            [0, 5],
            [0, 7],
            [2, 1],
            [2, 3],
            [2, 5],
            [2, 7],
            [4, 1],
            [4, 3],
            [4, 5],
            [4, 7]
          ],
          [
            [1, 0],
            [1, 2],
            [1, 4],
            [1, 6],
            [1, 8],
            [3, 0],
            [3, 2],
            [3, 4],
            [3, 6],
            [3, 8]
          ],
          [[1, 1], [1, 3], [1, 5], [1, 7], [3, 1], [3, 3], [3, 5], [3, 7]]
        ]
      },
      falling: {
        map: [[1], [1, 1], [0, 1]],
        combos: [
          [
            [0, 0],
            [0, 2],
            [0, 4],
            [0, 6],
            [0, 8],
            [2, 0],
            [2, 2],
            [2, 4],
            [2, 6],
            [2, 8]
          ],
          [
            [0, 1],
            [0, 3],
            [0, 5],
            [0, 7],
            [0, 9],
            [2, 1],
            [2, 3],
            [2, 5],
            [2, 7],
            [2, 9]
          ],
          [
            [1, 0],
            [1, 2],
            [1, 4],
            [1, 6],
            [1, 8],
            [3, 0],
            [3, 2],
            [3, 4],
            [3, 6],
            [3, 8]
          ],
          [
            [1, 1],
            [1, 3],
            [1, 5],
            [1, 7],
            [1, 9],
            [3, 1],
            [3, 3],
            [3, 5],
            [3, 7],
            [3, 9]
          ]
        ]
      },
      falling_flipped: {
        map: [[0, 1], [1, 1], [1]],
        combos: [
          [
            [0, 0],
            [0, 2],
            [0, 4],
            [0, 6],
            [0, 8],
            [2, 0],
            [2, 2],
            [2, 4],
            [2, 6],
            [2, 8]
          ],
          [
            [0, 1],
            [0, 3],
            [0, 5],
            [0, 7],
            [0, 9],
            [2, 1],
            [2, 3],
            [2, 5],
            [2, 7],
            [2, 9]
          ],
          [
            [1, 0],
            [1, 2],
            [1, 4],
            [1, 6],
            [1, 8],
            [3, 0],
            [3, 2],
            [3, 4],
            [3, 6],
            [3, 8]
          ],
          [
            [1, 1],
            [1, 3],
            [1, 5],
            [1, 7],
            [1, 9],
            [3, 1],
            [3, 3],
            [3, 5],
            [3, 7],
            [3, 9]
          ]
        ]
      },
      flipped: {
        map: [[1, 1], [0, 1, 1]],
        combos: [
          [
            [0, 0],
            [0, 2],
            [0, 4],
            [0, 6],
            [0, 8],
            [2, 0],
            [2, 2],
            [2, 4],
            [2, 6],
            [2, 8],
            [4, 0],
            [4, 2],
            [4, 4],
            [4, 6],
            [4, 8]
          ],
          [
            [0, 1],
            [0, 3],
            [0, 5],
            [0, 7],
            [2, 1],
            [2, 3],
            [2, 5],
            [2, 7],
            [4, 1],
            [4, 3],
            [4, 5],
            [4, 7]
          ],
          [
            [1, 0],
            [1, 2],
            [1, 4],
            [1, 6],
            [1, 8],
            [3, 0],
            [3, 2],
            [3, 4],
            [3, 6],
            [3, 8]
          ],
          [[1, 1], [1, 3], [1, 5], [1, 7], [3, 1], [3, 3], [3, 5], [3, 7]]
        ]
      }
    },
    "Small Gift": {
      default: {
        map: [[1, 1], [1, 1]],
        combos: [
          [
            [0, 0],
            [0, 2],
            [0, 4],
            [0, 6],
            [0, 8],
            [2, 0],
            [2, 2],
            [2, 4],
            [2, 6],
            [2, 8],
            [4, 0],
            [4, 2],
            [4, 4],
            [4, 6],
            [4, 8]
          ],
          [
            [0, 1],
            [0, 3],
            [0, 5],
            [0, 7],
            [0, 9],
            [2, 1],
            [2, 3],
            [2, 5],
            [2, 7],
            [2, 9],
            [4, 1],
            [4, 3],
            [4, 5],
            [4, 7],
            [4, 9]
          ],
          [
            [1, 0],
            [1, 2],
            [1, 4],
            [1, 6],
            [1, 8],
            [3, 0],
            [3, 2],
            [3, 4],
            [3, 6],
            [3, 8]
          ],
          [
            [1, 1],
            [1, 3],
            [1, 5],
            [1, 7],
            [1, 9],
            [3, 1],
            [3, 3],
            [3, 5],
            [3, 7],
            [3, 9]
          ]
        ]
      }
    },
    "Winter Star": {
      default: {
        map: [[0, 1], [1, 1, 1], [0, 1]],
        combos: [
          [[0, 0], [0, 3], [0, 6], [3, 0], [3, 3], [3, 6]],
          [[0, 1], [0, 4], [0, 7], [3, 1], [3, 4], [3, 7]],
          [[0, 2], [0, 5], [0, 8], [3, 2], [3, 5], [3, 8]],
          [[1, 0], [1, 3], [1, 6]],
          [[1, 1], [1, 4], [1, 7]],
          [[1, 2], [1, 5], [1, 8]],
          [[2, 0], [2, 3], [2, 6]],
          [[2, 1], [2, 4], [2, 7]],
          [[2, 2], [2, 5], [2, 8]]
        ]
      }
    },
    "Holiday Tree": {
      default: {
        map: [[0, 1], [1, 1, 1], [1, 1, 1]],
        combos: [
          [[0, 0], [0, 3], [0, 6], [3, 0], [3, 3], [3, 6]],
          [[0, 1], [0, 4], [0, 7], [3, 1], [3, 4], [3, 7]],
          [[0, 2], [0, 5], [0, 8], [3, 2], [3, 5], [3, 8]],
          [[1, 0], [1, 3], [1, 6]],
          [[1, 1], [1, 4], [1, 7]],
          [[1, 2], [1, 5], [1, 8]],
          [[2, 0], [2, 3], [2, 6]],
          [[2, 1], [2, 4], [2, 7]],
          [[2, 2], [2, 5], [2, 8]]
        ]
      },
      right: {
        map: [[1, 1], [1, 1, 1], [1, 1]],
        combos: [
          [[0, 0], [0, 3], [0, 6], [3, 0], [3, 3], [3, 6]],
          [[0, 1], [0, 4], [0, 7], [3, 1], [3, 4], [3, 7]],
          [[0, 2], [0, 5], [0, 8], [3, 2], [3, 5], [3, 8]],
          [[1, 0], [1, 3], [1, 6]],
          [[1, 1], [1, 4], [1, 7]],
          [[1, 2], [1, 5], [1, 8]],
          [[2, 0], [2, 3], [2, 6]],
          [[2, 1], [2, 4], [2, 7]],
          [[2, 2], [2, 5], [2, 8]]
        ]
      },
      left: {
        map: [[0, 1, 1], [1, 1, 1], [0, 1, 1]],
        combos: [
          [[0, 0], [0, 3], [0, 6], [3, 0], [3, 3], [3, 6]],
          [[0, 1], [0, 4], [0, 7], [3, 1], [3, 4], [3, 7]],
          [[0, 2], [0, 5], [0, 8], [3, 2], [3, 5], [3, 8]],
          [[1, 0], [1, 3], [1, 6]],
          [[1, 1], [1, 4], [1, 7]],
          [[1, 2], [1, 5], [1, 8]],
          [[2, 0], [2, 3], [2, 6]],
          [[2, 1], [2, 4], [2, 7]],
          [[2, 2], [2, 5], [2, 8]]
        ]
      }
      // Verify existence (12/8/2020: CBS confirms non-existence, but may add in future)
      // down: {
      //   map: [[1, 1, 1], [1, 1, 1], [0, 1]],
      //   combos: [
      //     [[0, 0], [0, 3], [0, 6], [3, 0], [3, 3], [3, 6]],
      //     [[0, 1], [0, 4], [0, 7], [3, 1], [3, 4], [3, 7]],
      //     [[0, 2], [0, 5], [0, 8], [3, 2], [3, 5], [3, 8]],
      //     [[1, 0], [1, 3], [1, 6]],
      //     [[1, 1], [1, 4], [1, 7]],
      //     [[1, 2], [1, 5], [1, 8]],
      //     [[2, 0], [2, 3], [2, 6]],
      //     [[2, 1], [2, 4], [2, 7]],
      //     [[2, 2], [2, 5], [2, 8]]
      //   ]
      // }
    },
    "Festive Wreath": {
      default: {
        map: [[1, 1, 1], [1, 0, 1], [1, 1, 1]],
        combos: [
          [[0, 0], [0, 3], [0, 6], [3, 0], [3, 3], [3, 6]],
          [[0, 1], [0, 4], [0, 7], [3, 1], [3, 4], [3, 7]],
          [[0, 2], [0, 5], [0, 8], [3, 2], [3, 5], [3, 8]],
          [[1, 0], [1, 3], [1, 6]],
          [[1, 1], [1, 4], [1, 7]],
          [[1, 2], [1, 5], [1, 8]],
          [[2, 0], [2, 3], [2, 6]],
          [[2, 1], [2, 4], [2, 7]],
          [[2, 2], [2, 5], [2, 8]]
        ]
      }
    },
    "Yule Log": {
      default: {
        map: [[1, 1, 1, 1, 1]],
        combos: [
          [
            [0, 0],
            [0, 5],
            [1, 0],
            [1, 5],
            [2, 0],
            [2, 5],
            [3, 0],
            [3, 5],
            [4, 0],
            [4, 5],
            [5, 0],
            [5, 5]
          ],
          [
            [0, 1],
            [0, 6],
            [1, 1],
            [1, 6],
            [2, 1],
            [2, 6],
            [3, 1],
            [3, 6],
            [4, 1],
            [4, 6],
            [5, 1],
            [5, 6]
          ],
          [[0, 2], [1, 2], [2, 2], [3, 2], [4, 2], [5, 2]],
          [[0, 3], [1, 3], [2, 3], [3, 3], [4, 3], [5, 3]],
          [[0, 4], [1, 4], [2, 4], [3, 4], [4, 4], [5, 4]]
        ]
      },
      standing: {
        map: [[1], [1], [1], [1], [1]],
        combos: [
          [
            [0, 0],
            [0, 1],
            [0, 2],
            [0, 3],
            [0, 4],
            [0, 5],
            [0, 6],
            [0, 7],
            [0, 8],
            [0, 9],
            [0, 10]
          ],
          [
            [1, 0],
            [1, 1],
            [1, 2],
            [1, 3],
            [1, 4],
            [1, 5],
            [1, 6],
            [1, 7],
            [1, 8],
            [1, 9],
            [1, 10]
          ]
        ]
      }
    },
    "Magical Holiday Hat": {
      default: {
        map: [[0, 1, 1], [0, 1, 1], [1, 1, 1, 1]],
        combos: [
          [[0, 0], [0, 4], [3, 0], [3, 4]],
          [[0, 1], [0, 5], [3, 1], [3, 5]],
          [[0, 2], [0, 6], [3, 2], [3, 6]],
          [[0, 3], [0, 7], [3, 3], [3, 7]],
          [[1, 0], [1, 4]],
          [[1, 1], [1, 5]],
          [[1, 2], [1, 6]],
          [[1, 3], [1, 7]],
          [[2, 0], [2, 4]],
          [[2, 1], [2, 5]],
          [[2, 2], [2, 6]],
          [[2, 3], [2, 7]]
        ]
      },
      flip: {
        map: [[1, 1, 1, 1], [0, 1, 1], [0, 1, 1]],
        combos: [
          [[0, 0], [0, 4], [3, 0], [3, 4]],
          [[0, 1], [0, 5], [3, 1], [3, 5]],
          [[0, 2], [0, 6], [3, 2], [3, 6]],
          [[0, 3], [0, 7], [3, 3], [3, 7]],
          [[1, 0], [1, 4]],
          [[1, 1], [1, 5]],
          [[1, 2], [1, 6]],
          [[1, 3], [1, 7]],
          [[2, 0], [2, 4]],
          [[2, 1], [2, 5]],
          [[2, 2], [2, 6]],
          [[2, 3], [2, 7]]
        ]
      },
      left: {
        map: [[0, 0, 1], [1, 1, 1], [1, 1, 1], [0, 0, 1]],
        combos: [
          [[0, 0], [0, 3], [0, 6]],
          [[0, 1], [0, 4], [0, 7]],
          [[0, 2], [0, 5], [0, 8]],
          [[1, 0], [1, 3], [1, 6]],
          [[1, 1], [1, 4], [1, 7]],
          [[1, 2], [1, 5], [1, 8]],
          [[2, 0], [2, 3], [2, 6]],
          [[2, 1], [2, 4], [2, 7]],
          [[2, 2], [2, 5], [2, 8]]
        ]
      },
      right: {
        map: [[1], [1, 1, 1], [1, 1, 1], [1]],
        combos: [
          [[0, 0], [0, 3], [0, 6]],
          [[0, 1], [0, 4], [0, 7]],
          [[0, 2], [0, 5], [0, 8]],
          [[1, 0], [1, 3], [1, 6]],
          [[1, 1], [1, 4], [1, 7]],
          [[1, 2], [1, 5], [1, 8]],
          [[2, 0], [2, 3], [2, 6]],
          [[2, 1], [2, 4], [2, 7]],
          [[2, 2], [2, 5], [2, 8]]
        ]
      }
    }
  };

  // Number of cells in shape - 1
  const maxThreshold = {
    "Chamberstick Candle": 4,
    "Candy Cane": 5,
    "Crescent Cookie": 4,
    "Large Gift": 5,
    "Cozy Scarf": 3,
    "Small Gift": 3,
    "Winter Star": 4,
    "Holiday Tree": 6,
    "Festive Wreath": 7,
    "Yule Log": 4,
    "Magical Holiday Hat": 7
  };

  /**
   * Generate a scored game board given an input board state plus a shape
   * @param {array} board Current board state
   * @param {string} shapeName Name of shape to process
   * @return {array} Scored board
   */
  function generateScoredBoard(board, shapeName) {
    const shape = allShapes[shapeName];
    const masterValueTrack = [];
    const masterHitCounter = [];

    // Track highest amount of hits for all combos of a shape
    let highestHit = 0;

    for (let type in shape) {
      const map = shape[type]["map"];
      for (let combo of shape[type]["combos"]) {
        // Convert shape positions into array of 1-66 values
        const valueTrack = [];
        for (let el of combo) {
          const row = el[0];
          const col = el[1];
          const valueArr = [];
          for (let i = 0; i < map.length; i++) {
            for (let j = 0; j < map[i].length; j++) {
              // Check for 1
              if (map[i][j] === 1) {
                valueArr.push(getValue(row + i, col + j));
              }
            }
          }
          valueTrack.push(valueArr);
        }
        masterValueTrack.push(valueTrack);

        // Array to track hits per shape in a single combo board
        const hitCounter = [];
        hitCounter.length = valueTrack.length;
        hitCounter.fill(0);

        // Calculate number of hits for each shape
        // A miss is permanently represented by -1
        for (let row = 0; row < 6; row++) {
          for (let col = 0; col < 11; col++) {
            const el = board[row][col];
            const [exists, index] = checkExist(getValue(row, col));
            if (exists && hitCounter[index] >= 0) {
              if (el === "miss") {
                hitCounter[index] = -1;
              } else if (el === "hit") {
                hitCounter[index] += 1;
              }
            }
          }
        }
        masterHitCounter.push(hitCounter);

        // Checks for existence in value tracking array
        function checkExist(value) {
          let returnBool = false;
          let index = 0;
          for (let i = 0; i < valueTrack.length; i++) {
            if (valueTrack[i].indexOf(value) > -1) {
              returnBool = true;
              index = i;
              break;
            }
          }

          return [returnBool, index];
        }

        for (let num of hitCounter) {
          highestHit = num > highestHit ? num : highestHit;
        }

        highestHit =
          highestHit > maxThreshold[shapeName]
            ? maxThreshold[shapeName]
            : highestHit;
      }
    }

    // Generate empty board for calculating scores
    const scoreBoard = generateEmptyGameBoard(0);

    // Populate with scores
    let iterCount = 0;
    let patternCount = 0; // For Paul's method
    let shapeHighScore = 0; // For Paul's method
    for (let type in shape) {
      const map = shape[type]["map"];
      for (let combo of shape[type]["combos"]) {
        const calcBoard = generateEmptyGameBoard(0);
        for (let x = 0; x < combo.length; x++) {
          const row = combo[x][0];
          const col = combo[x][1];
          for (let i = 0; i < map.length; i++) {
            for (let j = 0; j < map[i].length; j++) {
              if (
                masterValueTrack[iterCount][x].indexOf(
                  getValue(row + i, col + j)
                ) > -1
              ) {
                const calcScore = calculateScore(x, iterCount, highestHit);
                calcBoard[row + i][col + j] = calcScore;
                if (calcScore > shapeHighScore) {
                  shapeHighScore = calcScore;
                  patternCount = 1;
                } else if (calcScore === shapeHighScore) {
                  patternCount += 1;
                }
              }
            }
          }
        }
        for (let i = 0; i < 6; i++) {
          for (let j = 0; j < 11; j++) {
            scoreBoard[i][j] += calcBoard[i][j];
          }
        }
        iterCount++;
      }
    }

    /**
     * @param {number} index Index of the hit in its hitCounter array
     * @param {number} iter Index of the hitCounter array to use
     * @param {number} threshold highestHit
     * @return {number} Score
     */
    function calculateScore(index, iter, threshold) {
      const hit = masterHitCounter[iter][index];
      if (hit === -1) {
        return 0; // Miss
      } else if (hit === 0) {
        return 1; // Available
      } else if (hit >= 1) {
        const num = hit === threshold ? hit : 0;
        return num * 10 + 1; // Hit (hitWeight of 10)
      }
    }

    // Paul's modified weighting algorithm (based on remaining patterns)
    // https://www.mousehuntgame.com/forum/showthread.php?136423-Snowball-Showndown-Minigame-Probability-Spreadsheet&p=1421247&viewfull=1#post1421247
    return scoreBoard.map(row =>
      row.map(col => {
        return (col / (patternCount / (maxThreshold[shapeName] + 1))) * 5;
      })
    );
  }

  /**
   * Generates a 6-row x 11-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 = 11;
      arr.fill(defaultValue);
      returnBoard.push(arr);
    }

    return returnBoard;
  }

  /**
   * Convert array indices to an integer data-index value
   * @param {number} row Integer from 0-5
   * @param {number} col Integer from 0-10
   * @return {number} Integer from 1-66
   */
  function getValue(row, col) {
    return row * 11 + (col + 1);
  }

  /**
   * Convert an integer data-index value to proper boardState array indices
   * @param {number} value Integer from 1-66
   * @return {object}
   */
  function getArrayIndex(value) {
    let posX = (value % 11) - 1;
    let posY = Math.floor(value / 11);

    // Right-most column is a special case
    if (value % 11 === 0) {
      posX = 10;
      posY = Math.floor(value / 11) - 1;
    }

    return {
      x: posX,
      y: posY
    };
  }
})();