質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

90.51%

  • Python

    11685questions

    Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

  • Python 3.x

    9777questions

    Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

  • アルゴリズム

    498questions

    アルゴリズムとは、定められた目的を達成するために、プログラムの理論的な動作を定義するものです。

Pythonで数当てゲーム(Hit&Blow)を作りたいのですが...

受付中

回答 1

投稿

  • 評価
  • クリップ 0
  • VIEW 1,094

yomogian

score 2

 前提・実現したいこと

僕は最近Pythonを独学で勉強しはじめたプログラミング初心者です。
中学生の時、数当てゲームというのがクラスで流行ったことを思い出し作ってみたいと思ったのですが、
コンピュータを対戦できるようにするのはなかなか難しいことのようで、お力を借りたいなと思うのですが、どのようなプログラムを皆様ならお書きになりますか?

 Hit&Blowのルール

今回作るものルールは以下のようにしたいと思っています。

・隠れた4桁の数字(重複は無し)を当てる
・回答するとHITとBLOWの数が出る。
・HITは数字も桁数も同じ、BLOWは数字はあたっているけど桁数が違う

 試したこと

素人ながらとりあえずプログラムを書いてみました。
至らない点が多々あると思うので指摘していただけるとありがたいです。

# coding: utf-8

# In[3]:


import random
import copy
from collections import OrderedDict
n = 3
pnum = list(map(lambda i:0,range(0,n)))#player's number
onum = list(map(lambda i:0,range(0,n))) #opponent's number

# In[4]:

def check_num(pre,onum,n): #プレイヤーの数字チェック
    hit = 0
    blow = 0
    for i in range(0,n):
        for s in range(0,n):
            if pre[i] == onum[s]:
                if i == s:
                    hit += 1
                else:
                    blow += 1
    return(hit,blow)

class Opponent():
    def __init__(self):
        self.pres = OrderedDict()#記録順を保持する辞書,{(予想):(結果)}
        self.count = 0 
        self.pre_hb = [0,0] #前回の予想結果
        self.change = 0 #H+Bがn/2を超えている時、どの部分を変更したか
        self.deln = [] #H:0,B:0の時の数字を保持
    def check(self,pnum,n):
        same = 1 
        while same == 1:#過去の予想と重複していないか
            if self.pre_hb[0] + self.pre_hb[1] == n: #数字の組み合わせが揃った時
                opre = list(list(self.pres.keys())[self.count-1])
                random.shuffle(opre)
                ##リストシャッフル動作
                hit = 0
                blow = 0
                for i in range(0,n):
                    for s in range(0,n):
                        if opre[i] == pnum[s]:
                            if i == s:
                                hit += 1
                            else:
                                blow += 1



            elif self.count == 0: ##初回はランダム
                predup = True
                while predup: #同じ数字になっていないか
                    opre = list(map(lambda i:random.randint(0,9),range(0,n)))
                    if len(opre) == len(set(opre)):
                        predup = False

                hit = 0
                blow = 0
                for i in range(0,n):
                    for s in range(0,n):
                        if opre[i] == pnum[s]:
                            if i == s:
                                hit += 1
                            else:
                                blow += 1

                same = 0 #初回は被らない
                break

            elif self.count == 1:##2回目以降はHBが半分以上かどうかも判定
                if self.pre_hb[0] + self.pre_hb[1] == 0:#数字の組み合わせが合っている時
                    self.deln.extend(list(list(self.pres.keys())[self.count-1]))
                    self.deln = list(set(self.deln))
                if self.pre_hb[0] + self.pre_hb[1] > n/2:#半分以上あっているとき
                    ##半分以上なら前回のから1桁だけ変える
                    dup = True
                    while dup:
                        opre = list(list(self.pres.keys())[self.count-1])
                        self.change = random.randint(0,n-1)
                        opre[self.change] = random.choice(list(set(range(0,10)) - set(list(self.pres.keys())[self.count-1])))
                        for i in range(0,n):
                            if opre[i] in self.deln:
                                dup = True
                                break
                            else:
                                dup = False

                else:##違ったらランダム
                    dup = True
                    while dup:
                        predup = True
                        while predup:
                            opre = list(map(lambda i:random.randint(0,9),range(0,n)))
                            if len(opre) == len(set(opre)):
                                predup = False
                            for i in range(0,n):
                                if opre[i] in self.deln:
                                    dup = True
                                    break
                                else:
                                    dup = False


                hit = 0
                blow = 0
                for i in range(0,n):
                    for s in range(0,n):
                        if opre[i] == pnum[s]:
                            if i == s:
                                hit += 1
                            else:
                                blow += 1


            else:  ##3回目以降は変えた場所がうまく行ってるかも判定
                if self.pre_hb[0] + self.pre_hb[1] == 0:
                    self.deln.extend(list(list(self.pres.keys())[self.count-1]))
                    self.deln = list(set(self.deln))
                if self.pre_hb[0] + self.pre_hb[1] > n/2:
                    if self.pre_hb[0] + self.pre_hb[1] >= self.pres[list(self.pres.keys())[self.count-2]][0] + self.pres[list(self.pres.keys())[self.count-2]][1]:
                          ##うまくいってたら別のところ
                        dup = True
                        while dup:
                            opre = list(list(self.pres.keys())[self.count-1])
                            m = random.randint(0,n-1)
                            while m == self.change:
                                m = random.randint(0,n-1)
                            opre[self.change] = random.choice(list(set(range(0,10)) - set(list(self.pres.keys())[self.count-1])))

                            for i in range(0,n):
                                if opre[i] in self.deln:
                                    dup = True
                                    break
                                else:
                                    dup = False
                    else:  ##行ってなければランダムの場所
                        dup = True
                        while dup:
                            opre = list(list(self.pres.keys())[self.count-1])
                            self.change = random.randint(0,n-1)
                            opre[self.change] = random.choice(list(set(range(0,10)) - set(list(self.pres.keys())[self.count-1])))
                            for i in range(0,n):
                                if opre[i] in self.deln:
                                    dup = True
                                    break
                                else:
                                    dup = False

                else:##違ったらランダム
                    dup = True
                    while dup:
                        predup = True
                        while predup:
                            opre = list(map(lambda i:random.randint(0,9),range(0,n)))
                            if len(opre) == len(set(opre)):
                                predup = False
                            for i in range(0,n):
                                if opre[i] in self.deln:
                                    dup = True
                                    break
                                else:
                                    dup = False


                hit = 0
                blow = 0
                for i in range(0,n):
                    for s in range(0,n):
                        if opre[i] == pnum[s]:
                            if i == s:
                                hit += 1
                            else:
                                blow += 1



            for p in range(len(list(self.pres.keys()))): #過去の予想との重複チェック
                if list(list(self.pres.keys())[p]) == opre:
                    print("it's same")
                    same = 1
                    break
                else:
                    same = 0

        self.pres[tuple(opre)] = (hit,blow)
        self.pre_hb = copy.deepcopy([hit,blow])
        self.count += 1 
        return (hit,blow,opre)







# In[6]:


while len(pnum) != len(set(pnum)) or len(pnum) != n: 
    num = input("{}桁の数字を入力してください:\n ※数字の重複がないように".format(n))
    pnum = [int(x) for x in list(num)]

while len(onum) != len(set(onum)):
    onum = list(map(lambda i:random.randint(0,9),range(0,n)))


# In[7]:


## ゲーム開始
r = random.randint(0,1)
print("順番をランダムで決定します")
if r == 0:
    print("あなたは先行です")
    pre = [0]
    while len(pre) != n:
        print("予想する{}桁の数字を入力してください。同じ数字でも構いません".format(n))
        pre = input()
    pre = [int(x) for x in list(pre)]
    res = check_num(pre,onum,n)
    print("結果:HIT{},BLOW{}".format(res[0],res[1]))

else:
    print("あなたは後攻です")
    res = [0,0]


opponent = Opponent()
ores = [0,0,0]
while res[0] != n and ores[0] != n:
    ores = opponent.check(pnum,n)
    print("敵の結果{}:HIT{},BLOW{} \n".format(ores[2],ores[0],ores[1]))
    if ores[0] == n:
        break
    pre = [0]
    while len(pre) != n:
        print("予想する{}桁の数字を入力してください。同じ数字でも構いません".format(n))
        pre = input()
    pre = [int(x) for x in list(pre)]
    res = check_num(pre,onum,n)
    print("結果:HIT{},BLOW{}".format(res[0],res[1]))

    if opponent.count % 10 == 0:
        print("Now,deleted number is {}".format(opponent.deln))

if res[0] == n:
    print("正解!")
else:
    print("あなたの負け!")
  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 1

+3

かなりの力作ですねー。。
全部読み切れてませんが、気づいた点だけ

1,# In[4]:の行があることから推測しますが、pythonの対話モード(インタラクティブモード)で開発と実行をしていませんか? 
無料のエディタやIDE(統合開発環境)をインストールして、.pyのスクリプトファイルを作成し実行することをお勧めします。

2,以下の関数 check_numについて

def check_num(pre,onum,n): #プレイヤーの数字チェック
    hit = 0
    blow = 0
    for i in range(0,n):
        for s in range(0,n):
            if pre[i] == onum[s]:
                if i == s:
                    hit += 1
                else:
                    blow += 1
    return(hit,blow)

行っているのはhit&blowの判定なため、
2-1,HitAndBlowクラスを新規作成
2-2,関数名をcheck_numからHitAndBlow#Judgeに変更
2-3,答えの数字を生成するHitAndBlow#generate関数を追加。
0~9の数字配列を生成後にrandom#shuffleを使用して生成する。

class HitAndBlow(object):
    def __init__(self, n: int=3):
        assert 10 >= n
        self.N = n
        # HitAndBlowクラスが正解情報(onum)を管理するほうがいいです。
        # ※self.answerを使ったjudge関数は宿題にしておきます。
        self.answer = self.generate()

    def generate(self) -> list:
        """
        答えの数字を生成。
        :return:
        """
        digits = list(range(10))
        random.shuffle(digits)
        return digits[:self.N]

    def judge(self, guess: list, answer: list) -> tuple:
        """
        Hit&Blowの判定処理
        :param guess: 予測した数字
        :param answer: 正解の数字
        :return:
        """
        # 引数で渡されたpreとonumの桁数が同じでないといけない制約がありますよね。その時はassert文で表明します。
        assert len(guess) == len(answer)
        hit = sum(g == a for g, a in zip(guess, answer))
        blow = sum(g in answer for g in guess) - hit
        return hit, blow
# 使い方
game = HitAndBlow()
hit, blow = game.judge(pnum, onum)

3,check関数内で以下のようにcheck_num関数を使用せずに、hit&blowの判定を行っている箇所が4箇所あります。check_num関数の呼び出しに統一するとコードの行が削減できます。

                ##リストシャッフル動作
                hit = 0
                blow = 0
                for i in range(0,n):
                    for s in range(0,n):
                        if opre[i] == pnum[s]:
                            if i == s:
                                hit += 1
                            else:
                                blow += 1

■余談
Opponentクラスのcheck関数が158行と長すぎるためデバックしずらいです。
機能を絞った関数を作成して呼び出すようにするか、各数字を生成する部分/ループ処理/条件判定を分けたほうが、個々でテストできるようになります。

■参考情報
ヒットアンドブローをPythonで書いてみる

投稿

編集

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

同じタグがついた質問を見る

  • Python

    11685questions

    Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

  • Python 3.x

    9777questions

    Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

  • アルゴリズム

    498questions

    アルゴリズムとは、定められた目的を達成するために、プログラムの理論的な動作を定義するものです。