Numer0n
Numer0nは数当てゲームです。
コンピュータ戦のときは,コンピュータが決めた4桁の数をプレイヤーが当てようとします。
プレイヤーが何か4桁の数を一つ言うと,コンピュータはそれがどれくらい当たっているかを答えます。同じ桁に同じ数字があったら「EAT」,同じ数字があっても桁が違ったら「BITE」で,EATの数とBITEの数をコンピュータは教えてくれます。
例えば,コンピュータが決めた数が1493,プレイヤーの言った数が9471だった場合,「4」が同じ場所にあって,「9」と「1」はずれた場所にあるので,「1EAT 2BITE」となります。
これを繰り返してプレイヤーは最終的に答えに辿り着きます。
まずこのゲームをCで実装してみましょう。
#include <stdio.h> #include <stdlib.h> #include <time.h>
int main(){ int a, b; int eat, bite; srand((unsigned)time(NULL)); do{ a = rand()%9000 + 1000; }while( a%10 == a/10%10 || a%10 == a/100%10 || a%10 == a/1000 || a/10%10 == a/100%10 || a/10%10 == a/1000 || a/100%10 == a/1000 ); for(;;){ scanf("%d", &b); if(a == b){ printf("YES\n"); return 0; }else{ eat = bite = 0; if(a%10 == b%10) eat++; else if(a%10 == b/10%10) bite++; else if(a%10 == b/100%10) bite++; else if(a%10 == b/1000) bite++; if(a/10%10 == b%10) bite++; else if(a/10%10 == b/10%10) eat++; else if(a/10%10 == b/100%10) bite++; else if(a/10%10 == b/1000) bite++; if(a/100%10 == b%10) bite++; else if(a/100%10 == b/10%10) bite++; else if(a/100%10 == b/100%10) eat++; else if(a/100%10 == b/1000) bite++; if(a/1000 == b%10) bite++; else if(a/1000 == b/10%10) bite++; else if(a/1000 == b/100%10) bite++; else if(a/1000 == b/1000) eat++; printf("%dEAT %dBITE\n", eat, bite); } } }
最初から4桁と決まっているのでこれでいいのでは?
…なんていう姿勢でいるといつか痛い目に遭います。綺麗なコードを心がけましょう。
今度はC++で実装することにします。
Number.h
#pragma once #include <iostream> const int Keta = 4, NMin = '0', NMax = '9'; class Ans { unsigned eat, bite;
public: Ans() : eat(0), bite(0) {} bool operator==(const Ans &ans) { return eat == ans.eat && bite == ans.bite; } }; int check(char[]); int judge(char[], char[], Ans &); std::ostream &operator<<(std::ostream &os, const Ans &ans) { os << ans.eat << "EAT " << ans.bite << "BITE"; return os; }
Numer0n.cpp
#include "Number.h" #include <cstdlib> #include <ctime> int constexpr factorization(int); int main() { char a[Keta + 1], b[Keta + 1]; Ans ans; srand((unsigned)time(NULL)); do{ for(int i = 0; i < Keta; i++) a[i] = rand()%(NMax - NMin + 1) + NMin; }while(check(a)); for(;;){ std::cin >> b; if(!judge(a, b, ans)){ if(ans.eat == Keta) std::cout << "YES" << std::endl; else std::cout << ans << std::endl; } } } int constexpr factorization(int a) { return (a == 1) ? 1 : factorization(a - 1) * a; } int check(char a[]) { for (int i = 0; i < Keta; i++) if (a[i] < NMin || NMax < a[i]) return -1; for (int i = 1; i < Keta; i++) for (int j = 0; j < i; j++) if (a[i] == a[j]) return -1; return 0; } int judge(char a[], char b[], Ans &ans) { if (check(a) || check(b)) return -1; ans.eat = ans.bite = 0; for (int i = 0; i < Keta; i++) for (int j = 0; j < Keta; j++) if (a[i] == b[j]) { if (i == j) ans.eat++; else ans.bite++; } return 0; }
綺麗になりました。めでたしめでたし。
最後に,aと比較した結果がansになるような数を全て出力する関数int search(char [], const Ans &);を作ります。例えば引数に「1928」と「2EAT 2BITE」を渡すと,「1298 1982 1829 8921 2918 9128」と出力する関数です。
この関数の実装は次のようになります。
int match(char a[], char b[]) { int count = 0; for (int i = 0; i < Keta; i++) for (int j = 0; j < Keta; j++) if (a[i] == b[j]) count++; return count; } int eat(char a[], char b[]) { int count = 0; for (int i = 0; i < Keta; i++) if (a[i] == b[i]) count++; return count; } int search(char a[], const Ans &_ans) { char b[Keta + 1]; for (int i = 0; i < Keta - 1; i++) b[i] = NMin + i; b[Keta - 1] = NMin + Keta - 2; b[Keta] = '\0'; char *cur = b + Keta - 1; char c[Keta]; Ans ans; for (;;) { cur = b + Keta - 1; if (b + Keta + (*cur) <= cur + NMax) (*cur)++; else { do cur--; while (b + Keta + (*cur) > cur + NMax); if (b == cur + 1) return 0; for ((*cur++)++; cur < b + Keta; cur++) *cur = cur[-1] + 1; } if (_ans.eat + _ans.bite == match(a, b)) for (int i = 0; i < factorization(Keta); i++) { for (int j = 0; j < Keta; j++) c[j] = b[j]; int _i = i; for (int j = 2; j <= Keta; j++) { for (int k = 0; k < _i%j; k++) { int t = b[Keta - j]; for (int l = Keta - j + 1; l < Keta; l++) b[l - 1] = b[l]; b[Keta - 1] = t; } _i /= j; } if (eat(a, b) == _ans.eat) std::cout << b << ' '; for (int j = 0; j < Keta; j++) b[j] = c[j]; } } }
ええ…5重ループですね。かいつまんで説明します。
まず0~9の10個の数字から4個選ぶ選び方は10C4=210通りあります。それぞれに対して,数の並べ替え方は4!=24通り。なんで初めから10P4=5040通りを試さないのかというと,並べ替えたときにeatとbiteの和が変わらないからです。
4個の数字を選んで,eatとbiteの和が合っているかどうかを確かめてから,それを並べ替えてeatの数を確かめています。