Add Genesis UI testing tool mockup for Scrabble game#35
Conversation
Pixel-perfect 320x224 canvas with chunky pixel scaling. Features: - Multiple bitmap fonts (Tom Thumb, 4x6, 5x7, Five Pixel, Everex, IBM VGA) - Configurable board square size (8-16px, default 12) - Color customization for all board elements - Dither patterns (checkerboard, halftone, diagonal, etc.) - Live preview of Scrabble board layout https://claude.ai/code/session_01Rohrr3HgpoaoMupZMuR367
There was a problem hiding this comment.
Pull request overview
Adds a standalone, interactive HTML/Canvas mockup to visualize and tweak a Sega Genesis–resolution Scrabble UI for rapid layout/font/color/dither experimentation.
Changes:
- Added a complete HTML/JS tool that renders a 320×224 Scrabble board using pixel-level Canvas
ImageData. - Included multiple embedded bitmap fonts and selectable 8×8 dither patterns.
- Added a control panel for adjusting scale, square size, board position, and color palette.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
docs/mockup/genesis-ui.html
Outdated
| document.querySelectorAll('input, select').forEach(el => { | ||
| el.addEventListener('change', render); | ||
| el.addEventListener('input', render); | ||
| }); |
There was a problem hiding this comment.
All controls register both change and input listeners, which can cause duplicate renders for many inputs (range/color/number fire input continuously and then change). Consider using input for live-updating controls and change only for selects, or coalescing calls via requestAnimationFrame to avoid unnecessary redraw work.
| document.querySelectorAll('input, select').forEach(el => { | |
| el.addEventListener('change', render); | |
| el.addEventListener('input', render); | |
| }); | |
| document.querySelectorAll('input').forEach(el => { | |
| el.addEventListener('input', render); | |
| }); | |
| document.querySelectorAll('select').forEach(el => { | |
| el.addEventListener('change', render); | |
| }); |
| <div class="control-group"> | ||
| <label>Canvas Scale</label> | ||
| <input type="range" id="scale" min="1" max="4" value="3"> | ||
| <div class="range-value"><span id="scaleValue">3</span>x (960x672)</div> | ||
| </div> | ||
|
|
||
| <div class="control-group"> | ||
| <label>Board Square Size</label> | ||
| <input type="range" id="squareSize" min="8" max="16" value="12"> | ||
| <div class="range-value"><span id="squareSizeValue">12</span>px</div> |
There was a problem hiding this comment.
The form labels are not programmatically associated with their inputs (no for attribute / wrapping), and the color pickers rely on adjacent <span> text. This reduces screen-reader usability. Consider adding for attributes matching the input/select ids (or wrapping inputs in <label>).
docs/mockup/genesis-ui.html
Outdated
| for (let row = 0; row < 8; row++) { | ||
| const bits = glyph[row]; | ||
| for (let col = 0; col < 8; col++) { |
There was a problem hiding this comment.
drawGlyph always iterates 8 rows/8 columns regardless of the selected font’s declared width/height. With small fonts (e.g., height=6) and small square sizes (min 8px), this can draw outside the intended glyph box and potentially overwrite adjacent pixels (e.g., grid lines). Consider iterating row < fontData.height and col < fontData.width (or treating all fonts as 8x8 and updating the width/height metadata accordingly) so glyph rendering matches the font metrics used for centering and layout.
| for (let row = 0; row < 8; row++) { | |
| const bits = glyph[row]; | |
| for (let col = 0; col < 8; col++) { | |
| for (let row = 0; row < fontData.height; row++) { | |
| const bits = glyph[row]; | |
| for (let col = 0; col < fontData.width; col++) { |
| const colorEmpty = hexToRgb(document.getElementById('colorEmpty').value); | ||
| const colorGrid = hexToRgb(document.getElementById('colorGrid').value); | ||
| const colorTile = hexToRgb(document.getElementById('colorTile').value); | ||
| const colorBlank = hexToRgb(document.getElementById('colorBlank').value); |
There was a problem hiding this comment.
The blank-tile color picker is wired up (colorBlank) but the value is never used, so the control currently has no effect. Either use colorBlank when drawing blank tiles (and include a blank in the sample state) or remove the unused control/variable to avoid misleading UI.
| const colorBlank = hexToRgb(document.getElementById('colorBlank').value); |
docs/mockup/genesis-ui.html
Outdated
| drawString(uiFont, 'RACK:', 2, rackY, [255,255,255], null); | ||
| const rackTiles = ['A','E','I','O','N','R','T']; | ||
| const fontData = FONTS[tileFont]; | ||
| const tileW = fontData ? fontData.width + 2 : 10; |
There was a problem hiding this comment.
tileW is computed but never used. This is dead code and can confuse future changes; remove it or use it consistently for rack/tile layout calculations.
| const tileW = fontData ? fontData.width + 2 : 10; |
docs/mockup/genesis-ui.html
Outdated
| </div> | ||
| </div> | ||
|
|
||
| <button onclick="render()">Refresh</button> |
There was a problem hiding this comment.
The Refresh button uses an inline onclick handler while other inputs use addEventListener. To keep event handling consistent and avoid inline script attributes, give the button an id and attach the click handler in the script instead.
docs/mockup/genesis-ui.html
Outdated
| document.getElementById('scaleValue').textContent = scale; | ||
| document.getElementById('squareSizeValue').textContent = squareSize; | ||
| const dispW = WIDTH * scale; | ||
| const dispH = HEIGHT * scale; | ||
| document.querySelector('.range-value').innerHTML = `<span id="scaleValue">${scale}</span>x (${dispW}x${dispH})`; |
There was a problem hiding this comment.
render() updates #scaleValue via textContent, but then immediately replaces the entire .range-value contents via innerHTML, recreating the #scaleValue element and making the earlier update redundant. Prefer updating existing DOM nodes (e.g., separate spans for scale/dispW/dispH) and avoid innerHTML here.
Speed Test Benchmark ResultsPerformance Comparison
Detailed ResultsTest Results (refs/pull/35/merge){
"num_games": 50,
"lexicons": [
{
"name": "NWL23",
"shadow_mean": 308.2,
"noshadow_mean": 332.6,
"hybrid_mean": 285.8,
"move_count": 1148
},
{
"name": "CSW24",
"shadow_mean": 335.4,
"noshadow_mean": 386.4,
"hybrid_mean": 315.3,
"move_count": 1091
}
]
}Baseline Results (main){
"num_games": 50,
"lexicons": [
{
"name": "NWL23",
"shadow_mean": 308.2,
"noshadow_mean": 332.6,
"hybrid_mean": 285.8,
"move_count": 1148
},
{
"name": "CSW24",
"shadow_mean": 335.4,
"noshadow_mean": 386.4,
"hybrid_mean": 315.3,
"move_count": 1091
}
]
} |
- Fix accessibility: add for attributes to labels associating them with inputs - Fix event listeners: use input for live-updating controls, change for selects - Fix drawGlyph: iterate based on font's actual width/height, not hardcoded 8 - Fix colorBlank: now used for blank tile in rack (shows ? on purple) - Remove unused tileW variable - Fix redundant DOM update: use separate spans for scale value and dimensions - Fix button: use addEventListener instead of inline onclick https://claude.ai/code/session_01Rohrr3HgpoaoMupZMuR367
Summary
Added an interactive HTML-based UI testing tool for visualizing a Scrabble game board on Sega Genesis hardware (320x224 resolution). This mockup allows developers to experiment with different fonts, colors, dither patterns, and layout configurations.
Key Changes
docs/mockup/genesis-ui.html- A complete standalone HTML application featuring:Implementation Details
image-rendering: crisp-edgesfor authentic retro appearance