一筆書きのパズル(後編)

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

今回は前回の「一筆書きのパズル(中編)」の続きをお届けします。

本当はいつも通り前後編でいこうと思ったんですが…長くなってしまいましたね。
しかし、今回でいよいよ完結です。

前回までで、マップを作りこんでそれを表示し、空いているところを埋め尽くすとクリア、という風にできました。

しかし、前回はステージを自分で作らねばならず、結構面倒くさい。

そこで今回は、自動的にステージを作成することを考えてみます。

★ ★ ★

・・・といっても、まあ話は単純で

「一筆書きしたものであれば、逆にたどればやはり一筆書きできるはずだ」

ということで

「ランダムに一筆書きさせるプログラムを作ってそれを実行させる」
「その結果をステージとする」

というアプローチでやります。

まずは次のようにフィールドを追加します。

        private int vx = 0; // X方向移動増分

        private int vy = 0; // Y方向移動増分

        private int length = 0; // 移動する長さ

        private Random random = new Random(); // 乱数発生オブジェクト

フィールドの初期化は全体を 壁 (2) で埋め尽くした後、自機の場所に軌跡 (1) を置くようにします。

もともとあったInitメソッドはコメントアウトするか、別な名前に変えておきましょう。

        private void Init() // セル初期化(自動生成用)
        {
            for (int x = 0; x < width; x++)
            {
                for (int y = 0; y < height; y++)
                {
                    cells[x, y] = 2;
                }
            }

            cells[mx, my] = 1;

            timProgress.Start(); // 自動生成タイマースタート
        }

さて、ここから自動生成を行う処理を書くわけですが、普通に書くと一瞬で終わってしまい何が起こっているのかわからないので、タイマーを使って自動生成が少しずつ動いていることをわかるようにしたいと思います。

タイマーの名前は timProgress とし、インターバルはデフォルトのままでもいいですが、お好みで。

TICK時の処理を次のようにします。

        private void timProgress_Tick(object sender, EventArgs e) // タイマーTICK時処理
        {
            if (vx == 0 && vy == 0) // X方向移動量もY方向移動量も⇒初期状態なので、最初の移動増分を縦方向か横方向のどちらかに決める
            {
                if (plusOneOrMinusOne() == -1)
                {
                    vx = plusOneOrMinusOne();
                }
                else
                {
                    vy = plusOneOrMinusOne();
                }
                length = random.Next(10) + 5;
            }

            MoveCell(); // 1マス移動する
        }

ここで plusOneOrMinusOne というメソッドは1または-1をランダムに返すメソッドであり、MoveCellというメソッドは1つ先に進むメソッドです。

        private int plusOneOrMinusOne() // 1または-1をランダムに返すメソッド
        {
            return random.Next(2) * 2 - 1;
        }

        private void MoveCell() // 1マス移動する
        {
            int mx0 = mx;
            int my0 = my;

            mx += vx;
            my += vy;

            if (mx == -1 || my == -1 || mx == width || my == height || cells[mx, my] != 0)
            {
                mx = mx0;
                my = my0;
                if (IsEnded())
                {
                    timProgress.Stop(); // 自動生成タイマー停止
                    MessageBox.Show(this, "おわり"); // 自動生成が終わったことを知らせる
                }
                else
                {
                    Turn();
                }
            }
            else
            {
                cells[mx, my] = 1;
                length--;
                if (length == 0) Turn();
            }

            Refresh();
        }

また上記で新しいメソッドが登場しています。IsEndedはステージ作成が終了したか判定するメソッドであり、Turn は方向転換をするメソッドです。

        private bool IsEnded() // 終了したか判定する(4方が全て0になっていないか確認する) 
        {
            return (mx - 1 < 0 || cells[mx - 1, my] == 0) && (mx + 1 >= width || cells[mx + 1, my] == 0)
                && (my - 1 < 0 || cells[mx, my - 1] == 0) && (my + 1 >= height || cells[mx, my + 1] == 0);
        }

        private void Turn() // 方向転換する
        {
            if (vx != 0) // 横移動中だったら縦移動にする
            {
                vx = 0; // X方向増分を0にする
                vy = plusOneOrMinusOne(); // Y方向増分を+1または-1にする
            }
            else // 縦移動中だったら横移動にする
            {
                vx = plusOneOrMinusOne(); // X方向増分を+1または-1にする
                vy = 0; // Y方向増分を0にする
            }
            length = random.Next(10) + 5; // 移動長を決める(5~14)
        }

終了したかどうかは左右上下いずれかもすべて軌跡 (1) かどうかで判定しています。

方向転換は、「横方向に移動していたら縦移動に変更」「縦方向に移動していたら横移動に変更」ということをしています。

また、どのくらい進むかを5から14の間の整数で決めています。

ここまで実装して走らせると、こんな感じになります。

・・・勝手に一筆書きしてくれましたね!

★ ★ ★

続いて、作成してくれたステージを遊べるようにします。

以下のメソッドを追加します。

        private void StartGame() // 自動でステージ作成後、ゲームを開始する処理
        {
            cells[mx, my] = 1;

            Refresh();

            MessageBox.Show(this, "ゲーム開始");
        }

そして、ステージ作成終了時にこのメソッドを呼ぶようにします。

MoveCell メソッドの「おわり」と表示している個所をコメントアウトし、

先のメソッドが呼び出されるようにします。

        private void MoveCell() // 1マス移動する
        {
            ...
                if (IsEnded())
                {
                    timProgress.Stop(); // 自動生成タイマー停止
                    //MessageBox.Show(this, "おわり"); // コメントアウト
                    StartGame(); // 追加
                }
                else
                {
                    Turn();
                }
            ...
        }

実行すると、ステージが自動作成される→そのステージで遊べる
→クリアすると次のステージが自動作成される→そのステージで遊べる
...となります。

以下の動画は、1つのステージのクリア寸前のところから、クリアして、別なステージが生成されて、ゲームを開始して...という様子を収めたものです。

★ ★ ★

ということで「一筆書きのパズル(後編)」をお送りしてきましたがいかがでしたでしょうか。

何かが自動的に生成されるのを見るのは楽しいですが、

昔からよくあるようなものでも、実際に自分で作ってみてそれが動くとより楽しいですね。

それではこのへんで。

だんだん涼しくなってきます、お体にお気をつけて...

また次回!

追伸

ソースを公開しました!

「一筆書きパズル」のソース