HOWLRSProduct engineering相談する
Build notes
ゲーム・PWA公開中

ピクセルズ — Web で遊ぶ「思考に集中できる」ノノグラム (ピクチャーロジック)

Pixi.js v8 + React 19 + Zustand で構築した Web ノノグラム (ピクチャーロジック) パズル。21 個の手描きパズルすべてに「論理だけで解ける」一意性を CI で強制保証、Undo/Redo・25×25 用ズーム+パン・WebAudio 自前合成 BGM など集中して解くための機能を一通り備えました。

Project snapshot

この実装で扱ったこと

Stack
  • Pixi.js v8
  • React 19
  • Zustand 5
  • Cloudflare Pages
  • GitHub Actions
目次 12 sections
  1. ピクセルズとは
  2. 解決したかった「ノノグラムアプリの不揃い」
  3. パズル一覧 (5×5 から 25×25 まで 21 個)
  4. 「行/列が完成すると数字が緑になる」ライン完了表示
  5. 25×25 はズーム+パン UI で快適に
  6. Undo / Redo は履歴をセーブにも統合
  7. BGM は WebAudio 自前合成 (chiptune アンビエント)
  8. アクセシビリティ — WCAG AAA + キーボード完結
  9. 技術スタック
  10. 「論理だけで解ける」を CI で強制
  11. 開発過程の振り返り
  12. まとめ

ピクセルズとは

ピクセルズ は、行と列のヒント数字から塗るマスを論理だけで導く「ノノグラム / ピクチャーロジック」パズルを Web ブラウザで遊べるアプリです。

無料・登録不要、PWA でオフライン対応、5×5 から 25×25 まで合計 21 パズルを収録。すべての盤面は 「推測なしで論理だけで解ける (no-guess)」かつ「解が一意」 であることを CI で強制保証しています。

ハートパズルのプレイ画面。5×5 の盤面、ヒント数字 (上に 2/4/4/4/2、左に 1+1/5/5/3/1)、行 2 を 5 マス全部塗ったところ。左ヒントの「5」が緑色になり、行が完成したことを伝えています。HUD にはタイマー、進捗 10%、Undo/Redo、ズーム+/-/⤢、設定、ヘルプ、リセットボタン。

解決したかった「ノノグラムアプリの不揃い」

既存のノノグラムアプリは「広告だらけで集中できない」「課金導線が強くて遊びづらい」「論理だけで解けないパズルが混じっていて推測ゲーになる」「タッチ操作が荒くて 25×25 はストレス」など、不快ポイントが散りばめられていることが多いです。

そこでピクセルズは、最初から以下を満たすように設計しました。

  • 広告ゼロ・課金ゼロ — 一旦最初から最後まで遊んでもらう前提
  • 推測ゼロ — line solver で「ヒントだけで解ける」と CI 検証されたパズルのみ収録
  • タッチでも快適 — 1 本指で塗り、2 本指でピンチズーム+パン (25×25 でも崩れない)
  • 集中を妨げない — BGM はデフォルト OFF、ON にしても落ち着いた A マイナーペンタトニック

パズル一覧 (5×5 から 25×25 まで 21 個)

カテゴリ 数 例
5×5 3 ハート、ダイヤ、プラス
10×10 8 ねこ、いえ、ほし、きのこ、ハート(大)、かさ、ロケット、き
15×15 7 りんご、うさぎ、さかな、きりん、ぞう、かに、かたつむり
25×25 3 ちょう、しろ、ドラゴン

パズル選択画面。カテゴリ毎に「クリア x / x」の進捗を表示。最上部の「クリア 0 / 21」が全体進捗。各パズルカードはタップで開けます。

すべて手描きの絵柄を image-to-puzzle パイプラインで .grid (ASCII art) → JSON 化し、QA suite (line solver + bounded backtrack + 可視性 + 対称性) を通過したものだけを収録しています。

「行/列が完成すると数字が緑になる」ライン完了表示

ノノグラムで一番気持ちいい瞬間は「ある行や列が完全に正解と一致したとき」。ピクセルズでは、行や列のセルが正解通りに塗られた瞬間に その行/列のヒント数字が緑色 に切り替わります。

これがあるだけで「あと何行で全部完成するか」が一目で分かり、パズルを進める手が止まりません。判定はクライアント側で全マスのチェックではなく、その行/列だけをチェックするので 25×25 でも一切の遅延なし。

25×25 はズーム+パン UI で快適に

25×25 の盤面はモバイルで全体表示するとセル幅が約 14px となり、塗り間違いが頻発します。そこで Pixi.js v8 の Container.scale + position で盤面 root だけにビューポート変換を適用し、以下のジェスチャーを実装しました。

  • PC: マウスホイールでカーソル位置中心ズーム、+ − ⤢ ボタン
  • Mac トラックパッド: ピンチでズーム、2 本指 swipe でパン (ctrlKey 分岐で標準準拠)
  • スマホ/タブレット: 2 本指ピンチズーム、2 本指ドラッグでパン

排他制御は 'idle' / 'drawing' / 'gesture' の有限状態機械で、1 本指で塗っている途中に 2 本目が触れたら drawing をキャンセルして gesture に昇格、全指が離れるまで描画ロックします (誤爆防止)。

25×25 ドラゴンの初期画面。盤面外にずらりと並ぶ二桁混じりのヒント数字 (列 14, 13, 23 など) が、25×25 サイズの解き応えを物語ります。ピンチズームで好きな部分を拡大できます。

Undo / Redo は履歴をセーブにも統合

  • Cmd/Ctrl+Z で 1 ステップ undo、Cmd/Ctrl+Y または Cmd/Ctrl+Shift+Z で redo
  • HUD の ↶ ↷ ボタンでも操作可能
  • 履歴は (board, marks) のスナップショット配列、上限 100 件
  • LocalStorage の activePuzzle に履歴も含めて保存 → ブラウザを閉じても再開時に Undo 可能

ドラッグ操作は「endDrag したタイミングで 1 エントリ」として記録 (途中の中間状態は記録しない)。連続塗りでガタガタ Undo にならず気持ちよく戻せます。

BGM は WebAudio 自前合成 (chiptune アンビエント)

BGM は音源ファイルを使わず、OfflineAudioContext で 1 ループ 24 秒分の AudioBuffer を事前合成して AudioBufferSourceNode.loop = true で再生する方式。

  • テンポ: 80 BPM (落ち着き)
  • キー: A マイナー pentatonic (集中を妨げない)
  • 音色: ベース (sine 低音 / Am→Em→Fmaj→G の root note 進行) + メロディ (triangle / pentatonic 上下動)
  • フィルター: ローパス 2400Hz (高音をカット → 長時間集中向き)

setTimeout でのスケジュールはタブ非アクティブ時にスロットリングされるため不採用。AudioBuffer + loop なら復帰後も完璧に同期します。

設定モーダル。「音響」セクションにマスター音量 / 効果音 / バックグラウンド時にミュート / BGM (集中向けアンビエント) チェック / BGM 音量、「アクセシビリティ」セクションにアニメーションを減らす / ハイコントラストが並んでいます。BGM デフォルトは OFF (集中ゲームのため)。

bundle サイズ増加は +5 KiB のみ (楽曲データはすべてコード生成)。PWA precache に余計な重さを足さず、オフラインでも完全に動作します。

アクセシビリティ — WCAG AAA + キーボード完結

項目 実装
キーボード操作 矢印キー / Z (塗) / X (×) / C (消) で全操作完結
アニメーション抑制 prefers-reduced-motion + 設定モーダルで個別 OFF。クリア時の波状回転アニメも即終了
ハイコントラスト 黒背景 + 白塗の極限コントラスト (WCAG AAA 11:1 以上)
ARIA 進捗バー / モーダル focus trap / aria-live 適切化
タッチ領域 全 HUD ボタンが 44×44px 以上 (タッチターゲット最小)

特にハイコントラストは設定モーダルでトグル可能。グレード達成だけでなく、低視力ユーザーが日常使いできるレベルを狙いました。

ヘルプモーダルの「ズーム / パン」セクション。+ ボタン (ズームイン x1.25) / − ボタン (ズームアウト ÷1.25) / ⤢ ボタン (ズーム+パンを初期に戻す) / ホイール / Trackpad ピンチ / Trackpad 2 本指 swipe の 6 種類が一覧に。ヘルプは ? キーまたは HUD の ? ボタンで開きます。

技術スタック

層 採用
描画 Pixi.js 8.2 (WebGPU 既定 → WebGL2 fallback、bgRoot と boardRoot を分離してビューポート効果を盤面のみに限定)
UI React 19 + Zustand 5 (Canvas は ref、UI は React のハイブリッド)
スキーマ Valibot (型 + ランタイム検証 + LocalStorage マイグレーション)
パズル QA line solver (overlap 推論) + bounded backtrack + 可視性 + 対称性 (src/qa/)
音声 WebAudio 自前合成 (SE: synth.ts、BGM: bgm.ts)
ビルド Bun 1.2 + Vite 6
PWA vite-plugin-pwa (Service Worker、Manifest、precache 716 KiB)
配信 Cloudflare Pages + Universal SSL、pixels.howlrs.net
CI GitHub Actions (typecheck + test + build + 全 21 パズル QA 検証)

「論理だけで解ける」を CI で強制

ノノグラムにおいて最大のフラストレーションは「これは推測しないと解けない」というパズルに当たることです。ピクセルズでは line solver (overlap 推論を反復) で解けるパズルだけを収録するため、CI で全パズルを以下 4 観点で検証しています。

  • 一意性: 解が 1 つだけか (line + bounded backtrack で 2 解目を探索)
  • 論理可解性: line solver だけで解けるか (= no-guess)
  • 可視性: 塗り比率 / 連結成分数 / bbox 充填率
  • 対称性: 横/縦/点対称スコア

15×15 でも 1 ms 未満で判定し、25×25 では timeout + step 制限で計算爆発を防止。新しいパズルを追加するたびに bun run validate-puzzles が走り、QA を通らないと CI が落ちます。

開発過程の振り返り

タグ 内容
β2.0 HUD 分離 / SE / 共有 / 設定モーダル
β3.0 ライン完了表示 / reduce-motion / 進捗%
β4.0 ヘルプモーダル / ハイコントラスト
β5.0 Undo / Redo
β6.0 動的 canvas 解像度 / クリアマーク復元
β7.0 一時停止 (visibility 自動 paused)
β8.0 Undo/Redo を autoSave に統合
β9.0 画像→パズル変換パイプ (sharp 導入)
β10.0 ズーム+パン UI
β11.0 BGM (WebAudio 自前合成)

各リリースで Gemini Pro Deep / Review にコードレビューを依頼し、累計 50+ の指摘を反映しました。「viewport を localStorage に永続化すると端末間で UX 破綻」「BGM を setTimeout でスケジュールするとタブ非アクティブで崩れる → AudioBuffer + loop=true が堅牢」など、独力では見落としがちな盲点を拾えたのが大きかったです。

まとめ

  • 5×5 から 25×25 まで 21 パズル を収録、すべて no-guess + 一意解 を CI で強制
  • Undo/Redo (履歴 100 件 + LocalStorage 永続化) で「やってみて戻す」が気軽
  • 25×25 用に ズーム+パン UI (ピンチ / ホイール / トラックパッド / HUD ボタン)
  • WebAudio 自前合成 BGM で bundle 0 KB を保ったまま集中向け楽曲
  • PWA + オフライン対応、Cloudflare Pages で爆速配信
  • 広告ゼロ、課金ゼロ、登録不要 → https://pixels.howlrs.net/

通勤中の暇つぶしから、深く集中したいタイミングまで、論理だけで楽しめるノノグラムをぜひ。

Keep exploring

学習アプリ

百人一首暗記アプリ — 情景を味わいながら、100首を覚えるWeb体験

6 min read
開発基盤

SynapseGit — AIと人間の共創で「なぜこの形になったか」を残す

5 min read
AI・PWA

Generic Camera — カメラで撮るだけで「何か」を判定する汎用 PWA

7 min read