はじめに
はっきり言って、AIへの命令がうまい訳ではないです。
が、逆にそれを才能と思ってダメな命令=ダ命令を皆さんに見てもらうことで反面教師にしてもらおうかと思って記事にした次第です。
今回の教材
5年以上前に購入したアルゴリズムを鍛えるためのドリルです。
↑のリンクでも紹介されていてるボウリングのスコア計算についてバイブコーディングで実装していきたいと思います。
今回の相棒
Claude Code(claude-sonnet-4-20250514モデル)
1回目:無邪気につくる
pythonでボウリングのスコア計算関数を作成してください
- スコアは配列で10フレームまで与えられます。
- 返却値として合計スコアを返してください。
- これはscore_1.pyとして作成してください
アンチパターンにするために、具体的な命令は何もしてません。
ボウリングとは何ぞやすら教えずに作らせてみました。
結果
def calculate_bowling_score(scores):
total_score = 0
roll_index = 0
for frame in range(10):
if is_strike(scores[roll_index]):
total_score += 10 + strike_bonus(scores, roll_index)
roll_index += 1
elif is_spare(scores, roll_index):
total_score += 10 + spare_bonus(scores, roll_index)
roll_index += 2
else:
total_score += sum_of_balls_in_frame(scores, roll_index)
roll_index += 2
return total_score
def is_strike(roll):
return roll == 10
def is_spare(scores, roll_index):
return scores[roll_index] + scores[roll_index + 1] == 10
def strike_bonus(scores, roll_index):
return scores[roll_index + 1] + scores[roll_index + 2]
def spare_bonus(scores, roll_index):
return scores[roll_index + 2]
def sum_of_balls_in_frame(scores, roll_index):
return scores[roll_index] + scores[roll_index + 1]
if __name__ == "__main__":
# テストケース
frames = [10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 2]
print(calculate_bowling_score(frames)) # 出力: 292
frames = [9, 1, 8, 2, 10, 5, 0, 3, 6, 4, 2, 7, 3, 6, 3, 10, 9, 1, 9]
print(calculate_bowling_score(frames)) # 出力: 137
ちゃんと計算出来てしまう、という。
ボウリングのスコア計算がメジャーだった為に計算できてしまいました。
ただし教本だとフレーム毎の二次元配列を渡すので、本来は↓のような形で渡されます。
frames = [[10], [10], [10], [10], [10], [10], [10], [10], [10], [10, 10, 2]]
frames = [[9,1], [8,2], [10], [5,0], [3,6], [4,2], [7,3], [6,3], [10], [9,1,9]]
なので、実際は不具合で動きません。残念。ということにしましょう。
2回目:定義を書いてみる
命令を変えます。一から作成しなおしてください。
# ボウリングのスコア計算関数を作成するための命令
- pythonでボウリングのスコア計算関数を作成してください
- フレーム:1ゲームを構成する単位。2投目、または10本のピン全てを倒すまでを1フレームと呼ぶ。1ゲーム10フレーム。最後の10フレームはストライクまたはスペアを取ると三投目まで投げられる。
- 10フレームの特別なルール:
- 10フレーム目でストライクを取ると、3投目が投げられる。
- 10フレーム目でスペアを取ると、3投目が投げられる。
- 10フレーム目でノーマルの場合は2投目まで。
- ストライク:1投目で10本のピンを倒すこと
- スペア:2投目で10本のピンを倒すこと
- スコア:各フレームの得点。ストライク、スペア、またはノーマルの得点を計算する。
- 最高得点は300点。ストライクを10フレーム連続で取ると最高得点となる。
# 作成する関数について
- スコアは2次元配列で10フレームまで引数で与えられます。1フレームごとに倒したピンの数の配列になります。
- 例:[[1,0],[8,2],[10],[0,0],[5,5],[10],[0,2],[8,1],[9,0],[1,2]]
- スコアを返却すること
- ストライクの時、2投目までの点数が加算される
- スペアの時、1投目までの点数が加算される
という得点に関わる大切な定義を書き忘れてます
結果
def calculate_bowling_score(frames):
total_score = 0
for frame_idx in range(10):
frame = frames[frame_idx]
if frame_idx < 9: # 1-9フレーム
if is_strike(frame):
total_score += 10 + get_next_two_rolls(frames, frame_idx)
elif is_spare(frame):
total_score += 10 + get_next_roll(frames, frame_idx)
else:
total_score += sum(frame)
else: # 10フレーム
total_score += sum(frame)
return total_score
def is_strike(frame):
return len(frame) == 1 and frame[0] == 10
def is_spare(frame):
return len(frame) == 2 and sum(frame) == 10
def get_next_two_rolls(frames, frame_idx):
next_frame = frames[frame_idx + 1]
if is_strike(next_frame):
if frame_idx + 1 == 9: # 次が10フレームの場合
return next_frame[0] + next_frame[1]
else:
return next_frame[0] + frames[frame_idx + 2][0]
else:
return next_frame[0] + next_frame[1]
def get_next_roll(frames, frame_idx):
next_frame = frames[frame_idx + 1]
return next_frame[0]
if __name__ == "__main__":
# テストケース
frames = [[10], [10], [10], [10], [10], [10], [10], [10], [10], [10, 10, 2]]
print(calculate_bowling_score(frames)) # 出力: 292
frames = [[9,1], [8,2], [10], [5,0], [3,6], [4,2], [7,3], [6,3], [10], [9,1,9]]
print(calculate_bowling_score(frames)) # 出力: 137
ストライク、スペアの時のボーナス計算が何も言わなくても書かれてますね。
「仕様書にないことをするな」と怒ってはいけません。漏れたのは仕様書を書いた人間です。
1フレームから順に計算していくのもあり、ちょっと読みづらいですね。
- 次のフレームがストライクだったら、さらに次のフレームの投球を見に行く
- ただし、次が10フレーム目なら次のフレームがないのでそのまま二投目を足し合わせる
という内容になってます。
3-5回目:10フレームから逆順で計算する
これは教本にもあるんですけど、別に1フレーム目から計算する必要は全くないんですね。
10フレーム目から計算し、その時、1投目、2投目のスコアを保存しておく。
9フレーム目でストライクなら先ほど保存した1,2投目を足し合わせる。
1,2投目を更新して、8フレーム目へ。
という流れで計算をするとシンプルになります。
という訳で逆順で計算するように命令します。
が、ここで思うように命令が実行されませんでした。
皆さんだったらどう命令してましたか。
10フレーム目から計算するようにし、10,9,8フレームの順でスコア計算してください。途中のスコアは表示しないので最終結果のみ、効率よく計算することを目指したものを作成してください
- score_3.pyで作成してください
3回目結果
def calculate_bowling_score(frames):
total_score = sum(frames[9]) # 10フレーム目は単純合計
for frame_idx in range(8, -1, -1): # 9フレームから1フレームまで逆順
frame = frames[frame_idx]
if len(frame) == 1 and frame[0] == 10: # ストライク
total_score += 10 + get_next_two_rolls(frames, frame_idx)
elif len(frame) == 2 and sum(frame) == 10: # スペア
total_score += 10 + frames[frame_idx + 1][0]
else: # ノーマル
total_score += sum(frame)
return total_score
def get_next_two_rolls(frames, frame_idx):
next_frame = frames[frame_idx + 1]
if frame_idx + 1 == 9: # 次が10フレーム
return next_frame[0] + next_frame[1]
elif len(next_frame) == 1 and next_frame[0] == 10: # 次もストライク
return next_frame[0] + frames[frame_idx + 2][0]
else:
return next_frame[0] + next_frame[1]
いやー、違うんだよ。次のフレームを見に行くのをやめてくれって話をしたいんだ。
get_next_two_rolls()を使わずにnext_roll_1,next_roll_2と取れるのではないか
- score_4.pyとして作成してください
ここの命令は焦って、熟考せずに命令を書いてしまった(反省)
4回目結果
def calculate_bowling_score(frames):
total_score = sum(frames[9]) # 10フレーム目は単純合計
for frame_idx in range(8, -1, -1): # 9フレームから1フレームまで逆順
frame = frames[frame_idx]
next_frame = frames[frame_idx + 1]
if len(frame) == 1 and frame[0] == 10: # ストライク
if frame_idx + 1 == 9: # 次が10フレーム
next_roll_1 = next_frame[0]
next_roll_2 = next_frame[1]
elif len(next_frame) == 1 and next_frame[0] == 10: # 次もストライク
next_roll_1 = next_frame[0]
next_roll_2 = frames[frame_idx + 2][0]
else:
next_roll_1 = next_frame[0]
next_roll_2 = next_frame[1]
total_score += 10 + next_roll_1 + next_roll_2
elif len(frame) == 2 and sum(frame) == 10: # スペア
next_roll_1 = next_frame[0]
total_score += 10 + next_roll_1
else: # ノーマル
total_score += sum(frame)
return total_score
if __name__ == "__main__":
# テストケース
frames = [[10], [10], [10], [10], [10], [10], [10], [10], [10], [10, 10, 2]]
print(calculate_bowling_score(frames)) # 出力: 292
frames = [[9,1], [8,2], [10], [5,0], [3,6], [4,2], [7,3], [6,3], [10], [9,1,9]]
print(calculate_bowling_score(frames)) # 出力: 137
いや、出来てるんだけど、if/elseが多すぎる。
ちゃんとやりたいことを書きます
- 10フレーム目を取得時
- next_rolle_1 = frame[0], next_rolle_2 = frame[1]として保存する
- 9フレーム目を取得時
- ストライクなら「next_rolle_1+next_rolle_2」
- next_roll_1 = 10, next_roll_2 = next_roll_1
- ストライク以外:next_roll_1 = frame[0], next_roll_2 = frame[1]
- このようにしてscore_5.pyを作成してみてください
見返すとちゃんと書いてないのは目をつぶります。
5回目結果
def calculate_bowling_score(frames):
# 10フレーム目を処理して初期値設定
frame_10 = frames[9]
total_score = sum(frame_10)
next_roll_1 = frame_10[0]
next_roll_2 = frame_10[1]
# 9フレームから1フレームまで逆順処理
for frame_idx in range(8, -1, -1):
frame = frames[frame_idx]
if len(frame) == 1 and frame[0] == 10: # ストライク
total_score += 10 + next_roll_1 + next_roll_2
# 次のフレーム用の値更新
temp_next_roll_1 = next_roll_1
next_roll_1 = 10
next_roll_2 = temp_next_roll_1
elif len(frame) == 2 and sum(frame) == 10: # スペア
total_score += 10 + next_roll_1
# 次のフレーム用の値更新
next_roll_1 = frame[0]
next_roll_2 = frame[1]
else: # ノーマル
total_score += sum(frame)
# 次のフレーム用の値更新
next_roll_1 = frame[0]
next_roll_2 = frame[1]
return total_score
if __name__ == "__main__":
# テストケース
frames = [[10], [10], [10], [10], [10], [10], [10], [10], [10], [10, 10, 2]]
print(calculate_bowling_score(frames)) # 出力: 292
frames = [[9,1], [8,2], [10], [5,0], [3,6], [4,2], [7,3], [6,3], [10], [9,1,9]]
print(calculate_bowling_score(frames)) # 出力: 137
やっと、やりたい事が出来ました。
1,2回目の結果と見比べて、かなりシンプルな処理になったし分かりやすいものになったのではないかと思います。
まとめ
- 何も考えずに書いても、ある程度やりたい事は書いてくれる
- シンプルさ、可読性を考えたものを書かせるとなるとそれなりに書く必要がある
- これはAI的には読みにくさというものが存在しないのかも
一撃で5回目に書いたようなコードを書かせることができる!という人、募集しております。