はじめに
今回のはマジでネタです…って言いたいんですけど、あるゲームに組み込もうかなと考えてたりします。
前提
- プログラミング言語はC++
- 乱数はメルセンヌ・ツイスタを使用
相加平均と相乗平均について
相加平均
(a+b)/2
相乗平均
\sqrt{ab}
※ただし、a > 0かつb > 0
相加平均と相乗平均の間にはこういう関係式が成り立ちます
(相加平均)≧(相乗平均)
不等号をよく見て下さい。「大なり」ではなくて「大なりイコール」です。
どういう時にイコールになるかというと、a=bの時です。
実際にbにaを代入して計算してみて下さい。
ここでは面倒なのでわざわざ書きません。
コード
さて、本題のコードですが、正直なところそのまま相加平均を最大値、相乗平均を最小値にしてしまうと、乱数は常に正の値となり、攻撃力と守備力から求めた基礎ダメージ値よりも小さいダメージになるということが全くなく、生きるか死ぬかというハラハラ感なんてほとんどなく、基本的に「あ、これ死んだわ…」と悟るだけです。そんなゲーム、誰がやりたいんでしょうね。
じゃあ相乗平均に-1かければいいじゃんって?
じゃあ試しに基礎ダメージを純粋に攻撃力と守備力の差として、攻撃力を400、守備力を4として、攻撃力と守備力の相加平均と相乗平均計算してみましょう。
そうすると、相加平均が202、相乗平均は40となるので、あなたの発想で行くと、乱数の範囲は-40~202。
あまりにプラスに偏り過ぎじゃないですかね?
そこで私は考えました。
相乗平均は相加平均より大きくなることは決して無いので、相加平均から相乗平均の値を引いた値を使おうと。
ここで和を使わない理由はすごく単純で「乱数の範囲が広くなりすぎるから」です。
そしてこの値をAとしましょう。
私が東京理科大学の神楽坂一丁目通信局に所属していた時に作った「モンスター討伐ゲーム アンチ核家族化」(未だに何このタイトルと思ってる)のダメージ計算にこれを組み込むとします。
BPは特技や魔法の基礎威力、MGは属性の相性によって発生する倍率とした時、ダメージ値Xは
X=(ATK+BP×MG-DEF/2)+RAND(-A/16, A/16)
通常攻撃の場合を考えるとして、さっきの値を当てはめると、ダメージの範囲は
X=(400-4/2)+RAND(-16, 16)
つまり、ダメージの範囲は382~414となるわけです。現在HPが415以上あるなら最低1は残りますけど383~414であれば死ぬか死なないかでめちゃくちゃハラハラするわけですね。
この乱数生成をC++のコードにすると(小数点以下切り捨て)
#ifndef RANDOM_HPP
#define RANDOM_HPP
#include <random>
class Random {
private:
static thread_local std::mt19937 mt;
public:
static std::mt19937& GetEngine() { return mt; }
};
#endif
#include "Random.hpp"
#include <array>
#if defined(__MINGW32__) && defined(__GNUC__) && !defined(__clang__)
#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <system_error>
#include <limits>
#include <string>
#include <Windows.h>
#include <wincrypt.h>
namespace workaround_mingw_gcc {
class random_device {
private:
class crypt_context {
private:
HCRYPTPROV prov_;
public:
crypt_context(DWORD prov_type, LPCTSTR container = nullptr, LPCTSTR provider = nullptr, DWORD flags = 0) {
const auto success = ::CryptAcquireContext(&this->prov_, container, provider, prov_type, flags);
if (!success) {
throw std::system_error(
std::error_code(::GetLastError(), std::system_category()),
"CryptAcquireContext:(" + std::to_string(success) + ')'
);
}
}
crypt_context(const crypt_context&) = delete;
void operator=(const crypt_context&) = delete;
~crypt_context() noexcept {
::CryptReleaseContext(this->prov_, 0);
}
//HCRYPTPROV& get() noexcept { return this->prov_; }
const HCRYPTPROV& get() const noexcept { return this->prov_; }
};
crypt_context prov_;
public:
using result_type = unsigned int;
explicit random_device(const std::string& /*token*/ = "workaround_mingw_gcc ")
: prov_(PROV_RSA_FULL)
{}
random_device(const random_device&) = delete;
void operator=(const random_device&) = delete;
//~random_device() = default;
double entropy() const noexcept { return 0.0; }
result_type operator()() {
result_type re;
const auto success = ::CryptGenRandom(this->prov_.get(), sizeof(re), reinterpret_cast<BYTE*>(&re));
if (!success) {
throw std::system_error(
std::error_code(::GetLastError(), std::system_category()),
"CryptGenRandom:(" + std::to_string(success) + ')'
);
}
return re;
}
static constexpr result_type min() { return std::numeric_limits<result_type>::min(); }
static constexpr result_type max() { return std::numeric_limits<result_type>::max(); }
};
} // namespace workaround_mingw_gcc
namespace cpprefjp {
using workaround_mingw_gcc::random_device;
}
#else //defined(__MINGW32__) && defined(__GNUC__) && !defined(__clang__)
namespace cpprefjp {
using std::random_device;
}
#endif //defined(__MINGW32__) && defined(__GNUC__) && !defined(__clang__)
namespace {
std::mt19937 InitEngine() {
cpprefjp::random_device rand;
std::array<std::uint_least32_t, sizeof(std::mt19937) / sizeof(std::uint_least32_t)> v;
std::generate(v.begin(), v.end(), std::ref(rand));
std::seed_seq seed(v.begin(), v.end());
return std::mt19937(seed);
}
}
thread_local std::mt19937 Random::mt = InitEngine();
#include "Random.hpp"
#include <cmath>
inline int getRand(const int& attack, const int& defense) {
const int arithmeticMean = static_cast<int>((attack + defense) / 2);
const int geometricMean = static_cast<int>(std::sqrt(attack * defense));
const int randRangeVal = (arithmeticMean - geometricMean) / 16;
const std::uniform_int_distribution<int> rand(-randRangeVal, randRangeVal);
return rand(Random::mt);
}
ちなみにこれ組み込むの、アンチ核家族化オンラインだったりするんだけどね(笑)
こいつは技術をひたすらに無駄遣いして最高のクソゲーを作ってやろうっていうとんでもないプロジェクトで作ってるから、現状を言ってしまうと、モンスターのAIですらプレイヤーの行動を学習・予測して一番苦しくなるようにしてるから結構えぐいと思うし、そんなとんでもAI組み込むためだけに2DゲームなのにDirectX 12まで使おうとしてるくらいだから最高に技術の無駄遣いをしてやるつもり満々なわけなのよ。一体最終的にはどんな得体のしれない化け物AIとなるのかすごく楽しみで夜しか眠れない(笑)