第2章25 疑似乱数のシードを時間にする@イチからゲーム作りで覚えるC言語

疑似乱数のシードを時間にしたときのイメージ

疑似乱数のシードを時間にしたときのイメージ

イチからゲーム作りで覚えるC言語
連載ページ一覧へ
2章26 自作の関数を作ってみる
NEXT : 2章26 自作の関数を作ってみる
2章24 疑似乱数で任意の範囲の整数を表示しよう : PREV

この記事でやること

前回はプロジェクト「0195_generate_rand_with_range」を作り、疑似乱数の利用ついてお話ししました。
今回は、新しくプロジェクト「0200_change_randseed_to_nowtime」を作り、疑似乱数の種を現在のコンピュータの時刻に変更することで、起動するたびに違う乱数列がでるようなプログラムについてお話します。疑似乱数の種をコンピュータ時刻にすることは、タイミングによってランダムな結果となる敵の攻撃力や防御力、ドロップアイテムなど、いろいろなゲームで使われるいわば定石の方法です。

この記事はゆる~くC言語でコンソールゲームを作りながらプログラミングを学ぶための連載記事です。シリーズものですので記事の一覧はこちらを参照してくださいね。

疑似乱数の種を時刻にするプログラム

疑似乱数の種を時刻にするソースコードを実際にプログラムを動かして結果を見てみましょう。プロジェクト「0200_change_randseed_to_nowtime」を新しく作成し、ソースコード「DispRandomWithTime.c」を作成してみてください。

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main() {
 unsigned int nowtime = (unsigned int) time( NULL );
 srand( nowtime );
 char* playername = "ぷらんく";
 int zombiehp = 10;
 for (int i = 0; i < 5; i++ ) {
   int damage = rand() % 3 + 1;
   printf("%s の攻撃!ゾンビに %d ダメージ\n", playername, damage );
   zombiehp = zombiehp - damage;
   if (zombiehp <= 0) {
     printf("ゾンビは倒れた。\n");
     break;
   }
 }
 if (zombiehp > 0) {
   printf("ゾンビ:あぶなく死ぬところでしたよ。ハハ、にげよー!\n");
 }
 else {
   printf("ゾンビ:まさか死んでしまうとは!ガクッ\n");
 }
}

このコードを実行してみるとこんな結果になったかと思います。

ぷらんく の攻撃!ゾンビに 3 ダメージ
ぷらんく の攻撃!ゾンビに 3 ダメージ
ぷらんく の攻撃!ゾンビに 1 ダメージ
ぷらんく の攻撃!ゾンビに 2 ダメージ
ぷらんく の攻撃!ゾンビに 2 ダメージ
ゾンビは倒れた。
ゾンビ:まさか死んでしまうとは!ガクッ

ぷらんくが5 回攻撃をしています。当たり所が悪く(良く?)ダメージが入ってゾンビが倒れるかどうかは運しだいです。プログラムを起動するたびに違う結果となります。

乱数の種の時刻を設定を設定する

いままで疑似乱数を使用したプログラムは乱数の種が同じだったため、生成される乱数も同じ結果となっていました。
起動するたびに違う結果が得られるようにしたり、タイミングによってどの乱数が生成されるかわからないようにするにはどうしたら良いでしょうか。

一つの疑似的な方法として、乱数の種に「時刻」を使う方法があります。

srand関数は引数に、unsigned int型(正の整数)を設定して使います。今の時刻をうまくunsigned int 型に変換して、 srand関数に設定することができれば、プログラムを動かす時刻によってランダムに変わる結果がえられることになります。

時刻を扱うtime関数

C言語には標準的な関数として、時刻を扱うデータや関数が用意されています。
利用するためには、「#include <time.h>」をソースコード頭に記載してあげましょう。

これを記載することで、time関数など時間に関する基本的な関数を使うことができます。

時間関連の詳細な使い方はまた後ほど書いていこうと思いますが、今回の目的である「現在の時間を unsigned int 型に変換したものを取得する」方法は以下のような命令で実現できます。

(unsigned int) time( NULL );

これは、 time関数の第1引数に ”NULL” を代入した結果を、型キャストで unsigned int 型に変換しています。型キャストについては前のページでお話したので忘れた方はご参照してみてください。
NULL についてはポインタという仕組みをお話するときにしようと思いますが、とりあえず time ( NULL ) とすると、現在の時刻データをもつ、time_t型という専用の型が得られる作りになっています。
この現在の時刻データが入っている time_t 型を型キャストによって unsigned int 型に変換することで、数値で現在の時刻を整数で得ることができます。

これで得られる整数の単位はで、1970年1月1日(UTC)からの累計秒数が取得できる仕様となっています。

UTCは日本語では「協定世界時」と呼ばれ、Coordinated Universal Time(英語) や temps universel coordonné(フランス)などの複数言語から共通した頭文字をとって UTC と定められたようで、経度 0 度で定義されているイギリスのグリニッジ標準時(GMT)を基準とした時刻です。

ソースコードを追ってみよう

今回のソースコードを追ってみましょう。

必要なinclude 文

まずソースコードの頭で、疑似乱数の関数を使うため 「#include <stdlib.h>」、時刻を使うための関数として「#include <time.h>」を書いています。

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

乱数の種に現在時刻を設定

つぎに5行目で時刻(年月日時分秒)を unsigned int 型に変換して取得しています。

 unsigned int nowtime = (unsigned int) time( NULL );
 srand( nowtime );

unsigned int型の変数 nowtime に、1970年1月1日からの UTC としての累計秒数が代入されます。
その後、6行目で srand 関数に nowtime を設定しています。これで、秒を乱数の種にすることができました。以降、乱数を生成する rand 関数は時刻を種とした乱数列で生成されます。

変数の宣言と初期化

 char* playername = "ぷらんく";
 int zombiehp = 10;

7~8行目では今回のプログラムで使う変数の宣言と初期化をしています。
ゾンビの体力(zombiehp)は 10 と設定しています。

ゾンビへの攻撃を繰り返す処理

 for (int i = 0; i < 5; i++ ) {
   int damage = rand() % 3 + 1;
   printf("%s の攻撃!ゾンビに %d ダメージ\n", playername, damage );
   zombiehp = zombiehp - damage;
   if (zombiehp <= 0) {
     printf("ゾンビは倒れた。\n");
     break;
   }
 }

9行~17行目は for 文のループとなっています。 i が 0 から 4 の間は処理が繰り返し実行されます。
10行目で、「rand() % 3 + 1」を計算しています。前回のページでもお話しましたが、この式で 1 ~ 3 の整数がランダムに生成され、damage に代入されます。

11行目ではランダムに生成された damage でゾンビに攻撃した結果が表示されます。
12行目で実際にゾンビの体力は damage ぶん減らしていますね。

13~16行目は条件文で記載しています。もし、「zombiehp <= 0」 だったとき、つまり、ゾンビの体力が0以下となったときのみ実行されます。

14行目でゾンビが倒れたことを明記し、15行目で「break」により、forループから抜け出します。つまり、ゾンビの体力が 0 以下になった時点でループは終了することになります。

※運しだいですが、for ループで 5 回ゾンビを攻撃してもゾンビの体力が 0 以下にならないこともあります。その場合は for 文の繰り返し条件から外れるので、 for ループは終了します。

ゾンビからのコメント

最後にゾンビからのコメントがもらえます。if~else 文を使っています。

 if (zombiehp > 0) {
   printf("ゾンビ:あぶなく死ぬところでしたよ。ハハ、にげよー!\n");
 }
 else {
   printf("ゾンビ:まさか死んでしまうとは!ガクッ\n");
 }

攻撃が終わった後、ゾンビの体力をチェックして、もし体力が残っていれば、19行目が実行され、体力がなければ 22行目が実行されます。

あとがき

今回は時刻を乱数の種に設定するサンプルコードを中心にお話しました。
この方法もゲームではよくつかわれる方法です。少しだけ注意点ですが、
「(unsigned int ) time(NULL) 」で得られるのは秒単位であり、ミリ秒などもっと詳細な秒数は別の方法でとることになります。このあたりはまた今度。

イチからゲーム作りで覚えるC言語
連載ページ一覧へ
2章26 自作の関数を作ってみる
NEXT : 2章26 自作の関数を作ってみる
2章24 疑似乱数で任意の範囲の整数を表示しよう : PREV

非常に参考になったサイトさまや、参考文献など

ページの更新履歴

更新日 更新内容
2018/7/25 初版公開
2018/11/26 トップ画像、アイキャッチ画像を追加。