ポケモンの捕獲率をシミュレートするプログラムをつくるver0.01

BDSPが色々と面白く(そして世代なので懐かしく)ドハマリしているのでプログラミングの勉強をしながら遊ぶことにしました。
タイトル通りポケモンの捕獲率をシミュレートするプログラムを作ります。
言語はいつものC++(いつまでも勉強中)に加えて、OpenSiv3Dライブラリ(まだまだ勉強中)を使います。


あと、割といきあたりばったりに記事を書きながら進行形で書いていたので数値型が統一されて無くてバラバラだったりしました(パット見で直したけど直ってないところあるかも)

捕獲処理の計算式を知る

B=(((最大HP×3-現在HP×2)×捕捉率×捕獲補正)÷(最大HP×3))×状態異常補正

Bが255(FF)以上なら捕獲確定。 

捕捉率:捕獲するポケモンの捕まえやすさ。捕捉率一覧を参照。
捕獲補正:ボールの捕まえやすさ。捕獲補正率を参照。
状態異常補正:
こおり・ねむりなら2(第四世代以降では2.5)
どく・まひ・やけどなら1.5。

捕獲が確定しなかった場合、次にGを求める。

G=65535÷(4√(255÷B))
0~65535の乱数を発生させて、それが4回ともG以下なら捕獲成功。 Gより大きい乱数があった時点で失敗が確定し、それ以降揺れ判定は行われない。

wiki.xn--rckteqa2e.com

ポケモンwikiによるとこのような計算式のようです。
実際にBDSPの捕獲処理がこの通りなのかわかりませんが、とりあえずBであると仮定します。
またGはめんどくさいで無視し、Bの計算式が255以上なら確定であることを利用したプログラムをつくります。

捕獲処理式を関数にする

脳直で書いてみる(もちろんこのままでは問題がある)

float Pokemon_Capture_Method(Array<float>& Date) {
	const float MaxHP = Date[0] * 3;//Date[0]=任意のポケモンの最大HP 
	const float MinHP = Date[1] * 2;//Date[1]=ポケモンのHPをどれだけ減らすか
	const float CatchRATE = Date[2];//Date[2]=任意のポケモンの捕捉値
	const float BoalRATE = Date[3];//Date[3]=任意のボール補正値、ただしレベルボール、ヘビーボールは乗算ではなく加算処理
	const float StatusRATE = Date[4];//Date[4]=任意の状態異常補正値

	bool LEVELorHEAVY = false;//計算式を分岐しなければという意志の現れ,現状ただの飾り

	if (not LEVELorHEAVY) {
		return (((MaxHP - MinHP) * CatchRATE * BoalRATE) / MaxHP) * StatusRATE;
	}
	else {//レベルボール、ヘビーボールの場合
		return (((MaxHP - MinHP) * CatchRATE + BoalRATE) / MaxHP) * StatusRATE;
	}
}

とりあえず必要な引数は5つありそうなことがわかりました。
レベルボール、ヘビーボールの場合は処理が分岐しますがひとまず無視することにします。

この関数を動作させてみます。
例えば捕獲済みLV30メタモン(とりあえずHP80と設定)をHP1まで減らし、眠らせた上でリピートボールを投げる場合{80,1,35,3,2.5}です

void Main()
{
	Array<float>Metamon = { 80,1,35,3,2.5 };
	Print << U"メタモンを眠らせてみねうちしてリピートボールを投げるぜ!";
	Print << U"捕獲確率は" << Pokemon_Capture_Method(Metamon);
	while (System::Update())//メインループ
	{
	}
}

f:id:amvirosa:20211210102251p:plain
255以上なので捕獲確定だということが伺えます。やったね。

このままでは不便である。快適にするにはどうすれば?

現在のコードでは言うまでもなく不便です。具体的には

  1. 255以上が確定であることが直感的にわかりにくい
  2. 実HP入力が面倒
  3. 条件を満たす場合のボール補正と満たさない場合のボール補正を自動的に判断できない

1は人間に馴染みある100%表記に直すことでわかりやすくなるはずですし、実装もおそらく簡単です

2は実HP入力ではなくLV入力をユーザーに求め、個体値0の時の確率-個体値31の時の確率の範囲表記が良さそうです。
ポケモン固有のHP種族値のデータとLVの値からHP実数値を求める関数を実装すれば良さそう。実装はまあ出来なくもなさそう。

3はちょっと難易度が高そうなのでひとまず保留です。分岐が多すぎる……
面倒なガンテツボールを全部省略し、
各ボールの最大値のみ判定するならば簡単なので今回は妥協します。

100分率表記にする関数を実装

おそらくSTLにそういうものが見当たらなかったので自作します(あったらまあ……)
今までは特定の型だけに対応するだけの関数や、
複数型に対応するにしても同盟多重定義でちまちま書いていました。
が、c++のテンプレート機能を使えば全てが1つにまとまります。便利だが便利すぎて使いこなせない。

template<typename T>
T Percentage(T a,T b){
	return a / b * 100;	
}
//色々省略
void Main()
{
	Print << U"捕獲確率は" << Percentage(Pokemon_Capture_Method(Metamon), 255.0F) << U"%";
}

f:id:amvirosa:20211210112341p:plain
人間に優しい100分率表記にすることでいかにも捕獲確定していそうな感じになりました。

LVを入力したら実HPを計算してくれる関数を実装

まずポケモンのHP実数値の計算式を調べます。

ポケモンの能力値(ゲームのポケモン確認画面で表示されるステータス数値)は、HPとそれ以外の能力とで式が異なり、下記の計算式で決定されています。 なお、計算途中で小数点以下が生じる度に切り捨てされます。
HP
能力値=(種族値×2+個体値努力値÷4)×レベル÷100+レベル+10

yakkun.com
ポケモン徹底攻略によるとこのような計算式のようです。
浮動小数点型を一個も使わずに関数を組み立てれば良さそうですね。

uint16 Pokemon_ExistLV_HP(uint16 SyuzokuHP,uint16 Level,uint16 KotaiHP) {
	constexpr uint16 DoryokuHP = 0;//今回は野生ポケモンなので0
	uint16 Answer = ((((SyuzokuHP * 2) + KotaiValue + (DoryokuValue/ 4)) * Level / 100) + (Level + 10));
	return Answer;
}
//とんでメイン関数
	Print << U"レベル30のメタモンのHPを調べるぜ!";
	Print << Pokemon_ExistLV_HP(48, 30, 0) << U"-" << Pokemon_ExistLV_HP(48, 30, 31);

f:id:amvirosa:20211210114853p:plain

ポケモンの名前,HP種族値,捕捉率のデータを予め読み込んでおく処理

ポケモン種族値リスト(ポケモン徹底攻略)捕捉率一覧(ポケモンwiki)から必要な情報を抜き出したcsvファイルを作りました
docs.google.com
これを読み込みます。OpenSiv3Dの相対パスの基準は開発時はAppフォルダ基準なのでAppフォルダにcsvファイルをぶちこめば./hoge.csvで展開できます。

struct PokeDate {
	uint16 SyuzokuHP;
	uint16 CatchRATE;
};
void Main(){
	const CSV csv{U"./PokemonDate.csv"};//[0][0]-[493][3]//csvファイル読み込み
	if (not csv) {
		throw Error{ U"Failed to load `PokemonDate.csv`" };//読み込めなかった場合のエラー処理
	}
	HashTable<String, uint16>NameTable; //名前と図鑑Noが紐付いたハッシュテーブル(はやいらしい)
	Array<PokeDate>PokeValue;//i番目のデータ=図鑑No iのポケモンのHP種族値と捕捉率になる配列

	for (auto i : step(csv.rows())) {//csvのデータを配列にぶちこんでいく処理
		NameTable.emplace(csv[i][1], i);
		PokeDate kari;
		kari.SyuzokuHP = Parse<uint16>(csv[i][2]);
		kari.CatchRATE = Parse<uint16>(csv[i][3]);
		PokeValue << kari;
	}
	//NameTable.contains(U---);完全一致するテキストの検索;
	//NameTable[U--];一致テキストのキー(今回は図鑑No兼行番号);


	TextEditState tex01;
	String Kari = U"Nodate";

	// メインループ
	while (System::Update())
	{
		ClearPrint();
		if (NameTable.contains(Kari)) {//もし検索文字列がヒットするなら
			Print << U"図鑑No:" << NameTable[Kari];
			Print << U"HP種族値" << PokeValue[NameTable[Kari]].SyuzokuHP;
			Print << U"捕捉率" << PokeValue[NameTable[Kari]].CatchRATE;
		}
		else {
			Print <<U"テキストボックスにポケモン名を入力してね";
		}
		SimpleGUI::TextBox(tex01, Vec2{ 100, 140 });
		if (SimpleGUI::Button(U"検索", Vec2{ 320,100 }))
		{
			Kari = tex01.text;
			syokizyoutai = false;
		}
	}
}

f:id:amvirosa:20211210151027p:plain
もっと良いデータ構造の作り方が多分あると思いますが、
一応これでポケモン名を入力するだけで固有の数値を引っ張り出すことが出来ているのでヨシ!

そして色々あって完成した

githubで公開した
github.com

githubc++もopensiv3dも使い方わかんない、初心者なので…