clone

an offline sliding-block puzzle game in gleam and lustre, with an endless ladder of puzzles ranked by search

Density is a setting now, said in the only terms worth saying it in

Dami committed e5d8739
Three buffer pixels per layout unit was judged on device and kept, but the
cost of it is real and it is quadratic: nine times the pixels to compose,
diffuse and blit. A phone that cannot keep up with that is better off
drawing four than dropping frames, so two of the rungs are offered.

Not as a number. Nobody outside render.js has any reason to choose buffer
pixels per layout unit; what a person can actually tell is whether the
screen looks right and whether the phone in their hand keeps up. So the row
reads INTENSITY, LOW and DEFAULT, and the mechanism stays where it belongs.

1 is deliberately not on it. At 1 the coarse half of the ruling ladder stops
being reachable -- a screen needs a period and a half of the block to read
as a pattern, and POSTER and BILLBOARD get stepped back down -- so it is a
different picture rather than a cheaper one. It remains a constant for
anyone who wants the 1984 look from the console.

DENSITY becomes a live binding with setDensity(), which re-cuts the backing
stores and clears the sprite cache by calling layout() -- the same path a
rotation already takes every day, so nothing else in the drawing code learns
a thing. The screenshot harness gets it as a getter rather than the copy it
was taking at boot, which would have reported whatever the value happened to
be when the page loaded.

The sheet is re-ordered while it is open. Theme, blocks and intensity are
three settings about how the picture is printed, and reading them with the
hints and the sound in between made them look like three unrelated ones.
commit
e5d8739b3e9acee8c181f6a9c67c44b109139317
parent
aa31fefd515fba22dc0203571210f3fd4e31298a
author
Dami <dami.etoile@gmail.com>
8 files changed, +105 -20
MREADME.md9++++++++
Mmockup/index.html33+++++++++++++++++++-----------
Mmockup/src/game.js31+++++++++++++++++++++++++++-
Mmockup/src/main.js7+++++--
Mmockup/src/render.js33++++++++++++++++++++++++++++--
Mmockup/src/store.js8++++++-
Mmockup/styles.css2+-
Mmockup/sw.js2+-
M README.md +9 -0
M mockup/index.html +21 -12
@@ -109,23 +109,17 @@
109109 <div id="gate" class="zone"><span class="val beat">TAP TO BEGIN</span></div>110110 111111 <div id="sheet">112- <!-- Filled by game.js from THEMES, so adding a theme is a one-object112+ <!-- The three that change how the picture looks come first and together:113+ what it is printed in, what the blocks are printed with, and how114+ finely. Reading them apart, with the hints and the sound in between,115+ made three settings about one thing look like three unrelated ones.116+117+ Filled by game.js from THEMES, so adding a theme is a one-object113118 edit and never a markup one. -->114119 <div class="group">115120 <div class="grouplbl">THEME</div>116121 <div class="row" id="themeRow"></div>117122 </div>118- <div class="group">119- <div class="grouplbl">HINTS</div>120- <div class="row" id="hintRow"></div>121- </div>122- <div class="group">123- <div class="grouplbl">SOUND</div>124- <div class="row">125- <button data-toggle="sound">TONES</button>126- <button data-toggle="haptics">HAPTICS</button>127- </div>128- </div>129123 <!-- A pull-down rather than a row of buttons: eight shapes plus AUTO130124 wrapped into three ragged lines and read as a wall. Built by game.js131125 out of real elements, not a <select> -- a native menu is the one
@@ -139,6 +133,21 @@
139133 <div class="menulist" id="shadeList" role="listbox"></div>140134 </div>141135 </div>136+ <div class="group">137+ <div class="grouplbl">INTENSITY</div>138+ <div class="row" id="densityRow"></div>139+ </div>140+ <div class="group">141+ <div class="grouplbl">HINTS</div>142+ <div class="row" id="hintRow"></div>143+ </div>144+ <div class="group">145+ <div class="grouplbl">SOUND</div>146+ <div class="row">147+ <button data-toggle="sound">TONES</button>148+ <button data-toggle="haptics">HAPTICS</button>149+ </div>150+ </div>142151 <div class="credit" id="credit"></div>143152 <button id="btnClose">CLOSE</button>144153 </div>
M mockup/src/game.js +30 -1
@@ -24,7 +24,7 @@ import {
2424 import {2525 view, LY, fx, layout, layoutBoard, setTheme, requestRender, consumeDirty,2626 renderFrame, toVirtual, atlasGeometry, familyRowRect, atlasContentHeight,27- PERIOD, clearSprites,27+ PERIOD, clearSprites, setDensity, DENSITIES,2828 } from './render.js';2929 import { THEMES } from './theme.js';3030 import { PATTERNS } from './shading.js';
@@ -481,6 +481,32 @@ function closeMenu() {
481481 $('shadeTitle').setAttribute('aria-expanded', 'false');482482 }483483 484+/* How finely the picture is composed -- DENSITY, said in the only terms anyone485+ * outside this file has any reason to care about.486+ *487+ * Nobody wants to choose a number of buffer pixels per layout unit. What they488+ * can tell is whether the screen looks the way it should and whether the phone489+ * in their hand keeps up, so the control names the look and not the mechanism,490+ * and offers the two rungs where the answer to that can differ. It sits with491+ * the theme and the blocks because it is the third thing about how the picture492+ * is printed, and reading it apart from them made one setting look like three.493+ *494+ * A row, like the hints: two choices, and they are ordered. */495+(function buildDensityRow() {496+ const row = $('densityRow');497+ for (const { n, label } of DENSITIES) {498+ const b = document.createElement('button');499+ b.textContent = label;500+ b.dataset.density = n;501+ b.onclick = () => {502+ save.density = n;503+ setDensity(n); // re-cuts the buffers and the sprite cache504+ persist(); syncSheet(); placeDom(); requestRender();505+ };506+ row.appendChild(b);507+ }508+})();509+484510 document.querySelectorAll('[data-toggle]').forEach(b => {485511 b.onclick = () => {486512 save[b.dataset.toggle] = !save[b.dataset.toggle];
@@ -495,6 +521,9 @@ function syncSheet() {
495521 b.classList.toggle('armed', b.dataset.shading === save.shading));496522 document.querySelectorAll('[data-hint]').forEach(b =>497523 b.classList.toggle('armed', b.dataset.hint === save.hints));524+ // The dataset is a string and the save is a number, so it is compared as one.525+ document.querySelectorAll('[data-density]').forEach(b =>526+ b.classList.toggle('armed', +b.dataset.density === save.density));498527 $('shadeTitle').textContent =499528 save.shading === 'auto' ? 'AUTO' : (PATTERNS[save.shading] || PATTERNS.atkinson).label;500529 document.querySelectorAll('[data-toggle]').forEach(b =>
M mockup/src/main.js +5 -2
M mockup/src/render.js +31 -2
@@ -84,10 +84,39 @@ export const PERIOD = 8; // slides are quantised to this
8484 * times the buffer pixels is three times the room, so the whole ladder is8585 * reachable for the first time.8686 *87- * 1 is the 1984 look, should it ever be wanted back. */88-export const DENSITY = 3;87+ * 1 is the 1984 look, should it ever be wanted back.88+ *89+ * Two of the rungs are a setting, because the cost is real: the buffer grows90+ * with the square, and a phone that cannot keep up with nine times the pixels91+ * is better off drawing four than dropping frames. 3 is the standard and 2 is92+ * the way out; 1 is not offered, because at 1 the coarse half of the ruling93+ * ladder stops being reachable and the picture is a different one, not a94+ * cheaper one. */95+/* The rungs offered, in order, and what each is called where somebody actually96+ * has to choose one -- nobody outside this file has any reason to pick a number97+ * of buffer pixels per layout unit. The settings row is built from this, so a98+ * third rung would be a one-object edit and never a markup one. */99+export const DENSITIES = [100+ { n: 2, label: 'LOW' },101+ { n: 3, label: 'DEFAULT' },102+];103+/** Anything not offered lands on the standard. */104+export const cleanDensity = n => (DENSITIES.some(d => d.n === n) ? n : 3);105+106+export let DENSITY = cleanDensity(save.density);89107 const dpx = n => Math.round(n * DENSITY);90108 109+/* Changing it re-cuts every pixel the app owns: the backing stores are sized in110+ * buffer pixels and the sprite cache is baked at the old ones. layout() does111+ * both, and is the same path a rotation takes, so there is nothing here that a112+ * resize does not already do every day. */113+export function setDensity(n) {114+ const d = cleanDensity(n);115+ if (d === DENSITY) return;116+ DENSITY = d;117+ layout();118+}119+91120 /* ------------------------------------------------------------------ canvas */92121 const cvs = document.getElementById('board'); // the dithered picture93122 const octx = cvs.getContext('2d');
M mockup/src/store.js +7 -1
M mockup/styles.css +1 -1
M mockup/sw.js +1 -1