実現したいこと
学校の課題でプログラミングでヒットアンドブローというゲームを作ろうと思ったのですが、ランダムに生成された数字と自分で入力した数字を比較する際、input()で入力した値がうまく反映されず、エラーになってしまいます。エラーを起こさずにinputで入力した値をリスト化し、ランダムで生成した値と比較する方法を教えていただきたいです。
発生している問題・分からないこと
input()で得た文字列の数字をリストにしてrandom_numberと比較したいのに、inputした値をリストにすることができず、エラーになってしまいます。
該当のソースコード
import random a = random.randint(100, 999) b = str(a) secret_number = list(b) s = 0 print("回答に対して、出題者はヒットとブローの数でヒントを与える。 回答と正解を比べて、数と桁位置の両方が同じであることをヒットと呼び、数だけが同じで桁位置が異なることをブローと呼ぶ。 たとえば、正解が 1234 で、回答が 1354 なら、出題者は「2 ヒット、1 ブロー」というヒントを与え、正解までこれを繰り返す。") while s < 100: h = 0 b = 0 print("100から999の3桁の数字を入力してください。") m = input() person_number = list(m) if secret_number[0] == person_number[0]: h += 1 elif person_number[0] in secret_number: b += 1 if secret_number[1] == person_number[1]: h += 1 elif person_number[1] in secret_number: b += 1 if secret_number[2] == person_number[2]: h += 1 elif person_number[2] in secret_number: b += 1 print(f"hit数は{h}です。") print(f"blow数は{b}です。") s += 1
試したこと・調べたこと
- teratailやGoogle等で検索した
- ソースコードを自分なりに変更した
- 知人に聞いた
- その他
上記の詳細・結果
自分で考えて作りました。なので、参考する元のコードはありません。
補足
特になし
気になる質問をクリップする
クリップした質問は、後からいつでもMYページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2025/05/13 08:20
2025/05/13 11:50

回答3件
0
REPL再び
ゲームはこの「環境データ」Env型を「更新する」事でプレイできる。
単純なモデルは次のような無限ループだ。
Python
1env = init() 2while True: 3 env = print(eval(read, env))
もちろん、これは単なるモデルだ。Python組み込みのprint
はenv
を返す関数ではないし、eval
はPython自体の心臓部なんで、こういう風にホントに書いてはならない(自分で作った評価部、つまりゲームエンジンは別名にすべきで、eval
と言う名称でシャドウイングしてはならない)。
しかし大枠では、ゲーム進行、ってのは環境データを持ち回りで随時書き換えしていってるだけ、と言う観点を持とう。
これがREPL、つまり、Read-Eval-Print loopと言う「ソフトウェア設計の雛形」なんだ。
なお、ReadもPrintも「環境データを参照して良い」。
ただし、重要なルールに、ReadもPrintも「環境データを更新してはならない」と言う事がある。
「環境データの改変及び更新」は評価部しかやっちゃいけない事、なんだ。
肝に銘じておこう。
ゲーム終了条件
これも上に書いたんだけど、条件が良く分からん。
よって暫定的に次のような関数を終了条件とする。
Python
1# ゲーム終了? 2def is_game_over(x, env): 3 return env.s == 100 or x == env.secret_number
x
を入力文字列として、Env型env
のs
が100に到達するか、あるいはx
がenv
のsecret_number
と言う文字列と一致するか。
いずれにせよ、この関数は条件を満たした時真値(True
)を返し、そうじゃなければ偽値(False
)を返す。
読み込み部(Read)
まずは読み込み部(Read)を次のように書いてみよう。
Python
1# 読み込み部(Read) 2def read(prompt): 3 x = input(prompt) 4 n = int(x) 5 if x.isnumeric() and n >= mn and n <= mx: 6 return x 7 else: 8 raise ValueError
このゲームではread
には環境データの情報は必要ない。
単に入力情報の文字列(x
)が数値だけで構成されてるかどうかを判別し(isnumeric
)、また、それが想定範囲の値なのか調べる。そうならx
をそのまま返し、違うなら例外(ここではValueError
)を投げる。
評価部(Evaluator)
一般に、評価部は二引数関数で、一つは入力部から受け取ったデータ、もう一つは環境データ、となる。
このゲームの心臓部、言わばゲームエンジンは次のように書く。
Python
1# 評価器(Evaluator) 2def interp(x, env): 3 if is_game_over(x, env): 4 sys.exit() 5 else: 6 h = [i == j for i, j in zip(x, env.secret_number)].count(True) 7 return Env(env.secret_number, env.s + 1, h, [i in env.secret_number for i in x].count(True) - h) 8
ゲーム終了条件については既に関数定義してるのでいいだろう。
基本的には評価部は「新しく環境データを組み立てて」返す(人によってはここが気持ち悪いだろうが、ガベージコレクタ任せだ・笑)。
新しく組み立てる環境データの内訳は、答えとなるsecret_number
は変わらず、カウンタs
は一つ増え、あとはh
とb
を計算して格納するだけ、だ。
ここではh
を見る。
例えば解(env.secret_number
)が'893'、プレイヤーが入力した文字列(x
)が'123'だとしよう。関数zip
は次のようなペアを含むイテレータを生成する。
Python
1>>> list(zip('893', '123')) 2[('8', '1'), ('9', '2'), ('3', '3')] 3>>>
2つの文字列の要素順にペア(タプル)として纏められる。
あとは、そのタプルの要素が同値かどうか調べればいい。
それはこう書く。
Python
1>>> [i == j for i, j in zip('893', '123')] 2[False, False, True] 3>>>
各文字列の3番目が一致してるので計算結果のリストの3番目の要素はTrue
になってる。
この記述法をリスト内包表記と呼び、Pythonの強力な記述法になっている。
最初の方で、「制御方法なんざ後回しでいい」と書いた理由の一つがこれだ。リスト内包表記こそがプログラミング初心者がPythonで最初に学ぶべき「制御法」なんだ。チンタラfor
文でループを回す、なんつーかったるい事をせんでいい。
あとは、True
の数をカウントすれば、h
の数はわかる。
Python
1>>> [i == j for i, j in zip('893', '123')].count(True) 21 3>>>
一方、b
の方は難しい。原則、例えば次のようにすればいい筈だが。
Python
1>>> [i in '893' for i in '321'].count(True) - [i == j for i, j in zip('893', '321')].count(True) 21 3>>>
後半はh
の計算式だ。
しかし、次のようなパターンはどうなるんだろう。
- 解が'303'で入力文字列が'132'の場合
- 逆に解が'132'で入力文字列が'303'の場合
「数だけが同じで桁位置が異なる」とは言っても、重複がある場合、ブローは1になるべきかはたまた2なのか。
マスターマインドのように「重複許さず」なら一意に決まるんだけど、この問題の場合、仕様がハッキリせんので「わからない」。
よって暫定的に、この辺はスルーしておこう。
印字部(Print)
印字部は簡単だ。単に環境データenv
からh
とb
を引っ張ってきて表示、そしてenv
を返せばいい。
Python
1# 印字部(Print) 2def display(env): 3 print(msg['result'].format(env.h, env.b)) 4 return env
これで完成、だ。
エントリポイント(main
)
あとはエントリポイントを付け足して、全体的にはこうなる。
Python
1#!/usr/bin/env python3 2 3import random, sys 4 5# 入力値及び乱数の最小値と最大値 6mn, mx = 100, 999 7 8# メッセージ分離方式 9msg = {'intro': '回答に対して、出題者はヒットとブローの数でヒントを与える。 回答と正解を比べて、数と桁位置の両方が同じであることをヒットと呼び、数だけが同じで桁位置が異なることをブローと呼ぶ。 たとえば、正解が 1234 で、回答が 1354 なら、出題者は「2 ヒット、1 ブロー」というヒントを与え、正解までこれを繰り返す。', 10 'prompt': '100から999の3桁の数字を入力してください。\n', 11 'result': 'hit数は{}です。\nblow数は{}です。\n'} 12 13# 環境データ 14class Env(object): 15 def __init__(self, secret_number, s, h, b): 16 self.secret_number = secret_number 17 self.s = s 18 self.h = h 19 self.b = b 20 # デバッグ用 21 def __repr__(self): 22 return f'secret_number: {self.secret_number} s: {self.s} h: {self.h} b: {self.h}' 23 24# 環境データの初期化 25def init(): 26 print(msg['intro']) 27 return Env(str(random.randint(mn, mx)), 0, 0, 0) 28 29# ゲーム終了? 30def is_game_over(x, env): 31 return env.s == 100 or x == env.secret_number 32 33# 読み込み部(Read) 34def read(prompt): 35 x = input(prompt) 36 n = int(x) 37 if x.isnumeric() and n >= mn and n <= mx: 38 return x 39 else: 40 raise ValueError 41 42# 評価器(Evaluator) 43def interp(x, env): 44 if is_game_over(x, env): 45 sys.exit() 46 else: 47 h = [i == j for i, j in zip(x, env.secret_number)].count(True) 48 return Env(env.secret_number, env.s + 1, h, [i in env.secret_number for i in x].count(True) - h) 49 50# 印字部(Print) 51def display(env): 52 print(msg['result'].format(env.h, env.b)) 53 return env 54 55# main(エントリポイント設定) 56if __name__ == '__main__': 57 env = init() 58 while True: 59 try: 60 env = display(interp(read(msg['prompt']), env)) 61 except ValueError: 62 env 63
メインループ内で入力値が適正じゃなかった時に投げられた例外を捕まえて再入力を促す。これを例外処理と言う。
いずれにせよ、これでプログラムは完成だ。
繰り返すが、ソフトウェア設計時にRead-Eval-Print Loopと言う設計方針を忘れないようにしよう。全てのソフトウェアは基本インタプリタだ。
これを真言のように唱えようじゃないか。
投稿2025/05/31 17:30
総合スコア48
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
0
ちょっと仕様が良く分からんトコもあるんだけど。
そもそもヒットアンドブローってゲームを知らんのだけど、基本的に、マスターマインドと同じなのかしらん?
それはさておき、質問には直接関係ない(いや、無くはねぇんだけど)けど、恐らくコーディングを見る限り、プログラミング初心者っぽいんで、いくつか注意事項、と言うか、プログラムを書く際のコツを書いていく。
まず最初に言っておかなきゃならない事が3つある。
Pythonで最初に覚えなければならないのは関数定義
もちろん、四則演算の導入を否定するつもりはないんだけど、何より最初に把握せなアカンのはPythonの関数定義法だ。これは制御構造(つまり、条件分岐や反復)より関数定義を先に学べ、って事だ。
ハッキリ言えば、大抵の教科書の導入順序が間違ってるんだ。関数定義が面倒くさいプログラミング言語だと関数定義より制御構造を先に学ばなければならないんだけど、生憎Pythonの場合は関数定義が簡単だ。
しかも、Pythonの場合、制御構造の記述法には色んな選択肢がある。よって通り一編のやり方だと、後遺症がデカイんだよ。
従って、まずは関数定義法を学ぼう。
もう一つの理由としては、確かにPythonはスクリプト言語と言われる。ここで言うスクリプトとは、言っちゃえば、往年のBASICのようなプログラム記述、って事だ。これはモダンなプログラミング観点だとマズいんだ。
そして、最初に「望ましくない」プログラム記述法をやると、思ったよりその「手癖」の修正に手間がかかる。良くないマナーも後遺症を産むんだ。
関数と言うのは単純に処理単位の事だ。
いずれにせよ、手持ちの教科書の関数定義の項目か、あるいはこれに目を通しておこう。
もう一つ、関数定義にまつわる重要な事を書く。
関数と入出力は切り離せ
これはちと語弊がある。「入力用関数」とか「出力用関数」を書くな、とは言ってないんだ。もちろん書くべきだ。
重要なのは、「何らかの計算ロジックを記述する関数」に入力とか出力を混ぜるな、ってのがここでの題意だ。
入力用関数を書く時には「入力に纏わるモノ」だけを纏め、出力用関数を書く時には「出力に纏わるモノ」だけを纏める。
問題は、「何らかの計算を行う」関数は入力や出力を絡めてはいけない。全ては関数の引数として「データ」を受け取るようにして、あとはreturn
で粛々と返り値を返す。
計算は計算だけで分けておけ、って事だな。
これは次の「ソフトウェア記述の基本」から導かれる。
全てのソフトウェアはインタプリタが基本
Pythonはインタプリタだ。入力値があって、評価されて、計算結果が返る。
これは「プログラミング言語だから」こういう構造を持ってる、って思われてるんだけど、実のトコ、この「インタプリタの動作」ってのは全てのソフトウェアの基本なんだよ。
例えばMicrosoft Excelを考えてみよう。セルに計算式や値を「入力」する。何らかの計算をExcelが行う。その結果を表示する。
実は全くプログラミング言語(インタプリタ)と同じ構造になる。
動画編集ソフトもそうだよな。動画を入力し、編集を(マウスで色々やるにせよ)入力する。編集ソフトが内部的に色々計算する。結果が出来る(出力される)。
これも大枠は言語インタプリタと同じだ。
繰り返すが、全てのソフトウェア制作の基本は、実の事を言うと「特殊なインタプリタを記述する」事なんだ。これが基本中の基本で、ゲームでもこの大枠は変わらない。
っつーか、ある意味「ワンパターン」なんだよ。
この「インタプリタを組み立てる」設計方針をREPL(Read-Eval-Print Loop)と呼ぶ。
Read-Eval-Print Loop
Read-Eval-Print Loopと聞くと人によっては「大学の宿題で出たアカデミックな、実用性に欠けたネタ」とか思ってるブツ、なんだけど、実の事を言うと、上に書いた通り、「あらゆるソフトウェア記述」の大枠も大枠だ。
しかもぶっちゃけ、難しい事は何も言ってない。
ゲームもそうなんだけど、単にソフトウェアを書く際に、
- 読み込み部(Read): 入力を司る部分
- 評価部(Evaluator): 計算を司る部分
- 印字部(Print): 表示を司る部分
の3つに分けて書け、っつってるだけだ。
あとは、この3つを組み合わせて「グルグルとループさせろ」と。
それだけ、なんだけど、それは非常に強力な設計方針なんだ。
Read-Eval-Print Loopを知ってるか知らないか、で今後大きく違ってくるだろう。知らない人は徒手空拳でソフトウェア作成に対処せなアカン、と言うくらいの「ソフトウェア設計の基本方針」なんだ。
ハッキリ言うけど、「ソフトウェアの作り方」を学ぶのなら、REPLと言う概念の獲得が目標だ。しかも、これさえ覚えておけばあとは些事なんだよ(笑)。いや、マジで。
プログラミング初心者段階からREPLと言うコンセプトに触れておくべきだと思う。これナシで、やれアルゴリズムだ、とかオブジェクト指向だ、とか言っても意味がないんだ。
と言うわけで、この方針に従って進んでいこう。
Pythonでファイル冒頭に書いておく事
まずはファイル先頭は次のように記述する。
Python
1#!/usr/bin/env python3 2 3import random, sys
1行目にはシェバングとかシバンと呼ばれるモノを記述しておく。
これは元々、UNIX系OSで使われてたモンなんだけど、幸い、PythonだとWindowsでも働いてくれて賢い。
これは、端末(あるいはDOS窓やPowerShell)でこのプログラムを呼び出す際に、明示的にPythonを呼ぶ必要がなく、このファイル名単独でプログラム、として認識して走ってくれるようになる魔法の言葉だ。
次にライブラリをimport
している。貴方が書いた通り、乱数ライブラリのrandom
が要り用だ。
もう一つ使うのがsys
だ。これにはプログラムを終了させる為のsys.exit
が含まれる。
正直言うと、貴方の書いたコードだと「どうゲームが終わるのか」、その終了条件が良く分からん。多分想定的には、「入力文字列」がsecret_number
に一致するか、あるいはカウンタs
が100に到達すると終了するんだろうけど、詳細が分からんので、取り敢えず「終わる」時にはsys.exit
でプログラムを終わらせる事とする。
次に「ゲームで使われるデータ」を作っていこう。
大域的(グローバル)なデータ
まずは、乱数の下限値と上限値を大域変数として定義しておく。
Python
1# 入力値及び乱数の最小値と最大値 2mn, mx = 100, 999
もちろん、直接randint
の引数として100と999を埋め込んでもいいんだけど、入力制御はどうなるの?ってぇんで大域変数にした。ゲームの前提として、100〜999の範囲を超えた値を入力した際に構わないのか、あるいはプレイヤーに「再入力を促す」のかイマイチ分からなかったんだけど、一応、入力をやり直させるようにしてる。
なお、mn
、mx
と言う名称で何故にmin
、max
じゃないのか、と言うのは、min
、max
と言う関数がPythonのビルトイン関数として提供されているから、だ。こういう組み込み関数は同名の関数を作ってシャドウイングすべきじゃない。
次も大域変数だ。
メッセージ分離方式
print
で表示されるメッセージも大域変数として纏めてしまおう。
Python
1# メッセージ分離方式 2msg = {'intro': '回答に対して、出題者はヒットとブローの数でヒントを与える。 回答と正解を比べて、数と桁位置の両方が同じであることをヒットと呼び、数だけが同じで桁位置が異なることをブローと呼ぶ。 たとえば、正解が 1234 で、回答が 1354 なら、出題者は「2 ヒット、1 ブロー」というヒントを与え、正解までこれを繰り返す。', 3 'prompt': '100から999の3桁の数字を入力してください。\n', 4 'result': 'hit数は{}です。\nblow数は{}です。\n'} 5
print
に直接文字列を埋め込まず、メッセージはメッセージとして分離して纏める方式をメッセージ分離方式、と呼ぶ。
これは特に2000年代以降でポピュラーになったやり方だ。ソフトウェアが国際化した煽りだな(笑)。
元々あったやり方ではあるんだけど、ソフトウェアのローカライズが頻繁に起こるようになってからポピュラーになったんだ。貴方が作ったソフトウェアを「英語でもリリースしたい」とかなった時に、プログラムでのprint
使用部分を探しまくって・・・と言う手間を軽減する為にテキスト部分は一箇所に纏めた方が良いだろう、って事だよな。
まぁ、本当は、テキスト部分はまた別ファイルに纏めて・・・ってのが方法論になるんだろうけど、そこまではしない(笑)。
なお、データ型としては辞書型(あるいはディクショナリ型)を使っている。ザックリ言うと、key(キー)を指定するとvalue(値)が返ってくるデータ型だ。
Pythonでは辞書型を良く使う。いや、Pythonに限らず、モダンなプログラミング言語だと連想配列と呼ばれるデータ型を良く使うんだ。
よって早いウチに慣れておこう。
次はユーザー定義データ型だ。
色んなデータは構造体(クラス)で纏めてしまえ
プログラム、と言うモノは大雑把に言うと、「データ」を「プロセス」(処理)する為のモノだ。
しかし、貴方が書いたように、a
があってb
があってsecret_number
があって・・・とバラバラにデータがあれば見通しが悪くなる。
よってユーザー定義データ型、で新しくデータ型を作って、それらを纏めてしまえば面倒臭くなくなる。
通常、そういう場合、構造体とかレコード型、と言うシステムで新しくデータ型を定義するんだけど、生憎Pythonには存在しない。
そこで代わりにクラスと呼ばれるモノを利用する。
Python
1# 環境データ 2class Env(object): 3 def __init__(self, secret_number, s, h, b): 4 self.secret_number = secret_number 5 self.s = s 6 self.h = h 7 self.b = b 8 # デバッグ用 9 def __repr__(self): 10 return f'secret_number: {self.secret_number} s: {self.s} h: {self.h} b: {self.h}' 11
これで解になるsecret_number
とか、カウンタ代わりのs
等が、ゲーム用の環境データとして纏められる。
なお、殆どのPython本で間違ってるんだけど、クラスを使ったから、と言って、即オブジェクト指向で書かなきゃならない、なんて事はないんだ。多分その辺の事を大方のPython本の著者は勘違いしてる。
クラスは基本的にはユーザー定義型作成機能だ。この例だと新しくEnv型
と言うデータ型を定義してて、まんま構造体/レコード型代わりだ。そしてそれを関数の引数にしても構わない。
オブジェクト指向がどーの、って始める前に構造体/レコード型代わりに使ってクラスに慣れた方が結果理解が早いんだよ。
多くのPython本著者陣はその観点がない。
もっとも、ここでは__repr__
と言う特殊メソッドだけは定義している。デバッグ目的だ。これがないとEnv型の中身がどうなってんだか見えないんだ。
あとは、環境データEnvを初期化する関数を書く。
Python
1# 環境データの初期化 2def init(): 3 print(msg['intro']) 4 return Env(str(random.randint(mn, mx)), 0, 0, 0)
ここは思いっきり、最初に書いた「関数に出力を混ぜるな」と言う原則に違反してるが(笑)、いいんだ(笑)。どうせ一回しか呼び出されないし、ここでゲームの設定が環境データにセットされる。
Python
1>>> init() 2回答に対して、出題者はヒットとブローの数でヒントを与える。 回答と正解を比べて、数と桁位置の両方が同じであることをヒットと呼び、数だけが同じで桁位置が異なることをブローと呼ぶ。 たとえば、正解が 1234 で、回答が 1354 なら、出題者は「2 ヒット、1 ブロー」というヒントを与え、正解までこれを繰り返す。 3secret_number: 893 s: 0 h: 0 b: 0 4>>>
ここでは、解が乱数で893になってる。ヤクザだ(謎
いずれにせよ、この「初期値」を評価器(evaluator)に与える事でゲームがスタートする。
なお、マスターマインドだと解は「重複しない数(順列)」だったと思うんだけど、このゲームだと違うのかも。
結果、マスターマインドでは111と言う解はないと思う。123とか、各桁の数値は「別々じゃないと」ならないんだ。
まぁ、その辺ハッキリしないんで、原作である貴方のコードに従っている。
投稿2025/05/31 17:29
編集2025/05/31 17:36総合スコア48
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
0
提示された記述を「Python 3.13.3」で実行しましたがエラーは発生しませんでした。
もしかしたら提示された記述の手前に何らかの記述があり,その中で input
または list
を変数名として使っているのかもしれません。その場合は,TypeError: *** object is not callable
のようなエラーメッセージが出力されます。
なお,「文字列」は(イテラブルなオブジェクトなのでリスト化しなくても)for
文で1文字ずつ(文字列として)取り出すことができます。記述例を下記に示します。
Python
1import random 2 3secret_number = str(random.randint(100, 999)) 4# print(secret_number) 5 6s = 0 7while s < 100: 8 h, b = 0, 0 9 10 person_number = "0" 11 while len(person_number) != 3 or person_number[0] == "0": 12 print("100から999の3桁の数字を入力してください: ", end='') 13 person_number = input() 14 15 for pn, sn in zip(person_number, secret_number): 16 if pn == sn: 17 h += 1 18 elif pn in secret_number: 19 b += 1 20 21 print(f"hit: {h}, blow: {b}") 22 if h == 3: break 23 s += 1
投稿2025/05/31 07:12
総合スコア453
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。