RPG戦闘もどき(一応一区切り版)+乱数のスケーリング

乱数のスケーリング

自分で好きな範囲の乱数を発生させたいなって話。

RAND_MAX:システムで決められた乱数の最大値
乱数関数 rand() は0~RAND_MAXの値を発生させる
(RAND_MAXは結構でかい値になっているよ。)

話を簡単化するために、
仮にRAND_MAXが10だとした時のお話をします。

RAND_MAX:10
0 ~5の値が欲しい時
* 簡単に思いつく方法

 rand()は整数を発生させる ⇒ rand()を6で割った余りを使う。

(RAND_MAX:10なので0~10の値が生成される)
x: 3 5 1 0 9 2 5 8 10 3
y = x % 6
y: 3 5 1 0 3 2 5 2 4 3
0~10の範囲の乱数を0~5の範囲の乱数に変換できた。

別にこれでもいいですが、求めたい範囲が整数の場合ばかりではない。
そこでもっと汎用的な方法。

例 その2)
2 ~ 7の値が欲しい

方針:

  1. 2~7を0 ~ N ベースで考えてみる
    • 0(+2) ~ 5(+2) ⇒ 2 ~ 7
  2. 乱数を発生(範囲 0~RAND_MAX)
  3. 範囲を正規化(範囲 0.0 ~ 1.0)
  4. 0.0 ~ 5.0の範囲に変換 (0.0 ~1.0) * 5 ⇒ 0.0*5 ~ 1.0*5 ⇒ 0.0 ~ 5.0
  5. 範囲に2を足す 0.0+2 ~ 5.0+2 ⇒ 2.0 ~ 7.0

0.0 ~ 1.0 の値の乱数は、乱数値/RAND_MAXで生成できる
(RAND_MAX:10なので0~10の値が生成される)

         x:   3   5   1   0   9   2   5   8  10   3 
x/RAND_MAX: 0.3 0.5 0.1 0.0 0.9 0.2 0.5 0.8 1.0 0.3

0.0 ~ 5.0 の乱数範囲に変換

      x/RAND_MAX: 0.3 0.5 0.1 0.0 0.9 0.2 0.5 0.8 1.0 0.3
5.0*(x/RAND_MAX): 1.5 2.5 0.5 0.0 4.5 1.0 2.5 4.0 5.0 1.5

0.0+2.0 ~ 5.0+2.0 ⇒ 2.0 ~ 7.0

  5.0*(x/RAND_MAX): 1.5 2.5 0.5 0.0 4.5 1.0 2.5 4.0 5.0 1.5
5.0*(x/RAND_MAX)+2: 2.5 4.5 2.5 2.0 6.5 3.0 4.5 6.0 7.0 3.5

これで、整数ではなく小数点数で範囲を限定した乱数を生成することができた。
あとは、この操作の応用でどんな範囲のものも大体生成できるはず。

それでは改めて、0.4~0.7の範囲の範囲の乱数を作って、防御力のパラメータに係数としてかける。

//ランダムで防御力の 0.3 ~ 0.7 まで効果が出るように関数を書き換える! \\
//0.0~0.4 の乱数作って、0.3 足す! \\
r = rand() : 乱数値 \\
r/RAND_MAX : 0.0 ~ 1.0 \\
0.4*(r/RAND_MAX) : 0.0 ~ 0.4 \\
0.4*(r/RAND_MAX) + 0.3 \\
: 0.0+0.3 ~ 0.4+0.3 => 0.3 ~ 0.7 \\

実際に乱数を生成してみる

#include <iostream>
 
int main()
{
	using namespace std;
	//初期化
	srand((unsigned int)time(nullptr));
	//0.3 ~ 0.7の乱数を生成してみる
	//rand()は 0 ~ RAND_MAX までの整数を返す
	for (int i = 0; i < 20; i++)
	{
		cout << 0.4*(float)rand()/RAND_MAX + 0.3 << endl;
	}
}

RPG戦闘もどき 大体これでいいかな版

#include <iostream>
#include <string>
 
 
using std::string;
using std::cin;
using std::cout;
using std::endl;
 
 
//ドラ〇エのキャラクターを考える
//name  名前:キャラクターの名前
//job   職業番号 0:戦士 1:魔法使い 2:僧侶 3:盗賊
// HP  生命力:これが尽きると死す
// MP  魔法力:これが尽きると魔法が使えない
//STR  強さ:力の強さ→攻撃力に影響
//ATK  攻撃力:攻撃能力の高さ
//DEF  防御力:防御能力の高さ
//MGK   魔法能力:魔法を操る能力の高さ 
//ING   かしこさ:インテリジェンスの高さ
//LUK  運:運の強さ
 
//僕のヒーローのパラメータ
struct mychar {
	string name;//名前
	int job;//職業番号
	int hp;
	int mp;
	int str;
	float atk;
	float def;
	float mgk;
	int ing;
	int luk;
};
 
string jobs[] = { "戦士","魔法使い","僧侶", "盗賊" };
//jobs[0] 戦士, jobs[1] 魔法使い。。。
void setCharacterStatus(mychar* _mc,
	string _name,
	int _job,
	int _hp,
	int _mp,
	int _str,
	float _atk,
	float _def,
	float _mgk,
	int _ing,
	int _luk)
{
	//-> アロー演算子:アドレスで渡された構造体のメンバ変数を参照する
	_mc->name = _name;
	_mc->job = _job;
	_mc->hp = _hp;
	_mc->mp = _mp;
	_mc->str = _str;
	_mc->atk = _atk;
	_mc->def = _def;
	_mc->mgk = _mgk;
	_mc->ing = _ing;
	_mc->luk = _luk;
 
}
//素敵なフォーマットでキャラのパラメータを表示する関数
//引数で、キャラクターを表す構造体を渡す(ポインタでアドレス渡し)
void printCharacterStatus(struct mychar* _mychar)
{
	cout << "==================" << endl;
	cout << " 名前:" << _mychar->name << endl;
	cout << "  職業:" << jobs[_mychar->job] << endl;
	cout << "    HP:" << _mychar->hp << endl;
	cout << "    MP:" << _mychar->mp << endl;
	cout << "   STR:" << _mychar->str << endl;
	cout << "   ATK:" << _mychar->atk << endl;
	cout << "   DEF:" << _mychar->def << endl;
	cout << "   MGK:" << _mychar->mgk << endl;
	cout << "   ING:" << _mychar->ing << endl;
	cout << "   LUK:" << _mychar->luk << endl;
	cout << "==================" << endl;
}
//素敵なフォーマットでキャラのパラメータを表示する関数
//引数で、キャラクターを表す構造体を渡す(ポインタでアドレス渡し)
void printCharSummary(struct mychar* _mychar)
{
	cout << "------------------" << endl;
	cout << " 名前:" << _mychar->name << endl;
	cout << "    HP:" << _mychar->hp << endl;
	cout << "    MP:" << _mychar->mp << endl;
	cout << "------------------" << endl;
}
// mychar構造体を2つ引数にとって、第1引数のほうが、第2引数の方に1回だけ攻撃する関数をつくる!
// 第1引数 struct mychar *_to
// 第2引数 struct mychar *_from
void beatCharacter(struct mychar* _to, struct mychar* _from)
{
	cout << _to->name << "の攻撃" << endl;
	int dam = int(_to->atk + _to->str * 0.5) - int(_from->def * 0.25);
	cout << _from->name << "の受けたダメージ:" << dam << endl;
	_from->hp = _from->hp - dam;
	printCharSummary(_from);
}
float getDefRatio();
float getDefRatio()
{
	return(0.4 * (float)rand() / RAND_MAX + 0.3);
}
// 第1引数 struct mychar *_to 攻撃する側
// 第2引数 struct mychar *_from 攻撃される側
void beatDefence(struct mychar* _to, struct mychar* _from)
{
	float ratio;//防御力にかける倍率の係数 0.3~0.7
	//ここで乱数使ってごにょごにょして
 
	ratio = getDefRatio();
	//0.4*(float)rand()/RAND_MAX + 0.3
	//0.3~0.7の値を発生させる関数
	cout << _from->name << "は防御の姿勢をとった!" << endl;
	cout << _to->name << "の攻撃" << endl;
	int dam = int(_to->atk + _to->str * 0.5) - int(_from->def * ratio);
	//            敵   100       100 => 150        自  100 => 25       
 
	//↑この式にごにょごにょする!
	//防御を選んだ時に、通常防御力は0.25までしか効果がないが、
	//ランダムで防御力の0.3~0.7まで効果が出るように関数を書き換える!
	//0.0~0.4の乱数作って、0.3足す!
	//ダメージがマイナスになったらHP増えちゃうから、dam = 0にする
	cout << _from->name << "の受けたダメージ:" << dam << endl;
	if (dam < 0)dam = 0;
	_from->hp = _from->hp - dam;//
	printCharSummary(_from);
}
 
void printBattleMenu()
{
	cout << "どうする?" << endl;
	cout << "+---------------+" << endl;
	cout << "| 1: たたかう   |" << endl;
	cout << "| 2: まほう     |" << endl;
	cout << "| 3: 防御       |" << endl;
	cout << "| 4: ステータス |" << endl;
	cout << "| 5: 逃げる    |" << endl;
	cout << "+---------------+" << endl;
}
 
int main()
{
	srand((unsigned int)time(nullptr));
	mychar hero; //作っただけでは初期化されないのでメンバー変数は何も入ってない
	setCharacterStatus(&hero, "オルテガ", 0, 100, 10, 50, 100.0, 80.0, 10.0, 5, 20);
	//                 構造体,      名前, job, hp, mp,str,   atk,  def,  mgk,ing, luk
	//printCharacterStatus(&hero);
	//printCharSummary(&hero);
	mychar mob;
	setCharacterStatus(&mob, "モブ夫", 3, 500, 80, 10, 20.0, 15.0, 5.0, 10, 10);
	//                 構造体,      名前, job,  hp, mp,str,  atk,  def,  mgk, ing, luk
	//printCharacterStatus(&mob);
 
	cout << mob.name << "が現れた!" << endl;
 
	bool turn = true;
	int command;
	bool isDefence = false;
	//turnがtrueの時はheroの攻撃ターン
	//turnがfalseの時はmobの攻撃ターン
	while (hero.hp > 0 && mob.hp > 0)
	{
		if (turn)//自分のターン
		{
 
			do {
				printCharSummary(&hero);
				printBattleMenu();
				cin >> command;
				std::system("cls");
			} while (command < 1 || command > 5);
			//1~5の値によって分岐
			//今のところ1を選んだ時だけ攻撃
			//その他は、それはできないと表示
			switch (command)
			{
			case 1:
				beatCharacter(&hero, &mob);
				turn = !turn;
				break;
			case 2:
				cout << "それはできない" << endl;
				break;
			case 3:
				isDefence = true;
				turn = !turn;
				break;
			case 4:
				//ステータス表示の処理を完成させる!
				//全ステータス表示する関数あったよね!
				printCharacterStatus(&hero);
				break;
			case 5:
				cout << "それはできない" << endl;
				break;
			default:
				cout << "エラー! それはできない" << endl;
				break;
			}
			cin.get(); //エンター待ち
		}
		else//敵のターン
		{
			if (isDefence)
			{
				beatDefence(&mob, &hero);
				isDefence = false;
			}
			else
			{
				beatCharacter(&mob, &hero);
			}
			cout << "Push Enter to Next Turn";
			cin.get(); //エンター待ち
			std::system("cls"); //画面クリア
			turn = !turn;
		}
		//ターンの切り替え
		//turn = !turn;
		//cout << turn << endl;
	}
 
	return 0;
}

関数型プログラミング的処理の概要

main()の中
・乱数の初期化の処理
・キャラクタの宣言(heroとmob)
・キャラクタのパラメタの初期処理
 1.heroの初期化
 2.mobの初期化
・メッセージ(敵の出現):オープニング?
・こっからバトルループ
 1.自分のターン
  ・コマンド選択
  ・たたかう、まほう、ステータス表示、
   防御、逃げる
   1:戦うが選ばれた時の処理(実装済み)
   2:魔法が選ばれた時の処理(未実装)
   3:ステータス表示が選ばれた時の処理
   4:防御の処理(実装済み)
   5:逃げるの処理(未実装)
 2.敵のターン
   1:防御が選ばれていないときの敵の攻撃
   2:防御が選ばれているときの敵の攻撃

関数型言語では、まず仕事を「まとまりごと」に分け、切り分けた仕事を並べて処理の流れを作り、それ(処理のまとまり=切り分けた仕事)を関数として設計していくことでプログラムを組み立てていくことが多い。

main()の中
・乱数の初期化の処理
 => srand((unsigned int)time(nullptr))
・キャラクタの宣言(heroとmob)
 => struct mychar hero, mob;
・キャラクタのパラメタの初期処理
 1.heroの初期化
   => setCharacterStatus(&hero, 省略);
 2.mobの初期化
   => setCharacterStatus(&mob, 省略);
・メッセージ(敵の出現):オープニング?
・こっからバトルループ
 1.自分のターン(turn == 1の時)
  ・コマンド選択(cin >> command)
  ・たたかう、まほう、ステータス表示、
   防御、逃げる (1~5を入力)
   1:戦うが選ばれた時の処理(実装済み)
     戦う(hero -> mob) : beatCharacter(&hero, &mob);
   2:魔法が選ばれた時の処理(未実装)
        「 それはできない」を表示
   3:ステータス表示が選ばれた時の処理
    詳細ステータス表示:printCharacterStatus(&hero);
   4:防御の処理(実装済み)
    防御フラグをON:実際の処理は敵->自分の攻撃の時に効いてくる
   5:逃げるの処理(未実装)
        「それはできない」:を表示
 2.敵のターン
   1:防御が選ばれていないときの敵の攻撃
     戦う(mob -> hero):beatCharacter(&mob, &hero);
   2:防御が選ばれているときの敵の攻撃
     防御した相手と戦う(mob -> hero):beatDefence(&mob, &hero); isDefence = false;

関数型プログラミングでは、
このように、何の処理を何の変数に施すか?により処理を切り分け、仕事分けを行い、その仕事を関数にしていく。
何の変数に、が処理をされる変数=敵や、自分のキャラ、その他さまざまなゲーム中の変数やパラメータ、状態となる。
つまり、これらのパラメータや状態、変数などを操作するための関数を効率よく作っていくというプログラミングスタイルになる。