【C言語】乱数を生成するためには”種”が必要

C言語

ようこそ、KiRiOのブログへ!

本記事では、「新・明解C言語中級編」の自由課題について解説します。


自由課題のとおり、この参考書には解答が載っておりません。
そこで、本記事で私なりの解答を共有しますので、ぜひ解答の参考にしてみてください。

今回解説する課題は「演習1-1」で、課題内容は以下のとおりです。

演習1-1《おみくじ》プログラムを作成せよ。0〜6の乱数を生成し、その値に応じて、〔大吉〕〔中吉〕〔小吉〕〔吉〕〔末吉〕〔凶〕〔大凶〕を表示すること。

まずは、この課題内容を分解していきます。

  1. おみくじプログラムを作成
  2. 0〜6の乱数を生成
  3. 生成した乱数の値に応じて占い結果を表示

これらを順番に設計していきます。

設計

《おみくじ》プログラムを作成

「プログラム」というと大層に聞こえますので、実際の状況から考えていきます。
実際のおみくじでは、みくじ棒またはみくじ箋を引くことで自分の吉凶を占います。

すなわち、「ユーザが《おみくじ》プログラムに対して何らかの操作を行うことで、プログラム内の処理が実行され、占い結果が表示される」というように設計します。

0〜6の乱数を生成

次に、乱数を生成します。
C言語で乱数を生成するためには、2つの処理を使用します。

  1. 乱数生成の準備をする
  2. 乱数を生成する

ここでの内容は、文章だけだとわかりづらいので、あまり深く考えすぎずに流し読みでも結構です。

C言語では、0からヘッダファイルで定義された定数値までの範囲内で疑似乱数を生成します。
“擬似”乱数と書きましたが、乱数を生成する際には基準値というものが用いられます。
この基準値のことを種(seed)と呼び、初期状態では定数値1が設定されています。

基準値が設定されていることでどのような問題が起こるか、について説明します。

例えば、0〜9の乱数を10回表示するプログラムを作成した場合を考えます。
このプログラムを実行すると、プログラム内では乱数を表示するたびに0〜9の整数がランダム生成されます(例えば、1→0→3→9→8→8→8→3→0→1)。

しかし、一度プログラムを終了してから再度プログラムを実行すると、また同じ整数が表示されます(1→0→3→9→8→8→8→3→0→1)。
これは、乱数生成時の種が毎回同じ値として設定されているからです。

これでは、プログラムを何度も繰り返し実行しているうちに乱数の値や順番を覚えてしまうため、乱数を生成しているとは言えません(ゆえに疑似乱数と呼ばれます)。

この問題を解決するために必要な処理が、乱数生成の準備です。
ここでいう準備とは「プログラム実行時に、基準値を毎回異なる値に設定する」というものです。

上記内容をもとに、C言語では乱数を生成します。
ここで完璧に理解する必要はありませんが、実装時に内容が結び付けられるように、頭の片隅に置いておいてください。

生成した乱数の値に応じて占い結果を表示

最後に、生成した乱数をもとに占い結果を表示します。

すなわち「生成した乱数が***であれば、占い結果を***とする」ということです。
今回生成する乱数の範囲は0〜6の整数と、占い結果の種類と一致するため、単純に「0→大吉、1→中吉、…、6→大凶」とします。

乱数 0 1 2 3 4 5 6
占い結果 大吉 中吉 小吉 末吉 大凶

これで、基本的な設計は完了です。

おまけ(追加機能)

以上の設計で課題内容を実現できるのですが、もう少し実際の状況にあわせて機能を追加していきます。
今回追加する機能は以下の3つです。

  1. おみくじの開始を選択できる
  2. おみくじを再度引くか選択できる
  3. 占い結果によって文字色を変更する

おみくじの開始を選択できる

おみくじを引くためにプログラムを実行するので、絶対的に必要な機能ではありませんが、万が一誤ってプログラムを実行したことを想定して、この機能を追加しておきます。
すなわち、

  • プログラム実行後に「おみくじを引きますか?(はい/いいえ)」というメッセージを表示
  • 「はい」を選択するとおみくじを引くことができる
  • 「いいえ」を選択するとプログラムが終了する
  • それ以外の値が入力されると再度メッセージが表示される

というものです。

おみくじを再度引くか選択できる

「おみくじを1回引いて終わり」だけですと面白みがありませんから、ユーザが何回でも引くことができ、ユーザのタイミングでおみくじを終了することができるようにします。
すなわち、

  • 1回目の占い結果が表示された後に「もう一度引きますか?(はい/いいえ)」というメッセージを表示
  • 「はい」を選択すると再度おみくじを引くことができる
  • 「いいえ」を選択するとプログラムが終了する
  • それ以外の値が入力されると再度メッセージが表示される

というものです。

占い結果によって表示色を変更する

この機能はデザイン性の向上を目的としたものですので、参考程度に見ておいてください。
C言語では、文字属性制御の一つとして、文字色を設定することができます。
設定できる文字色は「」「」「」「」「」「」「水色」「」の8色ですので、今回は「赤→大吉黄→中吉緑→小吉白→吉水色→末吉青→凶紫→大凶」とします。

文字色 水色
占い結果 大吉 中吉 小吉 末吉 大凶

今回は背景色が黒色(黒画面)の場合を前提としておりますので、白画面で実行する場合は「白」と「黒」を反転しておいてください。

追加機能を含めた設計は以上となります。

実装

それでは、設計内容をもとに実装していきます。
実装順番は以下のとおりとします。

  1. おみくじの開始を選択する
  2. 乱数の種を設定して乱数を生成する
  3. 生成した乱数の値に応じて占い結果を表示する
  4. おみくじを再度引くか選択する

おみくじの開始を選択する

こちらは「do-while」を使いましょう。

do文の中では、メッセージを表示してその後の処理を選択させます。
設計段階では「はい/いいえ」でしたが、ここでは簡単のため「1を入力したら『はい』、0を入力したら『いいえ』」とすることにします。
また、いいえ(0)が入力されたら、プログラムを終了しなければならないため、if文を用いて「return 0;」が実行されるようにします。

while文の中では、選択処理が繰り返し行われるための条件を記述するため、「ユーザからの入力値が『はい(1)』でも『いいえ(0)』でもなかった場合」とします。

これらを実装したものが下記プログラムです。

#include <stdio.h>

int main(void)
{
	int try;

	do
	{
		printf("おみくじを引きますか?【はい…1/いいえ…0】:");
		scanf("%d", &try);

		if (try == 0)
		{
			return 0;
		}
	} while (try != 0 && try != 1);

	return 0;
}

乱数の種を設定して乱数を生成する

設計段階では抽象的な説明となっていましたが、ここではその内容を具体的な作業に落とし込んでいきます。

まず、乱数の生成ですが、C言語ではrand()を用います。
rand()0〜RAND_MAXの範囲内で擬似乱数整数列を計算し、生成した擬似乱数を返します。
RAND_MAX<stdlib.h>で定義されているオブジェクト形式マクロで、処理系によってその値は異なります。

rand
ヘッダ #include <stdlib.h>
形式 int rand(void);
機能 0 以上 RAND_MAX 以下の範囲の擬似乱数整数列を計算する。
戻り値 生成した疑似乱数

乱数の生成範囲を指定する際は、最小値と最大値を、rand()の前後に以下のように記述します。

a 以上 (a + b) 以下の乱数を生成a + rand() % (b + 1);

今回の場合では、0〜6の乱数を生成するので「rand() % 7;」となります。

次に、乱数生成の準備ですが、C言語ではsrand()を用います。
srand()はその名のとおり、乱数を生成するための事前準備を行い(start rand)、設定する種を引数に渡します。

srand
ヘッダ #include <stdlib.h>
形式 void srand(unsigned seed);
機能 rand()の呼び出しで返す、新しい擬似乱数整数列の種をseedに設定する。
srand()を同じ種の値で呼び出すと、同じ疑似乱数整数列が生成される。
srand()より前にrand()を呼び出した場合、srand()が最初に種の値を1として呼び出された時と同じ整数列が生成される。
戻り値 なし

しかし、これだけでは種を変えることができるだけで、プログラム実行時に毎回異なる種が設定されるわけではありません(srand()を使用しない場合とsrand(1)を実行するのは同じことです)。

そこで、一般的に使われている手法が「プログラム実行時の時刻を種にする」というものです。
ここでは、time()を使い、引数には空ポインタ定数のNULLを渡します。

すなわち、srand(time(NULL));とすることで、プログラムを実行するたびに異なる種が設定されることになります。

これらを実装したものが下記プログラムです。

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main(void)
{
	int try;

	srand(time(NULL));

	do
	{
		printf("おみくじを引きますか?【はい…1/いいえ…0】:");
		scanf("%d", &try);

		if (try == 0)
		{
			return 0;
		}
	} while (try != 0 && try != 1);

	rand() % 7;

	return 0;
}

生成した乱数の値に応じて占い結果を表示する

設計段階で説明したとおり、生成した乱数に応じて「0→大吉、1→中吉、…、6→大凶」とします。
この処理を実現するためには条件分岐が必要ですが、比較対象が整数値のため、if文ではなく「switch」を用いた方が適切です。

そして、おみくじを再度引くかの選択ですが、こちらはおみくじの開始選択と同じ「do-while」を使用します。

do文の中では、占い結果を表示して再度おみくじを引くか選択させ、「1を入力したら『はい』、0を入力したら『いいえ』」とします。

while文の中では、再度おみくじを引くための条件式を記述するため、「ユーザからの入力値が『はい(1)』の場合」とします。

また、文字色の変更ですが、C言語では、文字列を表示する前に文字色を設定し、文字列表示後に文字色をリセットします。
文字色を設定する際は、printf()の引数に「\x1b[3nm」を渡します(nはカラーコード)。
文字色をリセットする際は、printf()の引数に「\x1b[0m」を渡します。

カラー
コード
水色
占い結果 0 1 2 3 4 5 6 7

これらを実装したものが下記プログラムです。

#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main(void)
{
	int omikuji;
	int try, retry;

	srand(time(NULL));

	do
	{
		printf("おみくじを引きますか?【はい…1/いいえ…0】:");
		scanf("%d", &try);

		if (try == 0)
		{
			return 0;
		}
	} while (try != 0 && try != 1);

	do
	{
		omikuji = rand() % 7;

		switch (omikuji)
		{
			case 0: printf("x1b[31m"); puts("\n【大吉】でした。"); break;
			case 1: printf("x1b[33m"); puts("\n【中吉】でした。"); break;
			case 2: printf("x1b[32m"); puts("\n【小吉】でした。"); break;
			case 3: printf("x1b[37m"); puts("\n【吉】でした。"); break;
			case 4: printf("x1b[36m"); puts("\n【末吉】でした。"); break;
			case 5: printf("x1b[34m"); puts("\n【凶】でした。"); break;
			case 6: printf("x1b[35m"); puts("\n【大凶】でした。"); break;
			default: break;
		}

		printf("x1b[0m");
		printf("もう一度引きますか?【はい…1/いいえ…0】:");
		scanf("%d", &retry);
	} while (try != 0 && try != 1);

	puts("おみくじを終了します。");

	return 0;
}

以上で完了となります。
プログラムを実行して、《おみくじ》プログラムとして機能しているか確認してみてください。

まとめ

それでは、本記事で説明した内容を以下にまとめます。

  1. 乱数を生成する際は、srand()で種を設定し、設定する種の値を引数に渡す。
  2. 一般的な種の設定方法は「プログラム実行時の時刻を種とする」で、time(NULL)を引数に渡す。
  3. 種の設定後は、rand()を用いて任意の範囲内で乱数を生成する。
  4. 生成範囲を指定しない場合は、0から<stdio.h>内で定義されているRAND_MAXまでの範囲で乱数が生成される。
  5. 生成範囲を指定する際は以下のように記述する。
    a 以上 (a + b) 以下の乱数:a + rand() % (b + 1)

ここまで、記事を読んでいただき、ありがとうございました。

最後に、今回取り上げた課題が記載されている本を紹介します。

今回扱った課題以外にも様々なプログラム開発について記載されているため、この記事とあわせて読んでいただくと、より理解が深まると思います。


また、参考として以下の本もご紹介しておきます。

こちらもオススメです。2021年12月14日に最新版が発売されました!

それでは、また別の記事でお会いしましょう!

コメント

タイトルとURLをコピーしました