16パズルを作ってみる(前編)

こんにちは、きうちです。

うだるような暑さが続きますが、いかがお過ごしでしょうか?

暑いのはかなわないですが、暑いからこそビールが美味い。

ビアガーデンとか久々に行きたいなー・・・とか考えながら日々の業務をこなしております^^

★ ★ ★

今回は、「16パズル」を作ってみたいと思います。

ご存じとは思いますが、4×4のフィールドに1~15の数字が描かれた(あるいは絵が描かれた)四角いピースが置かれていて、ランダムな状態から綺麗な順番に並べ替える、というあれです。

私は「16パズル」という名前で憶えているのですが…WikiPediaさんによると、「15パズル」のようですね。

https://ja.wikipedia.org/wiki/15%E3%83%91%E3%82%BA%E3%83%AB

まあ、このブログでは「16パズル」と記載します。

そんなに難しくはないと思いますので、例によって自力で組み立ててみたいと思います!

言語は例によってC#にします。Windowsフォームアプリとして作成します。

今回はピクチャーボックスを1つ配置して、そこに描画することにします。

16パズル・・・1~15の数字が書かれた(あるいは絵柄などが描かれた)15個のピースを所定の場所に移動させるパズルゲームですが、
この世界を表現するために、フォームクラスに4×4のint型の2次元配列 cells を用意します。

そして SetupCells というメソッドを作ってピースを並べる処理を実行させます。

1~16のピースを cells に並べていきます。そのあと、16は不要なので消します。

コンストラクタにて SetupCells を呼び出します。

    public partial class Form1 : Form
    {
        private readonly int[,] cells = new int[4, 4];

        public Form1() // コンストラクタ
        {
            InitializeComponent();

            SetupCells();
        }

        private void SetupCells() // 準備
        {
            for (int x = 0; x < 4; x++) // ピースを並べる
            {
                for (int y = 0; y < 4; y++)
                {
                    cells[x, y] = y * 4 + x + 1;
                }
            }
            cells[3, 3] = 0; // 一番右下のピースをなくす
        }
    }

続いて、これを画面に描画するためのイベントメソッドを用意します。
また、コンストラクタの最後でRefreshを実行し、画面を描画させます。

        public Form1() // コンストラクタ
        {
            InitializeComponent();

            SetupCells();
            Refresh();
        }

        ...

        private void pictureBox1_Paint(object sender, PaintEventArgs e) // ピクチャーボックス描画イベント
        {
            for (int x = 0; x < 4; x++)
            {
                for (int y = 0; y < 4; 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);
                    }
                }
            }
        }

さて、実行させてみましょう。

ピースが順番に並びました!

★ ★ ★

続いて、ピースを動かせるようにします。

ピースがある箇所をクリックすると、そのピースが動ける方向に動くことにします。何もないところをクリックするか、移動できないピースをクリックした場合は何も起きないことにします。

イベントはMouseClickでやります。

実際に動かす処理はイベントメソッドとは分けて記述します。

        private void pictureBox1_MouseClick(object sender, MouseEventArgs e) // マウスクリック時処理
        {
            int x = e.X / 64;
            int y = e.Y / 64;
            MovePiece(x, y);
        }

        private void MovePiece(int x, int y) // ピースを動かす
        {
            if (cells[x, y] == 0) return; // 何もないところをクリックしたら何もせずreturn

            if (x > 0 && cells[x - 1, y] == 0) // クリックされた位置の左が空の場合はそちらへ移動
            {
                cells[x - 1, y] = cells[x, y];
                cells[x, y] = 0;
            }
            else if (x < 3 && cells[x + 1, y] == 0) // クリックされた位置の右が空の場合はそちらへ移動
            {
                cells[x + 1, y] = cells[x, y];
                cells[x, y] = 0;
            }
            else if (y > 0 && cells[x, y - 1] == 0) // クリックされた位置の上が空の場合はそちらへ移動
            {
                cells[x, y - 1] = cells[x, y];
                cells[x, y] = 0;
            }
            else if (y < 3 && cells[x, y + 1] == 0) // クリックされた位置の下が空の場合はそちらへ移動
            {
                cells[x, y + 1] = cells[x, y];
                cells[x, y] = 0;
            }

            Refresh();
        }

実行してみます。

動かせるようになりました!

★ ★ ★

ランダムに並んだ状態から、ゴール(綺麗に並んでいる状態)を目指せるようにしたいですね。

というわけで、そうする処理を追加しましょう。

Shuffleというメソッドを追加してそこに処理を書きます。
コンストラクタで、SetupCellsの次にそれを呼びます。

        public Form1() // コンストラクタ
        {
            InitializeComponent();

            SetupCells();
            Shuffle(); // 追加
            Refresh();
        }

        private void Shuffle() // シャッフル
        {
            for (int i = 0; i < 5000; i++)
            {
                int x = random.Next(0, 4);
                int y = random.Next(0, 4);
                MovePiece(x, y);
            }
        }

ランダムに位置を決定し、そのピースを動かそうとする、というのを5000回試行する処理になっています。
本当は位置決定は穴が開いているところの周辺4ピースのいずれかにするのがいいのでしょうけれども、まあ面倒なので(笑)

実行してみます。

ランダムになりました!

★ ★ ★

といったところで、今日はここまで。

後編では、数字じゃなくて何か図柄が描かれたものにしようかなー・・・と思っています。

それでは!