C言語関係掲示板

過去ログ

No.393.数当てゲーム

[戻る] [ホームページ]
No.2698

数当てゲーム
投稿者---よー(2002/09/14 02:50:10)


こんばんわ。
今、数当てゲームを作っています。
本などを見ながら作りましたが、いまいちよくわかりません。
一応、そのソースをできたところまで下に
記してますがどなたか、ここはおかしいとか
こうやったほうがいいなど言っていただけたらうれしいです。

ソースはかなり見にくいですが何卒よろしくお願いします。


#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<ctype.h>
#include<time.h>

#define BUFF_SIZE 128
#define FIGURES 4

#define TRUE 1
#define FALSE 0
#define OTHER -1

void MakeUniqueNumbers( int [] );
int UniqueCheck( int [] );
int InputNumber( int [] );
int EndJudge( char * );
int CompareNumbers( int [] , char [] );
int ContinueGame( void );
int ContinueJudge( char * );

void main(void)
{
int data[FIGURES];

while( TRUE )
{

(void)MakeUniqueNumbers( data );

if( InputNumber( data ) == FALSE)
break;

if( ContinueGame() == FALSE)
break;

}
}

void MakeUniqueNumbers( int data[] )
{
int i;

srand((unsigned int)time((time_t *)0));

while( TRUE )
{
for( i = 0; i < FIGURES - 1; i++)
{
data[i] = rand() % 10;
}

if(UniqueCheck(data) == TRUE )
break;
}
}

int UniqueCheck(int data[])
{
int i,j;

for( i = 0; i < FIGURES -1; i++)
for(j = i + 1; j < FIGURES; j++)
if(data[i] == data[j])
return( FALSE );
return( TRUE );
}

int InputNumbers( int data[] )
{
char answer[BUFF_SIZE];

(void)printf("%d桁の数字を当ててください\n",FIGURES );
while( TRUE )
{

(void)printf("> 数値を入力してください");
(void)gets( answer );

if (EndJudge( answer ) == TRUE)
return( FALSE );

if( CompareNumbers(data, answer ) == TRUE)
return( TRUE );
}
}

int CompareNumbers( int data[], char answer[])
{
int i,j;
int ans_i;
int ans_d[FIGURES];
int hits = 0;
int blows = 0;

if( strlen( answer ) != FIGURES )
return( FALSE );

ans_i = atoi( answer );

for( i = FIGURES - 1; i >= 0; i--)
{
ans_d[i] = ans_i % 10;
ans_i /= 10;
}

if( UniqueCheck( ans_d ) == FALSE )
return( FALSE );

for( i = 0; i < FIGURES; i++)
{
for(j = 0; j < FIGURES; j++)
{
if( ans_d[i] == data[j] )
if( i == j )
hits++;
else
blows++;
}
}

if( hits == FIGURES )
{

(void)printf(" %dH\n", FIGURES );
return( TRUE );
}
(void)printf(" %dH and %dB\n", hits,blows );
return( FALSE );
}



int ContinueGame(void)
{
char answer[BUFF_SIZE];
int result;

while( TRUE )
{

(void)printf("ゲームを続けますか? (y/n) ");
(void)gets( answer );

if( EndJudge( answer ) == TRUE )
return( FALSE );

if((result = ContinueJudge( answer )) == TRUE )
return( TRUE );
else if( result == FALSE )
return( FALSE );
}
}


int EndJudge( char *answer )
{
if(( strcmp( answer, "q" ) == 0 )
|| ( strcmp( answer, "Q" ) == 0 )
|| ( strcmp( answer, "bye" ) == 0 ))
return( TRUE );
return( FALSE );
}

int ContinueJudge( char *answer )
{
if(( strcmp( answer, "y" ) == 0 )
|| ( strcmp( answer, "Y" ) == 0 ))
return( TRUE );
if(( strcmp( answer, "n" ) == 0)
|| (strcmp( answer, "N" ) == 0 ))
return( FALSE );
return( OTHER );
}


No.2700

Re:数当てゲーム
投稿者---TDa(2002/09/14 12:02:28)


こんにちは。いくつかつっこませてもらいます。

※コメントは書かないのですか?
関数の意味やグローバル変数、マクロ定義の意味くらいは書いておきましょう。
これだけでだいぶコードが読みやすくなります。

※1, 0のマクロ定義
がとても気になります。Cには論理型はなくて式を評価した結果が0か非0かで判定して
いるのはご存じでしょう。ですから1とTRUEは同値ではありません。それに大抵のプログラマに
とっては0と1はマジックナンバーではありません。TRUEとタイプするより1とタイプする方が
楽ですしwhile (1)なら疑問の余地はないですがwhile (TRUE)なら一応どういう風に定義
しているか確認しようと思う人は多いでしょう。
関数の正常終了を表すものとしてはstdlib.hのEXIT_SUCCESS,EXIT_FAILUREを使用する
場合が多いと思います。

※for( i = 0; i < FIGURES - 1; i++)
^^^^^
配列の宣言はint data[FIGURES];なのにforループの処理は上記のようになっています。
data[3]が初期化されません。これ意図した通りですか?

※(void)printf("%d桁の数字を当ててください\n",FIGURES );
このprintfをvoidにキャストしているのは意味がわかってしていますか?
コンパイラの警告を黙らせることが習慣になると怖いので一応つっこみます。
戻り値を大事にコーディングするなら
if (printf("%d桁の数字を当ててください\n",FIGURES) != 1)
assert(0);
みたいに書くんでしょうけど、確かにこれはあんまりだと思います。



No.2711

数当てゲーム
投稿者---よー(2002/09/16 02:44:22)


この間は、指摘ありがとうございました。実は前のソースは
あるテキストを参考にしたんですが自分でもちょっと分かりにくく
ここの掲示板に載せてもらったんですが、あまり好ましくないものでしたか・・・
そこで今度は自分で考えて数当てゲームを考えましたので
アドバイスお願いします。

ちなみにルールは4桁の数を12回以内に当てるというもので
4桁を入力する都度、HIT数とBLOW数が表示されます。
しかし下記のソースをコンパイルすると
1回目はHIT数とBLOW数が表示されるんですが、
2回目以降は出てきません。
あともし文法的に間違っているところがあれば
厳しく指摘してください。
何度もすいませんがよろしくお願いします。


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


main()
{

int try, hit, blow;
int q1, q2, q3, q4, g1, g2, g3, g4;
int guess;

/* 題名表示 */
puts(" * * * * * * * * * * * * * * * ");
puts(" * * ");
puts(" *   HIT & BLOW * ");
puts(" * * ");
puts(" * * * * * * * * * * * * * * * ");

printf("4桁の数????を12回以内で当てて下さい\n");


/* 乱数の発生 */
srand(time(NULL));

/* 正解(1〜9の数字4個の重複のない並び)を乱数で生成する */
q1 = (rand() % 10) +1; /* 左端 */
q2 = (rand() % 10) +1; /* 左から二つ目 */
while (q2 == q1) /* 同じ数だったらもう一度 */
q2 = rand()%10+1;
q3 = rand()%10+1; /* 左から三つ目 */
while (q3 == q1 || q3 == q2) /* 同じ数だったらもう一度 */
q3 = rand()%10+1;
q4 = rand()%10+1;
while (q4 == q1 || q4 == q2 || q4 == q3) /* 右端 */
q4 = rand()%10+1;

for (try = 1; try <= 10; try++){
/* キーボードから4桁の数を入力 */
printf("\n%d回目 --> ", try);
scanf("%d", &guess);

/* 各桁を取り出す */
g1 = guess/1000; /* 左端 */
g2 = guess/100%10; /* 左から二つ目 */
g3 = guess/10&10; /* 左から三つ目 */
g4 = guess%10; /* 右端 */

/* 1〜9の数字4個の並びかどうかチェックする */
if ( g1 > 10 || g1 < 1 || g2 < 1 || g3 < 1 || g4 < 1)
continue;

/* その4個の数字に重複がないかどうかチェックする */
if( g1 == g2 || g2 == g3 || g3 == g4 || g3 == g1 || g4 == g1 || g2 == g4)
continue;

/* hitとblowの数を数える */
hit = 0; blow = 0;
if (g1 == q1) hit++;
if (g2 == q2) hit++;
if (g3 == q3) hit++;
if (g4 == q4) hit++;
if (g1 == q2 || g1 == q3 || g1 == q4) blow++;
if (g2 == q1 || g2 == q3 || g2 == q4) blow++;
if (g3 == q1 || g3 == q2 || g3 == q4) blow++;
if (g4 == q1 || g4 == q2 || g4 == q3) blow++;

/* hitとblowの数を表示 */
printf("hit = %d blow = %d", hit ,blow);

/* 正解なら繰り返しを終了 */
if ( hit == 4)
break;
}

/* 正解ならばALL HITと表示。12回以内で正解が入力されなかったら、正解を表示する。 */
printf("ALL HIT !!\n\n");

if ( try > 12)
printf("正解は[%d%d%d%d]です。\n", q1, q2, q3, q4);
}


No.2713

Re:数当てゲーム
投稿者---TDa(2002/09/16 12:18:03)


サンプルコードmain

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

#define ANSWER_SIZE     4   /*答えの桁数*/
#define TRY_MAX         12  /*答えることのできる回数。*/

void initialize(void);
int randN(int n);
void shuffle(int *array, int size);
void setAnser(int *array, int size);

int main(void)
{
    int try_cnt, hit, blow;
    int ans[ANSWER_SIZE];
    char guess[ANSWER_SIZE];
    int i, j;

    /* 初期化 */
    initialize();
    /* 正解をセット */
    setAnser(ans, ANSWER_SIZE);
    
    for (try_cnt = 1; try_cnt <= TRY_MAX; try_cnt++){
        /* キーボードから4桁の数を入力 */
        printf("\n%d回目 --> ", try_cnt);
        scanf("%s", guess);

        /* hitとblowの数を数える */
        hit = blow = 0;
        for (i = 0; i < ANSWER_SIZE; ++i) {
            if (ans[i] == guess[i] - '0') {
                hit++;
            }
        }
        for (i = 0; i < ANSWER_SIZE; ++i) {
            for (j = 0; j < ANSWER_SIZE; ++j) {
                if (ans[i] == guess[j] - '0'){
                    blow++;
                    break;
                }
            }
        }

        /* hitとblowの数を表示 */
        printf("hit = %d blow = %d", hit ,blow);

        /* 正解なら繰り返しを終了 */
        if ( hit == ANSWER_SIZE)
            break;
    }

    /* 正解ならばALL HITと表示。TRY_MAX回以内で正解が入力されなかったら、正解を表示する。 */
    if (try_cnt < TRY_MAX) {
        printf("ALL HIT !! %d回目\n", try_cnt);
    } else {
        printf("正解は[%d%d%d%d]でした\n", ans[0], ans[1], ans[2], ans[3]);
    }

    return 0;

}


No.2714

Re:数当てゲーム
投稿者---TDa(2002/09/16 12:34:10)


長い投稿になり申し訳ありません。掲示板のリソースが気になるのですが
関数への切り分けというのは有用なものだと思い投稿させていただきます。

さてコードに関するコメントですが各処理ごとに関数に切り分けましょう。
大きな関数を作るのではなく独立性を高めた関数をいくつも作る方がよいのです。

現行のコードで一番の手抜き部分は入力処理です。エラーチェックはもちろん
入力の妥当性も何もチェックしていません。この部分は一つの関数にする方が
いいでしょう。練習としてやってみてください。

void initialize(void)
/** ======================================
 * 初期化関数
 * タイトルを表示して乱数の種を作る
 * ======================================= */
{
    /* 題名表示 */
    puts(" * * * * * * * * * * * * * * * ");
    puts(" * * ");
    puts(" *   HIT & BLOW * ");
    puts(" * * ");
    puts(" * * * * * * * * * * * * * * * ");

    printf("4桁の数????を12回以内で当てて下さい\n");

    /*現在の時刻より種生成*/
    srand((unsigned) time(NULL));
}

int randN(int n)
/* ===============================
* 0からN-1までの範囲の乱数を返す。
* NはRAND_MAXより小さいこと
* ================================ */
{    
    return rand() / (RAND_MAX / n + 1);
}

void shuffle(int *array, int size)
/* =================================
* 要素数sizeの配列arrayの順番を
* ランダムに並び替える。
* bcc32においてはRAND_MAXが32767であり
* sizeがこれを越えた場合は想定しないことにする。
* ================================== */
{
    int i, temp;
    int rand_number;
    
    for (i = 0; i < size; i++) {
        rand_number = randN(size);
        temp = array[i];;
        array[i] = array[rand_number];
        array[rand_number] = temp;
    }
}

void setAnser(int *array, int size)
/** =========================================
 * 正解をarrayにセット
 * 正解は1〜9の数字4個の重複のない並び
 * ========================================== */
{
    int temp[9];
    int i;
    
    for (i = 0; i < 9; ++i) {
        temp[i] = i + 1;
    }
    
    shuffle(temp, 9);
    
    for (i = 0; i < size; ++i) {
        array[i] = temp[i];
    }
}


No.2715

Re:数当てゲーム
投稿者---かずま(2002/09/16 17:28:03)


> int randN(int n)
> /* ===============================
> * 0からN-1までの範囲の乱数を返す。
> * NはRAND_MAXより小さいこと
> * ================================ */
> {    
>     return rand() / (RAND_MAX / n + 1);
> }
30000 は RAND_MAX(=32767) より小さいので、randN(30000) は、0〜29999
の値を返すべきなのに、0〜16383 の値しか返さないのではありませんか。
なぜなら、rand() が返す 0〜32767 を 2 で割るわけですから。

100 は RAND_MAX より小さく、randN(100) は 0〜99 の値を返しますが、
0〜98 に比べて、99 を返す確率が小さくなります。
なぜなら、rand() が返す 0〜32767 のうち、
0〜327 の 328個の値に対しては、0 を返す。
328〜655 の 328個の値に対しては、1 を返す。
656〜983 の 328個の値に対しては、2 を返す。
 :
32144〜32471 の 328個の値に対しては、98 を返す。
32472〜32767 の 296個の値に対しては、99 を返す。

なぜ、このような問題のある randN() を使うのか、説明をお願いいたします。

No.2717

Re:数当てゲーム
投稿者---TDa(2002/09/16 18:45:44)


>32144〜32471 の 328個の値に対しては、98 を返す。
>32472〜32767 の 296個の値に対しては、99 を返す。
>
>なぜ、このような問題のある randN() を使うのか、説明をお願いいたします。

これはCfaqにあるコードをそのまま持ってきたものです。
コメントに入れるべきだったのでしょうがRAND_MAXにたいしてnは十分小さいこと
という条件が付いています。nがRAND_MAXにたいして大きくなるとおっしゃるような
問題があるからです。

でなぜこのようなコードになっているかというとrand()の返す値の下のビットは
ランダムで無いことが多いので
rand() % nを返すよりよい場合が多いと言うことです。


No.2718

Re:数当てゲーム
投稿者---TDa(2002/09/16 18:50:57)


&nbsp;&nbsp;&nbsp;&nbsp;char&nbsp;guess[ANSWER_SIZE];
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;scanf("%s",&nbsp;guess);

手抜きコードとはいえこれはまずかったですね。
'\0'の分が確保されていないですね。
どっちにしてもここは手を入れないと駄目ですけど。
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;

No.2720

Re:数当てゲーム
投稿者---かずま(2002/09/16 22:50:46)


> 1回目はHIT数とBLOW数が表示されるんですが、
> 2回目以降は出てきません。

q1〜q4 の値が求まったところに、printf("q? = %d %d %d %d\n", q1, q2, q3, q4);
g1〜g4 の値が求まったところに、printf("g? = %d %d %d %d\n", g1, g2, g3, g4);
を挿入してみると、何が悪いのか分かります。

No.2731

数当てゲーム
投稿者---よー(2002/09/18 22:46:35)


TDaさん、かずまさんアドバイスありがとうございました。
アドバイスのおかげで、ゲームを楽しめるようになりました。
またこの掲示板を利用させてもらうと思いますので、
そのときはまたよろしくお願いします。