11
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Siv3DAdvent Calendar 2018

Day 1

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

Last updated at Posted at 2018-12-01

ゲームやアプリで 抽選ランダムな選択 を行うことがあります。選ばれる確率が一定 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
★★★
★★
★
★
★
★
★
★★
★

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

11
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
11
6

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?