From 8f06a0e126a5d8ba6321f0e15d97f82c90e6c0b4 Mon Sep 17 00:00:00 2001 From: pommicket Date: Thu, 18 Sep 2025 22:13:06 -0400 Subject: Improvements for mobile --- extractor/extractor.py | 1 - pre-commit.sh | 8 ++ pub/blankplays.js | 65 +++++++++--- pub/index.css | 196 ++++++++++++++++++++++++++++++++++++ pub/index.html | 264 +++++++++++++++++-------------------------------- 5 files changed, 345 insertions(+), 189 deletions(-) create mode 100755 pre-commit.sh create mode 100644 pub/index.css diff --git a/extractor/extractor.py b/extractor/extractor.py index ed58921..0960eb1 100644 --- a/extractor/extractor.py +++ b/extractor/extractor.py @@ -153,7 +153,6 @@ for filename in args.log_files: game = games[game_id] plays = game.blank_plays() nice_plays = {play[0] for play in plays if is_nice(play)} - print(nice_plays) # require at least 3 squares that make 5+-letter words if len(nice_plays) >= 3: game.output(f'{output_dir}/{game_idx:05}.txt') diff --git a/pre-commit.sh b/pre-commit.sh new file mode 100755 index 0000000..114f78f --- /dev/null +++ b/pre-commit.sh @@ -0,0 +1,8 @@ +#!/bin/sh +was_modified() { + git diff --name-status HEAD -- $1 | grep -q 'M\s*'"$1" +} + +if was_modified blankplays.js; then + npx eslint blankplays.js || exit 1 +fi diff --git a/pub/blankplays.js b/pub/blankplays.js index 964aade..ff964e2 100644 --- a/pub/blankplays.js +++ b/pub/blankplays.js @@ -1,10 +1,5 @@ 'use strict'; -/* -TODO: -- clear solution -*/ - const N = 15; // board size const NOTHING = "∅"; @@ -17,15 +12,19 @@ localStorage.setItem('prevLexicon', lexicon); function updateBoardSize() { let boardElem = document.getElementById('board'); // sucks for desktop zooming, but there's no way around it. - let width = innerWidth; + let width = document.body.clientWidth; // use clientWidth to not include body margins let height = innerHeight; - let boardSize = Math.min(width - 20, Math.floor(height * 0.7)); - let fontSize = (boardSize / N - 4) * 0.6; - boardElem.style.fontSize = fontSize + 'px'; + let boardSize = Math.min(width, Math.floor(height * 0.7)); + let tileSize = boardSize / N; + // use large font size if it's <=1cm, + // small font size if it's >1cm + // use 1cm font size otherwise. + let fontSize = `max(${tileSize * 0.6}px,min(${tileSize * 0.7}px,1cm))`; + boardElem.style.fontSize = fontSize; boardElem.style.width = boardSize + 'px'; boardElem.style.height = boardSize + 'px'; let selectContainer = document.getElementById('select-container'); - selectContainer.style.fontSize = fontSize + 'px'; + selectContainer.style.fontSize = fontSize; selectContainer.style.width = boardSize + 'px'; selectContainer.style.height = boardSize / N * 2 + 'px'; } @@ -385,7 +384,8 @@ function skipDueToLength(row, col) { function updateSkipWordsOfLength() { let skip2s = document.getElementById('skip-2s'); let skip3s = document.getElementById('skip-3s'); - skipWordsOfLength = skip3s.checked ? 3 : skip2s.checked ? 2 : 1; + let skip4s = document.getElementById('skip-4s'); + skipWordsOfLength = skip4s.checked ? 4 : skip3s.checked ? 3 : skip2s.checked ? 2 : 1; localStorage.setItem(`skip-${lexicon}`, skipWordsOfLength); for (let row = 0; row < 15; row++) { for (let col = 0; col < 15; col++) { @@ -401,6 +401,7 @@ function showSolution() { finished = true; saveAttempt(); deselectTile(); + document.getElementById('submit').style.display = 'none'; document.getElementById('select-nothing').style.display = 'none'; document.getElementById('select-heading').style.display = 'none'; document.getElementById('place-heading').style.display = 'none'; @@ -472,8 +473,10 @@ function showSolution() { document.getElementById('incorrect-plays').innerText = incorrectPlays; document.getElementById('missed-plays').innerText = missedPlays; document.getElementById('stats').style.display = 'block'; - let shareText = `I got ${score}/100 on today's BlankPlays!`; - if (score !== 100); + let shareText = `I scored ${score}/100 on today's BlankPlays!`; + if (score === 100) + shareText += ' 😎'; + else shareText += '\nCan you do better?'; shareText += `\nhttps://blankplays.pommicket.com?lexicon=${lexicon}`; let shareElem = document.getElementById('share'); @@ -492,7 +495,28 @@ function showSolution() { }); } +function showDialogById(id) { + let elem = document.getElementById(id); + if (elem.showModal) { + elem.showModal(); + } else { + // support for browsers without + // (older iOS safari mainly) + elem.style.display = 'block'; + elem.scrollIntoView(); + } +} + function startup() { + document.getElementById('how-to-play-button').addEventListener('click', () => { + showDialogById('how-to-play'); + }); + document.getElementById('credits-button').addEventListener('click', () => { + showDialogById('credits'); + }); + document.getElementById('report-issue-button').addEventListener('click', () => { + showDialogById('report-issue'); + }); let lexiconSelect = document.getElementById('lexicon'); lexiconSelect.value = lexicon; lexiconSelect.addEventListener('change', () => { @@ -501,16 +525,29 @@ function startup() { let boardElem = document.getElementById('board'); let skip2s = document.getElementById('skip-2s'); let skip3s = document.getElementById('skip-3s'); + let skip4s = document.getElementById('skip-4s'); skip2s.checked = skipWordsOfLength >= 2; skip3s.checked = skipWordsOfLength >= 3; + skip4s.checked = skipWordsOfLength >= 4; skip2s.addEventListener('change', () => { - if (!skip2s.checked) + if (!skip2s.checked) { skip3s.checked = false; + skip4s.checked = false; + } updateSkipWordsOfLength(); }); skip3s.addEventListener('change', () => { if (skip3s.checked) skip2s.checked = true; + if (!skip3s.checked) + skip4s.checked = true; + updateSkipWordsOfLength(); + }); + skip4s.addEventListener('change', () => { + if (skip4s.checked) { + skip2s.checked = true; + skip3s.checked = true; + } updateSkipWordsOfLength(); }); document.getElementById('submit').addEventListener('click', showSolution); diff --git a/pub/index.css b/pub/index.css new file mode 100644 index 0000000..7928e00 --- /dev/null +++ b/pub/index.css @@ -0,0 +1,196 @@ +body { + font-family: Helvetica, sans-serif; + --tile-margin: 5%; +} +/* use minimal margins on small screens, + to maximize available space for board. */ +@media (width < 10cm) { + body { + margin: 2px; + --tile-margin: 0%; + } +} +#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: 1px; + background-color: #dde; + line-height: 1; + position: relative; +} +.board-square.double-letter { + background-color: #acf; +} +.board-square.triple-letter { + background-color: #afc; +} +.board-square.double-word { + background-color: #fac; +} +.board-square.triple-word { + background-color: #c66; +} +.tile { + background: #eca; + position: absolute; + width: calc(100% - 2 * var(--tile-margin)); + height: calc(100% - 2 * var(--tile-margin)); + top: var(--tile-margin); + left: var(--tile-margin); + border-radius: 3px; + display: flex; + justify-content: center; + align-items: center; + font-weight: bold; +} +.blank { + color: #333; + font-weight: normal; +} +.highlight { + border: min(0.5vw,0.5vh) solid rgba(60,60,60,0.6); + width: 100%; + height: 100%; + box-sizing: border-box; + position: absolute; + cursor: pointer; + display: flex; + justify-content: center; + align-items: center; +} +.highlight.nothing { + border-color: #f00; +} +#board .highlight.selected { + border-color: #00f; +} +.highlight.all-correct { + border-color: #0a0; +} +.highlight.some-mistakes { + border-color: #f80; +} +.possibilities { + word-break: break-all; +} +.point-container { + position: relative; + width: 100%; + height: 100%; +} +.point-value { + position: absolute; + bottom: calc(1% + var(--tile-margin)); + right: calc(1% + var(--tile-margin)); + text-align: center; + font-size: 30%; +} +#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; + margin: 2px; +} +.select-tile-container .tile { + background-color: transparent; + font-weight: normal; + border: 2px solid black; +} +.select-tile-container .tile.possible, +.select-tile-container .tile.correct { + background-color: #8c7; + font-weight: bold; + text-decoration: underline; +} +.select-tile-container .tile.missed { + background-color: #8c7; +} +.select-tile-container .tile.not-possible { + background-color: #c88; +} +.select-tile-container .tile.wrong { + background-color: #c88; + font-weight: bold; + text-decoration: underline; +} +.select-tile-container .tile.placing { + background-color: #88c; + font-weight: bold; + text-decoration: underline; +} +#submit { + outline: 0; + border: 2px solid #44a; + outline: 2px solid #00a; + box-shadow: 2px 2px 4px #00a; + margin: 4px; + border-radius: 5px; + padding: 2px 5px; + background: #ddf; +} +#submit:hover { + background: #bbf; +} +#submit:active { + transform: translate(2px, 2px); + box-shadow: 0 0 0; +} +.solution-letter.wrong { + text-decoration: line-through; + font-weight: bold; + color: #800; +} +.solution-letter.wrong { + text-decoration: line-through; + font-weight: bold; + color: #800; +} +.solution-letter.correct { + color: #060; +} +.solution-letter.missed { + color: #00a; + font-weight: bold; +} +/* all browsers which support grid should support + the JavaScript we use (if they have javascript at all). */ +@supports(display: grid) { + #no-grid { + display: none; + } +} +progress#score-meter::-moz-progress-bar { background: var(--color); } +progress#score-meter::-webkit-progress-value { background: var(--color); } +progress#score-meter { color: var(--color); } +h2, h3 { + margin: 2px; +} +.links { + text-align: right; + margin: 0; + line-height: 1; +} +dialog { + max-width: 50em; + display: none; +} +dialog[open] { + max-width: 50em; + display: block; +} diff --git a/pub/index.html b/pub/index.html index 04db2a4..d79521b 100644 --- a/pub/index.html +++ b/pub/index.html @@ -5,178 +5,7 @@ BlankPlays - + @@ -184,16 +13,24 @@ You must enable JavaScript in your browser to do these puzzles.

- Your browser is too old. You'll have to update to a newer version to + Your browser is too old. You’ll have to update to a newer version to do these puzzles.

- Find all the possible plays with a single blank!
+ +

BlankPlays

+ Find all possible plays with a single blank!
+
+ + + + +

How to Play

+

+ BlankPlays is a puzzle where you try to find all legal plays + from a given Scrabble™* position, where the only tile + on your rack is a single blank. +

+

+ To begin, click/tap on a square on the board that is highlighted with a gray outline. + Then click/tap on the tiles below the board to select which letters you think can go in that + position. +

+

+ You can also start by clicking a tile at the bottom of the board, then click on all positions + where you think it can go. +

+

+ If you think no letters play on a square, you can right-click it, or use the ø tile below the board, + to mark it in red (this doesn’t affect scoring but might help you keep track of things). +

+

+ When you’re done, click the “All done!” button. This will show you which plays you missed, + which of your guesses were incorrect (form illegal words), and which were correct. + You’ll get a score out of 100 based on how well you did. +

+

+ By default, squares which only allow for 2 letter words are disabled. + You can change this by turning off the “Skip 2’s” checkbox, + or go even further and disable squares which only make 3/4 letter words + with the “Skip 3/4’s” checkboxes. +

+

+ You can select the lexicon (word list) to use with the little selector at the top. + NWL is typically used in Canada and the United States, + and CSW is used in the rest of the English-speaking world. +

+

+ * BlankPlays is not an official or approved Scrabble product and is not associated + with Hasbro or Mattel in any way. +

+
+ +
+
+ +

Credits

+

+ These game positions were generated using the + Macondo + program’s autoplay feature, courtesy of César del Solar. +

+

+ The NWL lexicon is by the North American Scrabble Players’ Association (NASPA), + and the CSW lexicon is by HarperCollins. + +

+

+ Design/programming by pommicket. +

+
+ +
+
+ +

Report an issue

+

+ Found something that can be fixed or improved? Please let me know! + You can report an issue on the + GitHub page + or by sending an e-mail to + pommicket@pommicket.com. +

+
+ +
+
-- cgit v1.2.3