Skip to content

Add Genesis UI testing tool mockup for Scrabble game#35

Merged
olaugh merged 2 commits intomainfrom
claude/genesis-ui-testing-tool-ijzI5
Jan 26, 2026
Merged

Add Genesis UI testing tool mockup for Scrabble game#35
olaugh merged 2 commits intomainfrom
claude/genesis-ui-testing-tool-ijzI5

Conversation

@olaugh
Copy link
Owner

@olaugh olaugh commented Jan 26, 2026

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

  • New file: docs/mockup/genesis-ui.html - A complete standalone HTML application featuring:
    • Bitmap font support: Six different retro fonts (Tom Thumb, Five Pixel, Small 4x6, 5x7, Everex 5x8, UI 8x8/IBM VGA) with full glyph data for ASCII 32-127
    • Interactive controls: Real-time adjustable parameters including canvas scale (1-4x), board square size (8-16px), font selection, and dither patterns
    • Color customization: Individual color pickers for background, empty squares, grid lines, tiles, blanks, and all bonus squares (TWS, DWS, TLS, DLS)
    • Dither patterns: Six pattern options (none, checkerboard, halftone, diagonal, vertical, horizontal) for authentic retro rendering
    • Game visualization: Sample Scrabble board with placed tiles, player scores, tile rack, and move history
    • Authentic layout: Genesis-native 320x224 resolution with proper board positioning and UI elements

Implementation Details

  • Pixel-perfect rendering using Canvas ImageData API with image-rendering: crisp-edges for authentic retro appearance
  • Bitmap fonts stored as 8-byte glyph data arrays (one byte per row, bits represent pixels)
  • Dither patterns implemented as 8x8 lookup tables for flexible pattern application
  • Real-time rendering triggered on all control changes with immediate visual feedback
  • Responsive control panel with organized sections for easy parameter adjustment
  • Sample game state demonstrates typical mid-game scenario with multiple placed words

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
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +587 to +590
document.querySelectorAll('input, select').forEach(el => {
el.addEventListener('change', render);
el.addEventListener('input', render);
});
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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);
});

Copilot uses AI. Check for mistakes.
Comment on lines +108 to +117
<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>
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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>).

Copilot uses AI. Check for mistakes.
Comment on lines +404 to +406
for (let row = 0; row < 8; row++) {
const bits = glyph[row];
for (let col = 0; col < 8; col++) {
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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++) {

Copilot uses AI. Check for mistakes.
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);
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
const colorBlank = hexToRgb(document.getElementById('colorBlank').value);

Copilot uses AI. Check for mistakes.
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;
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
const tileW = fontData ? fontData.width + 2 : 10;

Copilot uses AI. Check for mistakes.
</div>
</div>

<button onclick="render()">Refresh</button>
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
Comment on lines +579 to +583
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})`;
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
@github-actions
Copy link

github-actions bot commented Jan 26, 2026

Speed Test Benchmark Results

Performance Comparison

Metric Test (refs/pull/35/merge) Baseline (main) Change
NWL23 Hybrid 285.8 frames 285.8 frames 0%
CSW24 Hybrid 315.3 frames 315.3 frames 0%

Detailed Results

Test 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
    }
  ]
}

View full workflow run

- 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
@olaugh olaugh merged commit 6f82a5e into main Jan 26, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants