候補リストと候補数リスト

コンピュータに答えの候補を教えなければなりません。答えの候補となるもののリストを候補リストと呼ぶことにします。

候補リストの作成

異なる数字からなる3桁の数のリストを以下のようにして作成します。

  1. 各桁の数を表す変数$n0, $n1, $n2を用意し、それぞれの数をforループで1から9まで変化させる。
  2. $n0, $n1, $n2が異なる数の場合には、$n0*100+$n1*10+$n2を計算し、配列@allkouhoに追加(push)する。

プログラム

# 候補リスト(@allkouho)の作成
for ($n0=1;$n0<=9;$n0++) {
	for ($n1=1;$n1<=9;$n1++) {
		if ($n1!=$n0) {
			for ($n2=1;$n2<=9;$n2++) {
				if ($n2!=$n0 && $n2!=$n1) {
					push(@allkouho, $n0*100+$n1*10+$n2);
				}
			}
		}
	}
}

&print_list(@allkouho); 

リスト表示サブルーチン

リストを表示するサブルーチンは以下のようになります。

# リスト表示サブルーチン
sub print_list {
	my @array=@_; # リストを受け取る
	my $youso;
	foreach $youso (@array) { # 配列の要素を一つずつ取り出して繰り返し
		print "$youso ";
	}
	print "\n";
}

候補数リスト

質問をした結果は、0H0B, 0H1B, 0H2B, 0H3B, 1H0B, 1H1B, 1H2B, 2H0B, 3H0Bのいずれかですから、質問の結果で、候補を9つに分類することができます。この分類に属している候補の数のリストを候補数リストと呼ぶことにします。

例 最初の「123」の質問で候補を分類した場合

答え 候補

候補数

3H0B 123 1
2H0B 124,125,126, ...723, 823, 923 18
1H2B 132, 213, 321 3
 :  

最初の質問で幸いにも「1H2B」が帰ってきた場合には、すでに候補が3個に絞られたということになります。

このようにHit&Blowは質問ごとに候補の数を減らしていくことで答えを見つけ出すゲームであるといえます。

分類した候補数を数える(count_kouhoサブルーチン)

現在の候補リストに対して質問を想定して分類した場合に、各分類の候補数がいくつになるかを数えるサブルーチンをつくります。

  1. 候補リストの候補を一つずつ取り出し、候補と質問を判定サブルーチンで判定する。

  2. 得られた結果の候補数を一つ増やす。

候補数を保管するのに配列を使いますが、インデックスとして「2H1B」を使うことはできませんから、

Hit数×4+Blow数

をインデックスとして代用することにします。(使わないインデックスができますが、簡略化のため。)

count_kouhoサブルーチン

# 分類した候補数を数えてリストとして返す
sub count_kouho {
	my ($number, @kouho)=@_; # 質問と候補リストを受け取る
	my @kouhosu; # 候補数を入れるリスト
	my $kouho, $hit, $blow;
	my $i;

	for ($i=0;$i<=12;$i++) { # 候補数リストを初期化
		$kouhosu[$i]=0;
	} 
	
	foreach $kouho (@kouho) { # 候補リストの全ての要素に対して
		($hit, $blow)=&hantei($number, $kouho);
		$kouhosu[$hit*4+$blow]++;
	}
	return @kouhosu;
}

候補数出力サブルーチン

# 候補数出力サブルーチン
sub print_kouhosu {
	my @kouhosu=@_;
	for ($i=0;$i<=12;$i++) { # 候補数リストの出力
		print "$i: $kouhosu[$i] ";
	}
	print "\n";
}

呼び出し側

@kouho=@allkouho;

@kouhosu=&count_kouho(123, @kouho); # 123で分類した候補数
&print_kouhosu(@kouhosu); # 候補数の出力

候補リストの更新

質問した結果が得られたら、該当する候補のみに候補リストを更新します。

  1. 候補リストの候補を一つずつ取り出し、候補と質問を判定サブルーチンで判定する。
  2. 得られた結果が質問した結果と等しい場合には、配列@new_kouhoに追加(push)する。
  3. 全ての候補について終わったら、配列@new_kouhoを返す。

候補リストの更新サブルーチン

# 候補リストの更新
sub refresh {
	my ($number, $hit, $blow, @kouho)=@_; # 質問, Hit数, Blow数, 候補リストを受け取る
	my @new_kouho; # 新たな候補リスト
	my $h, $b;

	foreach $kouho (@kouho) {
		($h, $b)=&hantei($number, $kouho); # 候補を判定
		if ($number!=$kouho && $hit==$h && $blow==$b) {
			push(@new_kouho, $kouho); # 新たな候補リストに候補を追加
		} 
	}

	return @new_kouho;
}

呼び出し側(メインルーチンのwhileループを置き換え)

while () {
	$count++; # 回数を増やす
	$number=&input; # 入力

	@kouhosu=&count_kouho($number, @kouho); # 質問で候補を分類
	&print_kouhosu(@kouhosu); # 候補数の出力

	($hit, $blow)=&hantei($answer, $number); # 判定
	print "$count回目 $number: $hit"."H$blow"."B\n"; # 結果表示

	@kouho=&refresh($number, $hit, $blow, @kouho); # 候補リストの更新
	&print_list(@kouho); # 候補リストの出力
	
	if ($hit==3) { # 正解かどうか場合分け
		print "$count回で正解!";
		exit;
	}
}

ここまでのプログラム

# 候補リスト(@allkouho)の作成
for ($n0=1;$n0<=9;$n0++) {
	for ($n1=1;$n1<=9;$n1++) {
		if ($n1!=$n0) {
			for ($n2=1;$n2<=9;$n2++) {
				if ($n2!=$n0 && $n2!=$n1) {
					push(@allkouho, $n0*100+$n1*10+$n2);
				}
			}
		}
	}
}

$answer=&make_answer; # 答え作成
$count=0; # 回数を数える変数

@kouho=@allkouho;

while () {
	$count++; # 回数を増やす
	$number=&input; # 入力

	@kouhosu=&count_kouho($number, @kouho); # 質問で候補を分類
	&print_kouhosu(@kouhosu); # 候補数の出力

	($hit, $blow)=&hantei($answer, $number); # 判定
	print "$count回目 $number: $hit"."H$blow"."B\n"; # 結果表示

	@kouho=&refresh($number, $hit, $blow, @kouho); # 候補リストの更新
	&print_list(@kouho); # 候補リストの出力
	
	if ($hit==3) { # 正解かどうか場合分け
		print "$count回で正解!";
		exit;
	}
}

# リスト表示サブルーチン
sub print_list {
	my @array=@_; # リストを受け取る
	my $youso;
	foreach $youso (@array) { # 配列の要素を一つずつ取り出して繰り返し
		print "$youso ";
	}
	print "\n";
}

# 分類した候補数を数えてリストとして返す
sub count_kouho {
	my ($number, @kouho)=@_; # 質問と候補リストを受け取る
	my @kouhosu; # 候補数を入れるリスト
	my $kouho, $hit, $blow;
	my $i;

	for ($i=0;$i<=12;$i++) { # 候補数リストを初期化
		$kouhosu[$i]=0;
	} 
	
	foreach $kouho (@kouho) { # 候補リストの全ての要素に対して
		($hit, $blow)=&hantei($number, $kouho);
		$kouhosu[$hit*4+$blow]++;
	}
	return @kouhosu;
}

# 候補数出力サブルーチン
sub print_kouhosu {
	my @kouhosu=@_;
	for ($i=0;$i<=12;$i++) { # 候補数リストの出力
		print "$i: $kouhosu[$i] ";
	}
	print "\n";
}

# 候補リストの更新
sub refresh {
	my ($number, $hit, $blow, @kouho)=@_; # 質問, Hit数, Blow数, 候補リストを受け取る
	my @new_kouho; # 新たな候補リスト
	my $h, $b;

	foreach $kouho (@kouho) {
		($h, $b)=&hantei($number, $kouho); # 候補を判定
		if ($number!=$kouho && $hit==$h && $blow==$b) {
			push(@new_kouho, $kouho); # 新たな候補リストに候補を追加
		} 
	}

	return @new_kouho;
}

# 入力サブルーチン
sub input {
	my $number;
	my $n0,$n1,$n2;
	while () { # 無限ループ
		print "1-9の数字を重複なしに組み合わせた3桁の数字を入力してください。";
		$number=<stdin>;
		chomp($number);
		if ($number>=123 && $number<=987) { # 数字チェック
			$n0=int($number/100); # 百の桁
			$n1=int(($number%100)/10); # 十の桁
			$n2=$number%10; # 一の桁
			if ($n0!=$n1 && $n1!=$n2 && $n2!=$n0 && $n0*$n1*$n2!=0) {# 重複&非零チェック
				return $number;
			}
		}
		print "入力が正しくありません。\n";
	}
}

# 判定サブルーチン
sub hantei {
	my $a, $b;
	my $a0, $a1, $a2, $b0, $b1, $b2;
	my $hit=0;
	my $blow=0;
	($a, $b)=@_; # 2引数を変数$a, $bに

	$a0=int($a/100); # 百の桁
	$a1=int(($a%100)/10); # 十の桁
	$a2=$a%10; # 一の桁
	$b0=int($b/100); # 百の桁
	$b1=int(($b%100)/10); # 十の桁
	$b2=$b%10; # 一の桁

	if ($a0==$b0) { $hit++; }
	if ($a1==$b1) { $hit++; }
	if ($a2==$b2) { $hit++; }

	if ($a0==$b1) { $blow++; }
	if ($a0==$b2) { $blow++; }
	if ($a1==$b0) { $blow++; }
	if ($a1==$b2) { $blow++; }
	if ($a2==$b0) { $blow++; }
	if ($a2==$b1) { $blow++; }

	return ($hit, $blow);
}

# 答え作成サブルーチン
sub make_answer {
	my $n0, $n1, $n2; # 各桁の変数を用意

	$n0=int(rand(9))+1; # 1〜9の乱数を作成

	do {
		$n1=int(rand(9))+1; # 1〜9の乱数を作成
	} until ($n1!=$n0); # $n0と$n1が異なるまで繰り返し

	do {
		$n2=int(rand(9))+1; # 1〜9の乱数を作成
	} until ($n2!=$n1 && $n2!=$n0); # $n0と$n1と$n2が異なるまで繰り返し

	return $n0*100+$n1*10+$n2;
}