Algorithm & Visual Engineering
「解けるパズル」を数学的に保証する
アルゴリズムとCanvas合成技術
スライダーパズルは、単純にタイルをランダムに並べ替えると「数学的に絶対に解けない状態」が50%の確率で発生します。この課題を回避し、かつスムーズな画像処理を実現するためのテクニックを公開します。
このツールの入力から出力まで
現行コードの確認日:
- 入力
- 写真と分割数を選び、空白に隣接するピースをタップします。
- 処理
- 各ピース内の背景要素を分割数倍に広げ、leftとtopで元画像の表示範囲を選びます。完成状態から合法手を動かしてシャッフルします。
- 出力
- クリア記録は 800×1000px の JPEG(品質指定 0.9)です。
まず確認する点:シャッフル回数は最短手数ではありません。盤面のプレイ画面と、クリア後に生成する記録画像を区別します。
仕上がりの例
猫の写真から作ったスライダーパズルのプレイ画面です。
掲載画像はサイトにある既存の公開作例です。保存済みの資料では運営者の愛猫の写真を使用しています。今回の確認日が、この作例を実行した日を表すわけではありません。
保存済み検証記録の条件を見る
資料の種類は「ツールのプレビュー・結果画面を保存した記録」です。公開作例と保存済み記録で素材や色などが異なる場合があるため、下の数値は保存済み検証記録の条件として読んでください。
- 記録の入力画像
- 800×1222px (JPEG)
- 記録の画面キャプチャ
- 441×441px (PNG)
| 画面の項目 | 記録の値 | 現行コードの初期値・範囲 |
|---|---|---|
| 記録の盤面 | 3×3 | — |
資料ファイルの作成日は実行日として扱っていません。個別端末の処理速度や、ほかの写真に対する検出・加工の精度を測定した資料でもありません。
実装の確認箇所:src/pages/game/puzzle/slider/index.astro。以下のコード抜粋と出力条件は、この本番実装に基づいて説明します。
仕組みを読み進めるときは、DOM操作の基礎(要素の生成と表示状態の更新)、イベントリスナーの基礎(タップやドラッグから描画へつなぐ流れ)が参考になります。
1. 逆シミュレーションによる正解可能性の保証
スライダーパズル(15パズル等)には「置換のパリティ」という概念があり、完全にランダムな配置は解けないリスクを孕んでいます。本ツールでは、完成した状態から「空きマスをランダムな隣接タイルと入れ替える」という動作を盤面のマス数の 10 倍だけシミュレートすることで、初期盤面を生成しています。
メリット: 数学的な証明に基づく複雑な転倒数計算を実装することなく、合法な移動を逆順にたどれば完成状態へ戻れる盤面を生成できます。難しさや最短手数を保証するものではありません。ユーザーに「絶対に解けないパズル」を渡してしまうという最悪の体験を回避しています。
移動可能なのは空白と上下左右で隣り合うマスだけです。マンハッタン距離が 1 かを調べます。
function isAdjacent(index, emptyIndex, gridSize) {
const row = Math.floor(index / gridSize), col = index % gridSize;
const er = Math.floor(emptyIndex / gridSize), ec = emptyIndex % gridSize;
return Math.abs(row - er) + Math.abs(col - ec) === 1;
} 2. Canvasによる角丸クリッピングと非破壊合成
クリア時の記録画像生成では、Canvas APIの roundRect と clip を駆使しています。アップロードされた画像そのものを破壊せず、メモリ上のCanvasで「トリミング」「角丸加工」「統計情報のオーバーレイ」を一気に行います。
メリット: CSSだけでは難しい「画像への動的な文字の焼き付け」と「デザイン性の高い形状(角丸等)」の両立を、クライアントサイドのみで完結させています。外部サーバーへの送信が不要なため、プライバシー保護と処理速度の向上に寄与しています。
3. 内側要素の left / top によるタイル表示
ゲームプレイ中、各ピースを表示するのに img タグを分割して量産するのではなく、1枚の画像を background-image に指定した div の集合として扱っています。内側の背景要素を分割数倍の大きさにし、ピースIDから求めた行・列をleft・topの負の値へ換算して、表示範囲だけを切り出します。background-position自体はcenter固定です。
メリット: DOM要素の数を最小限に抑え、画像を分割したファイルの生成を省きます。ただし移動後は盤面の DOM を作り直す実装です。これにより、同じ画像から異なる分割数の盤面を作れます。切り替え時間の実測値を示すものではありません。
出力の仕様と確認する点
完成状態から空白の隣のピースをランダムに動かしてシャッフルします。直前の手を打ち消す移動もあるため、シャッフル回数がそのまま難易度や最短手数にはなりません。
入力は長辺 1000px 以下。CSS の内側要素を分割数倍に広げ left / top で表示範囲を選びます。盤面は正方形なので、正方形以外の元写真は引き伸ばされて見える場合があります。
クリア記録は 800×1000px の JPEG(品質指定 0.9)です。時間は1秒ごとの加算で測るため、バックグラウンド時のタイマー制限で実時間とずれる場合があります。自動解答や途中状態の復元機能はありません。
