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

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

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

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

```cpp
# 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)` です。

```cpp
# 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()` が便利です。

```cpp
# 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](https://qiita-image-store.s3.amazonaws.com/0/30255/a500e87e-35dd-3578-e49d-3494c0771b3a.png)
サイコロや目的地のケースと異なり、**一度使用した番号を使わない** 制約があります。
このようなケースでは 1～75 の全ての数を配列に格納して `Array::choice(n)` を使うことで、重複しないように `n` 個の要素を選択できます。
結果の配列は、当初の順序を保って値が小さい順に並んでいるので、`Array::shuffle()` で順番をシャッフルするのを忘れないようにしましょう。

```cpp
# 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` になります。

```cpp
# 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](https://qiita-image-store.s3.amazonaws.com/0/30255/8506d104-2b16-d196-5d25-2d6124c3f43b.png)
ある選択肢は 30% の確率で出現、ある選択肢は 0.1% の確率で出現といったように、選択肢ごとに出現確率を制御したい場合は `DiscreteDistribution` を使います。
`DiscreteDistribution` にそれぞれの選択肢の選ばれやすさを指定し、選択肢とともに `DiscreteSample` に渡すと、その確率に応じた結果が返されます。
`DiscreteDistribution` で指定する値の合計は、ちょうど 1.0 や 100 である必要はありません。

```cpp
# 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
★★★
★★
★
★
★
★
★
★★
★
```

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