【C言語】レベルを選択できる数当てゲームを作成しよう

C言語

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

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


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

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

演習1-6開始時にプレーヤにレベルを選択させる《数当てゲーム》を作成せよ。たとえば、以下のように表示して選択させること。
レベルを選択してください (1)1〜9 (2)1〜99 (3)1〜999 (4)1〜9999:

今回の課題内容を整理しますと、以下のようになります。

  1. プレーヤによるレベルの選択
  2. レベルに応じた範囲の変更

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

設計

プレーヤによるレベルの選択

簡単のため、今回選択させるレベルは、課題内容の(1)〜(4)と同じに設定します。
そのため、プレーヤは1〜4の数字を入力することで、対応するレベルを選択できるようにします。

数字入力による操作は、ゲーム開始前に行う「ゲームの開始選択」と同じ原理で行えます。

【C言語】乱数を生成するためには”種”が必要
ようこそ、KiRiOのブログへ! 本記事では、「新・明解C言語中級編」の自由課題について解説します。 自由課題のとおり、この参考書には解答が載っておりません。 そこで、本記事で私なりの解答を共有しますので、ぜひ解答の参考にして...

すなわち「プレーヤが1〜4のいずれかの数値を入力するまでレベルを選択させる」となります。

レベルに応じた範囲の変更

プレーヤがレベルを選択したら、各レベルに合わせて「当てさせる数の範囲」と「当てさせる数(正解値)」を設定しなければなりません。
本問の場合ですと、範囲の最小値はいずれも「1」ですので、変更箇所は最大値のみとなります。
よって、選択レベルに応じて範囲の最大値を設定することとなります。

範囲の設定方法1:switch文を使う

設定方法としてまず思いつくのが「条件分岐」でしょう。条件分岐を用いますと以下のようになります。

  • レベル1 → 最大値「9」
  • レベル2 → 最大値「99」
  • レベル3 → 最大値「999」
  • レベル4 → 最大値「9999」

範囲の設定方法2:数式を用いて一般化する

条件分岐による設定でも問題ないですが、さらにスマートに設定できる方法はないでしょうか。
すなわち、最大値をレベル値を用いて表すことができれば、条件分岐を使う必要がなくなります
そこで、先ほどの条件分岐の説明部分を見直しますと、以下のように書き表すことができます。

  • レベル1 → 9 = 10 – 1 = 101 – 1
  • レベル2 → 99 = 100 – 1 = 102 – 1
  • レベル3 → 999 = 1,000 – 1 = 103 – 1
  • レベル4 → 9999 = 10,000 – 1 = 104 – 1

よって、選択したレベルが $n$ であれば、範囲の最大値は $10^{n} – 1$ となることがわかります。

ここまで、2つの設定方法について説明しました。
実際はこの内容をソースコード上で実装することになりますが、可読性を重視するのであれば「条件分岐分量を重視するのであれば「数式による一般化を選んだほうがよいでしょう。

設計は以上となります。

実装

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

  1. プレーヤによるレベルの選択
  2. レベルに応じた範囲の変更

プレーヤによるレベルの選択

設計部分でも述べましたが、プレーヤが1〜4のいずれかの数値を入力するまでレベルを選択させるため、do-whileを使います。
以上を実装したものが下記プログラムです。

#include <stdio.h>

int main(void)
{
	int try, level;

	do
	{
		printf("《数当てゲーム》を開始しますか?【はい…1/いいえ…0】:");
		scanf("%d", &try);

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

	do
	{
		printf("レベルを選択してください【(1)1~9 (2)1~99 (3)1~999 (4)1~9999】:");
		scanf("%d", &level);
	} while (level != 1 && level != 2 && level != 3 && level != 4);

	return 0;
}

レベルに応じた範囲の変更

switch文を使った条件分岐

switch文を使う場合、入力したレベル値で条件分岐を行います。
そして、分岐後に最大値を指定し、switch文を抜けた後に正解値を設定します。
なお、正解値の設定時は範囲の最小値が「1」ですので、シンプルに「1 + rand() % (最大値)」と表せます。
以上を実装したものが下記プログラムです。

#include <stdio.h>
#include <stdlib.h> // stdlib.hをインクルード
#include <time.h> // time.hをインクルード

int main(void)
{
	int try, level;
	int ans;

	do
	{
		printf("《数当てゲーム》を開始しますか?【はい…1/いいえ…0】:");
		scanf("%d", &try);

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

	do
	{
		printf("レベルを選択してください【(1)1~9 (2)1~99 (3)1~999 (4)1~9999】:");
		scanf("%d", &level);
	} while (level != 1 && level != 2 && level != 3 && level != 4);

	/* 選択レベルに応じて範囲の最大値を設定 */
	switch (level)
	{
		case 1: max = 9; break;
		case 2: max = 99; break;
		case 3: max = 999; break;
		case 4: max = 9999; break;
		default: break;
	}

	/* 乱数の種を設定 */
	srand(time(NULL));

	/* 当てさせる数の設定 */
	ans = 1 + rand() % max;

	return 0;
}

数式による一般化

設計部分でも述べましたとおり、範囲の最大値はレベル値 $n$ を用いて「$10^{n} – 1$」を表されます。
C言語で指数計算を行う場合は、ヘッダファイル「math.h」をインクルードして数学関数「pow()」を使う方法が便利です。

pow
ヘッダ #include <math.h>
形式 double pow(double x, double y);
機能 xy 乗を計算する。
戻り値 xy 乗の値。
x が有限な負の値 && y が有限だが整数値でない」場合、「x = 0 && y <= 0 」の場合は定義域エラー発生の可能性がある。

ご覧のように、pow()の戻り値はdouble型のためint型へキャストする必要があります。

以上を実装したものが下記プログラムです。

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <math.h> // math.hをインクルード

int main(void)
{
	int try, level;
	int ans;

	do
	{
		printf("《数当てゲーム》を開始しますか?【はい…1/いいえ…0】:");
		scanf("%d", &try);

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

	do
	{
		printf("レベルを選択してください【(1)1~9 (2)1~99 (3)1~999 (4)1~9999】:");
		scanf("%d", &level);
	} while (level != 1 && level != 2 && level != 3 && level != 4);

	srand(time(NULL));

	/* 当てさせる数の設定(数式による一般化) */
	ans = 1 + rand() % ((int)pow(10, level) - 1);

	return 0;
}

その他の「最大入力回数の設定」や「入力値と正解値との比較」については過去のブログで解説していますので説明は省略します。詳細は下記ブログをご覧ください。

【C言語】数当てゲームの入力回数は”指数不等式”で設定せよ
ようこそ、KiRiOのブログへ! 本記事では「新・明解C言語中級編」の自由課題について解説します。 自由課題のとおり、この参考書には解答が載っておりません。 そこで、本記事で私なりの解答を共有しますので、ぜひ解答の参考にしてみ...
【C言語】関数電卓の必要な計算は"数学関数"を活用せよ
ようこそ、KiRiOのブログへ! 本記事では「新・明解C言語中級編」の自由課題について解説します。 自由課題のとおり、この参考書には解答が載っておりません。 そこで、本記事で私なりの解答を共有しますので、ぜひ解答の参考にしてみ...

また、今回はレベルの選択がテーマとなっておりますので、ゲーム開始メッセージは

レベル◯:1〜○の数を当ててください(制限回数:◯回)

のように、選択したレベルが表示されるようにします。

すべてを実装したものが下記プログラムです。

▼条件分岐を用いた場合

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

int main(void)
{
	int try, level;
	int ans;

	do
	{
		printf("《数当てゲーム》を開始しますか?【はい…1/いいえ…0】:");
		scanf("%d", &try);

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

	do
	{
		printf("レベルを選択してください【(1)1~9 (2)1~99 (3)1~999 (4)1~9999】:");
		scanf("%d", &level);
	} while (level != 1 && level != 2 && level != 3 && level != 4);

	switch (level)
	{
		case 1: max = 9; break;
		case 2: max = 99; break;
		case 3: max = 999; break;
		case 4: max = 9999; break;
		default: break;
	}

	srand(time(NULL));
	ans = 1 + rand() % max;

	input_max = (int)ceil(log2(max));
	input_remain = input_max;

	printf("\nレベル%d:1〜%dの数を当ててください(制限回数:%d回\n", level, max, input_max);

	do
	{
		printf("%2d回目:", (input_max - input_remain) + 1");
		scanf("%d", &num);

		input_remain--;

		if (input_remain == 0)
		{
			break;
		}
		
		if (num > ans)
		{
			printf("\a%3dよりも小さいです(残り%d回)\n", num, input_remain);
		}
		if (num < ans)
		{
			printf("\a%3dよりも大きいです(残り%d回)\n", num, input_remain);
		}
	} while (num != ans);

	if (num == ans)
	{
		printf("正解です!(入力回数:%d回)\n", input_max - input_remain);
	}
	if (num < ans)
	{
		printf("残念!正解は%dでした。\n", ans);
	}

	return 0;
}

▼数式を利用した場合

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

int main(void)
{
	int try, level;
	int ans;

	do
	{
		printf("《数当てゲーム》を開始しますか?【はい…1/いいえ…0】:");
		scanf("%d", &try);

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

	do
	{
		printf("レベルを選択してください【(1)1~9 (2)1~99 (3)1~999 (4)1~9999】:");
		scanf("%d", &level);
	} while (level != 1 && level != 2 && level != 3 && level != 4);

	srand(time(NULL));
	ans = 1 + rand() % ((int)pow(10, level) - 1);

	input_max = (int)ceil(log2(pow(10, level) - 1));
	input_remain = input_max;

	printf("\nレベル%d:1〜%dの数を当ててください(制限回数:%d回\n", level, max, input_max);

	do
	{
		printf("%2d回目:", (input_max - input_remain) + 1");
		scanf("%d", &num);

		input_remain--;

		if (input_remain == 0)
		{
			break;
		}
		
		if (num > ans)
		{
			printf("\a%3dよりも小さいです(残り%d回)\n", num, input_remain);
		}
		if (num < ans)
		{
			printf("\a%3dよりも大きいです(残り%d回)\n", num, input_remain);
		}
	} while (num != ans);

	if (num == ans)
	{
		printf("正解です!(入力回数:%d回)\n", input_max - input_remain);
	}
	if (num < ans)
	{
		printf("残念!正解は%dでした。\n", ans);
	}

	return 0;
}

以上で完了となります。
プログラムを実行して、レベルを選択できる《数当てゲーム》として機能しているか確認してみてください。

まとめ

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

  1. レベル選択後の範囲設定は条件分岐を用いて行う
    • レベル値で比較する場合はswitchが適切
    • レベル値と範囲が対応している場合は数式で一般化
      • レベル1:1〜9
      • レベル2:1〜99
      • レベル3:1〜999
      • レベル4:1〜9999
      • レベルn:1〜10n - 1

  2. 可読性を重視するのであれば条件分岐(switch文)
  3. 分量を重視するのであれば数式による一般化

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

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


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

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

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

コメント

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