LoginSignup
0
0

More than 1 year has passed since last update.

【Python】数字当てゲーム(ヒットアンドブロー Hit and Blow)を作ってみる

Last updated at Posted at 2021-11-22

はじめに

はるか昔,ボードゲームでやったことがある
何色かの色ピンを使ったボードゲームだったような…
場所があっていたら黒ピン,場所は違うけど色があっていたら白ピンを立てる
確か,そんなような…

家族がピンの代わりに数字でやる,現代版のやり方を教えてくれて,
少しはまっている

調べると,ヒットアンドブロー(Hit and Blow)というらしい
はるか昔の色ピンを使ったゲームは何ていう名のゲームだったんだろう…

Python で作ってみたらどうだろうと考えたら,
割と簡単にいけるんじゃないかと思ったので,作ってみる

ルール

4桁の数字行う場合のルールを書くと次のようだ

  • 親が決めた4桁の正解数字(使ってよい数字は0から9まで)を,子が当てる
  • 子が予想した数字を正解数字と比較して,Hit 数と Blow 数を言う
  • 数字とその数字が使われている場所(桁)があっている場合は Hit
  • 数字はあっているが,数字が使われている場所(桁)があっていない場合は Blow
  • 例えば,正解が「2578」のとき,「1270」は 1 Hit 1 Blow となる
  • 子が4 Hitの数字を予想するまで繰り返す(4 Hit になったらゲーム終了)

要件

Python で作るにあたっての要件を決めておこう

  • 3桁から6桁の数字で遊べること
  • 桁数はゲーム起動時のコマンドライン引数で与えられること
  • 途中で,ギブアップ(ゲーム終了)できること
  • 何回で正解したか分かること

コマンドライン引数(argparse.ArgumentParser)

コマンドライン引数の処理には,argparse.ArgumentParser を使う
桁数のデフォルトを4にして,'-k' または '--keta' で引数を与えられるようにする

test01.py
import argparse;

parser = argparse.ArgumentParser();
parser.add_argument('--keta', '-k', help = '桁数', type = int, default = 4);
args = parser.parse_args();
print(args.keta);

test01.py を動かすと次のようだ

引数が何もない場合はデフォルトの 4

 python test01.py
4

'-k' または '--keta' を与えた場合は,引数で与えた値

 python test01.py -k 5
5

 python test01.py --keta 2
2

'-k' または '--keta' を指定して数字以外を与えた場合はエラー

 python test01.py -k w
usage: test01.py [-h] [--keta KETA]
test01.py: error: argument --keta/-k: invalid int value: 'w'

'-k' または '--keta' を指定して値を指定しない場合はエラー

 python test01.py -k
usage: test01.py [-h] [--keta KETA]
test01.py: error: argument --keta/-k: expected one argument

'-m' のような定義されていない引数の場合はエラー

 python test01.py -m 3
usage: test01.py [-h] [--keta KETA]
test01.py: error: unrecognized arguments: -m 3

'-h' または '--help' で使用法

 python test01.py -h
usage: test01.py [-h] [--keta KETA]

optional arguments:
  -h, --help            show this help message and exit
  --keta KETA, -k KETA  桁数

標準入力(input)

input 関数を使えばよい
プロンプトとして,'> ' を表示して,
プレイヤーが入力した数字を取得する場合は次のようにすればよい

v = input('> ');

ランダムな数列(numpy.random.shuffle)

numpy.arange を使って,0から始まる要素数10個の数列を公差1で作る

import numpy as np;
_seq = np.arange(0, 10, 1);

そして,numpy.random.shuffle を使って,順番をランダムに並び変える

np.random.shuffle(_seq);

正解の数字として,先頭の4要素を取得する

answer = _seq[0: 4];

数字かどうかのチェック(try except ValueError)

int 関数で int への型変換を行う
行う際に型変換エラーをキャッチした場合は数字ではないと判断する

checkInt という関数を用意し,引数が int かどうか判断する
int であれば True,int でなければ False を返す

def checkInt(v):
    r = False;
    try:
        _ = int(v);
        r = True;
    except ValueError:
        pass;
    return r;

文字の重複チェック(count)

プレイヤーが入力した文字に重複があればエラーとしたい
入力した文字をスライス [start: end] を count で確認すればよい
必ず1個はあるので,1より大きければ重複だ

def checkSame(v):
    for i in range(0, len(v)):
        if v.count(v[i: i + 1]) > 1:
            return False;
    return True;

numpy 配列の要素に条件を満たすものがあるかどうかのチェック(numpy.count_nonzero)

プレイヤーが入力した数字の Blow 判定だが,
正解の数字(answer)が numpy の配列なので,numpy.count_nonzero が使えそう

numpy.count_nonzero はデフォルトでは 0 以外の要素数を返すが,
条件式にしてあげれば,その条件を満たす要素数を返す

>>> import numpy as np;
>>> _seq = np.arange(0, 10, 1);
>>> np.random.shuffle(_seq);
>>> answer = _seq[0: 4];
>>> answer;
array([7, 6, 9, 1])
>>> np.count_nonzero(answer);
4 ## 0 以外の要素数を返している
>>> np.count_nonzero(answer == 9);
1 ## 条件(数字の9)を満たす要素数を返している

OS判定(os.name)

ゲームを起動した直後に画面のクリアを行いたいと思ったが,
画面クリアコマンドがOSにより違う
WSL や Mac は clear
Windows は cls
なので,ゲームを動かす環境により,コマンドを出しわける必要がある

Python で環境の違いでコマンドを出しわけたいような場合は,
os.name で判断ができる

WSL(Debian),Mac

>>> import os;
>>> os.name
'posix'

Windows

>>> import os;
>>> os.name
'nt'

なので,次のように CLEAR 変数にコマンド名を入れるようにすればいいだろう

CLEAR = 'clear';
if os.name == 'nt':
    CLEAR = 'cls'

まとめ

完成したソースはこちらからどうぞ(HitAndBlow.py)

さいごに

  • 簡単だと思ったが,調べてみないと実現しないことが多々あった
  • コンソール画面で行うゲームも面白いが,やはり人と対戦でやったほうが面白い!

補足

コメントをいただきまして,numpy を使わなくても標準の random 関数だけで実現できることがわかりました

ありがとうございます

正解の数字は random 関数で次のようにできます

answer = random.sample(range(0, 10), 4);

また,プレイヤーが入力した数字の Blow 判定は,
numpy.count_nonzero を使わずに,list の count で行える

np.count_nonzero(answer == 9);  ## numpy
answer.count(9);  ## list

完成したソースはこちらからどうぞ(HitAndBlowNoNumpy.py)

補足2

コメントをいただきました
ありがとうございます

文字の重複チェックを loop で count を使うコードを書いていましたが,
set(集合)を使えば簡単に書けますね

def is_unique(player_string):
    return (len(set(player_string)) == len(player_string));

checkSame という関数名でしたが,is_unique という関数名に変えています

0
0
6

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
0
0