ローグライク攻略&開発雑記

コンシューマー/PCに限らず、適当に綴っていくブログ 無駄にあれこれ遊んだので要らん知識だけは豊富

変愚蛮怒

変愚蛮怒3.0.0 進捗状況 2024総集編

この記事はRoguelike Advent Calendar 2024 の16日目の記事です
前日はTaro 0910さんの「変愚蛮怒の泉 〜素晴らしきムダ知識をあなたに〜」でした

こんにちは、Hourierです
2024/12/15 時点での作業量を見てみましょう

コミット数:約1730回
内、自分のコミット:約1000回
チケット:約100件

はい、いつも通り(?)一人で突っ走っております
グローバル変数は覚えているだけで6つ、覚えていないのも含めればもっと削減できていると思います
いつものように「半年前のコードは別物」が延々と続いております

GitHubのチケットは最近運用が変わりまして、「大きめの作業を分割実施する時」はプロジェクト機能を使うことになりました
但し完了したのは15件中4件だけで、残りはごちゃっとしています
これは色々理由があるのですが、その一つに「1つの作業を根を詰めて作業してもレビュー完了までの期間が長過ぎる」ことがあります
例えば30回とかコミットを積んでしまうと、いざ最初の1コミット目で「ここ間違ってるよ」とか言われたら30回も遡及修正する羽目になります
それを避けるために、10個くらいコミットを積んだら別な作業をする……というのを繰り返すのですが、開発メンバーのマンパワーが絶望的に不足しているので、最近は常に開発行数/日 > レビュー行数/日 です
つまりいつまで経っても自他の作業がメインラインへマージされないことを意味します
これの緩和を目論んでメンバー募集などもしてみましたが、今のところ目立った成果はありません
新機能を入れたいのは本当に山々なんですが、ぐっちゃぐちゃのソースコードをあっちこっち弄って更なるぐっちゃぐちゃを招くことは好ましくない……というか、後々手間が増えてしまうのでぶっちゃけ避けたいというのが正直なところです
AngbandやZangbandで積み重ねてきたクソコードをことここに至っても積み重ねたら、多分誰にも保守してもらえなくなります
今いるメンバーが100年後もいる訳ないですからね
世にも珍しいオープンソースのゲームであるからこそ、いつまでも開発が続けられるように地ならしをしていきたいです
(最近はいくつか枷が外れてきてテコ入れの余地も若干ながら出てきました)

今年は例年以上にコードの変遷が激しいのでステップ数やクラス数の統計は載せていません
落ち着く可能性も現時点では余り見込めず、開発メンバー内でも正式版への移行タイミングは手探り状態なので、もうしばらくお待ち頂ければ幸いです
むしろベータ版でバグ報告して頂けるととっても助かります!

と、とりとめのない話をしたところで今日はこの辺で
より詳しい情報を知りたい方はDiscord までお越し頂ければ説明します!

明日も私の記事です、お楽しみに!

変愚蛮怒スポイラー モンスター生成編

この記事はRoguelike Advent Calendar 2024 の12日目の記事です
この記事を読んでいるそこの君、まだまだカレンダーは余裕があるぞ!
「Necrodancer踊ってみた」でも「シレン6買ってみた」でも何でもいいから書くんだ!

ということで改めましてこんにちは、Hourierです
今日はモンスター生成ルーチンを読み解いてみましょう
本体がmonster-list.cpp のget_mon_num() にある他、生成禁止モンスターをフィルタする処理が別途あります
但し、フィルタの話まですると面倒になるのでここでは抜きにします
例1:地上専用モンスターはダンジョンに出現させない
例2:オークの洞窟ではオークの出現率を上げる

モンスター分布は、実のところ出現階層とレアリティだけで決まります
ここでは例としてモンスターを8体用意します
これまた話を簡単にするため、1Fと2Fだけにします
後ろの数字はレアリティ値です
コード上、厳密には「確率値 = 100/レアリティ (小数点未満切り捨て)」を使うのですが、面倒なのでこのまま行きます
1F:1A(1)、1B(1)、1C(2)、1D(3)
2F:2A(1)、2B(1)、2C(2)、2D(3)

変愚蛮怒プレイヤーの方なら、「モンスターのレアリティ値が大きければ出会う確率が低い」というのは感覚的に分かるかと思います
V2の頃は「おまぬけトロル3人組」のレアリティは7でした (今は3)
狙わない限り3人全員撃破、とはならないはずです
ではどういうルーチンかというと、ざっくり言って「レアリティNのモンスターは、レアリティ1のモンスターに比べて出現率が1/Nである」です
(ざっくりなのは確率値が切り捨てであるため)

従って、1Fに出現するモンスターの分布は、レアリティから最小公倍数を求めるとこうなります
即ち1Cは1A&1Bの1/2、1Dは1Aの1/3です
1A:6体
1B:6体
1C:3体
1D:2体

上記を合計すると、6 + 6 + 3 + 2 = 17 です
レアリティ1のモンスターだけで各6/17 ≒ 35.3% 、2体合計で70%以上という高確率で出現します
レアリティ2のモンスターは3/17 ≒17.5%、そしてレアリティ3のモンスターは2/17 ≒ 11.8% です
実際はレアリティ1や2のモンスターはもっと沢山いるので、レアリティ6の『グレーター・ヘル=ビースト』は実のところ相当に出現確率が低いです
「レアリティ1は頻繁に見かける、2はそこそこ、3はレア、4以上は滅多に見かけない」というのはこのルーチンによります

さて、2F以降はどうなるでしょうか
2Fにも1Fのモンスターが出現するのは、変愚蛮怒プレイヤーの方にはおなじみですね
何なら127Fまで潜っても1Fのモンスターは低確率ながらいる時はいます
この分布はランダムに決まります

フロアとは無関係にレアリティだけで選ぶ:40% (1:1:1:...)
フロアに線形比例して確率が上がる:50% (1:3:5:...)
フロアに2乗比例して確率が上がる:10% (1:7:19:...)
となります
一番上の「40%」が選択されれば、127Fでもレアリティ1ならざっくり1/127 の確率で選ばれることになります
(実際は上記の通りモンスターテーブル全体の影響を受けるので単純計算にはなりませんが、概念的に)

一番確率の高い50% の場合を考えてみましょう
この場合、n階でレベルk (但しn ≧ k)のモンスターが選択される確率は「(2k-1)/(n^2)」です
特に、n階でレベルnのモンスターが選択される確率は「(2n-1)/(n^2)」です
(変数の次元は、分子が1で分母が2なので差し引き1次、即ち線形比例)

10Fでレベル10のモンスターが選択される確率は、(2*10-1)/(10^2) = 19/100 ≒ 1/5
30Fでレベル30のモンスターが選択される確率は、(2*30-1)/(30^2) = 59/900 ≒ 6.6%

おや、30Fで30Fのモンスターはあまり出てきませんね?
鉄獄を高速に駆け下りられる時とそうでない時がありますが、モンスターが闇鍋ガチャになりやすいだけでなく、深ければ深いほど階層より低いレベルのモンスターが出る確率が高いためでもあります
ちなみにこれをどう補正しているかというと、「部屋ブースト」です
端的には、ブーストのかかった部屋を避けると高速潜行できます

一部の部屋は、「+2F」であるとか「+5F」などのブーストがかかっています (部屋の形で判別できますが、この記事では紹介しきれないのでスキップ)
有名なのはVaultの「+40F」です
「+5F」は鉄獄のそこかしこでフツーに生成されるので、これをもって考えてみましょう

10Fでレベル10以上のモンスターが選択される確率:
(2*15-1 + 2*14-1 + 2*13-1 + 2*12-1 + 2*11-1 + 2*10-1)/(15^2)
=(30+28+26+24+22+20 - 5)/225
=145/225 ≒ 64.4%

30Fでレベル30以上のモンスターが選択される確率:
(60+58+56+54+52+50 - 5)/900
= 約36.7%

なんと! 数倍に跳ね上がりました!
当然ですが2乗比例モードを引くと更に確率が上がります
逆に、イークの洞窟は「部屋ブーストをキャンセルする処理」が入っているので、現階層以上のモンスターは絶対に出現しません
「イーク洞は簡単」というのはつまりこういうことなのです
当然、nasty生成 (ゲーム内日数に応じた階層ブースト)がかかるともっと確率が上がります

さて、これを元に「2Fのモンスター分布」を考えてみましょう
引き続き線形比例モードで考えてみます
上記より「2Fのモンスターは3/4、1Fのモンスターは1/4の確率で生成される」ことになります
2Fでレアリティ1のモンスター、2Aと2Bはそれぞれ約26.5% です
一方、1Fでレアリティ3のモンスター、1Dの出現確率は……なんと3%弱!
8種類しかいないサンプル分布ですらこれなので、実際の闇鍋ガチャではほとんど見かけないですね

ところで、2Fにいるユニークでおなじみの『おおかみ』などの犬類はレアリティ2なので、『フリージア』の1より出現率が低いです
しかしフリージアは1であるが故に3F以降でもそこそこ見かけますが、おおかみは3Fくらいならともかく6Fまで潜ってしまうと出現率が1桁%に落ちるので、まず見かけなくなります
これは以下のように計算できます

6Fに2Fのモンスターが出る確率:(2*2-1)/36 = 3/36 となり1/10未満
レアリティ2なので単純計算で半分とすると、ほぼ4%

更にここから、以下のことが言えます
「プレイヤーがN階にいる時、N階に出現するレアリティ2のモンスターと、N/2階に出現するレアリティ1のモンスターは、概ね同じ確率で出現する」です
階層が低いと分子の-1が効きますが、20Fのレンジャー(レアリティ1)と40Fの達人超能力者(レアリティ2)は、40Fを長時間うろつけば概ね同じくらいの回数戦うことになります
(40%の確率でレンジャーの方が高くなり、10%の確率で達人超能力者の確率が高くなり、ブーストがあって……と全部考え合わせても、極端にどっちが出現率高いとは言いにくい)
確率はあくまで確率ですが、モンスター分布の平均値やブースト時の危険性が数学的に紐解けると覚えておくと、何%の確率でやべーのと出会ってしまうか概算できて論理的なプレイができるかと思います


この記事は以上です、お付き合い頂きありがとうございました!

変愚蛮怒スポイラー 財宝編

この記事はRoguelike Advent Calendar 2024 の3日目の記事です
前日はagrathさんの「JNetHack近況報告」でした

こんにちは、Hourierです
あっという間に年の瀬ですね
今年からは、スポイラー系の変愚蛮怒固有な話はブログ、プログラミングなど技術系の話はQiitaにでも投稿しようと思います
そっちの方がエンタメとしてではなく実利需要もありそうだと判断しました
詳細は公開の時が来たらこっちにもリンクを貼ります
といっても、愚痴に近いような「今年の変愚蛮怒リファクタリングはここがクソだったぜ!!」みたいなのはこっちに投稿します

さて本題に参りましょう
「フロアに自然生成される or 財宝ドロップ持ちのモンスターを倒した時のドロップ」についてです
クリーピングコインがシンボルに応じた財宝を落とすのは、このブログをご覧の方なら周知の事実かと思いますが、その辺りのルーチンは技術的すぎるためこの記事では省略します
また本記事の情報はv3.0.1.21-Beta 時点でのものです
v3.0.1.22-Beta では早速とばかりにルーチンが色々変わっているのでご注意下さい
(その内説明しようと思います)

まず、財宝には「財宝グレード」と呼ぶべき情報が含まれています (※1)
現在、グレード番号は以下の通りです (※2)
今のところ「グレードが高い=金額も高い」が成立しています (※3)

銅貨:0、1、2
銀貨:3、4、5
ガーネット:6、7
金貨:8、9、10
オパール:11
サファイア:12
ルビー:13
ダイヤモンド:14
エメラルド:15
ミスリル:16
アダマンタイト:17

で、このグレード情報をどうするかというと、以下のように計算します
小数点未満切り捨て、かつ18以上ならば17 (最大値)に固定です

通常グレード = (1d(基準階層 + 2) + 2) / 2 -1
強化グレード = グレード + 1d(基準階層 + 1)

基準階層の計算は以下の通りです
  • 床落ち:そのフロアレベル (10Fなら10)
  • モンスターのドロップ:(モンスターのレベル + フロアレベル) / 2
強化グレードは、財宝生成の度に判定され、1/10 の確率で発生します
計算結果は、例えば以下のようになります
ここでは1dXの平均値と最大値を取ってみます (最低は常に0:銅貨なので考える必要なし)

1Fでの平均通常グレード = (1d3 + 2) / 2 - 1 = (2 + 2) / 2 - 1 = 1
1Fでの最大通常グレード = (3 + 2) / 2 - 1 = 1.5 → 1
1Fでの平均強化グレード = 1 + 1d2 = 2.5 → 2
1Fでの最大強化グレード = 1 + 2 = 3

10Fでの平均通常グレード = (1d12 + 2) / 2 - 1 = (6.5 + 2) / 2 - 1 = 3.25 → 3
10Fでの最大通常グレード = (12 + 2) / 2 - 1 = 6
10Fでの平均強化グレード = 3 + 1d11 = 3 + 6 = 9
10Fでの最大強化グレード = 6 + 11 = 17

以上より、「1Fでも低確率で銀貨は落ちている」「10Fなら銀貨が普通になってきて、通常グレードでも運次第でガーネットまでは見かけることができ、理論上アダマンタイトすらあり得る」となります
但し確率は1/120 (しかも財宝アイテムの1/120、武器とか巻物とか床落ちアイテムは他にもあるので更に低確率)です

ちなみに逆算もできます
ここでは通常グレードで試算してみます、強化グレードについては各自電卓を叩いてみて下さい
アダマンタイトが手に入る最低階層は:
17 = (1dX + 2) / 2 - 1となるXを求めれば良いです
18 = (1dX + 2) / 2
36 = 1dX + 2
34 = 1dX
よってX = 34の時、アダマンタイトが手に入る可能性があります
更に、67~68F以降のフロアならば、床落ち財宝は半分以上がアダマンタイトであると言えます

なお、この計算は財宝アイテムが増えれば増えるほど変な計算になります
仮にグレード番号が30まであったとすると、上記の計算より「60Fまで行ってやっと財宝の1/60、平均的にグレード30の財宝を拾いたければ120Fまで行け」となってしまいます
(これではバランスが悪いので、v3.0.1.22-Beta ではもっと柔軟な計算式になりました)

そんなところで今回の記事をおしまいにします
また次回お会いしましょう!
明日の記事はdis- さんの「クエスト:宝物庫を色統一してみた」です
お楽しみに!

※1 ソースコード上では「オフセット」と呼んでいました
※2 同じ番号が複数あるのは、例えば銅貨なら金額レンジの微妙に違う銅貨が3つあるということを意味しています。BaseitemDefinitions.jsonc のID480~482をご覧下さい
※3 やろうと思えば「最高グレード銅貨の金額>最低グレード銀貨の金額」を満たすアイテムを追加できます。v3.0正式リリース後にはそういうのもやってみたいです

変愚蛮怒3.0.0 進捗状況 2023総集編 (技術編)

この記事はRoguelike Advent Calendar 2023 の16日目の記事です
https://adventar.org/calendars/9031

こんにちは、Hourierです
技術編と銘打っておきながら年末進行の都合でそんなに細かく書けない状態です
備忘録的な設計はGitHub のWiki にまとめているので、併せてご参照下さい

この1年何やってたかというと大半がリファクタリングです
まぁそれは3年前から何も変わっていないのですが、逆に言えば「3年経ってもまだまだ作業中」ということを意味します
3年あればどうにでもなるだろうというのは素朴に考えれば大抵合っていますが、残念ながら変愚蛮怒の泥沼は想像以上に深かった、ということです
単純に、ボランティアベースの活動であるが故に全員の作業スピードが不安定であることも問題の長期化に拍車をかけてしまっています

では今年のテーマは何だったのかというとズバリ「モデル化」です
この辺がその第一歩だったように思いますが、「フィールド変数がpublicなせいでどこでもget/setできる」という、非オブジェクト指向言語の欠陥を見事に踏み抜きまくっていました
「同一処理が何箇所もコピペされていて見通しが悪すぎる」という問題が起こり、その結果「何をやっているのか分からないので保守できない」に発展していました
「何十ファイルも同時に追跡しないとアイテム強化ルーチンを把握できない」とか
(これはC時代に分割しまくった名残でもありますが、当時は「ファイルが長すぎて誰一人読む気が起きないし誰も修正したがらない」というもっと重篤な問題が起きていました)

変愚蛮怒の前身であるZangband が更新停止した理由もこういう問題が遠因だっただろうという話は一度ならずDiscord でも出てきました
この辺りにようやくメスを入れ、「フィールド変数を外から引っ張り出してあれこれする」から「クラス内でできることはクラス内でやる」という、まともなカプセル化の道を歩み始めました
しかしあまりにも道が半ばで先は全く見通せません

多少でもコーディング能力がある方はぜひ開発チームにいらして下さい
むしろなくても1から教えます
去年の今頃に比べれば見違えるような綺麗さのコードにはなりました
それどころか「半年前のコードはもう見たくない」というレベルでモダナイズを続けているプロダクトでもあります
先進技術に触れてみたいという方も大歓迎です
Discord にてお話頂ければ誰かしら案内をするはずですので、ぜひ覗いてみて下さい

変愚蛮怒3.0.0 進捗状況 2023総集編

この記事はRoguelike Advent Calendar 2023 の9日目の記事です

こんにちは、Hourierです
ブログ更新が年1になってすみません……HUNTER×HUNTER よりはマシ
GitHub にコミュニティ機能があったりコード差分が可視化されやすかったりで敢えてブログに書くほどの記事って中々ないんですよね
ということで年に1回しかできないこと──そう「総括 in 榛名ベース」を行います

以下、「最新のコード」とは12/3 時点のβ2を指します

2023年最初のリリースはα74から始まりました
最新は上記の通りβ2です
αはホットフィックス (リリース直後に発覚した緊急バグの修正)も含めて91まで伸びました
またβは無印 (実質β0)から始まっています
途中夏休み等で開発メンバーが全員不在になってリリースが滞ったりもしつつ、合計21回のリリースを行いました
ちなみにGitHubでリリースノートがまともに整備されたのはα64、2022/07/31 のできごとです
色々集計しやすくなって便利の極みです

ということで簡単に集計してみました
予想通りというかバグ修正とリファクタリングばっかしてんなお前

リリース番号 リリース日 PR数 自分のPR数 新機能数 自分の新機能数
α74 01/08 21 4 0 0
α75 01/22 25 2 7 0
α76 02/05 4 0 0 0
α77 02/19 10 0 3 0
α78 02/21 2 0 0 0
α79 05/01 31 23 1 1
α80 05/02 3 0 0 0
α81 05/03 9 5 0 0
α82 05/05 5 3 1 1
α83 05/14 16 9 1 0
α84 05/28 25 15 1 1
α85 06/11 37 25 7
5
α86 06/25 27 7 2 0
α87 07/09 30 9 4 0
α88 07/23 17 9 1 0
α89 08/07 5 2 0 0
α90 10/16 25 10 1 0
α91 10/18 5 2 0 0
β0 10/30 10 4 0 0
β1 11/12 19 10 1 0
β2 11/26 8 4 0 0






合計: N/A 334 143 30
8

今年1年のPR (Pull Request≒1作業)に占める新機能率は9.0%!
自分担当に限れば新機能率5.6%!!
世のオープンソースでこんなにリファクタリング率の高いプロジェクトはそうそうないと思います
(普通は目を引くために新機能追加するんだけどね変愚は無理なの)

さて新機能というか機能廃止も含まれていますが、自分の機能変更を列挙してみましょう
  • 固定アーティファクトの追加 (トロルズベーン、黒のガラドリエル)
  • 羊皮紙の追加 (洞察の魔法大全×3、アーティファクトの伝承×3、宝の地図)
  • ウィザードコマンド強化 (帰還、アイテム生成、モンスター生成)
  • 全コマンド強制確認機能削除
  • 256色以下のモニタサポート削除
……しょっぱいな!!
しかも固定アーティファクトはリファクタリングを目的に「取り敢えず埋めた」ってレベルの大したことない品です
羊皮紙に至っては「誰得の巻物」専用ドロップなので見たことがないプレイヤーもいるかもしれません
おまけに「洞察の魔法大全」も「アーティファクトの伝承」もタイトル以外未翻訳というふざけた有様です
どなたか翻訳して下さい、私は「最大999個まで羊皮紙を追加できる機能」を開発した時点で力尽きました

もちろん「開発者にしかメリットのないリファクタリングだけしていた」のではありません
例えば:
  • 分離/合体ユニークを気軽に追加できる設計改善 (正常に動作するモンスター種族定義を添えて追加要求頂ければ1日以内にコード面は実装可能!)
  • 休憩ターン数を始めとした数値入力プロンプト改善 (※ 長期の改善項目で複数バージョンに跨るため上記には載せていない)
  • いくらかの高速化 (体感できるかどうかは不明)
  • 英語版対応 (英語だと何も表示されなかったり汎用メッセージだったのを日本語と同様に拡張)
嬉しいことに海外プレイヤーも時折GitHubやDiscordでコメントしてくれているので、少しずつ変愚蛮怒の輪が広がっているのを感じます
この調子でどんどん活気が出てくると良いですね!

(なお作業はまだまだ完了する見込みなし……正式版のリリースは未定です)

明日はまだ埋まってないよ!
みんなも記事を書いてみよう!
ギャラリー
  • 開発者になろう GitHub編
  • 開発者になろう GitHub編
  • 変愚蛮怒3.0.0 進捗状況 その36
  • 変愚蛮怒 3.0.0 進捗状況 特別編 その3 C#への可能性
  • 開発者になろう
  • 開発者になろう
  • 変愚蛮怒2.2.2 進捗状況 その21
  • 変愚蛮怒(2.2.2 開発中版) ハイエルフレンジャー その5
  • 変愚蛮怒(2.2.2 開発中版) ハイエルフレンジャー その3