スライドパズル(15パズル)
スライドパズル
スライドパズルは子ども向けのおもちゃでよく見かけるもので, たとえば4×4のマスに15枚のパズルと 1枚分の空白を利用してパズルの絵柄を揃えていくものです。
移動できるパズルは空白に隣接するものに限られるので, 完成には多少頭を使います。
15枚のスライドパズルを15パズルともいい, 8パズルなどもあります。
15パズルの場合は揃えられる配置について, 最短で80手必要であることが知られています。
プログラミングする際に初期配置をランダムにすると完成できない場合があるので注意が必要です。
今回は初期配置は完成状態として, とりあえずパズルとして動くようにしています。

実装
400×400のパズルにしたい画像を用意します。
自分でペイントで描いても良いです。
プログラムと同じフォルダに用意した画像を入れて下記で読み込みます。
PImage img = loadImage("sample.png");//パズル画像
Tento君画像 sample.png
読み込んだ画像を100×100の大きさで切り取り15個に分割します。 下のプログラムは元の画像を(0, 0)から縦100横100の範囲で切り取ったものです。
PImage div = img.get(0, 0, 100, 100);//分割画像
これを15回繰り返して15枚のパズルにします。 div[16]として16個の画像を格納する配列を用意します。 for文の練習に使ってください。
15枚のパズルが完成したら, 空白に相当する100×100の黒で塗りつぶした画像を用意します。(ペイントで作れます。)
div[15] = loadImage("blank.png");//最後のパズルは黒色のものを作成
空白用のパズル画像 blank.png
画像の表示は下記で行います。 下は(100,100)に1枚画像を表示する例です。
image(div[0], 100, 100);
次にパズルを操作する上で必要な変数のデータ構造を決めます。 まず, パズルの配置を記憶する配列hairetu[16]を作成します。
int[] hairetu = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 };//パズルの配列
この配列に書かれた順番に対応するパズルを初期配置にします。
マウスがクリックされた座標がパズルのどの位置かを判断して, そのパズルが空白マスに隣接している際にはhairetu[16]の中身を入れ替える処理をします。 hairetu[16]の通りにパズルを表示することであたかもパズルをスライドさせるように見せかけます。
int k=0; for (int j=0; j<4; j++) { for (int i=0; i<4; i++) { image(div[hairetu[k]], 110 * i, 110 * j);//パズルの配列通りに表示 k++; } }
マウスのクリックした座標がパズルのどの位置に対応するかは下記のようなプログラムで処理します。
//マウスクリック int click = 0;//マウスのクリックした位置がパズルのどこに対応するかを入れる変数(0~15) k=0; if (mousePressed == true) { for (int i=0; i<4; i++) { for (int j=0; j<4; j++) { if (pmouseX >= 100 * j && pmouseX < 100 + 100 * j && pmouseY >= 100 * i && pmouseY < 100 + 100 * i) { click = k; } k++; } }
パズルの入れ替えの処理は以下で行います。
//入れ替え if(click <=15 && click !=3 && click !=7 && click !=11 && click !=15){//右に空白がある場合 if (hairetu[click + 1] == 15) { hairetu[click + 1] = hairetu[click]; hairetu[click] = 15; } } if(click !=0 && click !=4 && click !=8 && click !=12){//左に空白がある場合 if (hairetu[click - 1] == 15) { hairetu[click - 1] = hairetu[click]; hairetu[click] = 15; } } if(click <=11){//下に空白がある場合 if (hairetu[click + 4] == 15) { hairetu[click + 4] = hairetu[click]; hairetu[click] = 15; } } if(click >=4){//上に空白がある場合 if (hairetu[click - 4] == 15) { hairetu[click - 4] = hairetu[click]; hairetu[click] = 15; } }
実行結果
初期状態ではパズルが完成したものです。
パズルを動かして行くとバラバラになります。
課題
他にもいろいろな機能を追加してみてください。
- 初期配置をシャッフルする。
- 難易度別にシャッフル。
- プログラムに回答を探索させる。
- パズルがスライドしていく様子を描画する。
- タイマーを入れて完成時間を競う。など
完成プログラム
プログラム全体を載せます。
int[] hairetu = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15 };//パズルの配列PImage[] div = new PImage[16];// 分割画像int k = 0;
void setup() { size(500, 500); // 画像の一部を切り出す k=0; PImage img = loadImage("sample.png");//パズル画像 for (int i=0; i<4; i++) { for (int j=0; j<4; j++) { div[k] = img.get(0 + 100 * j, 0 + 100 * i, 100, 100);//元の画像からx,yに100ずつシフトしてパズルの部品を作る k++; } } div[15] = loadImage("blank.png");//最後のパズルは黒色のものを作成}
void draw() { // 表示 k=0; for (int j=0; j<4; j++) { for (int i=0; i<4; i++) { image(div[hairetu[k]], 110 * i, 110 * j);//パズルの配列通りに表示 k++; } } //マウスクリック int click = 0;//マウスのクリックした位置がパズルのどこに対応するかを入れる変数(0~15) k=0; if (mousePressed == true) { for (int i=0; i<4; i++) { for (int j=0; j<4; j++) { if (pmouseX >= 100 * j && pmouseX < 100 + 100 * j && pmouseY >= 100 * i && pmouseY < 100 + 100 * i) { click = k; } k++; } } //入れ替え if(click <=15 && click !=3 && click !=7 && click !=11 && click !=15){//右に空白がある場合 if (hairetu[click + 1] == 15) { hairetu[click + 1] = hairetu[click]; hairetu[click] = 15; } } if(click !=0 && click !=4 && click !=8 && click !=12){//左に空白がある場合 if (hairetu[click - 1] == 15) { hairetu[click - 1] = hairetu[click]; hairetu[click] = 15; } } if(click <=11){//下に空白がある場合 if (hairetu[click + 4] == 15) { hairetu[click + 4] = hairetu[click]; hairetu[click] = 15; } } if(click >=4){//上に空白がある場合 if (hairetu[click - 4] == 15) { hairetu[click - 4] = hairetu[click]; hairetu[click] = 15; } } }}