前回の練習問題の解答例

#include <stdio.h>

int main(void)
{
	int x, y, year;
	double interest, rate;
	
	printf("何年後までの金利を計算しますか?");
	scanf("%d",&year);
	
	//1行目
	printf("+----");
	for (x=2; x<=year; x++) {
		printf("--------");
	}
	printf("+\n");
	
	//2行目
	printf("|年利|");
	for (x=2; x<=year; x++) {
		printf("%2d年後 |",x);
	}
	printf("\n");
	
	//3行目
	printf("|----");
	for (x=2; x<=year; x++) {
		printf("+-------");
	}
	printf("|\n");
	
	//本体
	for (y=1; y<=20; y++) {
		printf("|%.1f%%|",(double)y/10.0);
		rate=1.0+(double)y/1000.0;
		interest=rate;
		for (x=2; x<=year; x++) {
			interest=interest*rate;
			printf("%6.3f%%|",(interest-1.0)*100.0);
		}
		printf("\n");
	}
	
	//最終行
	printf("+----");
	for (x=2; x<=year; x++) {
		printf("--------");
	}
	printf("+\n");

	return 0;
}
前回の最後のプログラム例を書き換えればよい。
scanf関数で年数を変数yearに代入し、前回のプログラム例で繰り返し条件が10年後までとなっている部分を、yearまでに書き換える。
さらに、1行目と最終行の横線の長さを可変にする。 3行目の横線を書いている部分をコピーして、一部の記号を別の記号に置き換えればよい。 実行結果を見ながら修正すれば簡単である。

擬似乱数

数列x1,x2,x3,・・・が、x1からxiまでの値からxi+1を予測できず、 かつ、どの値になる確率も等しいとき、この数列を乱数という。 簡単に言えば、乱数とは完全にでたらめな数列である。 C言語では完全な乱数を作ることはできないが、乱数に近い擬似乱数を作ることはできる。

rand関数

rand関数擬似乱数を生成する関数である。 この関数を使うにはstdlib.hをインクルードする必要がある。 関数の括弧()の中に記入するもののことを引数というが、rand関数には引数がない(したがって括弧の中に何も書かない)。 rand乱数は、呼び出されるたびに1つの整数を返す。 それらを並べると擬似乱数になるという仕組みである。
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
	int counter, num;
	
	for(counter=1; counter<=10; counter++) {
		num=rand();
		printf("%d ",num);
	}
	printf("\n");
	return 0;
}
このプログラムを動かすと、でたらめな10個の整数が並ぶ。 しかし、これには欠点がある。 何度実行しても、同じ数列が作られるのである。 この欠点を解消するのが、次のsrand関数である。

srand関数

srand関数擬似乱数の種を設定する関数である。 引数は種とする整数である。
rand関数は、ある数式にxiを代入することでxi+1を得ている。 そのため、数列を作るには最初の値x0が必要である。 このx0を「擬似乱数の種」という。 rand関数を単独で使う場合は1が種となるが、srand関数を使えば1以外の数値を種とすることができる。
#include <stdio.h>
#include <stdlib.h>
	
int main(void)
{
	int counter, num, seed;
		
	printf("擬似乱数の種:");
	scanf("%d",&seed);
	srand(seed);

	for(counter=1; counter<=10; counter++) {
		num=rand();
		printf("%d ",num);
	}
	printf("\n");
	return 0;
}
様々な値を入力してみると、種の値によって異なる数列が生成されることが分かる。

プログラム例

種が1,2,…,10の場合の擬似乱数を並べて比較してみよう。
#include <stdio.h>
#include <stdlib.h>

int main(void)
{
	int counter, num, seed;
	
	for (seed=1; seed<=10; seed++) {
		srand(seed);
		printf("種=%2d:",seed);
		for(counter=1; counter<=10; counter++) {
			num=rand();
			printf("%5d ",num);
		}
		printf("\n");
	}
	return 0;
}
毎回異なる数列が作られるが、種の値が近いと後に続く数値の一部も近くなってしまう。 擬似乱数の限界である。

time関数

time関数グリニッジ標準時1970年1月1日0時0分0秒からの経過時間を秒単位で返す関数である。 この関数を使うにはtime.hをインクルードする必要がある。 引数は「NULL」とする。NULLの意味は授業の範囲を超えるので説明を割愛する。
time関数の返却値を種として擬似乱数を生成するプログラム:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main(void)
{
	int counter, num;
	
	srand(time(NULL));

	for(counter=1; counter<=10; counter++) {
		num=rand();
		printf("%d ",num);
	}
	printf("\n");
	return 0;
}
time関数の返却値を擬似乱数の種とすることで、実行のたびに異なる数列が生成される。

乱数の応用

乱数は、ゲームを作る時などによく利用する。
2つのサイコロの目の和が偶数(丁)か奇数(半)かを当てるゲーム(10回繰り返す):
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main(void)
{
	int counter, dice1, dice2, answer;
	
	srand(time(NULL));
	for (counter=1; counter<=10; counter++) {
		dice1=rand()%6+1;	//1個目のサイコロ
		dice2=rand()%6+1;	//2個目のサイコロ
		
		//0か1を入力
		printf("丁か半か(丁:0 半:1)?");
		while (1) {
			scanf("%d",&answer);
			if (answer==0 || answer==1) {
				break;
			}
			printf("0か1で答えてください:");
		}
		
		//勝敗判定
		printf("サイコロの目:%d,%d   ",dice1,dice2);
		if(answer==(dice1+dice2)%2) {
			printf("当たり!\n");
		}
		else {
			printf("はずれ\n");
		}
	}
	return 0;
}
rand()%6+1 が1〜6の範囲になることを利用してサイコロの目を作っている。

練習問題

ヒット&ブローは、ヒントに基づいて4桁の数を当てるゲームである。
  1. 重複しない4つの数字が「当たりの数値」として選ばれる
  2. 当たりの数値はユーザーに示されない
  3. ユーザーは当たりの数値を予想して答える
  4. ユーザーの数字の1つが当たりの数値に含まれていればブロー
  5. 含まれているだけでなく位置(桁)も合っていればブローではなくヒット
  6. ヒットとブローの個数がユーザーに示される
  7. ユーザーはこれに基づき予想し直して次の数値を答える
  8. 当たるまで繰り返す
例えば、当たりが「0275」でユーザーが「2015」と答えたら「1ヒット2ブロー」である (0275と2015で同じ位置に5があるのでヒット1つ、0275と2015で両方に2と0が含まれるが位置が違うのでブロー2つ)。

下に示したのは、ヒット&ブローの数値を3桁に減らしたプログラムである。 このプログラムの動作を確認した上で、通常のヒット&ブロー(4桁)のプログラムに書き換えなさい。
#include <stdio.h>
#include <stdlib.h>
#include <time.h>

int main(void)
{
	int num1, num2, num3;
	int user, user1, user2, user3;
	int hit, blow, count;
	
	//当たりの数値を決める
	srand(time(NULL));
	num1=rand()%10;
	while (1) {
		num2=rand()%10;
		if (num2!=num1) {
			break;
		}
	}
	while (1) {
		num3=rand()%10;
		if (num3!=num1 && num3!=num2) {
			break;
		}
	}
	//printf("%d%d%d\n",num3,num2,num1);	//動作確認用(本番は使わない)
	
	//ゲーム開始
	count=1;
	printf("重複しない3つの数字:");
	while(1) {
		while (1) {
			scanf("%d",&user);
			user3=user/100;
			user2=(user/10)%10;
			user1=user%10;
			if (user1!=user2 && user1!=user3 && user2!=user3) {
				break;
			}
			printf("重複してます!重複しない3つの数字:");
		}
		
		//当たったら終了
		if (user1==num1 && user2==num2 && user3==num3) {
			break;
		}
		
		//はずれたらヒット数とブロー数を表示
		hit=0;
		blow=0;
		if (user1==num1) {
			hit++;
		}
		if (user2==num2) {
			hit++;
		}
		if (user3==num3) {
			hit++;
		}
		if (user1==num2 || user1==num3) {
			blow++;
		}
		if (user2==num1 || user2==num3) {
			blow++;
		}
		if (user3==num1 || user3==num2) {
			blow++;
		}
		printf("%dヒット%dブロ−\n",hit,blow);	//長音記号「ー」はダメ文字なので全角ハイフン「−」を使う
		printf("次の数値:");
		count++;
	}
	printf("%d回で当たりました!\n",count);
	return 0;
}