summaryrefslogtreecommitdiff
path: root/pub
diff options
context:
space:
mode:
authorpommicket <pommicket@gmail.com>2025-09-17 15:35:44 -0400
committerpommicket <pommicket@gmail.com>2025-09-17 15:35:44 -0400
commit6662ff6460bfe3a963564adb8fbe0d52370f882c (patch)
tree0858572a7ef735d5a29d8a05fef3478523613e95 /pub
Basic interface
Diffstat (limited to 'pub')
-rw-r--r--pub/blankplays.js201
-rw-r--r--pub/index.html118
2 files changed, 319 insertions, 0 deletions
diff --git a/pub/blankplays.js b/pub/blankplays.js
new file mode 100644
index 0000000..4dc05ee
--- /dev/null
+++ b/pub/blankplays.js
@@ -0,0 +1,201 @@
+'use strict';
+
+const N = 15; // board size
+
+let lexicon = 'nwl23';
+
+function updateBoardSize() {
+ let board = document.getElementById('board');
+ // sucks for desktop zooming, but there's no way around it.
+ let width = innerWidth;
+ let height = innerHeight;
+ let boardSize = Math.min(width - 20, Math.floor(height * 0.6));
+ let fontSize = (boardSize / N - 4) * 0.6;
+ board.style.fontSize = fontSize + 'px';
+ board.style.width = boardSize + 'px';
+ board.style.height = boardSize + 'px';
+ let selectContainer = document.getElementById('select-container');
+ selectContainer.style.fontSize = fontSize + 'px';
+ selectContainer.style.width = boardSize + 'px';
+ selectContainer.style.height = boardSize / N * 2 + 'px';
+}
+
+const DOUBLE_LETTER = 'double-letter';
+const TRIPLE_LETTER = 'triple-letter';
+const DOUBLE_WORD = 'double-word';
+const TRIPLE_WORD = 'triple-word';
+
+function getBonus(row, col) {
+ row = Math.min(row, N-1 - row);
+ col = Math.min(col, N-1 - col);
+ let id = Math.min(row * N + col, col * N + row);
+ if (id == 0 || id == 7) {
+ return TRIPLE_WORD;
+ } else if (id == N+5 || id == 5*N+5) {
+ return TRIPLE_LETTER;
+ } else if (id == 3 || id == 2*N+6 || id == 3*N+7 || id == 6*N+6) {
+ return DOUBLE_LETTER;
+ } else if (id == 7*N+7 || id == N+1 || id == 2*N+2 || id == 3*N+3 || id == 4*N+4) {
+ return DOUBLE_WORD;
+ } else {
+ return '';
+ }
+}
+
+function pointValue(letter) {
+ return {
+ 'A': 1, 'B': 3, 'C': 3, 'D': 2, 'E': 1,
+ 'F': 4, 'G': 2, 'H': 4, 'I': 1, 'J': 8,
+ 'K': 5, 'L': 1, 'M': 3, 'N': 1, 'O': 1,
+ 'P': 3, 'Q': 10,'R': 1, 'S': 1, 'T': 1,
+ 'U': 1, 'V': 4, 'W': 4, 'X': 8, 'Y': 4,
+ 'Z': 10,
+ }[letter];
+}
+
+let boardSquareElems = [];
+
+function makeTile(container, letter, showPointValue) {
+ let tile = document.createElement('span');
+ tile.classList.add('tile');
+ let blank = false;
+ if (letter === letter.toLowerCase()) {
+ blank = true;
+ }
+ let text = document.createElement('span');
+ text.appendChild(document.createTextNode(letter.toUpperCase()));
+ let points = document.createElement('span');
+ if (showPointValue)
+ points.appendChild(document.createTextNode(blank ? '0' : pointValue(letter) + ''));
+ points.classList.add('point-value');
+ if (blank)
+ text.classList.add('blank');
+ tile.appendChild(text);
+ container.appendChild(tile);
+ container.appendChild(points);
+}
+
+function putTile(row, col, letter) {
+ let squareElem = boardSquareElems[row][col];
+ makeTile(squareElem, letter, true);
+}
+
+function selectTile(elem, row, col) {
+ deselectTile();
+ elem.classList.add('selected');
+ document.getElementById('select').style.display = 'block';
+}
+
+function deselectTile() {
+ let selected = document.querySelector('.selected');
+ if (selected)
+ selected.classList.remove('selected');
+ for (let tile of document.querySelectorAll('.tile.possible')) {
+ tile.classList.remove('possible');
+ }
+ document.getElementById('select').style.display = 'none';
+}
+
+function clickedSquare(highlight, row, col) {
+ return (e) => {
+ if (e.button === 0) {
+ if (highlight.classList.contains('selected')) {
+ deselectTile();
+ } else {
+ selectTile(highlight, row, col);
+ }
+ e.preventDefault();
+ } else if (e.button === 2) {
+ if (highlight.classList.contains('nothing')) {
+ highlight.classList.remove('nothing');
+ } else {
+ highlight.classList.add('nothing');
+ if (highlight.classList.contains('selected'))
+ deselectTile();
+ }
+ e.preventDefault();
+ }
+ };
+}
+
+// TODO : error handling
+async function loadChallenge(id) {
+ let result = await fetch(`challenges-${lexicon}/${id}.txt`);
+ let body = await result.text();
+ let lines = body.split('\n');
+ let board = [];
+ for (let row = 0; row < 15; row++) {
+ board.push([]);
+ for (let col = 0; col < 15; col++) {
+ board[row].push(lines[row][col]);
+ }
+ }
+ for (let row = 0; row < 15; row++) {
+ for (let col = 0; col < 15; col++) {
+ let letter = board[row][col];
+ if (letter !== '.') {
+ putTile(row, col, letter);
+ continue;
+ }
+ let neighbours = [];
+ if (row > 0)
+ neighbours.push(board[row-1][col]);
+ if (row < N-1)
+ neighbours.push(board[row+1][col]);
+ if (col > 0)
+ neighbours.push(board[row][col-1]);
+ if (col < N-1)
+ neighbours.push(board[row][col+1]);
+ if (neighbours.filter((x) => x !== '.').length === 0) {
+ // not connected
+ continue;
+ }
+ let highlight = document.createElement('div');
+ highlight.classList.add('highlight');
+ boardSquareElems[row][col].appendChild(highlight);
+ highlight.addEventListener('contextmenu', (e) => e.preventDefault());
+ highlight.addEventListener('mousedown', clickedSquare(highlight, row, col));
+ }
+ }
+}
+
+function startup() {
+ updateBoardSize();
+ for (let row = 0; row < N; row++) {
+ let rowElem = document.createElement('div');
+ rowElem.classList.add('board-row');
+ board.appendChild(rowElem);
+ boardSquareElems.push([]);
+ for (let col = 0; col < N; col++) {
+ let squareElem = document.createElement('div');
+ squareElem.classList.add('board-square');
+ let bonus = getBonus(row, col);
+ if (bonus) squareElem.classList.add(bonus);
+ rowElem.appendChild(squareElem);
+ boardSquareElems[row].push(squareElem);
+ }
+ }
+ let selectContainer = document.getElementById('select-container');
+ for (let row = 0; row < 2; row++) {
+ let rowContainer = selectContainer.querySelectorAll('.select-container-row')[row];
+ for (let i = row*N; i < (row+1)*N; i++) {
+ let tiles = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
+ if (i >= tiles.length) break;
+ let elem = document.createElement('span');
+ elem.classList.add('select-tile-container');
+ makeTile(elem, tiles[i], false);
+ let tileElem = elem.querySelector('.tile');
+ elem.addEventListener('click', () => {
+ if (tileElem.classList.contains('possible'))
+ tileElem.classList.remove('possible');
+ else
+ tileElem.classList.add('possible');
+ });
+ rowContainer.appendChild(elem);
+ }
+ }
+ loadChallenge('00000');
+}
+
+window.addEventListener('load', startup);
+window.addEventListener('resize', updateBoardSize);
diff --git a/pub/index.html b/pub/index.html
new file mode 100644
index 0000000..b920617
--- /dev/null
+++ b/pub/index.html
@@ -0,0 +1,118 @@
+<!DOCTYPE html>
+<html lang="en">
+ <head>
+ <meta charset="utf-8">
+ <meta content="width=device-width,initial-scale=1" name="viewport">
+ <title>Blank Plays</title>
+ <link rel="icon" href="data:,"><!--TODO-->
+ <style>
+ body {
+ font-family: sans-serif;
+ }
+ #board {
+ display: grid;
+ grid-template-rows: repeat(15, 1fr);
+ }
+ .board-row {
+ display: grid;
+ grid-template-columns: repeat(15, 1fr);
+ grid-auto-flow: column;
+ }
+ .board-square {
+ margin: 2px;
+ background-color: #dde;
+ line-height: 1;
+ position: relative;
+ }
+ .board-square.double-letter {
+ background-color: #acf;
+ }
+ .board-square.triple-letter {
+ background-color: #458;
+ }
+ .board-square.double-word {
+ background-color: #fac;
+ }
+ .board-square.triple-word {
+ background-color: #900;
+ }
+ .tile {
+ background: #eca;
+ position: absolute;
+ top: 6%;
+ left: 6%;
+ width: 88%;
+ height: 88%;
+ border-radius: 4px;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ font-weight: bold;
+ }
+ .blank {
+ color: #333;
+ font-weight: normal;
+ }
+ .highlight {
+ border: 4px solid #ff0;
+ width: calc(100% - 8px);
+ height: calc(100% - 8px);
+ position: absolute;
+ cursor: pointer;
+ }
+ .highlight.nothing {
+ border: 4px solid #f00;
+ }
+ .highlight.selected {
+ border: 4px solid #00f;
+ }
+ .point-container {
+ position: relative;
+ width: 100%;
+ height: 100%;
+ }
+ .point-value {
+ position: absolute;
+ bottom: 8%;
+ right: 8%;
+ text-align: center;
+ font-size: 40%;
+ }
+ #select-container {
+ display: grid;
+ grid-template-rows: repeat(2, 1fr);
+ }
+ .select-container-row {
+ display: grid;
+ grid-template-columns: repeat(15, 1fr);
+ grid-auto-flow: column;
+ width: 100%;
+ }
+ .select-tile-container {
+ position: relative;
+ cursor: pointer;
+ }
+ .select-tile-container .tile {
+ background-color: #c88;
+ font-weight: normal;
+ }
+ .select-tile-container .tile.possible {
+ background-color: #8c7;
+ font-weight: bold;
+ }
+ </style>
+ <script src="/blankplays.js" async></script>
+ </head>
+ <body>
+ Find all the possible plays with a single blank!
+ <button>All done!</button>
+ <div id="board"></div>
+ <div id="select" style="display: none;">
+ Select which letters can go here:
+ <div id="select-container">
+ <div class="select-container-row"></div>
+ <div class="select-container-row"></div>
+ </div>
+ </div>
+ </body>
+</html>