抽選するプログラム (C++/OpenSiv3D)


ゲームやアプリで 抽選ランダムな選択 を行うことがあります。選ばれる確率が一定 or 異なる、一度に 1 つを選ぶ or 複数を選ぶなど、様々なケースに応じた C++/OpenSiv3D プログラムの書き方を紹介します。

ある確率でイベントが起こる

30% の確率で当たりくじを引く

RandomBool(p) 関数は、引数に渡された確率 p にしたがって、ランダムに truefalse を返します。
30% の確率で true を得るには Random(0.3) です。

# include <Siv3D.hpp>

void Main()
{
    for (int32 i = 0; i < 10; ++i)
    {
        // 30 % の確率で true, 70% の確率で false を返す
        Print << RandomBool(0.3);
    }

    while (System::Update());
}
出力例.txt
false
false
false
false
false
true
false
true
false
true

複数の選択肢からランダムに 1 つ選ぶ(均等な確率)

サイコロをふる

同様に確からしい確率で、ある範囲に含まれる整数をランダムに選ぶ場合は Random(min, max) 関数を使います。
1~6 が書かれたサイコロの結果を得るには Random(1, 6) です。

# include <Siv3D.hpp>

void Main()
{
    for (int32 i = 0; i < 10; ++i)
    {
        // 1 以上 6 以下の数をランダムに返す
        Print << Random(1, 6);
    }

    while (System::Update());
}
出力例.txt
6
4
3
5
6
1
5
4
1
4

集合からランダムに 1 つ選択する(均等な確率)

ランダムに目的地を決める

用意してある配列データから 1 つの値を選ぶときは Array::choice() が便利です。

# include <Siv3D.hpp>

void Main()
{
    const Array<String> cities =
    {
        U"札幌", U"仙台", U"東京", U"横浜", U"名古屋",
        U"京都", U"大阪", U"神戸", U"広島", U"福岡"
    };

    for (int32 i = 0; i < 10; ++i)
    {
        // 配列の要素からランダムに 1 つ選択
        Print << cities.choice();
    }

    while (System::Update());
}
出力例.txt
京都
福岡
東京
京都
大阪
横浜
仙台
仙台
大阪
大阪

集合から複数の要素をランダムに取り出す(均等な確率)

ビンゴカードを作る


サイコロや目的地のケースと異なり、一度使用した番号を使わない 制約があります。
このようなケースでは 1~75 の全ての数を配列に格納して Array::choice(n) を使うことで、重複しないように n 個の要素を選択できます。
結果の配列は、当初の順序を保って値が小さい順に並んでいるので、Array::shuffle() で順番をシャッフルするのを忘れないようにしましょう。

# include <Siv3D.hpp>

void Main()
{
    // { 1, 2, 3, ..., 75 } の配列を作成
    const Array<int32> numbers = Range(1, 75);

    // 重複を許さず 25 個の要素を選択
    Array<int32> results = numbers.choice(25);

    // この時点で数の順序は変わっていない(小さい順)
    Print << results;

    // 順序をランダムにするためにシャッフル
    results.shuffle();

    Print << results;

    while (System::Update());
}
出力例.txt
{1, 3, 16, 18, 30, 31, 34, 35, 39, 42, 44, 46, 47, 49, 52, 53, 57, 60, 61, 63, 67, 68, 69, 72, 74}
{35, 61, 57, 18, 53, 69, 60, 30, 72, 63, 52, 68, 39, 46, 44, 49, 16, 1, 74, 42, 67, 31, 3, 34, 47}

uint32 型のランダムな ID を 100 個生成する

ビンゴカードのケースと異なり、uint32 型の ID の選択肢は 40 億個以上 あるため、配列を作成して Array::choice(n) することはできません。
このようなケースでは、ランダムな値を生成するごとに、HashSet を使ってこれまでの ID と重複がないかをチェックします。
HasSet::insert は、要素を追加するとき、その値がすでに存在していたら、戻り値の second 部分が false になります。

# include <Siv3D.hpp>

void Main()
{
    Array<uint32> idList;

    for(HashSet<uint32> reserved; idList.size() < 100;)
    {
        // ランダムな uint32 型の値を生成
        const uint32 value = Random<uint32>(Largest<uint32>());

        if (!reserved.insert(value).second)
        {
            // すでに同じ値がある場合はやりなおし
            continue;
        }

        idList << value;
    }

    Print << idList;

    while (System::Update());
}
出力例.txt
{3229347734, 1953432812, 550965512, 1371021958, 1375041536, 3487488955, 3627240948, 1691294923, 566624821, 26500009, 879028738, 1592832790, 4157735074, 2387705014, 3139796959, 2655139832, 3485895190, 3338896242, 294058348, 337388880, 8780761, 3203784010, 3044718502, 790169906, 2808952376, 4086756990, 2172472708, 2881672836, 63719007, 2178763472, 681461789, 2859872541, 1732324329, 1205894110, 1990285747, 2849400797, 2881360344, 1066258756, 1726436106, 2895113654, 1665898651, 3132533310, 1090053983, 2651873845, 3681606511, 1083403134, 212008756, 1913963110, 2751918639, 1769198644, 317470050, 1000279371, 4016423153, 420798583, 2749822270, 22767687, 1408002915, 1889722540, 2854369142, 2244555779, 18554211, 3573335033, 1399786361, 3647524309, 3643605667, 86764166, 622672486, 2012885757, 2424136685, 1004656069, 123439045, 4213705520, 4075920828, 519017689, 3120796712, 823653176, 665862823, 3249708005, 2803952774, 3750178571, 1010630758, 1074553705, 449881868, 2900021154, 3647712880, 2797866448, 1690735920, 188786311, 3036402802, 1842889409, 496441267, 3088651060, 3669787076, 3749964443, 3017167199, 1038745033, 3857463142, 3557317987, 3294000666, 2878195577}

複数の選択肢(それぞれ異なる出現確率)からランダムに選択する

出現確率を制御する


ある選択肢は 30% の確率で出現、ある選択肢は 0.1% の確率で出現といったように、選択肢ごとに出現確率を制御したい場合は DiscreteDistribution を使います。
DiscreteDistribution にそれぞれの選択肢の選ばれやすさを指定し、選択肢とともに DiscreteSample に渡すと、その確率に応じた結果が返されます。
DiscreteDistribution で指定する値の合計は、ちょうど 1.0 や 100 である必要はありません。

# include <Siv3D.hpp>

void Main()
{
    // 選択肢
    const Array<String> items
    {
        U"★",
        U"★★",
        U"★★★",
        U"★★★★",
        U"★★★★★"
    };

    // それぞれの選択肢の「選ばれやすさ」
    // ★ は ★★★★★ の 100 倍出現しやすい
    const DiscreteDistribution<size_t> weights
    {
        100.0,
        50.0,
        10.0,
        4.0,
        1.0
    };

    for (int32 i = 0; i < 10; ++i)
    {
        Print << DiscreteSample(items, weights);
    }

    while (System::Update());
}
出力例.txt
★★★
★★
★
★
★
★
★
★★
★

用途に合わせた関数を使って、簡潔で高速な抽選システムを実装しましょう。