MS−DOSから利用する単純なクイズゲームです。
問題を作成するエディターも内蔵しています。
ソースのダウンロード
※)サンプルの作成はVC++5.0で行いました。
※2)サンプルは標準Cライブラリーを用いています。
解説
(全体の流れ)
コマンドライン引数をチェックして場合わけをします。2.ファイルをオープンし、ファイルのヘッダを照合した後、各モードに対応する関数を呼び出す。
引数が与えられなかった場合は、モードをゲームモードに設定し、ファイルの入力をユーザーに求めます。(Quiz_main.cpp)
if (argc == 1) { // プログラムに引数がなかった場合 printf ("問題ファイル名を指定してください。\n"); printf ("ファイル名 ? : "); scanf ("%s", file_name); mode = GAME_MODE; // モードはゲームモードに設定 } else if (argc == 3) { if (!strcmp (argv[2], "-e")) { // オプションが'-e' strcpy (file_name, argv[1]); mode = EDIT_MODE; } else if (!strcmp (argv[2], "-n")) { // オプションが'-n' strcpy (file_name, argv[1]); mode = N_EDIT_MODE; } else if (!strcmp (argv[2], "-q")) { // オプションが'-q' strcpy (file_name, argv[1]); mode = GAME_MODE; } else { // オプション指定がおかしい場合 usage(); // 使用方法を出力 return 1; } } else { // 間違った用法の場合 usage (); // 使用方法を出力 return 1; }
オプションの種類によってモードを設定しておき、後の処理でそれを利用します。
また、コマンドライン引数が不正なときには関数usage()を呼び出して使用方法を表示します。
各モードによって場合分けをして、入力ファイルをオープンします。3.問題を解く場合の処理内容
その後、ヘッダー情報を照合しますが、新規にファイルを作る場合のみ、ファイルの先頭にヘッダー情報を書き出します。
それが終わったら、ファイルポインタを引数にして各関数を呼び出します。(Quiz_main.cpp)
switch (mode) { // 各モードに突入! case GAME_MODE: in_file = fopen (file_name, "rb"); if (!in_file) { printf ("ファイルのオープンに失敗しました。\n"); return 0; } // まずは構造体を0リセットする。 memset ((void*)&header, 0, sizeof(QUIZFILEHEADER)); fread ((void*)&header, sizeof(QUIZFILEHEADER), 1, in_file); // ヘッダー情報の最初の4バイトを確認する if (header.file_type != FT_ID) { printf ("指定したファイルはクイズ問題ファイルではありません\n"); return 0; } play_game (in_file); break; case EDIT_MODE: in_file = fopen (file_name, "rb+"); if (!in_file) { printf ("ファイルのオープンに失敗しました。\n"); return 0; } // 構造体は0リセットする memset ((void*)&header, 0, sizeof(QUIZFILEHEADER)); fread ((void*)&header, sizeof(QUIZFILEHEADER), 1, in_file); // 指定したファイルは問題フィルか? if (header.file_type != FT_ID) { printf ("指定したファイルはクイズ問題ファイルではありません\n"); return 0; } edit (in_file); break; case N_EDIT_MODE: // 指定したファイル名でファイルを新規作成 in_file = fopen (file_name, "wb+"); if (!in_file) { printf ("ファイルのオープンに失敗しました。\n"); return 0; } // ヘッダー情報を書き出す。 memset ((void*)&header, 0, sizeof(QUIZFILEHEADER)); header.file_type = FT_ID; fwrite ((void*)&header, sizeof (QUIZFILEHEADER), 1, in_file); edit (in_file); break; default: printf ("致命的なエラーが起きました。アプリケーションを終了します。\n"); return 0; }
モードによってファイルのアクセス指定も違っていますので注意してください!
また、一応ヘッダーファイルの中身も見ておいてください。
問題を解く場合は、まず最初に何問解くか、問題数の入力をユーザーに求めます。4.問題を作る場合の処理内容
子のときファイルのヘッダーを再度読み込んで、ファイル内の問題数を確認し、不正な入力を回避します。(play_game.cpp)
// ファイルのヘッダー情報を読み込む memset ((void*)&head, 0, sizeof(QUIZFILEHEADER)); fseek (in_file, 0, SEEK_SET); fread ((void*)&head, sizeof(QUIZFILEHEADER), 1, in_file); quiz_max = head.quiz_num; // このファイル内にある問題数を取得 printf ("何問解くか入力してください。\n?> "); scanf ("%d", &quiz_num); while (quiz_num < 1 || quiz_num > quiz_max) { // 問題数が不正のとき printf ("?> "); scanf ("%d", &quiz_num); }
次に、問題をメモリー上に読み込むのですが、このときランダムな順列を用意して、それを利用して読み込みます。
// 問題構造体のメモリを取得 quiz = (QUESTION*) calloc (quiz_num, sizeof(QUESTION)); // 出題の順番を格納する配列を確保する shutsudai_jun = (int *) calloc (quiz_max, sizeof(int)); make_junretu (shutsudai_jun, quiz_max); // ランダムな順列の作成 // 問題をメモリに読み込む for (i = 0; i < quiz_num; i++) { fseek (in_file, sizeof(QUIZFILEHEADER)+sizeof(QUESTION)*shutsudai_jun[i], SEEK_SET); fread ((void*)&quiz[i], sizeof(QUESTION), 1, in_file); }
問題を出すときには、解答群もランダムな順列を利用してシャッフルします。
// 実際の出題処理 for (i = 0; i < quiz_num; i++) { system ("CLS"); printf ("問題 : %s\n", quiz[i].q_string); // 解答郡の順序を格納する配列を作成 junjo = (int *) calloc (4, sizeof (int)); make_junretu (junjo, 4); // ランダムな順列を作る for (j = 0; j < 4; j++) { printf ("%d.%s\n", j+1, quiz[i].answer[junjo[j]].ans_string); } printf ("? >> "); scanf ("%d", &ans_no); while (ans_no < 1 || ans_no > 4) { printf ("? >> "); scanf ("%d", &ans_no); } if (quiz[i].answer[junjo[ans_no-1]].ans_flag == 1) { printf ("\t正解!\n\n"); seikai_count++; } else printf ("\t\a不正解!\n\n"); } system ("CLS"); printf ("出題数 正解数 正答率\n"); printf ("%6d %6d %4.1f\n", quiz_num, seikai_count, (double)seikai_count/(double)quiz_num*100.0);
問題を全部解き終えたら正解数などの情報を出力します。
問題を作る処理はいたって簡単です。まず、構造体を1つ用意しておき、それにユーザーが入力をしていきます。
次に、ファイルの末端に書き込み、ファイルヘッダーの情報も更新しておきます。
これをユーザーが終了を要求するまでwhileループを使って繰り返していきます。(edit_quiz.cpp)
// ファイルのヘッダー情報を読み取る fseek (in_file, 0, SEEK_SET); fread ((void*)&head, sizeof(QUIZFILEHEADER), 1, in_file); fseek (in_file, 0, SEEK_END); // 構造体の0リセット memset ((void*)&q_buf, 0, sizeof(QUESTION)); printf ("問題文を入力してください。(128バイト以内)\n?> "); scanf ("%s", &(q_buf.q_string)); printf ("解答郡をスペースで区切って4つ入れてください。\n?> "); scanf ("%s %s %s %s", &(q_buf.answer[0].ans_string), &(q_buf.answer[1].ans_string), &(q_buf.answer[2].ans_string), &(q_buf.answer[3].ans_string)); printf ("正解の番号を1から4で入力してください\n?> "); scanf ("%d", &no); while (no < 1 || no > 4) { // 間違った入力値の場合 printf ("?> "); scanf ("%d", &no); } q_buf.answer[no-1].ans_flag = 1; // 出来上がった問題をファイルに書き出す。 fwrite ((void*)&q_buf, sizeof(QUESTION), 1, in_file); // ファイルヘッダ部の問題数を更新する。 head.quiz_num++; fseek (in_file, 0, SEEK_SET); fwrite ((void*)&head, sizeof(QUIZFILEHEADER), 1, in_file); fseek (in_file, 0, SEEK_END); printf ("続けて入力しますか? 1 : 2 = YES : NO\n?> "); scanf ("%d", &choice); while (choice < 1 || choice > 2) { printf ("?> "); scanf ("%d", &choice); } while (choice == 1) { memset ((void*)&q_buf, 0, sizeof(QUESTION)); printf ("問題文を入力してください。(128バイト以内)\n?> "); scanf ("%s", &(q_buf.q_string)); printf ("解答郡をスペースで区切って4つ入れてください。\n?> "); scanf ("%s %s %s %s", &(q_buf.answer[0].ans_string), &(q_buf.answer[1].ans_string), &(q_buf.answer[2].ans_string), &(q_buf.answer[3].ans_string)); printf ("正解の番号を1から4で入力してください\n?> "); scanf ("%d", &no); while (no < 1 || no > 4) { printf ("?> "); scanf ("%d", &no); } q_buf.answer[no-1].ans_flag = 1; fwrite ((void*)&q_buf, sizeof(QUESTION), 1, in_file); head.quiz_num++; fseek (in_file, 0, SEEK_SET); fwrite ((void*)&head, sizeof(QUIZFILEHEADER), 1, in_file); fseek (in_file, 0, SEEK_END); printf ("続けて入力しますか? 1 : 2 = YES : NO\n?> "); scanf ("%d", &choice); while (choice < 1 || choice > 2) { printf ("?> "); scanf ("%d", &choice); } }
間違いを発見した方、質問をしたい方はこちらへお願いします。