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の数を確かめています。