Greasy Fork

MouseHunt - Snowball Showdown Helper

Tool to help with Snowball Showdown puzzle boards

目前为 2018-12-22 提交的版本。查看 最新版本

// ==UserScript==
// @name         MouseHunt - Snowball Showdown Helper
// @author       Tran Situ (tsitu)
// @namespace    https://greasyfork.org/en/users/232363-tsitu
// @version      1.0
// @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 ===
        "https://www.mousehuntgame.com/managers/ajax/events/snowball_showdown.php"
      ) {
        console.group("Snowball Showdown Helper");
        let game;
        try {
          game = JSON.parse(this.responseText)["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 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);
        }
      }
    }
    // console.log(scoreArray);
    // console.log(highScore);

    /**
     * Place suggestion(s) onto UI
     */
    displayStats(board);

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

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

      if (tile) {
        // Inject "X" target(s) into UI
        const textSpan = document.createElement("span");
        textSpan.className = "snowball-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(".snowball-tile-target")) {
            node.remove();
          }
          for (let i = 1; i < 67; i++) {
            const tile = document
              .querySelector(".winterHunt2014Minigame-layer.tiles")
              .querySelector(
                `.winterHunt2014Minigame-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 === "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(".winterHunt2014Minigame-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: none; white-space: pre; z-index: 100; position: absolute; color: black; 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
  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
      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 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 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
    };
  }
})();