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

ピクセルズとは

ピクセルズ は、行と列のヒント数字から塗るマスを論理だけで導く「ノノグラム / ピクチャーロジック」パズルを 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、bgRootboardRoot を分離してビューポート効果を盤面のみに限定) | | 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/

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