#50 サイコロ Shut the Box
● 問題
サイコロを使ったひとり遊びです。
すぐに終わってしまいます。
メッセージを表示している部分の前のコードを疑ってみましょう。
import random
import itertools
card = [str(i+1) for i in range(9)]
select_card = []
def sum_not_match():
global count
for r in select_card:
card.append(r)
print('選択したカードの値の合計とサイコロの合計が一致しません')
print('')
select_card.clear()
count = 0
card.sort()
clearflag = 0
while clearflag != 1:
count = 0
dice1 = random.randint(1,6)
dice2 = random.randint(1,6)
dice_sum = dice1+dice2
print('')
print(f'サイコロ1:{dice1},サイコロ2:{dice2},合計:{dice_sum}')
print('')
for n in range(1,5):
for conb in itertools.combinations(card, n):
sumflag = False
conb_i = [int(i) for i in conb]
if dice_sum == sum(conb_i):
sumflag = True
break
if sumflag == False:
print(f'カード:{"・".join(card)}')
print(f'どのカードを組み合わせても{dice_sum}になりません')
break
while True:
print(f'カード:{"・".join(card)}')
cardNum = input('カード番号?')
if cardNum in card:
card.remove(cardNum)
select_card.append(cardNum)
count += 1
else:
print('そのカードはありません')
select_card_i = [int(i) for i in select_card]
if sum(select_card_i) == dice_sum:
select_card.clear()
break
elif sum(select_card_i) > dice_sum:
sum_not_match()
if len(card) == 0:
if sum(select_card_i) == dice_sum:
clearflag = 1
print('素晴らしい!!すべてのカードがなくなりました')
break
else:
sum_not_match()
● 解答
if sumflag == False: 成り立つので,即終了となっています。使えるカードのすべてを組み合わせて,その合計を求め,2つのサイコロの和と同じ組み合わせがなければ,ゲーム終了となります。
例えば,使えるカードが1・2・3の場合,すべての組み合わせは,1,2,3,1+2,1+3,2+3,1+2+3の7通りになります。
配列の要素のすべての組み合わせを求めるアルゴリズムは再帰関数を使うなど,初心者にとっては結構ハードルが高いのですが,Pythonではitertoolsモジュールを使えば簡単に求めることができます。
それが,itertools.combinations(card, n)になります。たったこれだけでできるなんて魔法です。
itertools.combinations(card, 1)で,1,2,3,itertools.combinations(card, 2)で,1・2,1・3,2・3です。
for n in range(1,5):として,nを1~4まで変化させることで,1枚から4枚までの全組み合わせを求めています。
どんな場合でも4枚までの組み合わせにしているのは,2つのサイコロの目の和の合計は最大で12です。カードを最小の1から組み合わせた場合,12になるのは1,2,3,4のときなので,4枚より多いカードを組み合わせを考える必要がないのです。
こうして,カードの組み合わせから合計を求め,サイコロの目の和と同じかどうかを判断している部分が,if dice_sum == sum(conb_i):です。
条件が成り立てば,sumflag = Trueとして,breakでループを抜ける。
一見するとよさそうですが,ここに落とし穴があります。
forループは2重ループなので,単純にbreakとしただけでは,1つめのループを抜けるだけなのですね。そのため,再度sumflag = Falseが実行されてしまうのです。
2重ループを抜けるためのコードを3行追加しました。
どのように動作するかコメントしてあるので,コードがどのような順に動作するのかを確認するとよいでしょう。
2重ループを抜ける方法には,様々な方法がありますが,今回はfor elseを採用しました。
import random
import itertools
card = [str(i+1) for i in range(9)]
select_card = []
def sum_not_match():
global count
for r in select_card:
card.append(r)
print('選択したカードの値の合計とサイコロの合計が一致しません')
print('')
select_card.clear()
count = 0
card.sort()
clearflag = 0
while clearflag != 1:
count = 0
dice1 = random.randint(1,6)
dice2 = random.randint(1,6)
dice_sum = dice1+dice2
print('')
print(f'サイコロ1:{dice1},サイコロ2:{dice2},合計:{dice_sum}')
print('')
for n in range(1,5):
for conb in itertools.combinations(card, n):
sumflag = False
conb_i = [int(i) for i in conb]
if dice_sum == sum(conb_i):
sumflag = True
break #実行されると★に飛ぶ
else: # 追加
continue # 追加
break # 追加 ★ これが実行されるとループから完全に抜ける
if sumflag == False:
print(f'カード:{"・".join(card)}')
print(f'どのカードを組み合わせても{dice_sum}になりません')
break
while True:
print(f'カード:{"・".join(card)}')
cardNum = input('カード番号?')
if cardNum in card:
card.remove(cardNum)
select_card.append(cardNum)
count += 1
else:
print('そのカードはありません')
select_card_i = [int(i) for i in select_card]
if sum(select_card_i) == dice_sum:
select_card.clear()
break
elif sum(select_card_i) > dice_sum:
sum_not_match()
if len(card) == 0:
if sum(select_card_i) == dice_sum:
clearflag = 1
print('素晴らしい!!すべてのカードがなくなりました')
break
else:
sum_not_match()
実際にやってみるとわかりますが,shut the boxになるのはかなり難しいです。私は何度も挑戦しましたが,未だにできていません。
しかし,プログラムがきちんと動くか確かめるためには,成功できる場合も確認する必要があります。
そんなときは,以下のようにサイコロの目の和を自由に設定できるようにするとよいでしょう。
# dice1 = random.randint(1,6)
# dice2 = random.randint(1,6)
# dice_sum = dice1+dice2
dice_input = input('サイコロの目の和?')
dice_sum = int(dice_input)
#Python #プログラミング
この記事が気に入ったらサポートをしてみませんか?