[前へ]   [目次へ]   [次へ]

単語当てゲームLv3の実装(C/C++)


今回は、 第81回 で完成した単語当てゲームLv2をLv3の仕様に合わせて改造していきます。

まずはLv2のコードを再掲載します。
<  1>
<  2>
<  3>
<  4>
<  5>
<  6>
<  7>
<  8>
<  9>
< 10>
< 11>
< 12>
< 13>
< 14>
< 15>
< 16>
< 17>
< 18>
< 19>
< 20>
< 21>
< 22>
< 23>
< 24>
< 25>
< 26>
< 27>
< 28>
< 29>
< 30>
< 31>
< 32>
< 33>
< 34>
< 35>
< 36>
< 37>
< 38>
< 39>
< 40>
< 41>
< 42>
< 43>
< 44>
< 45>
< 46>
< 47>
< 48>
< 49>
< 50>
< 51>
< 52>
< 53>
< 54>
< 55>
< 56>
< 57>
< 58>
< 59>
< 60>
< 61>
< 62>
< 63>
< 64>
< 65>
< 66>
< 67>
< 68>
< 69>
< 70>
< 71>
< 72>
< 73>
< 74>
< 75>
< 76>
< 77>
< 78>
< 79>
< 80>
< 81>
< 82>
< 83>
< 84>
< 85>
< 86>
< 87>
< 88>
< 89>
< 90>
< 91>
< 92>
< 93>
< 94>
< 95>
< 96>
< 97>
< 98>
< 99>
<100>
<101>
<102>
<103>
<104>
<105>
<106>
<107>
<108>
<109>
<110>
<111>
<112>
<113>
<114>
<115>
<116>
<117>
<118>
<119>
<120>
<121>
<122>
<123>
<124>
<125>
<126>
<127>
<128>
<129>
<130>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

int SJISMultiCheck(unsigned char c){
   
if(((c>=0x81)&&(c<=0x9f))||((c>=0xe0)&&(c<=0xfc)))return 1;
   
else return 0;
}

int SelectQuestion(const char (*question)[2][64],size_t question_cnt,char *hint,size_t hint_cnt){
   
/********************************************
   乱数を使って問題を選択して表示する
   
   戻り値:0以上:選択された問題のID
           -1:失敗
   
   const char (*question)[2][64]:選択対象の問題セット。
   size_t question_cnt:questionの要素数
   char *hint:[出力]ヒント文字列を格納する領域
   size_t hint_cnt:hintの要素数
   ********************************************/
   
int i,sel_question;
   sel_question=rand()%question_cnt;
   
if(hint_cnt<=strlen(question[sel_question][1]))return -1;
   hint[0]=
'\0';
   puts(question[sel_question][0]);
   
for(i=0;question[sel_question][1][i]!='\0';){
      
if(SJISMultiCheck(question[sel_question][1][i])){
         strcat(hint,
"*");
         i+=2;
      }
      
else{
         strcat(hint,
"*");
         i++;
      }
   }
   
return sel_question;
}

void HintUpdate(const char question[2][64],const char *player_ans,char *hint_str){
   
/***********************************************
   ヒント文字列を入力された答えに合わせて更新する。
   
   const char question[2][64]:選択されている問題
   const char *player_ans:入力された答え
   char *hint_str:[入出力]更新するヒント文字列
   ***********************************************/
   
int i,j,ans_len,player_len;
   
const char *answer_str;
   answer_str=question[1];
   ans_len=strlen(answer_str);
   player_len=strlen(player_ans);
   j=0;
   
for(i=0;(i<ans_len)&&(i<player_len);){
      
while(j<i){
         
if(SJISMultiCheck(answer_str[j]))j+=2;
         
else j++;
      }
      
if(i!=j){
         
if(SJISMultiCheck(player_ans[i]))i+=2;
         
else i++;
      }
      
else{
         
if(SJISMultiCheck(player_ans[i])){//日本語文字の場合
            
if((player_ans[i]==answer_str[i])&&
               (player_ans[i+1]==answer_str[i+1])){
               hint_str[i]=answer_str[i];
               hint_str[i+1]=answer_str[i+1];
            }
            i+=2;
         }
         
else{//半角文字の場合
            
if(player_ans[i]==answer_str[i]){
               hint_str[i]=answer_str[i];
            }
            i++;
         }
      }
   }
}

int main(void){
   
//ソース上に直接書き込んだ問題
   
char question[10][2][64]={
      {
"「プログラム」を英語で書くと?","program"},
      {
"日本で一番高い山は?","富士山"},
      {
"3*5=","15"},
      {
"「library」をカタカナ読みすると?","ライブラリ"},
      {
"日本の都道府県の数は?","47"},
      {
"「檸檬」はなんて読む?","れもん"},
      {
"「万」の上の単位は?","億"},
      {
"「迎撃」はなんて読む?","げいげき"},
      {
"22+33*5=","187"},
      {
"「メモリ」を英語で書くと?","memory"}
   };
   
//選択された問題
   
int sel_question;
   
//入力された答え
   
char player_ans[80]="";
   
//ヒント文字列
   
char hint_str[80]="";
   
   srand(time(NULL));
   
//機能「乱数を使って選択し表示します」
   
sel_question=SelectQuestion(question,10,hint_str,80);

   
while(1){
      
//機能「ヒント文字列の表示」
      
printf("ヒント:%s\n",hint_str);
      
      
//機能「その問題の答えをプレイヤーはキーボードから入力」
      
fgets(player_ans,79,stdin);
      
if(player_ans[0]!='\0')player_ans[strlen(player_ans)-1]='\0';
      
      
//機能「ヒント文字列の更新」
      
HintUpdate(question[sel_question],player_ans,hint_str);
   
      
//機能「成否を判定します」
      
if(!strcmp(player_ans,question[sel_question][1]))break;
      puts(
"違います。もう一度入力してください。");
   }
   
   
//機能「クリア表示して終了」
   
puts("正解です。Enterを押すと終了します。");
   
   
//終了待ち
   
getchar();
   
return 0;

Lv2からLv3の変更点は問題をファイルから読み出すようにすることだけです。
なのでまず問題を書いたファイルをメモ帳などで作っておきます。
ファイルの書式は 第82回 を参照してください。

それでは、問題をファイルから読み出す関数を作っていきます。
関数名は LoadQuestion としておきます。

? LoadQuestion()

この関数は問題をファイルから読み出し、問題セットを作成する機能を持ちます。
内容を簡単にするため、問題を格納したファイルのファイル名は関数内に直接書いてしまいます。
また、問題数は最大10と制限しているので、呼び出し元が10問分のメモリを確保していれば問題ありません。
そうなると、必要な引数は問題セットを作成する配列へのポインタだけとなります。
SelectQuestion 関数の時と同様に二次元配列へのポインタとして受け取りますが、
10個分必要であることを強調するために char[10][2][64] として宣言します。

? LoadQuestion(char question[10][2][64])

戻り値は、読み込みの成否の意味も含めて、「読み込めた問題の数」としておき、 int 型にします。

int LoadQuestion(char question[10][2][64])


これでこの関数の仕様が決定したので、中身を作ります。
読み込むファイル名については使用しているコンパイラや実行条件などに合わせて設定してください。
<  1>
<  2>
<  3>
<  4>
<  5>
<  6>
<  7>
<  8>
<  9>
< 10>
< 11>
< 12>
< 13>
< 14>
< 15>
< 16>
< 17>
< 18>
< 19>
< 20>
< 21>
< 22>
< 23>
< 24>
< 25>
< 26>
< 27>
int LoadQuestion(char question[10][2][64]){
   
/*******************************************
   ファイルから問題を最大10問読み込み、問題セットを作る
   
   戻り値:読み込めた問題の数
   
   char question[10][2][64]:[出力]読み込んだ問題セットを出力する対象
   *******************************************/
   
FILE *fp;//ファイルハンドル用
   
int readid;//読み取り中の問題番号
   
int len;
   
if((fp=fopen("086question.txt","r"))==NULL)return 0;//(1)「086question.txt」を読み取りモードで開く
   
   
for(readid=0;readid<10;readid++){
      
if(fgets(question[readid][0],63,fp)==NULL)break;//(2)問題文を読み込む
      
len=strlen(question[readid][0]);
      
if((len>=1)&&(question[readid][0][len-1]=='\n'))question[readid][0][len-1]='\0';//(3)改行を削除
      
      
if(fgets(question[readid][1],63,fp)==NULL)break;//正解文を読み込む
      
len=strlen(question[readid][1]);
      
if((len>=1)&&(question[readid][1][len-1]=='\n'))question[readid][1][len-1]='\0';
   }
   
   fclose(fp);
   
   
return readid;
(1)12行目でファイルを開きます。
この指定だとカレントディレクトリにある「086question.txt」を開こうとします。
実行時にどこがカレントディレクトリになるか分かっている場合はこのようにしてもいいですし、
どこがカレントディレクトリになるか分からない場合は完全パスを指定するようにしてください。

実際のアプリケーションではこのような設定ファイルの読み込みは実行ファイルのある位置を基準にして
完全なファイル名を生成して使いますが、C/C++言語の標準仕様だけでは実行ファイルのある位置を
正確かつ確実に特定する手段がありません。

このような機能はOSによって提供されている場合が多く、OSが提供する関数を使うと調べられることが多いです。
例えばWindowsの場合は GetModuleFileName という関数によって
実行ファイルの完全なファイル名を取得することができます。

(2)15行目で問題文の読み込みを試みます。
一行ごとに問題と正解が出現することになっているので、問題文の場所に一行読み込みます。
この時、1問の長さは62バイト以下でなければならないという制限があります。
また、読み込みに失敗した場合はループを終了し、読み込めた問題数を返します。

(3)17行目では読み込んだ問題文の最後に付く改行文字を削除しています。
ファイルから読み込む場合、書式が一致していないなど不完全なファイルが与えられる可能性があります。

そのため、条件式のようにまず文字数が1以上であることを確認してから、
最後が改行文字であるかをチェックし、改行文字が確認出来た場合だけ改行文字を削除するようにします。
&& 演算子は左側を最初に実行し、左側が真ではない場合は右側を実行しません)


正解文の読み込みについてもやっていることは問題文と同じです。

main 関数の方も LoadQuestion 関数を呼び出すようにして完成です。
ファイルからの読み込みはファイルが見つからないなどの理由で失敗するので、成否確認は怠れません。
また、問題数が0だと SelectQuestion で問題を正しく選べず異常終了してしまうため、
問題を読み込めなかった場合は直ちに終了するようにしておきます。

<  1>
<  2>
<  3>
<  4>
<  5>
<  6>
<  7>
<  8>
<  9>
< 10>
< 11>
< 12>
< 13>
< 14>
< 15>
< 16>
< 17>
< 18>
< 19>
< 20>
< 21>
< 22>
< 23>
< 24>
< 25>
< 26>
< 27>
< 28>
< 29>
< 30>
< 31>
< 32>
< 33>
< 34>
< 35>
< 36>
< 37>
< 38>
< 39>
< 40>
< 41>
< 42>
< 43>
< 44>
< 45>
< 46>
< 47>
< 48>
< 49>
< 50>
< 51>
< 52>
< 53>
< 54>
< 55>
< 56>
< 57>
< 58>
< 59>
< 60>
< 61>
< 62>
< 63>
< 64>
< 65>
< 66>
< 67>
< 68>
< 69>
< 70>
< 71>
< 72>
< 73>
< 74>
< 75>
< 76>
< 77>
< 78>
< 79>
< 80>
< 81>
< 82>
< 83>
< 84>
< 85>
< 86>
< 87>
< 88>
< 89>
< 90>
< 91>
< 92>
< 93>
< 94>
< 95>
< 96>
< 97>
< 98>
< 99>
<100>
<101>
<102>
<103>
<104>
<105>
<106>
<107>
<108>
<109>
<110>
<111>
<112>
<113>
<114>
<115>
<116>
<117>
<118>
<119>
<120>
<121>
<122>
<123>
<124>
<125>
<126>
<127>
<128>
<129>
<130>
<131>
<132>
<133>
<134>
<135>
<136>
<137>
<138>
<139>
<140>
<141>
<142>
<143>
<144>
<145>
<146>
<147>
<148>
<149>
<150>
<151>
<152>
<153>
<154>
<155>
<156>
<157>
<158>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

int SJISMultiCheck(unsigned char c){
   
if(((c>=0x81)&&(c<=0x9f))||((c>=0xe0)&&(c<=0xfc)))return 1;
   
else return 0;
}

int SelectQuestion(const char (*question)[2][64],size_t question_cnt,char *hint,size_t hint_cnt){
   
/********************************************
   乱数を使って問題を選択して表示する
   
   戻り値:0以上:選択された問題のID
           -1:失敗
   
   const char (*question)[2][64]:選択対象の問題セット。
   size_t question_cnt:questionの要素数
   char *hint:[出力]ヒント文字列を格納する領域
   size_t hint_cnt:hintの要素数
   ********************************************/
   
int i,sel_question;
   sel_question=rand()%question_cnt;
   
if(hint_cnt<=strlen(question[sel_question][1]))return -1;
   hint[0]=
'\0';
   puts(question[sel_question][0]);
   
for(i=0;question[sel_question][1][i]!='\0';){
      
if(SJISMultiCheck(question[sel_question][1][i])){
         strcat(hint,
"*");
         i+=2;
      }
      
else{
         strcat(hint,
"*");
         i++;
      }
   }
   
return sel_question;
}

void HintUpdate(const char question[2][64],const char *player_ans,char *hint_str){
   
/***********************************************
   ヒント文字列を入力された答えに合わせて更新する。
   
   const char question[2][64]:選択されている問題
   const char *player_ans:入力された答え
   char *hint_str:[入出力]更新するヒント文字列
   ***********************************************/
   
int i,j,ans_len,player_len;
   
const char *answer_str;
   answer_str=question[1];
   ans_len=strlen(answer_str);
   player_len=strlen(player_ans);
   j=0;
   
for(i=0;(i<ans_len)&&(i<player_len);){
      
while(j<i){
         
if(SJISMultiCheck(answer_str[j]))j+=2;
         
else j++;
      }
      
if(i!=j){
         
if(SJISMultiCheck(player_ans[i]))i+=2;
         
else i++;
      }
      
else{
         
if(SJISMultiCheck(player_ans[i])){//日本語文字の場合
            
if((player_ans[i]==answer_str[i])&&
               (player_ans[i+1]==answer_str[i+1])){
               hint_str[i]=answer_str[i];
               hint_str[i+1]=answer_str[i+1];
            }
            i+=2;
         }
         
else{//半角文字の場合
            
if(player_ans[i]==answer_str[i]){
               hint_str[i]=answer_str[i];
            }
            i++;
         }
      }
   }
}

int LoadQuestion(char question[10][2][64]){
   
/*******************************************
   ファイルから問題を最大10問読み込み、問題セットを作る
   
   戻り値:読み込めた問題の数
   
   char question[10][2][64]:[出力]読み込んだ問題セットを出力する対象
   *******************************************/
   
FILE *fp;//ファイルハンドル用
   
int readid;//読み取り中の問題番号
   
int len;
   
if((fp=fopen("086question.txt","r"))==NULL)return 0;//(1)「086question.txt」を読み取りモードで開く
   
   
for(readid=0;readid<10;readid++){
      
if(fgets(question[readid][0],63,fp)==NULL)break;//(2)問題文を読み込む
      
len=strlen(question[readid][0]);
      
if((len>=1)&&(question[readid][0][len-1]=='\n'))question[readid][0][len-1]='\0';//(3)改行を削除
      
      
if(fgets(question[readid][1],63,fp)==NULL)break;//正解文を読み込む
      
len=strlen(question[readid][1]);
      
if((len>=1)&&(question[readid][1][len-1]=='\n'))question[readid][1][len-1]='\0';
   }
   
   fclose(fp);
   
   
return readid;
}

int main(void){
   
//問題セット
   
char question[10][2][64];
   
//選択された問題
   
int sel_question;
   
//入力された答え
   
char player_ans[80]="";
   
//ヒント文字列
   
char hint_str[80]="";
   
//読み込めた問題数
   
int question_cnt;
   
   
//機能「問題をファイルから読み込んで問題セットを構築する」
   
question_cnt=LoadQuestion(question);
   
if(question_cnt==0){
      puts(
"問題を読み込めませんでした。");
      getchar();
      
return 1;//続行できないのでここで終了する
   }
   printf(
"%d問の問題が利用できます。\n",question_cnt);
   
   srand(time(NULL));
   
//機能「乱数を使って選択し表示します」
   
sel_question=SelectQuestion(question,question_cnt,hint_str,80);

   
while(1){
      
//機能「ヒント文字列の表示」
      
printf("ヒント:%s\n",hint_str);
      
      
//機能「その問題の答えをプレイヤーはキーボードから入力」
      
fgets(player_ans,79,stdin);
      
if(player_ans[0]!='\0')player_ans[strlen(player_ans)-1]='\0';
      
      
//機能「ヒント文字列の更新」
      
HintUpdate(question[sel_question],player_ans,hint_str);
   
      
//機能「成否を判定します」
      
if(!strcmp(player_ans,question[sel_question][1]))break;
      puts(
"違います。もう一度入力してください。");
   }
   
   
//機能「クリア表示して終了」
   
puts("正解です。Enterを押すと終了します。");
   
   
//終了待ち
   
getchar();
   
return 0;
実行例:
10問の問題が利用できます。
「メモリ」を英語で書くと?
ヒント:******

memory
正解です。Enterを押すと終了します。

これで単語当てゲームLv3は完成ということにします。
次回からはLv4編となり、構造体についてやっていく予定です。

[前へ]   [目次へ]   [次へ]

プログラミング講座 総合目次

最終更新 2011/10/14