2009年11月27日

やっとモックアップ完成したよぅ

ダンジョンの自動生成を思い立ってから1ヵ月、やっとモックアップが完成したっぽい。
インチキ穴掘り方の自動生成ダンジョンプログラムだ。何がインチキかって、一番のインチキは最初に全ての迷路を完成させないところかな。
丁度一歩進むごとに次の穴を掘っている気分だと思ってもらえばよい
 今回のプログラムの要点として、以下の5つの関数を駆使していることをあげてみよう。

  • 現在いる場所と東西南北の向きから壁フラグの番号を計算するgetWallIndex
  • getWallIndexを利用して今の壁フラグをtrue/falseで返すcalcWall
  • 東西南北の壁のon/offを0〜15の数値で表現し、それが現在の周囲の壁フラグと整合性があるかを確認するfitWall
  • 0〜15の値を元に東西南北の壁フラグを設定するsetWall(迷路解放具合も確認できるよ!)
  • 実際に東西南北に移動するgoFloor

こいつらを駆使して自動生成を行うのだ。
どういう関数なのかちょっと説明してみよう。
数年後ソースの意味が分からなくなった自分への解説を兼ねて。


getWallIndex解説
int getWallIndex(int pos, int aquota) {
    int offset = (aquota / 4) * 24;
    int quota = aquota % 4;
    if (quota % 2 == 0) {
        if (pos % 4 == quota * 3 / 2) {
            return -1;
        }
        return pos - (pos / 4) + (quota / 2) - 1 + offset;
    } else {
        int pos2 = pos + quota * 2 + 6;
        if (pos2 < 12 || 24 <= pos2) {
            return -1;
        }
        return pos2 + offset;
    }
}
一つ目の引数のposはフロア番号。二つ目の引数aquotaでは西北東南を壁確定なら0123、通路確定なら4567であらわす。この二つの数値から参照すべき壁フラグの値を返す。っていうのを言葉で書いても意味不明なので、前のIKEA図を思い出してもらおう

WSM000206.JPG

青い四角がフロア番号、緑の四角が壁フラグの番号だ。フロア番号5で西(0方向)の壁番号を求めると、5の左となりの3を返す。通路番号なら3に24を足した27を返すという寸法だ。
ロジックの中身を解説すると超ややこしいけど、ざっと説明すると東西を見る場合はフロア番号に2/3を乗算したものに補正をプラス、南北を見る場合はフロア番号そのままに補正をちょとプラスして返す。という感じになっている。
ちなみにフロア7で東を参照、のように地図外の場所を参照しようとした場合には-1が返るようになっている。


calcWall解説
boolean calcWall(int pos, int quota) {
    int windex = getWallIndex(pos, quota);
    if (windex < 0) {
        return quota < 4;
    } else {
        return walls[windex];
    }
}
getWallIndex関数は、単に対象となる壁の番号を返しているだけなので、それを元に壁フラグが実際onになっているかどうかを返すのがcalcWall関数だ。getWallIndexが-1を返すとき、すなわち壁の外領域を参照しようとしたときは、壁確定フラグを参照した場合はtrue(すなわち壁on)、通路確定フラグを参照した場合はfalse(すなわち通路off)を返す。


fitWall解説
boolean fitWall(int pos, int wall) {
    for (int i = 0; i < 4; i++) {
        if (calcWall(pos, i)) {
            if ((wall & (1 << i)) == 0) {
                return false;
            } else {
                continue;
            }
        }
        if (calcWall(pos, i + 4) && ((wall & (1 << i)) != 0)) {
            return false;
        }
    }
    return true;
}
西に壁on=1、北に壁on=2、東に壁on=4、南に壁on=8として、それぞれを合計した数値を壁引数wallとして表現する。例えば東西に壁、南北に通路なら1+4で壁引数は5、北にだけ通路で他は壁なら1+4+8=13が壁引数だ。
この壁引数が現在の立ち位置に当てはまるかどうかを判定するのかfitWall関数。東西南北の壁確定フラグや通路確定フラグと矛盾していれば、falseを返す。ちなみに、壁確定フラグと通路確定フラグの両方がonになっている場合は壁確定フラグを優先する。

setWall解説
int setWall(int pos, int wall, boolean set) {
    int freeIndex = 0;
    for (int i = 0; i < 4; i++) {
        int offset = 0;
        if (((1 << i) & wall) == 0) {
            offset = 24;
        }
        int windex = getWallIndex(pos, i + offset / 6);
        if (windex >= 0) {
            if (set) {
                walls[windex] = true;
            } else {
                if (offset > 0) {
                    if (walls[windex]) {
                        freeIndex--;
                    } else {
                        freeIndex++;
                    }
                }
            }
        }
    }
    return freeIndex;
}
東西南北の壁確定フラグや通路確定フラグを壁引数と現在位置から判定できる値に設定する。ちなみに、第3引数のsetがこの自動生成システムの最大のミソで、後述する迷路の開放度を計算するものだ。第3引数にfalseを設定すると、実際にはフラグの設定をせずに開放度の計算だけをする。

goFloor解説
void goFloor(int pos) {
    for (int i = 0; i < 128; i++) {
        int wall = (int) (Math.random() * 16);
        if (fitWall(pos, wall)) {
            int free = setWall(pos, wall, false);
            if (freeIndex + free > 0) {
                freeIndex += free;
                setWall(pos, wall, true);
                break;
            }
        }
    }
}

実際に1〜15の壁引数を現在の位置に指定する。迷宮全体の総合開放度を判定し、総合開放度が0、すなわちゴールに到達できない壁の設定の仕方だったらもう一度壁引数を設定しなおすのだ。でもロジック考慮ミスにより無限ループに陥るのを防ぐため、256回リトライしてダメだったら諦める仕様になっている。そういう場合はゲーム続行不能になるよりもう一度迷宮

で、さっきから後述する後述すると言いまくっていた開放度。これはゴールへの通路確定フラグがonになっている壁の数を数えているわけだが、どうやってその値を数えているかは次回解説。そんなに難しいロジックではないんだけどね。

今回のロジックをまとめたソースは

posted by LoyalTouch at 18:02| Comment(0) | TrackBack(0) | 5つの宝島 | このブログの読者になる | 更新情報をチェックする
この記事へのコメント
コメントを書く
お名前:

メールアドレス:

ホームページアドレス:

コメント:

この記事へのトラックバックURL
http://blog.seesaa.jp/tb/134065060

この記事へのトラックバック