summaryrefslogtreecommitdiff
path: root/codenames
diff options
context:
space:
mode:
Diffstat (limited to 'codenames')
-rw-r--r--codenames/assets/css/codenames.css28
-rw-r--r--codenames/assets/js/codenames.js211
-rw-r--r--codenames/templates/codenames_base.html.j227
-rw-r--r--codenames/templates/codenames_duet.html.j232
-rw-r--r--codenames/templates/codenames_root.html.j26
5 files changed, 304 insertions, 0 deletions
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 @@
+<!DOCTYPE html>
+<html dir="ltr" lang="en">
+<head>
+ <meta charset="utf-8"/>
+ <title>{{ title }}</title>
+ <meta name="viewport" content="width=device-width, initial-scale=1"/>
+ <meta property="og:title" content="{{ title }}" />
+ <meta property="og:type" content="website" />
+ <meta property="og:url" content="{{ url }}" />
+ <link rel="stylesheet" href="{{ site.assets_path }}/css/codenames.css" />
+ <link rel="stylesheet" href="{{ site.assets_path_static }}/css/bootstrap-grid.min.css" />
+</head>
+<body>
+<div class="bootstrap-wrapper">
+ <div class="container">
+ <div class="row">
+ <main class="col-md-9">
+ {% block content required %}{% endblock %}
+ </main>
+ </div>
+ </div>
+</div>
+</body>
+<footer>
+ <script src="/assets/js/codenames.js"></script>
+</footer>
+</html>
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 %}
+<div id="duet_p1"></div>
+<div id="duet_p2"></div>
+<form action="{{ url }}" method="get">
+ <label for="s">Seed:</label>
+ <input type="text" name="s" id="s" required />
+ <button>Random</button>
+ <br>
+ <label>Size:</label>
+ <input type="number" name="w" id="w" value="5" required />
+ <span>x</span>
+ <input type="number" name="h" id="h" value="5" required />
+ <br>
+ <label for="g">Green tiles (per person):</label>
+ <input type="number" name="g" id="g" value="9" required />
+ <br>
+ <label for="cg">Shared green tiles:</label>
+ <input type="number" name="cg" id="cg" value="3" required />
+ <br>
+ <label for="b">Black tiles:</label>
+ <input type="number" name="b" id="b" value="3" required />
+ <br>
+ <label for="cb">Shared black tiles:</label>
+ <input type="number" name="cb" id="cb" value="1" required />
+ <br>
+ <label for="bg">Black-green tiles:</label>
+ <input type="number" name="bg" id="bg" value="1" required />
+ <br>
+ <button type="submit">Generate</button>
+</form>
+{% 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 %}
+ <a href="/classic.html">Classic/Images</a>
+ <br>
+ <a href="/duet.html">Duet</a>
+{% endblock %} \ No newline at end of file