今回は多くの人が間違った解釈をしていると思われる確率について、私の考えを述べたいと思います。これはあくまでも私の考えですので、正しいとも間違っているともどう思われても構いません。ただ、これをきっかけに何かを感じていただければ幸いです。


  • 乱数の結果は予測不能
具体的な例え話をすると分かりやすいと思うのでサイコロの目で説明してみます。サイコロは6面体です。重量バランスが計算されていて、各面が上になる確率は等しく 1/6 になっています。では、サイコロの目で 1 を出したい時は 6回振ったら確実に 1 が出るのでしょうか?

これは確率論的には間違った考え方です。サイコロの目が表示される確率は、サイコロを振る度に常に 1/6 なのです。これをプログラムで記述するとして例えば…

int num = 0; int dice = -1; do { dice = (rand() % 6) + 1; printf("%2d回目: サイコロは%dです。\n", ++num, dice); } while (dice != 1);
これを実行してみます。運が悪いと
運が悪いサイコロの目
こんな風に 20回目にしてようやく 1 が出るなんてことも起こり得ます。運が良ければいきなり 1 が出ることもあります。※ 乱数の偏りは今は無視します。
運が良かったサイコロの目
これが確率です。アタリが出る確率が 1/6 だとして、6回チャレンジしても出ないのは当たり前です。出ないからバグだー!インチキだー!と叫ぶのは、本来は確率論的にはお門違いなのです。

※ ちょっとした部品を作るにはとても便利。今まで3Dプリンタはあまり必要性を感じていなかったんですが、既に部品が発売されていない、例えばPCの蓋の部分とか、同人ハードのケースとか、自分で作れるのはとても便利だと思うようになりました。

  • 間違った確率論を再現する
さてさて、残念ながら正しい確率を理解してもらえない事は、実はかなり多いのです。そのため、私は実は確率を使うゲームで、その確率が目に見える場合は、単純な確率を使わないようにしています。私はシャッフルを使います。これなら勘違いしている人が納得できる確率となります。

先のサイコロの例で考えます。6面体ですから、出目は 1,2,3,4,5,6 の 6種類です。これを配列に入れます。そして、シャッフルします。その配列を最初から順番に読み出していけば、全ての値は 6回のうちに必ず1度は出現します。そして、配列要素を全て使い切ってしまったら、また新たにシャッフルし直します。

int rndtbl[] = { 1,2,3,4,5,6 }; int rndcnt = 0; /// <summary> /// サイコロの目をかき混ぜる /// </summary> void shuffleDice(void) { for (int i = 0; i < _countof(rndtbl) * 2; ++i)
{ int i1 = rand() % _countof(rndtbl);
int i2 = rand() % _countof(rndtbl);
int tmp = rndtbl[i1]; rndtbl[i1] = rndtbl[i2]; rndtbl[i2] = tmp; } } /// <summary> /// サイコロの目を取得する /// </summary> int getDice(void) { if (rndcnt >= _countof(rndtbl)) { shuffleDice(); rndcnt = 0; } return rndtbl[rndcnt++]; }
これで最初に一度だけ shuffleDice(); を呼び出しておけば、あとは getDice() を呼び出すだけで、勘違い確率に則った「期待される確率値」のサイコロの目が得られるようになります。

shuffleDice(); for (int i = 1; i <= 20; ++i) { printf("%2d回目: サイコロの目は%dです。\n", i, getDice()); }
シャッフルされたサイコロの目
私は思うのですが、確率の理解が出来ていないとユーザーに説明することは不可能です。ユーザー数は相当数おられますが、開発サイドは個人制作だと一人とか数名です。いちいち説明なんて無理に決まってます。ならば、勘違いされている人のイメージに合う結果を出してやれば良いのではないかと。それがゲームデザイン的に許容できるのであれば、本物のランダムではなくシャッフルで良いと私は思うのです。

※初めて買うならこちらで良いかも。というか、3Dプリンタはここまで安くなっているんですねぇ。まあ、印刷用の材料費はそれなりに掛かってはしまいますが、オリジナルの造形が出来ることを考えると安いと思います。

  • 確率を操作する ※ 2023/08/15 07:00 追記
さて、先の例では乱数とは言い難い結果になっています。これを少し調整してみます。まず、同じ数が連続することがないのは流石におかしいですね。なので、同じ数が3連続までは許容する場合は、配列を少しだけ弄ります。

int rndtbl[] = { 1,2,3,4,5,6,1,2,3,4,5,6,1,2,3,4,5,6, };

たったこれだけで、同じ数が3連続までは発生する事になります。
連続で同じ数が発生する
こちらのほうがより乱数っぽいですよね。また、大きい数ほど発生しづらくする事も出来ます。これも配列を弄るだけで出来てしまいます。例えば…

int rndtbl[] = { 1,1,1,1,1,1,2,2,2,2,2,3,3,3,3,4,4,4,5,5,6, };

これで 6 が発生するのは 21回に一度きりという確率になります。
大きい数ほど発生しづらい
ゲーム内でユーザーに気が付かれないように確率操作はこれで簡単に出来るようになります。ゲーム制作のなにかの参考になれば幸いです。

EPSON インクジェット複合機 [L判〜A4] エコタンク搭載モデル EW-M754TW ホワイト【楽天】
EPSON インクジェット複合機 [L判〜A4] エコタンク搭載モデル EW-M754TW ホワイト
※ インクで利益を取るタイプじゃないので本体は少しお高め。ですが、プリンタは本来コレぐらいの値段が正当価格だと思うんですよね。これで印刷代は 1/3 ぐらいに抑えられます。私ならこちらを買います。