プロデルで15パズルを作る

今回は、プロデルでパズルゲームを作ってみます。
中でも「15パズル」というスライディングパズルを作ってみます。

## スライディングパズルとは

スライディングパズルとは、駒を移動して駒を目的の順番に並べ替えるパズルです。
15パズルでは、板状の箱の中に、1から15までの番号が振られた15個の駒(ピース)がはめ込まれていて、空白の1マスを使って駒をスライドして動かして、1から15まで順番に並べ替えます。
このパズルの一種で数字の代わりに絵柄を使ったパズルもあったかと思います。昔プラスチック製のおもちゃが出回っていて遊んだ記憶があります。(ですが私はパズルはあまり得意ではないです)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/101225/428e7277-aa3f-7157-3755-f34cf8108def.png)

## ロジックを考える

スライディングパズルのプログラムを作る時には、まず何をプログラミングしなければいけないのか、ロジックや必要な材料を考えましょう。

スライディングパズルでは、次のようなロジックを考える必要があります。

- 駒を作る
- 駒を空いている空間へ移動する
- 盤面をシャッフルする
- 盤面が順番に並んでいるかをチェックする

### 駒を作る

まず、画面に駒を描画しましょう。
すでに説明した通り、15パズルでは1～15までの番号が振られた駒を用意する必要があります。
駒の描画には、[キャンバス部品](https://produ.irelang.jp/docs/pgraphics/canvas/canvas.htm)を使います。
駒を表す図形には、「子キャンバス」を使います。子キャンバスは、キャンバスの中に表示する小さなキャンバスです。この子キャンバスにさらに図形を入れることができます。今回は子キャンバスを15個、駒に見立てて描画します。

プログラムする際には、次のような変数を宣言しておくことにします。

- 「マス数」変数。盤面の縦と横のマス目の数
- 「盤面」2次元配列。盤面のどこにどの子キャンバスがあるかを記憶します。駒を動かしたときにこの盤面の内容も変化させます。空間には「無」を代入します。
- 「駒一覧」配列。作成した子キャンバスを作成した順番に記憶します。答え合わせに使います。

### 駒を空いている空間へ移動する

駒をクリックした時に、上下左右に空いている空間がある時には駒をその空間に移動します。

駒である子キャンバスに「クリックされた時の手順」を設定して、クリック時に「駒クリック」手順が実行されるようにします。駒クリックの手順が実行されると、クリックされた子キャンバスが「発生元」特殊変数に格納されています。

この時に次のようなことをします。

- 「発生元」の子キャンバスが盤面のどの座標にある駒か調べる
- 「盤面」から駒の座標の上下左右に空きがあるかどうかを調べる
- もし空きがあれば、駒をその場所へ移動する(子キャンバスの座標を変えて、盤面の内容も置き換える)

駒の移動には、「[駒]を移動する」手順と「[元座標]を[移動量]だけ移動する」手順を定義します。
「[元座標]を[移動量]だけ移動する」手順では、元座標にある駒が移動先へ移動できるかどうかを調べて、もし移動できれば、実際に移動します。
「[駒]を移動する」手順では、上下左右それぞれの方向に移動できるかどうかを順番に調べていきます。移動先が空間があるかどうかは、移動先の座標にある盤面の要素が「無」であるかどうかで判定します。

一方で、「[駒]の駒座標を求める」手順も定義しておきます。
これは、クリックした子キャンバスが、「盤面」配列のどの座標にあるか調べる手順です。

繰り返し文の中の「もし盤面(たて,よこ)が駒なら{よこ,たて}を返す」では、盤面にある子キャンバスが探している子キャンバスであるかどうかを比較しています。プロデルは、オブジェクト指向プログラミングができるので、このようにもし文と繰り返し文で部品を探すこともできます。
ここまでできると、実物のおもちゃのようなスライディングパズルが出来上がります。

### 盤面をシャッフルする
ゲームとして遊べるように、予めパズルをシャッフルしておきます。
シャッフルするために、乱数を使いますが、15個の駒を完全に適当な順に置いてしまうと、場合によっては解けないパズルとなってしまう可能性があります。
そこで、実際と同じように、駒を空いている空間に適当に移動する操作を十分な回数繰り返して予めシャッフルすることにします。

移動する駒は、```駒一覧(1からマス数*マス数-1までの乱数)```という部分で決めます。ただし、ここで選んだ駒の周囲に空間がないこともありますので、```選択駒を移動```して実際に移動できるまで、駒を選び続けます。
駒の移動回数は、「シャッフル回数」変数に決めておくことにします。シャッフル回数が大きくなればなるほど、難易度が高くなる可能性があります。

### 盤面が順番に並んでいるかをチェックする

盤面がシャッフルされた状態から、プレーヤーが実際に駒を移動します。
移動後は、駒が1から15までの順番に並んでいるかどうかをチェックします。
「盤面をチェックする」手順では、「盤面」配列と「駒一覧」配列とで、同じ順番に子キャンバスが並んでいるかどうかを繰り返し文を使って、チェックしていきます。
この時、「盤面」は、2次元配列であるのに対して、「駒一覧」配列は、1次元配列となっているので、添字の指定方法がそれぞれの配列で異なることに注意します。
「盤面」配列と「駒一覧」配列とで、並び順がひとつでも違っていれば、そこで比較をやめます。最後まで違っていなければ、「クリア！」と画面に表示させます。

## 完成したプログラム

```15パズル.rdr
メイン画面を表示する
待機する

メイン画面とは
	ウィンドウを継承する
	+マス数=4
	+盤面={}
	+駒一覧={}
	+シャッフル回数=20
	はじめの手順
		初期化する
		ーー貼り付けた部品に対する操作をここに書きます
		マスサイズは、320/マス数
		(マス数*マス数-1)回数にカウントして繰り返す
			N=数-1
			X=N%マス数
			Y=((N-N%マス数)/マス数)
			キャンバス1へ子キャンバスを作って駒とする
			　　その位置と大きさは｛20+X*マスサイズ,20+Y*マスサイズ,マスサイズ,マスサイズ｝
			　　その線色は、黒
			　　そのクリックされた時の手順は、駒クリック
			　　駒に数という文字を書く
				その大きさ調整を×に変える
				その大きさは、駒の大きさ
				そのフォントを「メイリオ」に変える
				その文字色を緑色に変える
				その文字配置を中央に変える
				その垂直文字配置を中央に変える
				その文字サイズを20に変える
			駒一覧(数)は、駒
			盤面(Y+1,X+1)は、駒
		繰り返し終わり
		盤面(マス数,マス数)は、無
		盤面をシャッフルする
		キャンバス1を更新する
	終わり
	初期化する手順
	ーー自動生成された手順です。ここにプログラムを書き加えても消える場合があります
		この実質大きさを｛550,550｝に変える
		この内容を「15パズル」に変える
		初期化開始する
		キャンバス1というキャンバスを作る
			その位置と大きさを｛0,0,550,550｝に変える
			そのドッキング方向を「全体」に変える
		初期化終了する
		この設計スケール比率を｛144,144｝に変える
	終わり
	駒クリックの手順
		駒は、発生元
		駒を移動する
		盤面をチェックする
	終わり
	[駒]を移動する手順
		駒座標は、駒の駒座標
		もし駒座標を{-1,0}だけ移動したなら
		他でもし駒座標を{0,-1}だけ移動したなら
		他でもし駒座標を{1,0}だけ移動したなら
		他でもし駒座標を{0,1}だけ移動したなら
		そうでなければ
			×を返す
		もし終わり
		○を返す
	終わり
	[元座標]を[移動量]だけ、移動する手順
		先座標は{元座標(1)+移動量(1),元座標(2)+移動量(2)}
		もし先座標(1)が1未満または先座標(1)がマス数より大きいなら×を返す
		もし先座標(2)が1未満または先座標(2)がマス数より大きいなら×を返す
		もし盤面(先座標(2),先座標(1))が無でないなら、×を返す
		駒は、盤面(元座標(2),元座標(1))
		駒の位置は｛駒の横+(先座標(1)-元座標(1))*マスサイズ,駒の縦+(先座標(2)-元座標(2))*マスサイズ｝
		盤面(元座標(2),元座標(1))と盤面(先座標(2),先座標(1))を交換する
		○を返す
	終わり
	[駒]の、駒座標を求める手順
		マス数回1からたてにカウントして繰り返す
			マス数回1からよこにカウントして繰り返す
				もし盤面(たて,よこ)が駒なら、{よこ,たて}を返す
			繰り返し終わり
		繰り返し終わり
		無を返す
	終わり
	盤面をシャッフルする手順
		シャッフル回数回繰り返す
			繰り返す
				選択駒は、駒一覧(1からマス数*マス数-1までの乱数)
				もし選択駒を移動したなら繰り返しから抜け出す
			繰り返し終わり
		繰り返し終わり
	終わり
	盤面をチェックする手順
		マス数*マス数回数にカウントして繰り返す
			N=数-1
			よこ=N%マス数+1
			たて=((N-N%マス数)/マス数)+1
			もし盤面(たて,よこ)が駒一覧(数)でないなら、×を返す
		繰り返し終わり
		キャンバス1に「クリア！」という文字を書く
			その位置は、{120,150}
			そのフォントを「メイリオ」に変える
			その文字色を赤に変える
			その文字サイズを20に変える
		キャンバス1を更新する
		○を返す
	終わり
終わり
```

## まとめ
今回は、プロデルで15パズルを作ってみました。
駒のデザインを凝ってみたり、駒をスライドするアニメーションを付けるなどすると、あたかもリアルなおもちゃのようなパズルゲームに仕上げることができるかと思います。