こんにちは、コン(@pippi_kon)です。
この記事では『C言語でのクイズゲームの作り方』をご紹介しています。
前回は、問題を愚直に表示するというシンプルな作りだったのを「ループ処理」でスッキリさせる方法をご紹介しました。
プログラムが短くなり見やすくなりましたが、問題文や選択肢を配列に詰め込んだせいでちょっとわかりづらくなってしまいました。
『q[i][0]』←ぱっと見で何を指しているかわからないですよね。
なので今回は、構造体を活用してプログラムの可読性(わかりやすさ)をアップさせます。
今回の目標
今回作成するプログラムの出力結果です。
出力結果自体は前回と変わりませんが、中身のプログラム処理が変わります。
問題文の「リンゴは英語で何と言う?」や選択肢の「apple」などは、char型の多次元配列に格納されていました。
これらを構造体に格納するように変更しています。
プログラム全文
今回作成したプログラムのご紹介です。
のちほど、プログラムの解説を行います。
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
typedef struct {
char mondai[100];
char sentaku[3][100];
int answer_no;
}quiz_t;
int main(void)
{
int ans;
int i;
quiz_t quiz[3];
strncpy(quiz[0].mondai, "リンゴは英語で何と言う?", sizeof(quiz[0].mondai));
strncpy(quiz[0].sentaku[0], "apple", sizeof(quiz[0].sentaku[0]));
strncpy(quiz[0].sentaku[1], "orange", sizeof(quiz[0].sentaku[1]));
strncpy(quiz[0].sentaku[2], "banana", sizeof(quiz[0].sentaku[2]));
quiz[0].answer_no = 1;
strncpy(quiz[1].mondai, "大正->昭和->○○->令和\n○○に入る年号は?", sizeof(quiz[1].mondai));
strncpy(quiz[1].sentaku[0], "慶応", sizeof(quiz[1].sentaku[0]));
strncpy(quiz[1].sentaku[1], "明治", sizeof(quiz[1].sentaku[1]));
strncpy(quiz[1].sentaku[2], "平成", sizeof(quiz[1].sentaku[2]));
quiz[1].answer_no = 3;
strncpy(quiz[2].mondai, "世界三大珍味はどれ?", sizeof(quiz[2].mondai));
strncpy(quiz[2].sentaku[0], "イカスミ", sizeof(quiz[2].sentaku[0]));
strncpy(quiz[2].sentaku[1], "キャビア", sizeof(quiz[2].sentaku[1]));
strncpy(quiz[2].sentaku[2], "チーズ", sizeof(quiz[2].sentaku[2]));
quiz[2].answer_no = 2;
for (i = 0; i < 3; i++) {
printf("[第%d問]\n", i + 1);
printf("%s\n", quiz[i].mondai);
printf("1:%s 2:%s 3:%s\n", quiz[i].sentaku[0], quiz[i].sentaku[1], quiz[i].sentaku[2]);
printf(">>> ");
scanf("%d", &ans);
if (ans == quiz[i].answer_no) {
printf("正解!\n\n");
}
else {
printf("不正解...\n");
printf("正解は %d:%s です。\n\n", quiz[i].answer_no, quiz[i].sentaku[quiz[i].answer_no - 1]);
}
}
return 0;
}
プログラムの解説
ヘッダーの追加(strncpy関数用)
#include <string.h>
新しく「string.h」をインクルードしています。
これはのちに出てくる「strncpy関数」を使用するために必要となります。
なお、atoi関数用に追加した「stdlib.h」は、今回atoi関数を使用しなくなったため削除しました。
構造体定義
typedef struct {
char mondai[100];
char sentaku[3][100];
int answer_no;
}quiz_t;
char型の多次元配列に格納していた問題文・選択肢・答えを構造体に格納するようにしました。
こうすることで、プログラム内で何問目のどの要素を指しているのかがわかりやすくなります。
例えば、前回の『q[i][0]』は、配列のi番目の要素0番目という意味で、それが何を指しているのかがわかりにくかったです。
しかし、構造体を使用することで『quiz[i].mondai』という名前で指定することができるようになり、配列i番目の問題文ということがわかりやすくなりました。
構造体の要素 | 意味 |
---|---|
mondai[100] | クイズの問題文 |
sentaku[3][100] | クイズの選択肢 sentaku[0]:選択肢1 sentaku[1]:選択肢2 sentaku[2]:選択肢3 |
answer | クイズの答えの番号 |
上記の構造体を使うために以下の変数定義も追加しました。
quiz_t quiz[3];
問題文・選択肢・答えの設定
strncpy(quiz[0].mondai, "リンゴは英語で何と言う?", sizeof(quiz[0].mondai));
strncpy(quiz[0].sentaku[0], "apple", sizeof(quiz[0].sentaku[0]));
strncpy(quiz[0].sentaku[1], "orange", sizeof(quiz[0].sentaku[1]));
strncpy(quiz[0].sentaku[2], "banana", sizeof(quiz[0].sentaku[2]));
quiz[0].answer_no = 1;
作成した構造体に問題文・選択肢・答えを格納します。
答え(answer_no)は整数型なのでそのまま値を代入しています。
問題文(mondai)や選択肢(sentaku)は文字列型なので、通常の代入ではなく『strncpy関数』を使用しています。
なぜstrcpy関数ではなくstrncpy関数(nがついている)なのか。
それはプログラムの安全性の確保のためです。
この辺りは次回以降解説します。
構造体メンバーに置き換え
for (i = 0; i < 3; i++) {
printf("[第%d問]\n", i + 1);
printf("%s\n", quiz[i].mondai);
printf("1:%s 2:%s 3:%s\n", quiz[i].sentaku[0], quiz[i].sentaku[1], quiz[i].sentaku[2]);
printf(">>> ");
scanf("%d", &ans);
if (ans == quiz[i].answer_no) {
printf("正解!\n\n");
}
else {
printf("不正解...\n");
printf("正解は %d:%s です。\n\n", quiz[i].answer_no, quiz[i].sentaku[quiz[i].answer_no - 1]);
}
}
前回の多次元配列(q[][])を使用していた箇所を構造体メンバーに置き換えています。
不正解の処理で「quiz[i].answer_no - 1」のようにしているのは、ずれを調整するためです。
表示する選択肢(入力される選択肢)は「1 or 2 or 3」ですが、それらの選択肢を格納している配列は「0 or 1 or 2」なので、「-1」して帳尻を合わせています。