Rogue 3.6.3: アイテムの初期化

今日は続いてアイテム類の初期化部分をみていく。init_playerの次に呼び出されるのはinit_things。これは以下の様になっている。

init_things()
{
    register struct magic_item *mp;

    for (mp = &things[1]; mp <= &things[NUMTHINGS-1]; mp++)
        mp->mi_prob += (mp-1)->mi_prob;
    badcheck("things", things, NUMTHINGS);
}

最初、何をやっているんだかわからなかったのだけれど、thingsという配列の中身をごにょっている。thingsはプレーヤーとかが入っている構造体thingとは無関係でmagic_itemという構造体だ。ややこしいなあ。

struct magic_item {
    char mi_name[30];
    int mi_prob;
    int mi_worth;
};

で、実際のthingsは以下の通り。

struct magic_item things[NUMTHINGS] = {
    { "",            27 },    /* potion */
    { "",            27 },    /* scroll */
    { "",            18 },    /* food */
    { "",             9 },    /* weapon */
    { "",             9 },    /* armor */
    { "",             5 },    /* ring */
    { "",             5 },    /* stick */
};

ぱっとみよくわからなかった。なんだこりゃ、と思ったけど、これはどうやらアイテムの出現確率を表しているもののようで、これ自体はthins.cのnew_thingという関数などで利用されている模様。magic_itemのmi_probはprobabilityのことみたいだ。つまりアイテムが生成される場合、薬が出てくる確率が27%、武器は9%という感じだ。最初の定義ではそれぞれの確立が27、27、18...と入っているけど、それをinit_thingsで、前の確率を次の確率に足し合わせていっているので、各データが27,54,72...という値に変換している。これは、コード上では見やすいデータにしておき、なおかつ処理をするときは処理をしやすいデータに変換する、ということなんだろうか。
最後にbadcheckを呼び出しているが、これは確率を足し合わせた結果がきちんと100になっているかどうかをチェックするものだ。なんかデバッグ時にだけ呼び出す様にしてもいい気がするけど、パラメータの変更は最もシンプルな改造だから、どう変えられてもおかしくならないようにしている、ということなんだろうか。

badcheck(name, magic, bound)
char *name;
register struct magic_item *magic;
register int bound;
{
    register struct magic_item *end;

    if (magic[bound - 1].mi_prob == 100)
        return;
    printf("\nBad percentages for %s:\n", name);
    for (end = &magic[bound]; magic < end; magic++)
        printf("%3d%% %s\n", magic->mi_prob, magic->mi_name);
    printf("[hit RETURN to continue]");
    fflush(stdout);
    while (getchar() != '\n')
        continue;
}

ちなみにmagic_itemにはmi_worthという要素もあるけどこれはアイテムの価値を決めるデータで、これは実際のアイテムの出現確率を決める際にはセットされている。たとえば以下は巻物のデータで、enchant armor(鎧強化)の魔法の巻物の出現確率が8%で、その価値が130であることがわかる。

struct magic_item s_magic[MAXSCROLLS] = {
    { "monster confusion",     8, 170 },
    { "magic mapping",         5, 180 },
    { "light",                10, 100 },
    { "hold monster",          2, 200 },
    { "sleep",                 5,  50 },
    { "enchant armor",         8, 130 },
    { "identify",             21, 100 },
    { "scare monster",         4, 180 },
    { "gold detection",        4, 110 },
    { "teleportation",         7, 175 },
    { "enchant weapon",       10, 150 },
    { "create monster",        5,  75 },
    { "remove curse",          8, 105 },
    { "aggravate monsters",    1,  60 },
    { "blank paper",           1,  50 },
    { "genocide",              1, 200 },
};

続いてinit_namesという初期化関数。名前がえらくわかりづらいけど、これは巻物の初期化関数。巻物や薬、魔法棒などは拾ったときは効能がわからず、巻物なら謎の文字列、薬なら色、といったように見た目だけで表される。一度使うか、identify(鑑定)を行うことで、その効果が明らかになる。init_namesはそれぞれの巻物に名前をつける関数である。
名前をつけるために、まずsyllsという文字列セットがinit.cで定義されている。

char *sylls[] = {
    "a", "ab", "ag", "aks", "ala", "an", "ankh", "app", "arg", "arze",
    "ash", "ban", "bar", "bat", "bek", "bie", "bin", "bit", "bjor",
    "blu", "bot", "bu", "byt", "comp", "con", "cos", "cre", "dalf",
        :
        :
    "ulk", "um", "un", "uni", "ur", "val", "viv", "vly", "vom", "wah",
    "wed", "werg", "wex", "whon", "wun", "xo", "y", "yot", "yu",
    "zant", "zap", "zeb", "zim", "zok", "zon", "zum",
};

で、これを使ってランダムな文字列を使って、巻物の名前を保持する変数s_namesにセットしている。

init_names()
{
    register int nsyl;
    register char *cp, *sp;
    register int i, nwords;

    for (i = 0; i < MAXSCROLLS; i++)
    {
    cp = prbuf;
    nwords = rnd(4)+2;
    while(nwords--)
    {
        nsyl = rnd(3)+1;
        while(nsyl--)
        {
        sp = sylls[rnd((sizeof sylls) / (sizeof (char *)))];
        while(*sp)
            *cp++ = *sp++;
        }
        *cp++ = ' ';
    }
    *--cp = '\0';
    s_names[i] = (char *) new(strlen(prbuf)+1);
    s_know[i] = FALSE;
    s_guess[i] = NULL;
    strcpy(s_names[i], prbuf);
    if (i > 0)
        s_magic[i].mi_prob += s_magic[i-1].mi_prob;
    }
    badcheck("scrolls", s_magic, MAXSCROLLS);
}

処理を追っかけると、二重ループになっていて、1〜3個の文字列をつなぎ合わせたものを、スペース区切りで2〜5個つないでいる。従って「zapeepe sosol aksdo」みたいな名前がつけられる。それを格納した後、すでにその巻物の効果を知っているかを表すs_knowというフラグにFALSEを、そしてs_guessという文字列の配列(これは巻物に自分で名前をつけた場合にその名前がセットされる)にNULLを格納する。効果がわかるとs_knowはTRUEになる。最後にやっぱり確率を加算して、badcheckで100になっているかをチェックしている。
続くinit_colors関数は、同様に薬の色を初期化する。これも名前がわかりづらい。init_portionsとかにすればいいのに。色の名前はrainbow(init.c)に格納されている。「赤」「青」とかから始まって「ターコイズ」とか「トパーズ」とか。24色あるんだけどrainbow。最初は7色しかなかったとかそういう感じかな?

char *rainbow[] = {
    "red",
    "blue",
    "green",
       :
       :
    "plaid",
    "tan",
    "tangerine"
};

色の割り当ては乱数なんだけど、もちろん同じ色は1回しか使われない。でも、最後の一色あたりは乱数だけでやってると時間がかかってしまいそうな気がするけどもいいのか。まあ、いいのか。シャッフルして頭から割り当てた方が良い気がするが。

init_colors()
{
    register int i, j;

    for (i = 0; i < NCOLORS; i++)
        used[i] = 0;
    for (i = 0; i < MAXPOTIONS; i++)
    {
        do
            j = rnd(NCOLORS);
        until (!used[j]);
        used[j] = TRUE;
        p_colors[i] = rainbow[j];
        p_know[i] = FALSE;
        p_guess[i] = NULL;
        if (i > 0)
                p_magic[i].mi_prob += p_magic[i-1].mi_prob;
    }
    badcheck("potions", p_magic, MAXPOTIONS);
}

続くinit_stonesは指輪(ring)の初期化で、名前の通り指輪は宝石の名前で表される。これは薬のロジックとほぼ同じ。
次は魔法棒(wand/巻物と違って同じ魔法を複数回発動できるが1回使うとチャージ時間がかかる)と杖(staff/チャージ時間はないけど使える回数が決まっている)を初期化するinit_materialsで、魔法棒は「銅」とか「鉛」「鉄」、杖は「樫」とか「マホガニー」などの木の名前で表されるので、併せてmaterialである。名前が格納されている配列はmetalとwoodだ。
init_materialsを読むと、利用するmagic_item(出現確率と価値、種類を表す)はws_magicという同じものを使い、魔法脳と杖の配分は乱数で決まっているようだ。つまり同じ効果を持つ魔法棒と杖は存在せず、どっちかになるってことだ。

init_materials()
{
    register int i, j;
    static bool metused[NMETAL];

    for (i = 0; i < NWOOD; i++)
    used[i] = FALSE;
    for (i = 0; i < NMETAL; i++)
    metused[i] = FALSE;

    for (i = 0; i < MAXSTICKS; i++)
    {
    for (;;)
        if (rnd(100) > 50)
        {
            j = rnd(NMETAL);
            if (!metused[j])
            {
                metused[j] = TRUE;
                ws_made[i] = metal[j];
                ws_type[i] = "wand";
                break;
            }
        }
        else
        {
            j = rnd(NWOOD);
            if (!used[j])
            {
                used[j] = TRUE;
                ws_made[i] = wood[j];
                ws_type[i] = "staff";
                break;
            }
        }

    ws_know[i] = FALSE;
    ws_guess[i] = NULL;
    if (i > 0)
        ws_magic[i].mi_prob += ws_magic[i-1].mi_prob;
    }
    badcheck("sticks", ws_magic, MAXSTICKS);
}

MAXSTICKS(魔法棒/杖を合わせた合計)の回数をループで回して乱数で魔法棒か杖かを決定し、さらに材質を決定する。一応、魔法棒と杖の確率は50%ずつなんだけど、素材の種類が魔法棒11個、杖が22個あるので、この処理法だと杖の方が多くなると思う。素材と、それぞれが杖か魔法棒かはws_madeとws_typeに格納される。しかしws_typeは「wand」と[staff」が入るのか。なんで定数にしていないんだろう。これから読んでいくと明らかになるのか?
これで各種初期化は終わりである模様。次に進む。