パソコン活用研究シリコンバレー(C、C++、の活用研究)

乱数を使う その1 (準備中)

=======================================================================================
乱数を使うための準備では、乱数を使うに当たっての心の準備に必要なことなどを書いてみましたが、今回は
実際に乱数を使って、ゲームの中で使うサイコロなどを作ってみます。

=======================================================================================

1 乱数関数でサイコロを作ってみる。

ゲームの中で、サイコロのマネをしたいときは、1〜6がランダムに出るようにしたいわけですが、
そんなランダムな数値が欲しい時に使うのが、乱数関数 rand です。

どのコンパイラでもrandの書式はほぼ同じと思いますが、LSI C-86、 Turbo C、 Borland C/C++ などでのrandの書式。

rand
【書式】 #include <stdlib.h>
     
     int rand( )

返り値 0〜32767の整数値の乱数を返します。     

乱数の最大値はLSI C-86ではstdlib.hの中で以下の通り RAND_MAXとして定義されています。
#define RAND_MAX 32767


randで乱数を表示するプログラムです。
以下は割り算の余りを使う方法です。randで得られた整数を6で割った時の余りは0〜5に
なりますので、これに1を足せば1〜6が得られます。

1〜6をランダムに表示し何かキーを押すと止まります。

#include<stdio.h>
#include<conio.h>
main(){
int x;
while(!kbhit()) {
x=rand() % 6+1;
printf("%d: ",x);
}
}

dice0.c Borland c/c++でコンパイル

実行結果は以下の通り

c:\bcc55\Bin>dice0
5: 3: 5: 5: 2: 4: 2: 5: 5: 5: 3: 2: 2: 1: 5: 1: 4: 6: 4: 6:
2: 3: 6: 3: 6: 1: 4: 2: 5: 4: 2: 2: 2: 3: 3: 2: 6: 4: 1: 4:
3: 3: 1: 1: 4: 3: 1: 6: 2: 4: 5: 2: 5: 1: 1: 6: 5: 5: 2: 4:
2: 1: 4: 5: 1: 6: 4: 3: 2: 6: 2: 6: 5: 6: 6: 3: 3: 5: 3: 1:
1: 3: 1: 4: 2: 3: 1: 5: 2: 3: 2: 2: 6: 6: 2: 6: 1: 5: 4: 6:
2: 4: 2: 3: 2: 6: 2: 3: 4: 3: 5: 1: 5: 1: 5: 2: 6: 3: 5: 3:
2: 5: 4: 2: 5: 1: 3: 1: 1: 2: 3: 6: 3: 2: 5: 6: 5: 5: 5: 4:

実行結果の最初の40回の目の出方を見ると
1: 4回  2:10回  3:6回  4:7回  5:8回  6:5回
その後の40回の目の出方
1: 8回  2:7回  3:7回  4:5回  5:7回  6:7回



1〜6をランダムに得る方法としては以下のような方法もあります。6をかけてからRAND_MAX+1で割ると、0〜6未満
の実数が得られます。これに1をくわえます。これを整数型に変換すると1〜6の整数が得られます。
RAND\MAXはLSI C, TUTBO C, Borland c/c++などでは32767です。

#include<stdio.h>
#include<conio.h>
main(){
int x;
while(!kbhit()) {
x=rand()*6/(RAND_MAX+1)+1;
printf("%d: ",x);
}
}


c:\bcc55\Bin>dice1
1: 3: 1: 3: 2: 4: 2: 5: 6: 2: 3: 1: 5: 4: 1: 1: 5: 5: 5: 5:
6: 2: 3: 6: 6: 6: 5: 3: 4: 4: 4: 4: 5: 1: 3: 1: 5: 3: 3: 4:
3: 6: 2: 2: 3: 5: 1: 4: 4: 4: 1: 5: 2: 3: 3: 3: 4: 4: 2: 2:
5: 2: 3: 3: 2: 3: 1: 3: 6: 1: 3: 1: 1: 2: 6: 3: 3: 2: 2: 2:
2: 5: 6: 6: 5: 3: 4: 1: 1: 3: 3: 4: 2: 1: 5: 4: 6: 6: 1: 4:
4: 4: 5: 2: 2: 2: 3: 4: 1: 4: 1: 6: 2: 5: 4: 5: 5: 4: 4: 2:
2: 6: 5: 4: 6: 6: 6: 5: 4: 2: 5: 2: 4: 3: 2: 4: 3: 5: 3: 4:
1: 1: 3: 6: 4: 4: 5: 4: 4: 5: 6: 3: 4: 3: 6: 1: 6: 3: 2: 4:
3: 4: 5: 4: 5: 3: 1: 6: 5: 6: 3: 3: 6: 4: 4: 5: 2: 2: 6: 1:
6: 4: 1: 6: 5: 5: 6: 3: 2: 5: 2: 3: 5: 6: 6: 2: 3: 6: 3: 3:

実行結果の最初の40回の目の出方を見ると
1: 7回  2:4回  3:8回  4:7回  5:9回  6:5回


どちらの方法でもいいのですが、最初(dice.0.c)の6で割った余りを使う方法は、コンパイラに実装された
乱数のアルゴリズムによっては、あまりいい乱数が得られないこともあるので後者(dice1.c)を好む人も
いるようです。
乱数の品質については、乱数を使うための準備をご参照ください。

2.乱数列の初期化

dice0.cにしろdice1.cにしろ、毎回同じ乱数が発生しています。


c:\bcc55\Bin>dice0
5: 3: 5: 5: 2: 4: 2: 5: 5: 5: 3: 2: 2: 1: 5: 1: 4: 6: 4: 6:
2: 3: 6: 3: 6: 1: 4: 2: 5: 4: 2: 2: 2: 3: 3: 2: 6: 4: 1: 4:
3: 3: 1: 1: 4: 3: 1: 6: 2: 4: 5: 2: 5: 1: 1: 6: 5: 5: 2: 4:

c:\bcc55\Bin>dice0
5: 3: 5: 5: 2: 4: 2: 5: 5: 5: 3: 2: 2: 1: 5: 1: 4: 6: 4: 6:
2: 3: 6: 3: 6: 1: 4: 2: 5: 4: 2: 2: 2: 3: 3: 2: 6: 4: 1: 4:
3: 3: 1: 1: 4: 3: 1: 6: 2: 4: 5: 2: 5: 1: 1: 6: 5: 5: 2: 4

c:\bcc55\Bin>dice0
5: 3: 5: 5: 2: 4: 2: 5: 5: 5: 3: 2: 2: 1: 5: 1: 4: 6: 4: 6:
2: 3: 6: 3: 6: 1: 4: 2: 5: 4: 2: 2: 2: 3: 3: 2: 6: 4: 1: 4:
3: 3: 1: 1: 4: 3: 1: 6: 2: 4: 5: 2: 5: 1: 1: 6: 5: 5: 2: 4

c:\bcc55\Bin>dice1
1: 3: 1: 3: 2: 4: 2: 5: 6: 2: 3: 1: 5: 4: 1: 1: 5: 5: 5: 5:
6: 2: 3: 6: 6: 6: 5: 3: 4: 4: 4: 4: 5: 1: 3: 1: 5: 3: 3: 4:
3: 6: 2: 2: 3: 5: 1: 4: 4: 4: 1: 5: 2: 3: 3: 3: 4: 4: 2: 2:

c:\bcc55\Bin>dice1
1: 3: 1: 3: 2: 4: 2: 5: 6: 2: 3: 1: 5: 4: 1: 1: 5: 5: 5: 5:
6: 2: 3: 6: 6: 6: 5: 3: 4: 4: 4: 4: 5: 1: 3: 1: 5: 3: 3: 4:
3: 6: 2: 2: 3: 5: 1: 4: 4: 4: 1: 5: 2: 3: 3: 3: 4: 4: 2: 2:

c:\bcc55\Bin>dice1
1: 3: 1: 3: 2: 4: 2: 5: 6: 2: 3: 1: 5: 4: 1: 1: 5: 5: 5: 5:
6: 2: 3: 6: 6: 6: 5: 3: 4: 4: 4: 4: 5: 1: 3: 1: 5: 3: 3: 4:
3: 6: 2: 2: 3: 5: 1: 4: 4: 4: 1: 5: 2: 3: 3: 3: 4: 4: 2: 2:




せっかく乱数を使ってランダム性をゲームに取り入れても、これでは毎回同じパターンになってしまいます。
そのために、初期値(シード)を変えてやらねばなりませんね。
C言語では、大抵のコンパイラでシードを変えるための関数として srand という関数が用意されています。

srand
【書式】 #include <stdlib.h>
     
     void srand(unsigned int seed)

発生する乱数列を初期化する。seedには0〜65535の整数を
与える。    


初期値(シード)を毎回変えてあげる工夫が必要ですが、現在の時間を初期値(シード)として
与えるという方法があります。

現在の時間を取得する関数がtime()です。
この関数で、万国標準時(UTC)での1970年1月1日 0時0分0秒からの秒単位での経過時間が得られます。

#include<stdio.h>
#include<conio.h>
#include<stdlib.h>
main(){
int x;
srand((unsigned int)time(NULL));
while(!kbhit()) {
x=rand()*6/32767+1;
printf("%d: ",x);
}
}


3.乱数を使ったプログラム例

表示されたキーを5秒間にいくつ叩けるかというプログラム

 
/* Win7以降のコマンドプロンプト用 */
#include<stdio.h>
#include<stdlib.h>
#include<time.h>
main(){
int c,x,count=0;
long starttime;
starttime=time(NULL);
srand((unsigned int)starttime);
x=rand()*26/32768+65;
printf("キートレーニング 5秒\n");
/* Win7以降のコマンドプロンプトでは */
/* putchの後にカーソル位置が元に戻ってしまうのでprintf("\n")を加えている */
putch(x);printf("\n");
while(time(NULL)-starttime<5) {
while(kbhit()) {
c=getch();
if (c==x) {
count++;
x=rand()*26/32768+65;
printf("*:%d\n",count);
putch(x);
printf("\n");}
}}
printf("\n count: %d", count);
}



Turbo CやBorland c/c++にはrandom( )という関数や、srandと同じような機能の関数として、randomize()
という乱数関数も用意されてます。

random(int num) はnum未満の整数をランダムに生成します。

randomizeは、Borland c/c++のstdlib.hには以下のようにrandomizeが定義されています。
#define randomize() srand((unsigned) time(NULL))

seedにtimeを与えて乱数列を初期化する関数になっていますね。




これらの問題は、どのプログラム言語であっても共通の話で、BASICの場合は、
パソコン活用研究5番街/乱数 にBASICでの乱数の同様の話を記載しています。

ということで、乱数を使う準備として線形合同法による乱数を使う場合、こういう欠点があることを
理解しておいたほうがいい場合があります、というお話でした。


TopPage