"use strict" const GAME_MODE_KEY = "data-game-mode"; const Tile = Object.freeze({ NEUTRAL: 0, BLACK: 1, GREEN: 2, RED: 3, BLUE: 4, }); function cyrb128(str) { let h1 = 1779033703, h2 = 3144134277, h3 = 1013904242, h4 = 2773480762; for (let i = 0, k; i < str.length; i++) { k = str.charCodeAt(i); h1 = h2 ^ Math.imul(h1 ^ k, 597399067); h2 = h3 ^ Math.imul(h2 ^ k, 2869860233); h3 = h4 ^ Math.imul(h3 ^ k, 951274213); h4 = h1 ^ Math.imul(h4 ^ k, 2716044179); } h1 = Math.imul(h3 ^ (h1 >>> 18), 597399067); h2 = Math.imul(h4 ^ (h2 >>> 22), 2869860233); h3 = Math.imul(h1 ^ (h3 >>> 17), 951274213); h4 = Math.imul(h2 ^ (h4 >>> 19), 2716044179); h1 ^= (h2 ^ h3 ^ h4), h2 ^= h1, h3 ^= h1, h4 ^= h1; return [h1>>>0, h2>>>0, h3>>>0, h4>>>0]; } function sfc32(a, b, c, d) { return function() { a |= 0; b |= 0; c |= 0; d |= 0; let t = (a + b | 0) + d | 0; d = d + 1 | 0; a = b ^ b >>> 9; b = c + (c << 3) | 0; c = (c << 21 | c >>> 11); c = c + t | 0; return (t >>> 0) / 4294967296; } } function shuffle(array, rand) { let currentIndex = array.length - 1; while (currentIndex !== 0) { let otherIndex = Math.floor(rand() * (currentIndex + 1)); [array[currentIndex], array[otherIndex]] = [array[otherIndex], array[currentIndex]]; currentIndex--; } } function generateEmptyGrid(width, height){ let grid = []; for (let ii = 0; ii < height; ii++) { let line = Array(width); line.fill(Tile.NEUTRAL); grid.push(line) } return grid; } function getPositions(width, height) { let positions = []; for (let ii = 0; ii < height; ii++) { for (let jj = 0; jj < width; jj++) { positions.push([ii, jj]); } } return positions; } function getPosIndexByValue(array, pos) { for (let ii = 0; ii < array.length; ii++) { if (array[ii][0] === pos[0] && array[ii][1] === pos[1]) { return ii; } } return -1; } // TODO: handle multiple rounds and memoize function generateDuetGrids(width, height, greenCount, blackCount, commonGreenCount, commonBlackCount, blackGreenCount, seed) { let seed_array = cyrb128(seed); let rand = sfc32(seed_array[0], seed_array[1], seed_array[2], seed_array[3]); let blackNeutralCount = blackCount - commonBlackCount - blackGreenCount; let greenNeutralCount = greenCount - commonGreenCount - blackGreenCount; let p1 = generateEmptyGrid(width, height); let p2 = generateEmptyGrid(width, height); let p1NeutralPositions = getPositions(width, height); let p2NeutralPositions = getPositions(width, height); shuffle(p1NeutralPositions, rand); shuffle(p2NeutralPositions, rand); let p1GreenPositions = []; let p1BlackPositions = []; // P1 generation for (let ii = 0; ii < greenCount; ii++) { let pos = p1NeutralPositions.pop(); p1GreenPositions.push(pos); p1[pos[0]][pos[1]] = Tile.GREEN; } for (let ii = 0; ii < blackCount; ii++) { let pos = p1NeutralPositions.pop(); p1BlackPositions.push(pos); p1[pos[0]][pos[1]] = Tile.BLACK; } // P2 BLACK generation for (let ii = 0; ii < commonBlackCount; ii++) { let pos = p1BlackPositions.pop(); p2NeutralPositions.splice(getPosIndexByValue(p2NeutralPositions, pos), 1); p2[pos[0]][pos[1]] = Tile.BLACK; } for (let ii = 0; ii < blackGreenCount; ii++) { let pos = p1GreenPositions.pop(); p2NeutralPositions.splice(getPosIndexByValue(p2NeutralPositions, pos), 1); p2[pos[0]][pos[1]] = Tile.BLACK; } for (let ii = 0; ii < blackNeutralCount; ii++) { let pos = p1NeutralPositions.pop(); p2NeutralPositions.splice(getPosIndexByValue(p2NeutralPositions, pos), 1); p2[pos[0]][pos[1]] = Tile.BLACK; } // P2 GREEN generation for (let ii = 0; ii < commonGreenCount; ii++) { let pos = p1GreenPositions.pop(); p2NeutralPositions.splice(getPosIndexByValue(p2NeutralPositions, pos), 1); p2[pos[0]][pos[1]] = Tile.GREEN; } for (let ii = 0; ii < blackGreenCount; ii++) { let pos = p1BlackPositions.pop(); p2NeutralPositions.splice(getPosIndexByValue(p2NeutralPositions, pos), 1); p2[pos[0]][pos[1]] = Tile.GREEN; } for (let ii = 0; ii < greenNeutralCount; ii++) { let pos = p1NeutralPositions.pop(); p2NeutralPositions.splice(getPosIndexByValue(p2NeutralPositions, pos), 1); p2[pos[0]][pos[1]] = Tile.GREEN; } return [p1, p2]; } function isValidUrl(params) { if (!params.has("gm") || !params.has("w") || !params.has("h") || !params.has("b") || !params.has("s")) { return false; } switch (params.get("gm")) { case "0": if (!params.has("rb")) { return false; } break; case "1": if (!params.has("cg") || !params.has("cb") || !params.has("bg")) { return false; } // fallthrough case "2": if (!params.has("g") || !params.has("p")) { return false; } break; default: return false; } return true; } function parseUrlAndGenerate() { let params = new URLSearchParams(window.location.search); if (!isValidUrl(params)) return null; switch (params.get("gm")) { case "1": return generateDuetGrids(+params.get("w"), +params.get("h"), +params.get("g"), +params.get("b"), +params.get("cg"), +params.get("cb"), +params.get("bg"), decodeURI(params.get("s"))); } return null; } function drawMap(grid, map, increment) { let rowIdx = (increment > 0) ? 0 : map.length - 1; let rowEndIdx = (increment > 0) ? map.length : -1; while (rowIdx !== rowEndIdx) { let row = map[rowIdx]; let rowDiv = document.createElement("div"); rowDiv.classList.add("tile_row"); grid.appendChild(rowDiv); let columnIdx = (increment > 0) ? 0 : row.length - 1; let columnEndIdx = (increment > 0) ? row.length : -1; while (columnIdx !== columnEndIdx) { let tileDiv = document.createElement("div"); tileDiv.classList.add("tile"); rowDiv.appendChild(tileDiv); switch (map[rowIdx][columnIdx]) { case Tile.NEUTRAL: tileDiv.classList.add("neutral"); break; case Tile.BLACK: tileDiv.classList.add("black"); break; case Tile.GREEN: tileDiv.classList.add("green"); break; case Tile.RED: tileDiv.classList.add("red"); break; case Tile.BLUE: tileDiv.classList.add("blue"); break; } columnIdx += increment; } rowIdx += increment; } } function generateMapsFromUrl() { const grid1 = document.getElementById("grid_p1"); const grid2 = document.getElementById("grid_p2"); if (grid1 != null) { grid1.innerHTML = ""; } if (grid2 != null) { grid2.innerHTML = ""; } let generatedMaps = parseUrlAndGenerate(); if (generatedMaps == null) { return; } let player = new URLSearchParams(window.location.search).get("p"); if (player === "0" || player === "2") { drawMap(grid1, generatedMaps[0], 1); } if (player === "1" || player === "2") { drawMap(grid2, generatedMaps[1], -1); } } function getPlayer() { return document.querySelector('input[name="p"]:checked').value; } function getGameIntParam(key) { return parseInt(document.getElementById(key).value) || 0; } // TODO: warn about bad inputs function parseInputAndSetUrl() { let params = new URLSearchParams(); let gamemode = document.documentElement.getAttribute(GAME_MODE_KEY); params.append("gm", gamemode); params.append("s", encodeURI(document.getElementById("s").value)); params.append("w", getGameIntParam("w")); params.append("h", getGameIntParam("h")); params.append("b", getGameIntParam("b")); switch (gamemode) { case "0": // NORMAL params.append("rb", getGameIntParam("rb")); break; case "1": // DUET params.append("cg", getGameIntParam("cg")); params.append("cb", getGameIntParam("cb")); params.append("bg", getGameIntParam("bg")); // fallthrough case "2": // DUET MAYHEM params.append("g", getGameIntParam("g")); params.append("p", getPlayer()); break; } history.pushState({}, "", window.location.origin + window.location.pathname + "?" + params.toString()); return true; } function onGenerateButton() { if (parseInputAndSetUrl()) { generateMapsFromUrl(); } } function onSetGameMode(mode) { document.documentElement.setAttribute(GAME_MODE_KEY, mode) if (mode === 0) { document.getElementById("b").value = 1; } else { document.getElementById("b").value = 3; } } function onRandomSeed() { let result = ''; const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; const charactersLength = characters.length; for (let i = 0; i < 10; i++) { result += characters.charAt(Math.floor(Math.random() * charactersLength)); } document.getElementById("s").value = result; } function setParamsFromUrl(params) { params.entries().forEach(function (item) { if (item[0] === "s") { document.getElementById("s").value = decodeURI(item[1]); } else if (item[0] === "gm") { onSetGameMode(parseInt(item[1])); } else if (item[0] === "p") { document.getElementById("p" + item[1]).click(); } else { document.getElementById(item[0]).value = parseInt(item[1]); } }) } function initPage() { let params = new URLSearchParams(window.location.search); if (isValidUrl(params)) { setParamsFromUrl(params); generateMapsFromUrl(); } else { onSetGameMode(0); onRandomSeed(); onGenerateButton(); } } initPage();