Help us understand the problem. What is going on with this article?

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

More than 1 year has passed since last update.

ゲームやアプリで 抽選ランダムな選択 を行うことがあります。選ばれる確率が一定 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
京都
福岡
東京
京都
大阪
横浜
仙台
仙台
大阪
大阪

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

ビンゴカードを作る

bingo_card.png
サイコロや目的地のケースと異なり、一度使用した番号を使わない 制約があります。
このようなケースでは 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}

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

出現確率を制御する

gachagacha_atari_man.png
ある選択肢は 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
★★★
★★
★
★
★
★
★
★★
★

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

Reputeless
小さくなってもコードはモダン!▼ 未定義動作なしの名コーダー!▼ 実装はいつも 3 つ!!(GCC/Clang/MSVC)▼ OpenSiv3D, cppmap 作者
https://ryo-suzuki-contact.github.io/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした