Rogue 3.6.3: 部屋の生成

データの初期化が終わると、続いて新たな階(レベル)が作られるためにnew_level(newlevel.c)が呼び出される。

new_level()
{
    register int rm, i;
    register char ch;
    coord stairs;

    if (level > max_level)
    max_level = level;
    wclear(cw);
    wclear(mw);
    clear();
    status();
    /*
     * Free up the monsters on the last level
     */
    free_list(mlist);
    do_rooms();                /* Draw rooms */
    do_passages();            /* Draw passages */
    no_food++;
    put_things();            /* Place objects (if any) */
    /*
     * Place the staircase down.
     */
    do {
        rm = rnd_room();
    rnd_pos(&rooms[rm], &stairs);
    } until (winat(stairs.y, stairs.x) == FLOOR);
    addch(STAIRS);
    /*
     * Place the traps
     */
    if (rnd(10) < level)
    {
    ntraps = rnd(level/4)+1;
    if (ntraps > MAXTRAPS)
        ntraps = MAXTRAPS;
    i = ntraps;
    while (i--)
    {
        do
        {
        rm = rnd_room();
        rnd_pos(&rooms[rm], &stairs);
        } until (winat(stairs.y, stairs.x) == FLOOR);
        switch(rnd(6))
        {
        case 0: ch = TRAPDOOR;
        when 1: ch = BEARTRAP;
        when 2: ch = SLEEPTRAP;
        when 3: ch = ARROWTRAP;
        when 4: ch = TELTRAP;
        when 5: ch = DARTTRAP;
        }
        addch(TRAP);
        traps[i].tr_type = ch;
        traps[i].tr_flags = 0;
        traps[i].tr_pos = stairs;
    }
    }
    do
    {
    rm = rnd_room();
    rnd_pos(&rooms[rm], &hero);
    }
    until(winat(hero.y, hero.x) == FLOOR);
    light(&hero);
    wmove(cw, hero.y, hero.x);
    waddch(cw, PLAYER);
}

長いんだけど読んでいくと、画面のクリアなどを行った後、free_listでmlistというリストをクリアしている。これはrogue.hで定義されていて、コメントを読むとその階に現在存在しているモンスターの一覧表らしい。

extern struct linked_list *mlist;        /* List of monsters on the level */

その構造はlinked_listで、これはプレーヤーの持ち物を表すためにも使われていた双方向リストだ。リスト操作はlist.cにまとめられている。

struct linked_list {
    struct linked_list *l_next;
    struct linked_list *l_prev;
    char *l_data;            /* Various structure pointers */
};

続いて、do_roomsという関数が呼び出される。これはroom.cにある、「部屋」を作る関数である。Rogueでは画面は3x3の9区画に分割されていて、それぞれに1つ部屋がある(無いこともある)。その大きさなどは毎度毎度新しく作られる。これがRogueのキモであって、不思議のダンジョンとかにも受け継がれているシステムな訳なので、ここはとっても重要ですよな。
部屋の情報はroomという構造体で現れ、roomsという配列変数に入っている。その配列の要素数はMAXROOMSに格納されているが3x3なので9である。

struct room {
    coord r_pos;            /* Upper left corner */
    coord r_max;            /* Size of room */
    coord r_gold;            /* Where the gold is */
    int r_goldval;            /* How much the gold is worth */
    int r_flags;            /* Info about the room */
    int r_nexits;            /* Number of exits */
    coord r_exit[4];            /* Where the exits are */
};

これをみると位置とサイズ、お金(gold)がどこにあるのか、その価値はいくらか、フラグと出口の数や出口の位置が書かれている。出口は東西南北に各一つずつある可能性がある(一つの壁に2つ以上あることはない)ので、4要素の配列変数になってる。r_goldでお金の位置が表されていると言うことは、お金は最大でも1部屋1つしかないと言うことかなあ。
で、do_roomsを読んでみる。

do_rooms()
{
    register int i;
    register struct room *rp;
    register struct linked_list *item;
    register struct thing *tp;
    register int left_out;
    coord top;
    coord bsze;
    coord mp;

    /*
     * bsze is the maximum room size
     */
    bsze.x = COLS/3;
    bsze.y = LINES/3;
    /*
     * Clear things for a new level
     */
    for (rp = rooms; rp <= &rooms[MAXROOMS-1]; rp++)
    rp->r_goldval = rp->r_nexits = rp->r_flags = 0;
    /*
     * Put the gone rooms, if any, on the level
     */
    left_out = rnd(4);
    for (i = 0; i < left_out; i++)
    rooms[rnd_room()].r_flags |= ISGONE;
    /*
     * dig and populate all the rooms on the level
     */
    for (i = 0, rp = rooms; i < MAXROOMS; rp++, i++)
    {
    /*
     * Find upper left corner of box that this room goes in
     */
    top.x = (i%3)*bsze.x + 1;
    top.y = i/3*bsze.y;
    if (rp->r_flags & ISGONE)
    {
        /*
         * Place a gone room.  Make certain that there is a blank line
         * for passage drawing.
         */
        do
        {
        rp->r_pos.x = top.x + rnd(bsze.x-2) + 1;
        rp->r_pos.y = top.y + rnd(bsze.y-2) + 1;
        rp->r_max.x = -COLS;
        rp->r_max.y = -LINES;
        } until(rp->r_pos.y > 0 && rp->r_pos.y < LINES-1);
        continue;
    }
    if (rnd(10) < level-1)
        rp->r_flags |= ISDARK;
    /*
     * Find a place and size for a random room
     */
    do
    {
        rp->r_max.x = rnd(bsze.x - 4) + 4;
        rp->r_max.y = rnd(bsze.y - 4) + 4;
        rp->r_pos.x = top.x + rnd(bsze.x - rp->r_max.x);
        rp->r_pos.y = top.y + rnd(bsze.y - rp->r_max.y);
    } until (rp->r_pos.y != 0);
    /*
     * Put the gold in
     */
    if (rnd(100) < 50 && (!amulet || level >= max_level))
    {
        rp->r_goldval = GOLDCALC;
        rnd_pos(rp, &rp->r_gold);
        if (roomin(&rp->r_gold) != rp)
        endwin(), abort();
    }
    draw_room(rp);
    /*
     * Put the monster in
     */
    if (rnd(100) < (rp->r_goldval > 0 ? 80 : 25))
    {
        item = new_item(sizeof *tp);
        tp = (struct thing *) ldata(item);
        do
        {
        rnd_pos(rp, &mp);
        } until(mvwinch(stdscr, mp.y, mp.x) == FLOOR);
        new_monster(item, randmonster(FALSE), &mp);
        /*
         * See if we want to give it a treasure to carry around.
         */
        if (rnd(100) < monsters[tp->t_type-'A'].m_carry)
        attach(tp->t_pack, new_thing());
    }
    }
}

とりあえず画面を3分割して部屋を作るので、そのサイズが画面サイズに依存する。そこで、画面の縦横サイズを3で割って一つの部屋の最大サイズを出している。それがbszeって変数に入る。まずはフラグなどを初期化した後、乱数で決めた0〜3個の部屋にISGONEフラグをセットしている。これは名前からして、おそらくその部屋が「消滅」した状態になっていることを示すんだろう。結果として部屋数は6〜9個になる。消滅する部屋はrnd_roomというランダムな部屋(消滅していないもの)を一つ返す関数で決める。この関数はなぜかnewlevel.cにて定義されている。room.cで定義すればいい気がするが。

rnd_room()
{
    register int rm;

    do
    {
    rm = rnd(MAXROOMS);
    } while (rooms[rm].r_flags & ISGONE);
    return rm;
}

ちなみに部屋番号は左上から横に1,2,3ときて右上が3、左下が7で右下が9である。
続いて各部屋をループで回して様々な設定を行っていく。まず「消滅」した部屋でも一応位置を決める。これは「単なる通路」を作るための処理だと思う。つまり部屋があるべき1点を決めて通路を延ばすことで、上下と左右から来た通路がつながって長い廊下ができる...と予想。きっとこれは通路を作成するロジックを読めば明らかになるだろうと思う。
続いて、暗い部屋(Rogueでは暗い部屋と明るい部屋があり、暗い部屋は魔法などで明るくしないと自分の周りしか見えない)の設定をする。部屋が暗いことを表すフラグはISDARK。暗い部屋になる確率は0〜9までの乱数を発生させてそれがレベルの数-1よりも小さいかどうかで判別する。つまり(地下)1階では一つも暗い部屋はないが、徐々に暗い部屋の出現確率が増えていって、地下11階以下ではすべての部屋が暗い部屋になってしまうと言うことになる。
続いて部屋のサイズと位置を乱数で決める。部屋の最小サイズは4x4だ。
次はお金の配置。お金が配置されるのは50%の確率。ただしamulet(Rogueはイェンダーの魔除け/Amulet of Yenderを探すゲームなので、たぶんこれを持っているかのフラグ)を持っていて、最下階(つまりこれまで到達したのよりも深い階、max_levelよりも新しいlevelが大きい)ではない場合は、お金は出ない。お金の価値はGOLDCALCというマクロで定義されている。もちろん深いレベルの方が大きい額が手に入るが、最低額は常に2だ。1階なら2〜61。

#define GOLDCALC (rnd(50 + 10 * level) + 2)

お金の場所はrnd_posで計算される。これは部屋情報(room構造体)を渡すとその中の場所をランダムに計算してくれる関数でroom.cで定義されている(簡単なので省略)。
最後にモンスターを配置する。確率は、お金が配置されているかどうかで変わる、というのはなるほどという感じで、お金がある場合は80%、無い場合は25%でモンスターが存在する。モンスターはアイテムを持っている(可能性がある)ので、まずはnew_itemでアイテムを生成してから、new_monsterでモンスターを生成している、と思われるがとりあえず長くなりそうなので今度にしようっと。位置はやっぱりrnd_posで決めている。