こんにちは、きうちです。
今回は前回の「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パズルを作ってみる(前編)」の続きをお届けしてきましたが、いかがでしたでしょうか。
工夫次第ではさらに面白いものができそうですね!
それではまた次回~(*´︶`*)ノ"またねー
★☆★☆★ 追伸 ★☆★☆★
前回と今回の分のソースを公開しました!
よかったらどうぞ~