LoginSignup
1
3

More than 3 years have passed since last update.

7 行でゲーム(ヒット&ブロー、または 3 桁 MOO)を作る

Posted at

いきなり結論のコード:

#include <time.h>
int printf(const char*,...);int scanf(const char*,...);char a[3],g[4],z=48;int
rand();void srand(unsigned);int f(char*x){return x[0]==x[1]||x[0]==x[2]||x[1]
==x[2];}int i,h,b;int main(){srand(time(NULL));do{a[0]=z+rand()%9+1;a[1]=z+ra\
nd()%10;a[2]=z+rand()%10;}while(f(a));do{h=0;do printf(">"),scanf("%3s",g);wh\
ile(f(g));for(i=0;i<3;i++)h+=a[i]==g[i]?g[i]=0,1:0;b=0;for(i=0;i<9;i++)b+=a[i/
3]==g[i%3]?1:0;printf("H%d B%d\n",h,b);}while(h!=3);printf("OK!\n");return 0;}

はじめに

 小学生のときに、紙と鉛筆だけでできる対戦ゲームが大流行したことがありました。今思うと 数当てゲーム MOOを 3 桁にした変形版なんだろうと思います。マスターマインドの変形版とも言えますが、当時の僕らは H がいくつで B がいくつ、といったフォーマットで情報をやりとりしていたので、ここではヒット&ブローという呼び名で統一したいと思います(正確に言えば、3 桁の Hit & Blow ということになるかとは思いますが、それはそれとして)。

 今回は、コンピュータが考えた答えをプレイヤーがひたすら推測し続ける、というプログラムとなります(対戦ゲームではありません)。

ゲームのルール

準備

 小学生の僕たちが遊んでいたこのゲームは、2 人で遊ぶゲームでした。各自、最初に答えとなる 3 桁の数字を、相手に見えないようにメモします。この 3 桁の数字は何でも良いというわけではありません。各桁は全て異なる数字でなければいけません。つまり、111 とか 565 などの数字は、答えの数字にはしてはダメです。もうひとつのルールとしては、最初の桁は 0 にしない、というものがありました。MOO の説明をみると、最初の桁が 0 であっても良いみたいなのですが、小学生だったので、012 は 12 で 2 桁じゃん!という考えがあったのではないかと思われます(なので、ここでは回答となる数字は少なくとも 102〜987 の範囲にある整数、ということになります)。

Hit と Blow

 いわゆる Hit & Blow と同じですが、念のため、ここでも説明しておきます。Hit は場所も数字も一致しているものの数を表します。例えば、987 という解に対して、907 と問い合わせる(攻撃した)場合、3 桁目の 9 と 1桁目の 7 が数字の種類と位置が合っているので、Hit は 2 となります(9 と 7 の 2 つの数字が Hit した、という意味です)。これを単に H2 と表記することとします。

 場所は合っていないけど、数字の種類が合っているものを Blow とします。Hit が命中なら、Blow はかすったという意味でしょうか。先の例で言うと、987 に対し、670 と問い合わせた場合、2 桁目の 7 は位置が異なっているけれども、種類は合っているので Blow となります。そしてそのような数はこの 7 だけなので、Blow は 1 となります。これを単に B1 と表記することにします。

 もちろん、987 に対し、123 という問い合わせをすると、Hit も Blow もひとつも無いので、H0 B0 という結果となります。このような結果が得られると、9 つの数字のうち、問い合わせに使った数字は全く関係ないこととなりますので、非常に大きなヒントとなります(今の例では、123 は解に存在しないので、4〜9 の 6 桁が解の候補となるわけです)。

H と B の数を用いて、このゲームの終了条件を書くと、H3 となる数を入力するまでゲームが続くこととなります。なお、ネットの記事を色々みてみると、H3 とは書かずに 3H と数字が先にくる書き方の方が主流のようですが、ここでは私の小学生時代のルールに倣い、数値を後に示す流儀でいきます。

清書版のコードと、その解説

// gcc spoiler.c

#include <time.h>

int printf(const char*,...);
int scanf(const char*,...);
int rand();
void srand(unsigned);

char a[3],g[4],z=48;    // 48=='0'
int i,h,b;  // h は Hit 数、b は Blow 数

// 同じ数字があったら true を返す
int f(char*x) {
    return x[0]==x[1] || x[0]==x[2] || x[1]==x[2];
}

int main() {
    srand(time(NULL));
    // 問題を作る
    do {
        a[0]=z+rand()%9+1;
        a[1]=z+rand()%10;
        a[2]=z+rand()%10;
    } while(f(a));

    do {
        do
            printf(">"), scanf("%3s",g); // カンマ式による短縮
        while(f(g));

        // Hit 数計算
        h=0;
        for(i=0;i<3;i++)
            h += a[i]==g[i] ? g[i]=0,1 : 0;

        // Blow 数計算
        b=0;
        for(i=0;i<9;i++)
            b += a[i/3] == g[i%3] ? 1 : 0;
        printf("H%d B%d\n",h,b);
    } while(h!=3);
    printf("OK!\n");
    return 0;
}

 コンピュータが想定した値は、配列 a に文字コードで格納します(実際は ASCII コード)。プレイヤーが入力した値は配列 g に格納されています。i=0,1,2 の全てに対して a[i]==g[i] が成立したらゲーム終了です。

Hit 数計算部分

 Hit 数計算部分では、カンマ式を使って Hit 数計算と、Hit した数が次の Blow 計算で再度カウントされないようにしています(以下の部分です)。

h += a[i]==g[i] ? g[i]=0,1 : 0;

もし、a[i]==g[i] が成立しているならば(つまり位置も数字も合っているならば)、g[i]=0,1 が実行されます。カンマ式のルールにより、g[i]=0 が実行され、その後 1 が評価されて、3 項演算子の評価結果は 1 となり、最終的には h+=1 が実行されることになります。

また、g[i]=0 としているため、次の Blow 数計算部分でも g[i] は Blow している入力としては評価されません(g には文字コードが入っているので、0 としておけば、'0'〜'9' の値(文字コード)とは異なる状況となります。

Blow 数計算部分

 Blow 数計算部分は、

        // Blow 数計算
        b=0;
        for(i=0;i<9;i++)
            b += a[i/3] == g[i%3] ? 1 : 0;

です。

i を 0〜8 まで回していますが、配列 a に対しては i/3 を、g に対しては g[i%3] としているので、結果として a[0],a[1],a[2] と b[0],b[1],b[2] の全てのペアを比較することとなります:

a[0] と b[0]  // i=0 のとき(以下同様)
a[0] と b[1]  // i=1
a[0] と b[2]  // i=2

a[1] と b[0]  // i=3
a[1] と b[1]  // i=4
a[1] と b[2]  // i=5

a[2] と b[0]  // i=6
a[2] と b[1]  // i=7
a[2] と b[2]  // i=8

その他

 ヘッダのインクルードを減らすため、自前でプロトタイプ宣言を書いたり、また、カンマ式を活用したりしていますが、基本的にはそんなにトリッキーなことはしていません。プログラムとしては結構素直な実行なのではないかな、と思っております。

ゲームの様子:

$ ./a.out
>123
H1 B0
>145
H1 B0
>167
H0 B0
>126
H0 B0
>146
H1 B0
>843
H2 B0
>943
H3 B0
OK!

この時の答えは 943 でした。

参考

1
3
8

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
3