迷路作成

proになるJava読書中、難しくなってきたのでメモがわりに。

- Sample Code

projava/Maze.java

    package projava;

    import java.io.IOException;

    public class Maze {
        public static void main(String[] args) throws IOException {
            record Position (int x, int y){}
            int[][] map = {
                    {1, 1, 1, 1, 1, 1, 1, 1},
                    {1, 0, 1, 0, 1, 0, 0, 1},
                    {1, 0, 1, 0, 1, 1, 0, 1},
                    {1, 0, 0, 0, 0, 0, 0, 1},
                    {1, 1, 0, 1, 1, 0, 1, 1},
                    {1, 0, 0, 0, 1, 0, 0, 1},
                    {1, 1, 1, 1, 1, 1, 1, 1}
            };

            var current = new Position(5, 1);
            var upper = false;
            var goal = new Position(6, 5);
    
            for (;;) {
                // 迷路の表示
                // 現在位置と縦横２マスのみを表示させる
                for (int y = current.y() - 2; y <= current.y() + 2; ++y) {
                    for (int x = current.x() - 2; x <= current.x() + 2; ++x) {
                        if (y < 0 || y >= map.length || x < 0 || x >= map[y].length) {
                            System.out.print("#");
                        }else if (x == current.x() && y == current.y()){
                            // upperがtrueであればO、falseであればoを表示する
                            System.out.print(upper ? "O" : "o");
                        } else if (map[y][x] == 1) {
                            System.out.print("*");
                        } else if (x == goal.x() && y == goal.y()) {
                            System.out.print("G");
                        } else {
                            System.out.print(".");
                        }
                    }
                    System.out.println();
                }
                //　ゴール判定
                if (current.equals(goal)){
                    System.out.println("GOAL!!");
                    break;
                }
                // キー入力処理
                int ch = System.in.read();
                // 入力なしにEnterを押したり２文字入力などをするとその後"o"が動かなくなるのを解消する
                if (ch == '\n') continue;
                // 押された方向の座標を得る
                var next = switch(ch) {
                    case 'a' -> new Position(current.x()-1, current.y());
                    case 'w' -> new Position(current.x(), current.y()-1);
                    case 's' -> new Position(current.x()+1, current.y());
                    case 'z' -> new Position(current.x(), current.y()+1);
                    default -> current;
                };
                // 押された方向が通路なら進む
                if (map[next.y()][next.x()] == 0){
                    // 移動するときにupperを反転する
                    if (!current.equals(next)){
                        upper = !upper;
                    }
                    current = next;
                }
                // Enterキーの入力を捨てる
                System.in.read();
            }
        }
    }
    
[w][a][s][z]のいずれかを入力し、Enterを押すと上下左右に移動します。
[*]が壁で[o]が現在位置となります。[.]が通路です。
(練習問題を終えた後のコードですので５×５マスの表示となっています。また、１マスごとに大文字小文字と切り替わります。
　[#]の部分は５×５で表示させた時のはみ出した部分（壁の更に外側）となっています。)

![スクリーンショット 2023-01-24 16.32.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3119204/66c2c157-c81c-4ccf-9ca0-14f5830cf5d0.png)


コードを一つづつ見ていきます。

    public static void main(String[] args) throws IOException {

throws IOExceptionは「このメゾッドで例外IOExceptionが発生するので呼び出し側で処理するように」というもので、
System.in.readメゾッドを使用するときに必要になります。

・ ↓例外IOExceptionはjava.ioパッケージ　に属しているので次の様なimport文も必要になります。
    
    import java.io.IOException;

・↓移動後の横位置xと縦位置yをまとめて扱うためにレコードでPosistionを定義します。

    record Position (int x, int y){}

・↓迷路の地図をint型の２次元配列で用意します。0→通路、１→壁
    
    int[][] map = {
                {1, 1, 1, 1, 1, 1, 1, 1},
                {1, 0, 1, 0, 1, 0, 0, 1},
                {1, 0, 1, 0, 1, 1, 0, 1},
                {1, 0, 0, 0, 0, 0, 0, 1},
                {1, 1, 0, 1, 1, 0, 1, 1},
                {1, 0, 0, 0, 1, 0, 0, 1},
                {1, 1, 1, 1, 1, 1, 1, 1}
        };

・↓現在位置を表す変数currentを用意し、現在位置はPositionレコードを使い保持します。
　　下記の場合、x５、y１が初期位置となります。
    
    var current = new Position(5, 1);

・↓ゴールの位置を変数goalを用意しPositionレコードで表します
　　下記の場合、x６、y５がゴール位置となります。

    var goal = new Position(6, 5);

・↓ゴールに辿り着くまで無限に処理を繰り返すので、条件を指定しないforループを使っています。

    for (;;) {

・↓迷路の表示で二重ループをおこなっています。
　　外側のループはmap.length回、縦方向の処理をします。（縦方向の処理なので変数yとしています。）
　　内側のループでは横方向の処理をするので変数xとしています。
　　それぞれの行の要素数だけ繰り返す様にしています。
　　（下記コードでは現在位置から縦横２マスのみの表示になるようにしています。）

    for (int y = current.y() - 2; y <= current.y() + 2; ++y) {
                for (int x = current.x() - 2; x <= current.x() + 2; ++x) {

・↓変数currentの値とx、yの値が一致する場合、「o」を表示します（現在位置）
　

    if (x == current.x() && y == current.y()){
              　　　　System.out.print("o");

・↓地図データが１の時は「*」で壁を表します。
  どちらにも当てはまらない場合、通路として「.」を表示します。

    else if (map[y][x] == 1) {
        System.out.print("*");
    } else {
        System.out.print(".");

・↓１行の最後に改行を行う様にします

       System.out.println();

・↓迷路表示後、現在位置がゴールであれば「GOAL!!」と表示してbreak文でループを抜けます。
　　ループを抜けた後つづきのコードはないのでプログラムは終了します。

    if (current.equals(goal)){
        System.out.println("GOAL!!");
        break;
    }

・↓System.in.readメゾッドで入力を１文字受け取ります。
　　入力を受け取るSystem.in.readメゾッドを使うために、throws IOExceptionの記述がmainの行に必要となります。
　（レコードの値が等しいかの判定は「==演算子」ではなく「equalsメゾッド」を使います）

    int ch = System.in.read();

・↓受け取った文字が何かによって移動した位置を表すPositionオブジェクトを作成し、移動先を保持する変数nextに割り当てます。
　　（''で囲むことで文字を表します。）

    var next = switch(ch) {
        case 'a' -> new Position(current.x()-1, current.y());
        case 'w' -> new Position(current.x(), current.y()-1);
        case 's' -> new Position(current.x()+1, current.y());
        case 'z' -> new Position(current.x(), current.y()+1);

・↓該当しないキーが押された場合には現在位置に移動させます。

    default -> current;

・↓移動先のデータが0（通路）であれば、変数currentにnextの内容を割り当て、移動します。
　　壁の場合、移動しません。

    if (map[next.y()][next.x()] == 0){
        current = next;

・↓移動方向の入力は対応するキー（a、w、s、z）とEnterキーの入力なので
　　Enterキーが入力された分を読み込み、スルーします。

    System.in.read();

・ここまでを処理したら、迷路表示に戻り、繰り返します。

ーーーー
・予備知識
mapのデータを次のように壁を厚くして、mapからはみ出す部分（#の部分）が表示されることがないようにすると、
「mapからはみ出したときの処理」を省けます。
処理スピードが求められる場合に使われるテクニックみたいです。
    （壁である「１」を外側に多めに配置することで移動してもはみ出すことがない状態にする）

    int[][] map = {
        {1, 1, 1, 1, 1, 1, 1, 1},
        {1, 1, 1, 1, 1, 1, 1, 1},
        {1, 1, 0, 1, 0, 0, 1, 1},
        {1, 1, 0, 0, 0, 1, 1, 1},
        {1, 1, 0, 1, 0, 0, 1, 1},
        {1, 1, 1, 1, 1, 1, 1, 1},
        {1, 1, 1, 1, 1, 1, 1, 1}
    };
このように、処理がはみださないように置かれるデータを「番兵」と呼ぶことがあります。番兵を置くことで処理を効率化します。


ーーーー