TOP > プログラミング関係の表記及び解説

プログラミング関係の表記及び解説

本攻略のトップページでも紹介しているが、本作のデータや各種計算式(ダメージ量の算出方法など)は、以下ウェブサイト様に掲載されており、本攻略でも引用させていただいている。

ゲームを普通にプレイする上で、それらの詳しい計算式まで細かく把握する必要はない。
どのステータスを上げたらどういう時に有利なのか、などといった大雑把な理屈がわかっていれば効率良く戦闘が進められる、という程度である。
もし計算式まできちんと理解したいという場合、ある程度の数学・プログラミングの基礎知識が必要となる。
ここでは計算式の表記方法や、プログラミングについて、軽く説明を載せておく。
プログラミングについては素人同然の筆者なので、間違いがあるかもしれない。

演算子(計算記号)

当攻略サイトでは、以下のように各演算子(計算記号)を表記する。

記号意味
+足し算11 + 4 = 15
-引き算11 - 4 = 7
*掛け算11 * 4 = 44
/割り算11 / 4 = 2.75
%剰余
(割り算の余り)
11 % 4 = 3
(2余り3の「3」)
^べき乗/累乗
114など)
11 ^ 4 = 14641

本作の計算式の引用元ウェブサイト様での表記を基準とした表記だが、できるだけプログラミングの知識がなくてもわかりやすくしたつもりである。
算数・数学で使う記号が、プログラミング言語ではまったく異なる意味になっていることがたまにあり、プログラミング初心者が混乱しがちである。
そのため本攻略では、引用元ウェブサイト様に加え、その両方の折衷、表計算ソフトや関数電卓での表記を基準とした。

  • 剰余演算子を「%」としているが、「mod」という表記をするプログラミング言語もある。
  • べき乗/累乗を「^」としているが、プログラミング言語によっては「^」は別の意味の演算子になることに注意。
    多くのプログラミング言語で、「^」は別の意味の演算子(排他的論理和XOR)である。排他的論理和 - Wikipedia
    PythonやJavaScriptだと、べき乗/累乗は「11 ** 4」、他に数学関数として「pow(11, 4)」でべき乗が提供されているプログラミング言語も多い。
    一方、表計算ソフトや関数電卓では「^」でべき乗/累乗計算ができる。
  • 割り算の「/」については、引用元ウェブサイト様だとおそらく暗黙の了解で整数除算の演算子(割り算の商の算出)を意味していると思われるのだが、一般的には馴染みがないので、本攻略サイトでは算数・数学的な「÷」の意味と同一としておく。
    この点については後にtrunc()の説明にて詳しく述べる。

数学関数

本作の計算式に関係のある数学関数「max()」「min()」「trunc()」のみ紹介する。
プログラミング言語により表記は異なるので、詳細は各プログラミング言語の解説参照。
また、ここで紹介する関数は、一般的な表計算ソフトにも実装されており、セルに「=max(5, 9, -10)」と入力すると実行してくれる。

max()

複数の値から最大値を求める関数。
例えばmax(5, 9, -10)は「5、9、-10の中で一番大きな数を選ぶ」という意味なので、
max(5, 9, -10) = 9
である。

min()

複数の値から最小値を求める関数。
min(5, 0.9, 10)は「5、0.9、10の中で一番小さい数を選ぶ」という意味なので、
min(5, 0.9, 10) = 0.9
である。

以上を踏まえた上で、max()min()が登場するのは、本作の単発ダメージ量計算式である。

・単発ダメージ:
(((min(max((8+技LV+自LV-敵LV),1),24)*LV差係数/8+(自能力値*係数))*((255-敵能力値)/2)/256)*2+攻撃力)*乱数(1~1.25)
(乱数幅は厳密には単発ダメージ*(0~63)/256)
 ・総ダメージ:
単発ダメージ*ヒット数-防御力

入り組んでいるのでわかりにくいのだが、max()は、
max((8+技LV+自LV-敵LV),1)
の部分、つまり(8+技LV+自LV-敵LV)1を比較して大きい方、の意味である。
min()は、
min(max((8+技LV+自LV-敵LV),1),24)
の部分、つまりmax((8+技LV+自LV-敵LV),1)24を比較して小さい方、の意味である。

もう少しわかりやすい話だと、本作におけるダメージ量の最大値が上げられる。
上の通り、総ダメージ量は、「単発ダメージ*ヒット数-防御力」、つまり多段ヒットする技なら多段でヒットした分を掛け算してから、防御の値を引くことになるのだが、この時の計算値が999以上であったら、カウンターストップとなってダメージ量が999に変更される。
つまり、総ダメージ量は厳密には、

総ダメージ = min(「単発ダメージ*ヒット数-防御力」, 999)

なのである。

trunc()

小数点以下の端数を切り捨てる関数。
trunc(123.45)は「.45」部分を削除して「123」になる。
trunc(9/2)は、9/2の結果が4.5だから、「.5」が削除されて「4」となる。
trunc(100/20)だったら、小数点以下の端数はないので、通常の割り算の結果と同じ「5」である。

trunc()は、割り算だったら「商」の部分のことである。
小学校では小数や分数を習う前、「7÷2=3 余り1」のように割り算を計算しただろう。
この「3」の部分が「商」であり、trunc(7/2)と等しい。

本作ではダメージ値計算や取得経験値の計算など、ゲーム内部で具体的な数値を算出する計算を行う時は、割り算の計算をするたびに、小数点以下の端数が出た時は切り捨てる処理をしている。
これは「整数除算」とも呼ばれる処理方法である。
すべての計算を終えた後に切り捨てではなく、計算式の途中に小数点以下の端数が出るとその度に行う。
たとえば上で紹介したダメージ計算式、

(((min(max((8+技LV+自LV-敵LV),1),24)*LV差係数/8+(自能力値*係数))*((255-敵能力値)/2)/256)*2+攻撃力)*乱数(1~1.25)

だが、実際には、

(trunc((trunc(min(max((8+技LV+自LV-敵LV),1),24)*LV差係数/8)+(自能力値*係数))*trunc((255-敵能力値)/2)/256)*2+攻撃力)*乱数(1~1.25)

のように計算する必要がある。
LV差係数/8など、割り算の「/」があるなら必ず計算後に小数点以下切り捨てを行わなければならない。
truncを無視して最後まで計算した後に切り捨てたり、小数点以下を四捨五入すると、異なる答えになってしまうので注意していただきたい。

なぜわざわざ、毎回整数除算をするのかだが、おそらく「プログラミング言語では仕様上、除算では商を結果として出力するのが標準」だからだろう。詳細は後で記す。
インターネットで調べた結果、スーパーファミコンのプログラムにはアセンブリ言語(アセンブラともいう。詳しくはアセンブリ言語 - Wikipedia)が使われていた。
スーパーファミコンのCPU(演算などを行う装置)は、65C816互換16ビットマイクロプロセッサ(厳密には「Ricoh 5A22」というカスタム版CPU)が使われていた。65816 (コンピュータ) - Wikipediaを参照。
アセンブリ言語はCPUにより仕様が変わるため、スーパーファミコンのプログラムは65C816向けのアセンブリ言語で書かれている。
「65C816 アセンブラ」で検索すると、スーパーファミコンのプログラムの解説も見つかる。

などによれば、65C816向けのアセンブリ言語で除算(割り算)を行うと、整数の商と余りを出力する。
先に述べた通り、商はそのままtrunc()のことである。
よって、本作における数値計算で割り算を行う場合、割り算の計算結果の商をそのまま採用しているので、切り捨て処理をしているように見えているだけ、ということなのだろう。

「プログラミング言語では仕様上、除算では商を結果として出力するのが標準」とはどういうことなのかについて、プログラミングの素人ながら説明する。
スーパーファミコンの頃でも現代のコンピュータでも共通して、コンピュータは普段我々が使う10進数での計算ができず、2進数でしか計算できないため、10進数で小数点がある数値は2進数を用いた近似値計算をする。結果、整数以外の計算を行うと、わずかずつだが誤差が生まれる。整数ではこの問題は起こらない。
この近似値のことを浮動小数点数という。浮動小数点数 - Wikipediaなど参照。
浮動小数点数の問題を起こさないようにするため、整数同士の除算=整数除算と決まっているプログラミング言語は多い。
歴史のあるC言語(C++やC#も)、Java、比較的新しいプログラミング言語だとRustやGoなど。
それらのプログラミング言語では、整数の計算か、浮動小数点数の計算かをきちんと区別してプログラムを書く必要がある。
逆に、明記しないと切り捨てなしで小数点以下の数値を算出するJavaScriptやPythonでは、たとえば5/2を演算させると、自動的に5も2も整数ではなく浮動小数点数として扱われるので計算結果が2.5になるのである。
ちなみにPythonだと5//2とすれば、小数点以下切り捨て除算になる。

計算例:

キューブ(レベル7固定)が、「ボーリングだま」(攻撃力+8)だけ装備した状態で、「スピンドライブ」をポウンバード(レベル7)に当てた時の単発ダメージを計算してみる。
「スピンドライブ」の技LVは「8」、LV差係数は「12」、ダメージ依存値は味方側も敵側も「速」であり、レベル7のキューブの速は「18(=自能力値)」、ポウンバードの速は「16(=敵能力値)」となるので、最後の乱数以外の計算は、

trunc((trunc(min(max((8 + 8 + 7 - 7), 1), 24)* 12 / 8) + (18 * 1) ) * trunc((255 - 16) / 2)/ 256) * 2 + 8 = 46

ダメージ量は46となる。
この値はポウンバードの正面に技を当てた時の計算値である。
自LV(自身レベル)と敵LV(敵レベル)には向き補正が入る。
向き補正の説明は、戦闘関係 > 向き > 向き補正(側面補正と背面補正)を参照のこと。
ポウンバードの側面補正は2、背面補正は4なので、側面へのダメージ量を計算する場合は、「敵LV」から2を引く。

trunc((trunc(min( max((8 + 8 + 7 - (7 - 2)), 1), 24)* 12 / 8) + (18 * 1))*trunc((255 - 16) / 2)/ 256)* 2 + 8 = 48

側面へのダメージ量は48。
更に背面へのダメージ量を計算すると、

trunc((trunc(min(max((8 + 8 + 7 - (7 - 4)), 1), 24)* 12 / 8) + (18 * 1))*trunc((255 - 16) / 2)/ 256 )* 2 + 8 = 52

背面へのダメージ量は52となった。
「スピンドライブ」のヒット数は1なので、この単発ダメージ量に、乱数を掛け算すると、ゲーム内での実際のダメージ量となる。
正面からの攻撃なら、

46 + (46 * (0~63の乱数) / 256)

である。
つまり最大値は、乱数が63の時であるから、

46 + trunc(46 * 63 / 256) = 57

最小値は乱数が0の時であり、結局単発ダメージ量と同じだが、

46 + trunc(46 * 0 / 256) = 46

ダメージ量は46~57の間でランダム、ということになる。
今回の計算では問題なかったが、先に総ダメージ量計算方法で述べた通り、実際にはダメージ量は最大で999であるから、計算値が999以上となった場合は999にダメージ量が変更される。
更に回避属性での半減の計算が必要な場合もあるが、ここでは省く。

なお、プログラミング環境がなくても、ExcelやLibreOffice Calc、Google スプレッドシートなどの表計算ソフトなら、セルに、
=trunc((trunc(min(max((8 + 8 + 7 - 7), 1), 24)*12/8)+(18*1)) * trunc((255 - 16)/2)/256) * 2 + 8
のように、計算式を入力すれば計算してくれる。

ランダム固定ダメージを与えるサモの「ほいこーろー」及び、敵が使う技「わいろ」は、上の計算方法には従わない。技 > サモの「ほいこーろー」についてを参照。
最終編のラストボス戦におけるオディオモールも例外で、ダメージ量計算を無視して、自身へのダメージ量もHP回復量も1Hitあたり1にするが、「ほいこーろー」のみ通常通りのダメージを与えることが可能。

HP回復量の計算式は、

(回復係数(LV差係数と同じ)*4*乱数(1~1.25) (敵が使用する場合は、回復係数*乱数)
(乱数幅は厳密には単発ダメージ*(0~63)/256)

技に設定されている「LV差係数」だけが回復量計算にかかわるため、使用者や回復してもらう側のステータス、向き補正等は一切関係しないということになる。
例えばアキラの「ヒールタッチ」なら、LV差係数が48と設定されているので、乱数最大と最小をそれぞれ計算してみると、

乱数最大(63の時):
48 * 4 + trunc(48 * 4 * 63 / 256) = 239

乱数最小(0の時):
48 * 4 + trunc(48 * 4 * 0 / 256) = 192

アキラや回復される側のステータスに関係なく、回復量は192~239の間でランダムということになる。

回復量計算式のみ、味方と敵とで計算式が異なるため、最終編のオルステッド主人公時にラストボスたちを味方キャラとして操作する時には、回復技の計算が味方側の計算式で計算されることになる。
例えば西部編ラストボスのO・ディオが使う回復技「突撃ラッパ」は、LV差係数が20なので、西部編ラストバトルで敵として使用する場合、回復量は、

20 + trunc(20 * 「0~63の乱数」 / 256)

で求められるので、最大値が24、最小値が20である。
最終編オルステッド主人公時では、この結果が4倍になる。つまり、

20 * 4 + trunc(20 * 4 * 「0~63の乱数」 / 256)

最大値が80、最小値が99となる。
O・ディオが強くなったという訳ではないのに回復量が上がっているのは、計算式が違うからなのである。

ダメージ量とレベルについて

max()min()の例としてもうひとつ、ダメージ量計算式について少し詳しく説明する。
ダメージ量計算式は、先に紹介したとおり、

(trunc((trunc(min(max((8+技LV+自LV-敵LV),1),24)*LV差係数/8)+(自能力値*係数))*trunc((255-敵能力値)/2)/256)*2+攻撃力)*乱数(1~1.25)

という、一見してとてもわかりにくい計算方法となっている。
max()min()がいきなり登場していることも原因かもしれない。
なぜ、わざわざ「2つの値を比べて大きい方を採用」などという計算をするのかは、実際に計算をしてみるとわかる。
ここでは、高原日勝の「Fシュタイナー」を例に説明する。

「Fシュタイナー」のダメージ量は、高原の「知」が自能力値、敵側の「体」が敵能力値として依存する。
技LVは2、LV差係数は80、係数は1/2(このため、「(自能力値*係数)」の計算も「trunc(自能力値*係数)」としなければならない)、攻撃力に依存しない。
高原の「知」はレベルアップで上がらず、25のままなので、装備補正しない限り変動しない。
つまり装備を変えなければ、レベルアップで影響するのは「自LV」だけ、ということになる。

では「Fシュタイナー」を、最終編の鍵のダンジョンにどのレベルでも登場する影、そしてラストボスのピュアオディオに正面から当てた場合、高原のレベルを変えていくと、ダメージ量はどうなるだろうか?
影のレベルは9、「体」は20で、ピュアオディオのレベルは30、「体」は170。
乱数を考慮せず(つまり乱数を1とする)、ダメージ量を計算した場合、高原のレベルを2から上げていくと、以下のようになる(レベル50以上は、レベル49の値と同じなので省略)

高原の
レベル
ピュア
オディオ
高原の
レベル
ピュア
オディオ
23862623022
34662723026
45662823030
56462923032
67463023036
78463123040
89263223042
910263323046
1011063423048
1112063523052
1212863623056
1313863723058
1414863823062
1515663923066
1616664023068
1717464123072
1818464223076
1919264323078
2020264423082
2121264523082
22220104623082
23230124723082
24230164823082
25230204923082

グラフにするとわかりやすいだろう。

ピュアオディオへのダメージ量がわかりやすいが、
「味方キャラのレベルが、敵に対して低すぎる場合でも、ダメージ量は最低限の一定の値は出せるようになっている」
「ある程度のレベルからは、レベルを上げるとダメージ量も増える」
「味方キャラのレベルが、敵に対して高すぎると、ダメージ量が頭打ちになり、一定の値以上出せなくなる」
という調整が入っていることがわかる。
影は最終編序盤から戦える敵なので、味方のレベルが低い時でも、「レベルを上げるとダメージ量も増える」状態が続くのだが、それもレベル23までで、それより味方のレベルを上げてもダメージ量は増えなくなる。

ややこしい計算式は、味方と敵との戦力差を補正するために配慮された結果、ということになるのだろう。

「ある程度のレベルからは、レベルを上げるとダメージ量も増える」の部分だが、計算値だけ見るとおかしな増え方をしているように見える。
ピュアオディオへのダメージ量だと、レベルを1上げたら2増えたり、4増えたりと不規則に見えなくもない。
だがグラフを見るとほぼ直線、つまりレベルとダメージ量が比例して増えている。
理由は、ダメージ量は小数点以下を切り捨てながら計算しているため。
ピュアオディオ相手のダメージ量は、

ダメージ量 = TRUNC((TRUNC(MIN(MAX((8+2+高原のレベル-30),1),24)*80/8)+TRUNC(25*0.5))*TRUNC((255-170)/2)/256)*2

このようになり、レベルとダメージ量が比例して増える部分は、MIN()MAX()の計算で「8+2+高原のレベル-30」が採用されているため、

ダメージ量 = TRUNC((TRUNC(8+2+高原のレベル-30)*80/8)+TRUNC(25*0.5))*TRUNC((255-170)/2)/256)*2

このようになっている。
TRUNCを外し、小数点以下切り捨てを行わずに計算してみると、

ダメージ量 = ((8+2+高原のレベル-30)*80/8+25*0.5)*((255-170)/2)/256*2

ダメージ量を y 、高原のレベルを x として計算すると、

y = 850/256x - 31875/512

傾きが850/256、切片が-31875/512となる一次関数である(中学校の数学では一次関数 y = ax + b の形で習うだろう)。
つまり、実際には、「高原のレベルが1上がると850/256(約3.32)ずつダメージ量が増える」のであるが、途中の小数点以下切り捨て処理のために、2増えたり4増えたりとバラバラになっているのである。

なお、敵から味方キャラへの攻撃の場合でも、レベル差でダメージ量が変わる。
ピュアオディオの技「キャンセラレイ」は、攻撃される側の依存ステータスが「知」なので、上の例と同じく「知」が25のままの高原に対し、高原のレベルを変えながらダメージ量を計算すると以下のようになる。
「キャンセラレイ」の技LVは15、LV差係数は68、自能力値依存なし(0で計算)。
高原は「知」25、防御0で計算。
レベル56以上は、レベル55の値と同じなので省略。

高原の
レベル
キャンセラレイ高原の
レベル
キャンセラレイ
218229182
318230174
418231168
518232158
618233152
718234144
818235136
918236128
1018237122
1118238114
1218239106
131824098
141824190
151824282
161824376
171824468
181824560
191824652
201824744
211824836
221824930
231825022
241825114
25182526
26182536
27182546
28182556

このように上限値と下限値があるが、レベル29以下ではレベルでダメージ量の変動がない。
つまり、通常の最終編クリアレベル帯あたりまでは、攻撃される側のレベルではダメージ量は変化しない。
「キャンセラレイ」のダメージ量を抑えるためには、装備で防御や「知」の値を上げることが重要である、ということもわかる。

乱数

乱数はランダムな値のことである。ということは特にいうまでもないだろうが、コンピュータゲームでは乱数を計算して作るので、本当の意味でランダムではなく「ランダムに見える」擬似的な乱数であり、本作では乱数を、
漸化式 An = (A(n-3) * 256 + A(n-4) ) / 13) % 256
という計算式で作っている、とのことである。
※上の式はrodgersia.ninpou.jp/livealive/sonohoka.txtからそのままコピペしたが、カッコの数が合わないようである。
おそらく実際にはAn = ( (A(n-3) * 256 + A(n-4) ) / 13) % 256だろう。

途中に割り算があるので、厳密には、
An = trunc( (A(n-3) * 256 + A(n-4) ) / 13) % 256
である。
最後に「% 256」を行っているため、乱数は常に「256で割った余り」、つまり0から255の間の値ということになる。

※漸化式の場合、数学的な表記を用いると、
An = ( (An - 3 * 256 + An - 4 ) / 13) % 256
のように斜体や下付き文字を使う。
少なくとも下付き文字を使わないと誤解を招くので、本攻略では以降、下付き文字を使用する。

漸化式は高等学校の数学あたりで習う、数列を計算する式のこと。
数列を作る各数字に、「A1, A2, A3, A4, ……」と番号を振った時にどういう規則性があるか? という問題を解くと漸化式が求められる。
たとえば奇数が並ぶ数列「1, 3, 5, 7, 9 ……」は、「初期値が1で、次からは2ずつ増えていく等差数列」であるから、漸化式だと、

An+1 = An + 2
(A1 = 1, nは自然数)

と、なる。
この数列ではランダムな数字をまったく生み出せていないことは、誰の目にも明らかである。
そもそも、漸化式は計算式なので、一定の決まった数字を生み出すことしかできない。
だが、「一見ランダムに見える数字を生み出す」ように漸化式をいろいろと改造してきた先達もいるのである。
線形合同法 - Wikipedia
もそのひとつで、擬似乱数列を生成するための漸化式である。
線形合同法では擬似乱数生成に漸化式

Xn+1 = ( A * Xn + B ) mod M
(A、B、Mは定数、M>A、M>B、A>0、B≥0)

が用いられている。
modは剰余演算子のことであり、上の演算子(計算記号)で記した通り、剰余演算子の%modも同じ意味である。
本作の
An = ( (An-3 * 256 + An-4 ) / 13) % 256
と比較すると、形式が似ていることがわかる。
ただし線形合同法では、Xn+1の計算に数列の1つ前の値Xnを使っていることに対し、本作での計算式は、Anの計算に数列の3つ前の値An-3と4つ前の値An-4を使うことで、より規則性がなさそうな、ランダムっぽい数値が出力されるようにしているのだろう。

本作の
An = ( (An-3 * 256 + An-4 ) / 13) % 256
だと、Anを求めるために、数列の3つ前の値「An-3」及び、数列の4つ前の値「An-4」が必要である。
つまり新たな乱数を生成する時に、直前に作った乱数4個を記録しておく必要がある。
最初に計算して作る乱数値、つまりA5(n = 5)の計算には、本作を起動した時点で決まっているA1, A2, A3, A4(n = 1~4)が使われているということになる(ただし筆者にはA1~A4が実際にいくつなのかはわからない。ゲーム内部で設定されているはずである)。
A5の生成は、
A5 = trunc( (A2 * 256 + A1 ) / 13) % 256
であり、生成した時点でA1がゲーム内の記録から削除される、ということになる。
本作では、マップ上にいる時、この乱数生成計算が、1フレーム、つまり1/60秒ごとに行われている。つまり1秒間に60回、乱数生成の計算式を計算し新たな乱数を生み出し続けているのである。

乱数はセーブデータには保存されず、リセットすると初期値A1~A4から再び計算開始になる。
バーチャルコンソール版での「まるごと保存」は、乱数を含めて保存される。

戦闘においては、ドロップアイテムの有無は戦闘開始時の乱数で決まり、戦闘中の行動ではドロップ率は変化しない。
戦闘中でもセーブが可能なバーチャルコンソール版で戦闘終了直前に「まるごと保存」し、「まるごと復元」を繰り返して敵を倒し続けても、敵がアイテムをドロップするかどうか、また、どのアイテムをドロップするかが変わることはない。

おまけ

試しにいくつかのプログラミング言語で、A1, A2, A3, A4に仮値として1, 2, 3, 4を当てはめ、100回分の乱数を作ってみた。
オンラインでプログラムを実行できるpaiza.IOで公開している。

実行結果は以下の通り(もちろん、どのプログラミング言語でも同じ結果が出力される)。

初期値:
[1, 2, 3, 4]
出力された乱数:
[39, 59, 79, 0, 140, 24, 6, 196, 227, 120, 20, 133, 76, 147, 60, 226, 84, 168, 103, 135, 242, 249, 106, 167, 58, 58, 224, 131, 122, 63, 36, 108, 226, 201, 81, 106, 135, 74, 45, 106, 187, 123, 42, 106, 132, 68, 42, 47, 69, 64, 160, 82, 241, 83, 91, 144, 117, 6, 26, 11, 127, 0, 218, 197, 9, 196, 56, 192, 20, 93, 201, 152, 40, 125, 192, 31, 160, 206, 113, 81, 228, 193, 67, 144, 234, 54, 24, 11, 57, 220, 218, 99, 240, 213, 174, 125, 116, 114, 170, 245]

試しに100個分出力したが、素人目には0~255からランダムな数値を生成しているようにしか見えない。ちょっと詳しく見てみても、たとえば奇数と偶数が規則正しく出現している様子もないし、数字の繰り返しも見られない。
だが実際にはこの後も計算値を生成し続けると、どこかで同じ数値列が出てループするので、完全な乱数にはならないのである。
よって、買い切りのコンピュータゲーム内での乱数ならともかく、この方法で暗号(暗証番号やログインパスワード)を作るのは禁物である。

確率

上のようにして生成された乱数は、本作でさまざまな計算に使われている。
ダメージ量計算式では、最後に乱数をかけてダメージ量のブレを表現している。

(((min(max((8+技LV+自LV-敵LV),1),24)*LV差係数/8+(自能力値*係数))*((255-敵能力値)/2)/256)*2+攻撃力)*乱数(1~1.25)
(乱数幅は厳密には単発ダメージ*(0~63)/256)

この計算式では乱数が0~63と、上の計算式より数値の幅が狭いが、0~255の乱数を生成してから4で割った商を使うとか、乱数生成式の最後の「% 256」を「% 64」にするなどしているのだろう(と筆者は勝手に考えているが、実際は異なるかもしれない)。

能力値増減判定:
(0~63)の乱数>敵LV+16

のような、バフ・デバフの成功確率の計算式があるが、これは0~63の間で生成された乱数と、「敵LV+16」の計算結果を比較して、0~63の間で生成された乱数の方が大きければ能力値増減が成功する、の意味である。
例えば敵LVが5の敵に対しては、敵LV+16 = 5 + 16 = 21であるから、乱数が22~63だったらバフ・デバフが成功である。
乱数は0~63、つまり64個の数字の中から1個が選ばれるので、乱数が22~63である確率は、
41÷64×100≒64.06%
だいたい64%の確率でバフ・デバフが成功する。

※この確率計算で、割り算なのにtruncで小数点以下を切り捨てないのかと思われるかもしれないが、ゲーム内では、確率を計算する必要はないし計算してもいない、という点に注意。
ゲーム内では乱数を算出して、判定式に合っているかどうかをチェックするだけである。
上の確率計算は、あくまでもこのくらいの確率だという目安を計算してみたという話である。

確率と試行回数

これはゲーム内の計算とは直接関係ないおまけの話であるが、たまに誤解される確率と試行回数について記しておく。
主にアイテムドロップ率に関する話になる。

基礎知識 > アイテム > 敵が落とすアイテムで、アイテムのドロップ率計算方法を説明してある。
隠しボスのキングマンモーのドロップアイテム「コーラのビン」のドロップ率は(1/4 + 1/8) * 15/16と計算され、だいたい35%くらいである。
と書くと、『3割くらいのドロップ率だから、3回戦えば1回は「コーラのビン」をドロップする』と考える方もいるようである。
実際には3回戦えば必ず1回は「コーラのビン」をドロップするということはない。

たとえば、「当たりくじが1枚、ハズレくじが2枚、合計3枚のくじが入っている、中身が見えない箱からくじを1枚引く」時の当たりくじが出る確率は1/3(3割くらい)である。
くじ引きの場合は、引いたくじは箱に戻さないから、3枚引けば必ず当たりくじが1枚出てくる(と同時に箱の中はカラになる)。
だが、ゲームにおけるアイテムドロップ率は、「引いたくじを必ず箱の中に戻して、またくじを引く」行為である。
確率は1/3だが、運が悪いと、何度チャレンジしても3枚の中から当たりくじが引けないこともあり得る。
だが、何度も試すことで、当たる確率は上がっていく。

当たる確率が1/3なら、逆に、外れる確率は2/3である。
ここからはキングマンモー戦を例にして、戦闘を1回行ってアイテムをドロップする確率は35%(35/100)、ドロップしない確率を65%(65/100)、と考えてみよう。
戦闘2回で運悪く、2回連続でドロップしない確率は65/100 * 65/100で42%ほど。
逆に言えば、残りの58%は、「戦闘1回目または2回目でドロップする確率」である。

「2回連続でドロップしない」の逆の現象(数学用語で「余事象」という。高校数学でも習うだろう)である「1回目または2回目でドロップする確率」を足すと100%になる理屈から、
「10回連続でドロップしない」の余事象は「1回目または2回目または3回目……または10回目でドロップする確率」になる。つまり、10戦中最低1回はドロップする確率である。
10回連続でドロップしない確率は65/100 * 65/100 * 65/100 * 65/100 * 65/100 * 65/100 * 65/100 * 65/100 * 65/100 * 65/100、つまり(65/100)^10で、1.3%ほどである。
この余事象は1 - (65/100)^10で計算できるということなので、だいたい98.7%が「ドロップ率が35%の時、10戦中最低1回はドロップする確率」になる。
キングマンモーと10回戦えば、98.7%の人が最低1回は「コーラのビン」を入手できる、ということになる。
……1.3%の人は、運悪く10回も戦ったのに「コーラのビン」をドロップしないということになるが。

昨今はソーシャルゲームアプリにおいて、ガチャでの排出確率を元にガチャを何回回せばどれくらいの確率で当たるかといったことを、上のような計算で確認している方が増えているかもしれない。

ビット演算

ビット演算(ビット演算 - Wikipedia)は主にコンピュータで使われる演算方法で、データをビット列、つまり2進法で記した場合での演算である。
行動順やターン、行動異常の諸々の計算で必要となるが、戦闘関係 > ターンや行動順の詳細の中で解説しているので参照していただきたい。

ここでは、ターンや行動順の説明とは関係がないがビット演算の例として、敵のHPについて記す。

本作はゲーム内部ではすべてのデータが16進法で記されている。
十六進法 - Wikipedia
16進法は0~9,A,B,C,D,E,Fの16文字での数値の記述方法である。普段我々が使う10進法は、1の位は0~9までの10文字が使えるが、16進法だと16文字使える。
更にいえば、コンピュータゲームの基本は2進法、つまり0か1のみで計算される。
単純に数値の計算をするだけではなく、0をOFF、1をONと考えると、ゲームにおけるフラグ管理もできる。
2進法の4ケタ分(0000~1111)は、16進法の1ケタ分、0~Fに対応しているので、記録するにも計算するにもコンピュータ側に都合が良い。

少し話が外れるが、16進法の2ケタ分(2文字分) = 2進法の8ケタ分のデータ容量を、1バイト(Byte)という。
2進法の1文字分は1ビット(bit)分のデータになり、1バイト(Byte) = 8ビット(bit)ということになる。
例えば、16進法2バイト分のデータ「1B 50」は2進法だと「00011011 01010000」であり、ON・OFFのみのデータなら16個分記録できる。
本作は属性が16種(手・足・突・鋭・鈍・締・飛・背・火・水・風・土・精・善・悪・無)あるから、たとえばキャラ1人分の回避属性の有無を2バイト分のデータに格納できる、ということになる。
ちなみに「1B 50」は、ハッシュの回避属性のデータである。
以下表で「1」に設定されている属性が、ハッシュの回避属性(鋭・鈍・飛・背・水・土)に当たる。

属性
16進数1B00011011
属性
16進数5001010000

「ライブ・ア・ライブ」は「16メガビットロムカセット」のスーパーファミコンソフトであるが、この「16メガビット」は「16,000,000ビット」のこと。バイトだと「2,000,000バイト」である。
と、数字ばかり出されてもピンとこないかもしれないが、2,000,000バイトは2メガバイト(2MB)。
現代のスマホで写真を撮影した場合の1~3枚分くらいの容量でしかない(画像ファイルとプログラムデータを比較するのは野暮かもしれないが)。
ちなみにリメイク版「ライブアライブ」のSwitch版の容量は4.7GB。スーファミ版の2,350倍の容量である(ボイスデータなどリメイク版で追加された容量の大きめなデータが含まれるため、こちらも比較するのは野暮かもしれないが、これがコンピュータとゲーム機の進歩でもある)。

話を戻すが、ビット演算は、2進法で表記された0と1の文字列の操作をする計算として便利な方法になる。
ビット演算についての説明は、論理演算も参考にしていただきたい。ビット演算は「ビット単位での論理演算」である。
論理演算 - Wikipedia
論理演算は高校数学あたりで習うかもしれない。
集合において「AまたはBまたはAかつB」を「論理和」、「AかつB」を「論理積」という……と書くとややこしいように見えるかもしれないが、例えば、中世編での攻略フラグを考えてみよう。
「フラグ1:ハッシュがパーティにいる」「フラグ2:ブライオンを所持している」のふたつの論理積が満たされている場合のみ、魔王山の入口が開くようになっている……と、置き換えてみると良い。
ビット演算の場合は2進法での「論理和」「論理積」を求めるので、「論理和」なら「AとBの数値のうちどちらかが1なら1」という計算、「論理積」なら「AとBの数値のうちどちらも1なら1」という計算である。

以上がビット演算に関する前置きである。
ここから、本作の敵のHPについて説明する。

本作の敵のHPデータは、「2ケタの16進法」で記録されている。つまり8ビット分の容量を使っている。
中世編のシングイーターは16進数のデータで「07」、10進数になおして7。
幕末編の藩士は16進数のデータで「6E」、10進数になおして110、となる。

2ケタの16進法の最大値は「FF」であり、10進法だと255である。
だが実際には、本作には256以上のHPを持つ敵もいる。
先に述べた通り、16メガビットという限られた容量にデータを詰め込む上で、敵HPの記憶領域を8ビット以上取らないように工夫した結果と思われるが、実際には敵のHPを以下のように計算している。

敵のHPデータが16進数の「80」(10進数の「128」)未満であれば、データの数値をそのままHPとする。
上でも記した通りに、藩士なら16進数のデータ「6E」は10進数になおして110であり、128未満だからそのままである。

敵のHPデータが16進数の「80」以上の時は、
(敵のHPデータ & 127) * 16
を、実際の敵のHPとする。

ここで出てきた「&」は、「ビット演算における論理積を表す演算子」である。
よって敵のHPデータ & 127を計算する場合はまず、それぞれを2進数に変換する必要がある。
ここでは、本作で一番高いHPの岩間さまやピュアオディオの敵のHPデータ「FF」を使ってみよう。
つまり彼らのHPは2ケタの16進法の最大値に設定されている。
16進数の「FF」は先に述べた通りに10進数の255である。そして10進数の255は、2進数だと「11111111」である。
一方、10進数の127は、2進数だと「1111111」である。
「11111111」と「1111111」の論理積は、「各位を比較して、どちらも1なら1」と計算する。
下表で縦方向に見比べるとわかりやすい。

255の2進数11111111
127の2進数01111111
論理積01111111(10進数だと127)

255 & 127の答えは127であるから、

(敵のHPデータ & 127) * 16 = 127 * 16 = 2032

となる。
岩間さまやピュアオディオの最大HP2032は、上のように計算されているのである。

※敵のHPデータが16進数の「80」以上の時、と記したが、実際には、(16進数の80 & 127) * 16の解は0であり、敵データにもHPが16進数の「80」と設定されている敵はいない。
16進数の「88」以上でなければ0より大きい計算結果にならないので、実質、16進数の「88」以上の時の話になる。

他、HPが128以上の敵の一例を以下に記す。

HPデータ
(16進数)
計算結果
(10進数)
88128リン、
アヌビノフォビア、
ブラキオペルタ など
96352尾手 院王
A4576J・イヤウケア
BE992おーでぃーおー、
液体人間W1号、
キングマンモー など

本作の敵のHPを見てみると、HPが128以上に設定されている敵はかならず16の倍数になっているが、HPが128以上だと計算上必ず最後に16を掛け算しているためである。
HP初期値は上のように1バイト分の領域を使って、スーパーファミコンのカセット内にプログラムデータとして記録されているのだが、戦闘中の現在HPは1~2032の間の値になる。戦闘中はダメージを受けたり回復したりするなど変動していくため、常に16の倍数にはならないから、上の計算方法では現在HPの値が表現できない。よって、戦闘中の現在HPは、2バイト分の容量を使ってメモリ(ゲーム内の計算値などを一時的に記録する記憶装置のこと。スーパーファミコン本体に内蔵されている)に記録される。

なお、HP以外のステータス(レベル、力、体、速、知)は最大値が255以下なので、素の16進数データがそのまま敵のステータスとして使用されている。

ちなみに、味方キャラの初期HPも同じ方法で計算されている。
初期HPが128以上のキャラはすべて、初期HPが16の倍数になっている。
味方キャラがレベルアップするとHPも増えていくため、「現在の味方キャラの最大HP」は2バイト分の容量を使ってメモリに保存し、セーブした時にはゲームソフト内のメモリに保存される(このメモリはゲームソフト内のコイン形電池を電源としている。いわゆるバッテリーバックアップ)。

さらなる余談だが、最終編でオルステッドを主人公にすると、各シナリオのラストボスを操作することになり、戦闘終了後にメニュー画面でオルステッドの装備画面を確認すると、レベルとHPが直前のボスのステータスそのままになっている、というバグがある。
「現在の味方キャラの最大HP」は2バイト分の容量を使ってメモリに保存しているが、おそらく戦闘終了時に、「現在の味方キャラの最大HPとレベル」が、そのままメニュー画面の味方のHPとレベルに書き込まれる仕組みがあるのだろう。
他シナリオであれば、フィールドでの操作キャラと、戦闘時の操作キャラは同一キャラになるから、戦闘終了時のHPのデータをメニュー画面のHPのデータにそのまま書き込んでも問題は起こらない。
だが最終編オルステッド主人公は、フィールドではオルステッドを操作、バトルでは各シナリオのボスを操作、と別キャラを操作することになるため、このような現象が起こるのだと思われる。
なお、ブリキ大王操作時については、実はブリキ大王は敵キャラとしてデータに登録されているらしく、ブリキ大王での戦闘終了後にメニュー画面のアキラのステータスがブリキ大王の値に上書きされることはない。



このページをシェアする

上へ