From cc6bdc68cb3ed40a6e51f885ea1c7c7a4e5fa6c5 Mon Sep 17 00:00:00 2001 From: Botond Hende Date: Sun, 1 Feb 2026 00:36:11 +0100 Subject: codenames grid generator wip --- __main__.py | 2 + codenames/assets/css/codenames.css | 28 ++++ codenames/assets/js/codenames.js | 211 +++++++++++++++++++++++++++++ codenames/templates/codenames_base.html.j2 | 27 ++++ codenames/templates/codenames_duet.html.j2 | 32 +++++ codenames/templates/codenames_root.html.j2 | 6 + config.py | 14 +- modules/codenames_generate.py | 32 +++++ 8 files changed, 348 insertions(+), 4 deletions(-) create mode 100644 codenames/assets/css/codenames.css create mode 100644 codenames/assets/js/codenames.js create mode 100644 codenames/templates/codenames_base.html.j2 create mode 100644 codenames/templates/codenames_duet.html.j2 create mode 100644 codenames/templates/codenames_root.html.j2 create mode 100644 modules/codenames_generate.py diff --git a/__main__.py b/__main__.py index fd26157..63f7250 100644 --- a/__main__.py +++ b/__main__.py @@ -5,6 +5,7 @@ import jinja2 from .config import Config from .modules import blog_generate from .modules import comic_generate +from .modules import codenames_generate def init_jinja_env() -> jinja2.Environment: @@ -25,6 +26,7 @@ def main(output_root_path: str, local: bool): blog_generate.generate(jinja_env, os.path.join(output_root_path, "blog"), local) comic_generate.generate(jinja_env, os.path.join(output_root_path, "comic"), local) + codenames_generate.generate(jinja_env, os.path.join(output_root_path, "codenames"), local) if __name__ == '__main__': diff --git a/codenames/assets/css/codenames.css b/codenames/assets/css/codenames.css new file mode 100644 index 0000000..db3fb71 --- /dev/null +++ b/codenames/assets/css/codenames.css @@ -0,0 +1,28 @@ +div.tile_row { + display: flex; +} + +div.tile { + width: 100px; + height: 100px; +} + +div.neutral { + background: antiquewhite; +} + +div.black { + background: black; +} + +div.green { + background: green; +} + +div.red { + background: red; +} + +div.blue { + background: blue; +} diff --git a/codenames/assets/js/codenames.js b/codenames/assets/js/codenames.js new file mode 100644 index 0000000..c838b48 --- /dev/null +++ b/codenames/assets/js/codenames.js @@ -0,0 +1,211 @@ +"use strict" + +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; +} + +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 parseAndGenerateDuet() +{ + let params = new URLSearchParams(window.location.search); + if (params.has("w") && params.has("h") && params.has("g") && params.has("b") && + params.has("cg") && params.has("cb") && params.has("bg") && params.has("s")) { + return generateDuetGrids(+params.get("w"), +params.get("h"), +params.get("g"), +params.get("b"), + +params.get("cg"), +params.get("cb"), +params.get("bg"), params.get("s")); + } + + return null; +} + +function generate_for_current_page() { + const duetP1 = document.getElementById("duet_p1"); + const duetP2 = document.getElementById("duet_p2"); + if (duetP1 !== null || duetP2 !== null) { + let grids = parseAndGenerateDuet(); + if (grids == null) { + return; + } + + const gridRoot = duetP1; + let grid = grids[0]; + + for (let ii = 0; ii < grid.length; ii++) { + let row = grid[ii]; + let rowDiv = document.createElement("div"); + rowDiv.classList.add("tile_row"); + gridRoot.appendChild(rowDiv); + for (let jj = 0; jj < row.length; jj++) { + let tileDiv = document.createElement("div"); + tileDiv.classList.add("tile"); + rowDiv.appendChild(tileDiv); + switch (grid[ii][jj]) { + 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; + } + } + } + } +} + +generate_for_current_page(); \ No newline at end of file diff --git a/codenames/templates/codenames_base.html.j2 b/codenames/templates/codenames_base.html.j2 new file mode 100644 index 0000000..e6751f5 --- /dev/null +++ b/codenames/templates/codenames_base.html.j2 @@ -0,0 +1,27 @@ + + + + + {{ title }} + + + + + + + + +
+
+
+
+ {% block content required %}{% endblock %} +
+
+
+
+ + + diff --git a/codenames/templates/codenames_duet.html.j2 b/codenames/templates/codenames_duet.html.j2 new file mode 100644 index 0000000..f5cd94b --- /dev/null +++ b/codenames/templates/codenames_duet.html.j2 @@ -0,0 +1,32 @@ +{% extends "codenames_base.html.j2" %} +{% block content %} +
+
+
+ + + +
+ + + x + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ +
+{% endblock %} \ No newline at end of file diff --git a/codenames/templates/codenames_root.html.j2 b/codenames/templates/codenames_root.html.j2 new file mode 100644 index 0000000..0cbbce7 --- /dev/null +++ b/codenames/templates/codenames_root.html.j2 @@ -0,0 +1,6 @@ +{% extends "codenames_base.html.j2" %} +{% block content %} + Classic/Images +
+ Duet +{% endblock %} \ No newline at end of file diff --git a/config.py b/config.py index 8337d29..d8bdd7d 100644 --- a/config.py +++ b/config.py @@ -3,15 +3,16 @@ import datetime class Config: + __DIR = os.path.dirname(__file__) ASSETS_IMPORT_PATH = "/assets" ASSETS_IMPORT_PATH_STATIC = "https://static.wazul.moe" RSS_FILE_NAME = "feed.xml" - BLOG_ROOT_URL = "https://blog.wazul.moe" TIMEZONE = datetime.timezone(datetime.timedelta(hours=2)) - __DIR = os.path.dirname(__file__) + # BLOG + BLOG_ROOT_URL = "https://blog.wazul.moe" BLOG_ASSETS_SOURCE_DIR = os.path.join(__DIR, "blog/assets") - TEMPLATES_SOURCE_DIR = [os.path.join(__DIR, "blog/templates"), os.path.join(__DIR, "comic/templates")] + TEMPLATES_SOURCE_DIR = [os.path.join(__DIR, "blog/templates"), os.path.join(__DIR, "comic/templates"), os.path.join(__DIR, "codenames/templates")] POST_SOURCE_DIR = os.path.join(__DIR, "blog/posts") BLOG_HOSTNAME = "yggdrasil" @@ -28,9 +29,14 @@ class Config: def get_tag_prompt(tag: str, cmd: str) -> str: return Config.get_prompt("~/tags/{}".format("" if tag == "" else tag + "/"), cmd) - + # COMIC COMIC_NAME = "Comics by Wazul" COMIC_ROOT_URL = "https://comic.wazul.moe" ISSUE_SOURCE_DIR = os.path.join(__DIR, "comic/issues") COMIC_ASSETS_SOURCE_DIR = os.path.join(__DIR, "comic/assets") + # CODENAMES + CODENAMES_NAME = "Codenames Grid Generator" + CODENAMES_ROOT_URL = "https://codenames.wazul.moe" + CODENAMES_ASSETS_SOURCE_DIR = os.path.join(__DIR, "codenames/assets") + diff --git a/modules/codenames_generate.py b/modules/codenames_generate.py new file mode 100644 index 0000000..55dac9e --- /dev/null +++ b/modules/codenames_generate.py @@ -0,0 +1,32 @@ +import os.path +import shutil +import jinja2 + +from ..config import Config +import os.path +import shutil + +import jinja2 + +from ..config import Config + + +def generate(jinja_env: jinja2.Environment, output_root_path: str, local: bool): + if os.path.exists(output_root_path): + shutil.rmtree(output_root_path) + + os.mkdir(output_root_path) + + jinja_env.globals.update(codenames={ + "codenames_url": Config.CODENAMES_ROOT_URL, + }) + + root_template = jinja_env.get_template("codenames_root.html.j2") + with open(os.path.join(output_root_path, "index.html"), "w") as f: + f.write(root_template.render({"ctx" : Config.CODENAMES_ROOT_URL, "title": Config.CODENAMES_NAME})) + + duet_template = jinja_env.get_template("codenames_duet.html.j2") + with open(os.path.join(output_root_path, "duet.html"), "w") as f: + f.write(duet_template.render({"ctx" : Config.CODENAMES_ROOT_URL + "duet.html", "title": Config.CODENAMES_NAME})) + + shutil.copytree(Config.CODENAMES_ASSETS_SOURCE_DIR, output_root_path + Config.ASSETS_IMPORT_PATH) \ No newline at end of file -- cgit v1.2.3-70-g09d2