こんにちは、きうちです。
今回は前回の「16パズルを作ってみる(前編)」の続きをお届けします。
★ ★ ★
前回までで、「ピースを並べる」「シャッフルする」「マウスクリックでピースを動かす」といったことはできるようになりました。
ただ、これだと綺麗に並べ終わっても何も起こらなくてさみしいですね。
「そろったか?」を判定して、そろっていたらメッセージが表示されるようにしてみましょうか。
そのためのメソッド、Judgeをフォームクラスに追加します。
private bool Judge() // そろっているか判定する
{
bool done = true;
for (int y = 0; y < cells.GetLength(0); y++)
{
for (int x = 0; x < cells.GetLength(1); x++)
{
if (x == 3 && y == 3) // 一番右下については「0かどうか」を確認する
{
if (cells[x, y] != 0)
{
done = false;
break;
}
}
else if (cells[x, y] != y * 4 + x + 1) // それ以外は所定の値になっているか確認する
{
done = false;
break;
}
}
}
return done;
}
cellsを左上からずっと「所定の値になっているか?」を見ていきます。1つでもそうなっていない箇所があればその時点でdone(できたかどうかを示す変数)をfalseにしてbreak!
というわけで、戻り値として「できていればtrue、できていなければfalse」が返ります。
続いて、このメソッドを呼び出す箇所。
マウスクリックイベント処理に追加します。
private void pictureBox1_MouseClick(object sender, MouseEventArgs e) // マウスクリック時処理
{
int x = e.X / 64;
int y = e.Y / 64;
MovePiece(x, y);
if (Judge()) // 判定処理の呼び出しを追加
{
MessageBox.Show("Congratulations!"); // できていたらメッセージ表示
SetupCells(); // メッセージダイアログのOKボタンを押したら、次のゲーム開始
Shuffle();
Refresh();
}
}
これで実行すると・・・ピースを所定の位置に並べ終わると「Congratulations!」と表示されます。

★ ★ ★
続いて、数字ではなくグラフィックにしてみましょうかね。
この前、都内某所で撮影した以下の風景写真にしてみたいと思います。

こんな風にプロジェクトに追加して、「出力ディレクトリにコピー」を「常にコピーする」にします。

そして、フィールドとしてBitmapの配列を16・・・本当は15でいいはずですが、なんとなく16。用意します。
また、画像を読み込んで64x64のピースにして上記配列に格納するメソッド PreparePiece を用意します。
private Bitmap[] bmps = new Bitmap[16]; // ピース画像格納用
private void PreparePiece() // ピース準備
{
Bitmap? srcBitmap = Image.FromFile("16puzzle_bg_sample.jpg") as Bitmap;
srcBitmap?.SetResolution(96, 96);
for (int x = 0; x < cells.GetLength(0); x++)
{
for (int y = 0; y < cells.GetLength(1); y++)
{
Rectangle srcRect = new Rectangle(x * 64, y * 64, 63, 63);
bmps[y * 4 + x] = srcBitmap!.Clone(srcRect, srcBitmap.PixelFormat);
}
}
}
コンストラクタにちょっと追加。
public Form1() // コンストラクタ
{
InitializeComponent();
PreparePiece(); // 追加
SetupCells();
Shuffle();
Refresh();
}
Paintイベントメソッドを次のように変更。
private void pictureBox1_Paint(object sender, PaintEventArgs e) // ピクチャーボックス描画イベント
{
for (int x = 0; x < cells.GetLength(0); x++)
{
for (int y = 0; y < cells.GetLength(1); y++)
{
if (cells[x, y] != 0)
{
//e.Graphics.FillRectangle(Brushes.AliceBlue, x * 64, y * 64, 63, 63);
//e.Graphics.DrawString(cells[x, y].ToString("D2"), new Font("MS ゴシック", 32), Brushes.Black, x * 64, y * 64 + 8);
e.Graphics.DrawImage(bmps[cells[x, y] - 1], new Point(x * 64, y * 64)); // 変更
}
}
}
}
実行すると・・・おお!絵バージョンの16パズルになりました。ちょいムズいね!

★ ★ ★
最後に・・・動いているものを16パズルにしたいなと思います。題材としては簡単に「時計」とかでどうでしょ。
以下のように、時計を描くPaintイベントメソッドを用意します。(あとでpictureBox2という名前のピクチャーボックスを用意して、そこに紐づけます。)
private void pictureBox2_Paint(object? sender, PaintEventArgs e) // 時計描画イベントメソッド
{
e.Graphics.DrawEllipse(Pens.White, 16, 16, 224, 224);
var t = DateTime.Now;
var dt = Math.PI / 30; // (=6.28/60)
double theta;
int cx = 128;
int cy = 128;
// 目盛りを描く
for (int i = 0; i < 60; i += 5)
{
theta = i * dt - Math.PI / 2D;
double x1 = Math.Cos(theta) * (i % 10 == 0 ? 95 : 100) + cx;
double y1 = Math.Sin(theta) * (i % 10 == 0 ? 95 : 100) + cy;
double x2 = Math.Cos(theta) * 110 + cx;
double y2 = Math.Sin(theta) * 110 + cy;
e.Graphics.DrawLine(Pens.White, (int)x1, (int)y1, (int)x2, (int)y2);
}
// 時針
theta = (Math.PI / 6) * (12 - (t.Hour % 12)) + Math.PI / 2;
double xx2 = Math.Cos(theta) * 60 + cx;
double yy2 = -Math.Sin(theta) * 60 + cy;
e.Graphics.DrawLine(Pens.Blue, cx, cy, (int)xx2, (int)yy2);
// 分針
theta = dt * (60 - t.Minute) + Math.PI / 2;
xx2 = Math.Cos(theta) * 80 + cx;
yy2 = -Math.Sin(theta) * 80 + cy;
e.Graphics.DrawLine(Pens.Red, cx, cy, (int)xx2, (int)yy2);
// 秒針
theta = dt * (60 - t.Second) + Math.PI / 2;
xx2 = Math.Cos(theta) * 100 + cx;
yy2 = -Math.Sin(theta) * 100 + cy;
e.Graphics.DrawLine(Pens.Green, cx, cy, (int)xx2, (int)yy2);
}
細かい説明は省きます(それをすると本題からそれるので・・・)。
ちなみに、この描画イベントを普通に実行するとこんな感じになります。

続いて、次のようにフィールドとセットアップ処理を追加します。
private PictureBox pictureBox2 = new(); // ピースのネタを描くためのピクチャーボックス
private System.Windows.Forms.Timer timClock = new(); // 時計を再描画するためのタイマー
private void SetupPictureBox2AndTimer()
{
// pictureBox2の準備
pictureBox2.SetBounds(0, 0, 256, 256, BoundsSpecified.Size);
pictureBox2.BackColor = Color.Black;
pictureBox2.Paint += pictureBox2_Paint;
pictureBox2.Refresh();
// タイマーの準備
timClock.Tick += timClock_Tick!;
timClock.Interval = 1000;
timClock.Start();
// ピース作成
PreparePiece2();
}
private void timClock_Tick(object sender, EventArgs e) // 時計更新タイマーTICKイベント処理
{
pictureBox2.Refresh(); // 時計を再描画する
PreparePiece2(); // ピースを作る
pictureBox1.Refresh(); // 16パズルを再描画する
}
そして、コンストラクタを次のように変更します。
public Form1() // コンストラクタ
{
InitializeComponent();
//PreparePiece(); // 先ほど追加したものは消すかコメントアウト
SetupPictureBox2AndTimer(); // 追加
SetupCells();
Shuffle();
Refresh();
}
実行すると・・・
ほら、時計が動いてる!
・・・これまた結構ムズい。
★ ★ ★
というわけで「16パズルを作ってみる(前編)」の続きをお届けしてきましたが、いかがでしたでしょうか。
工夫次第ではさらに面白いものができそうですね!
それではまた次回~(*´︶`*)ノ"またねー
★☆★☆★ 追伸 ★☆★☆★
前回と今回の分のソースを公開しました!
よかったらどうぞ~