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

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

新規登録して質問してみよう
ただいま回答率
85.50%
Python 3.x

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

オブジェクト指向

オブジェクト指向プログラミング(Object-oriented programming;OOP)は「オブジェクト」を使用するプログラミングの概念です。オブジェクト指向プログラムは、カプセル化(情報隠蔽)とポリモーフィズム(多態性)で構成されています。

リファクタリング

リファクタリングとはコードの本体を再構築するための手法であり、外見を変更せずに内部構造を変更/改善させることを指します。

Python

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

Q&A

解決済

1回答

1724閲覧

ブラックジャックのコードのリファクタリング (関数かクラスか)

退会済みユーザー

退会済みユーザー

総合スコア0

Python 3.x

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

オブジェクト指向

オブジェクト指向プログラミング(Object-oriented programming;OOP)は「オブジェクト」を使用するプログラミングの概念です。オブジェクト指向プログラムは、カプセル化(情報隠蔽)とポリモーフィズム(多態性)で構成されています。

リファクタリング

リファクタリングとはコードの本体を再構築するための手法であり、外見を変更せずに内部構造を変更/改善させることを指します。

Python

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

1グッド

1クリップ

投稿2019/12/05 23:20

編集2019/12/05 23:21

Qiitaの記事を見て、ブラックジャックのコードをPythonで書いてみました。
基本的な機能は簡単にコードにできたのですが、機能拡張を図る上でどのように設計すればいいかで悩んでいます。

Python

1# コードの概略 2class Card 3class Deck 4class Player: 5 def deal() 6class Dealer(Player): 7 def deal() 8 9def blackjack(): 10 # initialize deck, player, dealer 11 # player.deal() 12 # dealer.deal() 13 # showdown 14 15def confirm() 16def main(): 17 while True: 18 blackjack() 19 confirm()

player vs dealerの1対1の想定で、
ゲームのinitial deal -> player action -> dealer action -> showdownの4段階のうち、initial deal, showdownの2つがblackjack()内で、残りの2つがそれぞれのクラス内でdeal()として実装されています。
また、player.deal()でもう1枚カードを引くかをプレイヤーに確認するために、クラス外のconfirm()を呼び出しています。

ルールの拡張やプレイヤーの追加、各クラスを他のトランプゲームに使い回すことを考えると、ブラックジャック特有の機能をPlayer, Dealerから分けるべきだと考えています。
ここで悩んでいるのが、blackjack()の関数内に全ての処理を記述すべきなのか、新たにBlackjackクラスを用意して、その中で細かく処理を分けたほうがいいのかという点です。

関数内の処理は30-40行以内に収まるのが良いとも聞いたことがあります。
処理が複雑な場合、どういった基準で関数やクラス、あるいは他の形での実装を選ぶべきなのか、アドバイスをいただければ幸いです。

Python

1# コード全文 2# blackjack.py 3from random import shuffle 4 5 6class Card: 7 suits = ["H", "D", "S", "C"] 8 numbers = [i for i in range(1, 14)] 9 cards_converter = {**{i: i for i in range(2, 11)}, **{1: "A", 11: "J", 12: "Q", 13: "K"}} 10 11 def __init__(self, suit, number): 12 self.suit = suit 13 self.number = number 14 15 def __repr__(self): 16 return f"{self.suit}-{self.cards_converter[self.number]}" 17 18 19class Deck(list): 20 def __init__(self): 21 super().__init__(Card(suit, num) for suit in Card.suits for num in Card.numbers) 22 shuffle(self) 23 24 25class Player: 26 def __init__(self, deck: "Deck"): 27 self.deck = deck 28 self.hands = [] 29 self._initial_deal() 30 31 def __repr__(self): 32 return f"{self.__class__.__name__}: {self.score} pts / {self.hands}" 33 34 def _initial_deal(self) -> None: 35 for _ in range(2): 36 self.draw() 37 print(self) 38 39 def draw(self) -> int: 40 card = self.deck.pop() 41 self.hands.append(card) 42 return self.score 43 44 def deal(self) -> bool: 45 while confirm("Keep drawing a card?"): 46 score = self.draw() 47 print(self) 48 if score == 21: 49 print("<You win!>\n") 50 return False 51 elif score > 21: 52 print("<You lose...>\n") 53 return False 54 print_line() 55 return True 56 57 @property 58 def score(self) -> int: 59 # count royal cards as 10 60 score = sum(min(card.number, 10) for card in self.hands) 61 # exception of ace 62 if score <= 11 and 1 in {card.number for card in self.hands}: 63 score += 10 64 return score 65 66 67class Dealer(Player): 68 def _initial_deal(self) -> None: 69 self.draw() 70 print(self) 71 self.draw() # hide the second hand 72 73 def deal(self) -> bool: 74 while self.score < 17: 75 self.draw() 76 if self.score > 21: 77 print(self) 78 print("<You win!>\n") # dealer loses, and player wins 79 return False 80 print_line() 81 return True 82 83 84def blackjack() -> None: 85 # initialize / initial deal 86 deck = Deck() 87 player = Player(deck) 88 if player.score == 21: 89 print("<You win!>\n") 90 return 91 dealer = Dealer(deck) 92 93 # showdown 94 if player.deal() and dealer.deal(): 95 print(player, dealer, sep="\n") 96 if player.score == dealer.score: 97 print("<Draw>\n") 98 elif player.score > dealer.score: 99 print("<You win!>\n") 100 else: 101 print("<You lose...>\n") 102 print_line() 103 104 105def confirm(message: str) -> bool: 106 return input(f"{message} [y/n]").lower()[0] == "y" 107 108 109def print_line(length: int = 55) -> None: 110 print("-" * length) 111 112 113def main(): 114 blackjack() 115 while confirm("Play again?"): 116 print_line() 117 blackjack() 118 119 120if __name__ == "__main__": 121 main()
LouiS0616👍を押しています

気になる質問をクリップする

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

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

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

shiracamus

2019/12/06 06:46 編集

deal するから dealer。 Player は play します。deal しません。
退会済みユーザー

退会済みユーザー

2019/12/06 07:55

その通りですね。 手元のコードでは修正します。
guest

回答1

0

ベストアンサー

力作ですね。
しかし良く練られている一方で、少し読みづらさを感じるところもあります。

  • ゲーム進行のロジックとプレイヤーの行動が混じってしまっていること

・ 特に勝敗判定の分散が気になる

以上の点については、solzardさんも別の形で既にお気付きになっているとおりです。

ルールの拡張やプレイヤーの追加、各クラスを他のトランプゲームに使い回すことを考えると、ブラックジャック特有の機能をPlayer, Dealerから分けるべきだと考えています。

プレイヤーの分離を考える

プレイヤーをゲームそのものから分離するにあたって、プレイヤーの役割について再考しましょう。
トランプゲームにおけるプレイヤーの共通の振る舞いって何でしょう?

私は次のように考えました。

  • 特定の判断に従って、与えられたデッキからカードを引く
  • 特定の判断に従って、手札を場に出す/捨てる
  • 特定の判断に従って、自らの得点を申告する

『特定の判断』を分離できれば、プレイヤークラスを他のゲームにも流用できそうです。

Java

1class Player: 2 ... 3 4 def draw(self, deck, draw_strategy): 5 if draw_strategy(self.hand): 6 card = deck.pop() 7 self.hands.append(card) 8 return True # Enumの方が良いかも 9 10 return False

いわゆるStrategyパターンです。
次のような呼び方をすればユーザの判断を仰ぐことができます。

Python

1def interactive_strategy(hand): 2 print(hand) 3 return input('引く? [y/n]')[0] == 'y' 4 5user.draw(deck, interactive_strategy)

コンピュータもこんなふうに作れます。

Python

1def random_strategy(hand): 2 return random.random() < 0.5

Python

1def score_based_strategy(hand): 2 score = スコアを計算 3 return score < 17

ただし外部の関数からユーザの手札を覗き見ることになりますので、
それが気になるようであればブラックジャック専用のプレイヤークラスを作れば良いです。

Python

1def BlackJackPlayer(Player): 2 def draw(self, deck): 3 super().draw(self, deck, self.xxx_strategy) 4 5 @staticmethod 6 def xxx_strategy(hand): 7 ...

勝敗判定の分離を考える

スコア計算はゲームに強く依存しますので、特にプレイヤークラスから引き離すべきです。
先と同じように、計算用の関数オブジェクトを外部から受け取れば良いでしょう。

Python

1class Player: 2 ... 3 4 def report_score(self, scorer): 5 return scorer(self.hand)

プレイヤーは得点を申告するだけで、勝敗判定は呼び出し側の責任で行います。
毎度スコア計算方法をしていするのが面倒であれば、インスタンス化の際に一緒に渡してしまいます。

呼び出し側

次のように書けそうです。

Python

1def scorer(hand): 2 ... 3 4def player_strategy(hand): 5 ... 6 7def dealer_strategy(hand): 8 ... 9 10... 11 12# 13# 14user = Player() 15dealer = Player() 16deck = Deck() 17 18player_to_strategy = { 19 user: user_strategy, dealer: dealer_strategy 20} 21 22# 23# 24for _ in range(2): 25 user.draw(deck, lambda _: True) 26 27dealer.draw(deck, lambda _: True) 28プレイヤーとディーラーのカードを公開 29dealer.draw(deck, lambda _: True) 30 31# 32# 33players = [user, dealer] # プレイ順 34while True: 35 for player in players: 36 did_draw = player.draw( 37 deck, player_to_strategy[player] 38 ) 39 40 score = player.report_score(scorer) 41 if 22 < score: 42 バーストの発生を通知 43 break 44 else: 45 勝敗判定 46 if どっちかが勝った or 引き分け: 47 break 48 49 continue 50 51 勝敗の表示 52 break

想像で書いているので、実際にはもうちょっと調整が必要かと思います。
例えば賭けの実装は考慮していません。


ここで悩んでいるのが、blackjack()の関数内に全ての処理を記述すべきなのか、新たにBlackjackクラスを用意して、その中で細かく処理を分けたほうがいいのかという点です。

個人的には関数で充分かと思います。
継承したり、インスタンスを複数作ったりする必要性を感じません。

ただし別のゲームも実装するならば、モジュールは分けた方が良いです。

投稿2019/12/06 00:49

編集2019/12/06 00:50
LouiS0616

総合スコア35658

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

退会済みユーザー

退会済みユーザー

2019/12/06 01:31

詳しい回答、本当にありがとうございます。 Stragety patternという単語を調べてみて、デザインパターンというのがまさに今自分が必要としている知識だとわかりました。 最近スクリプトを超える長さのコードをちらほら書くようになり、可読性の悪さに悩んでいたので、さっそくいくつか本を買ってこようと思います。 > プレイヤーは得点を申告するだけで、勝敗判定は呼び出し側の責任で行います。 この部分をどう分ければ良いかが一番の課題だったので、このアドバイスはありがたいです。
LouiS0616

2019/12/06 01:43

出版から四半世紀を既に迎えており、GoFのデザインパターンは前時代的だと考える方も多いようです。 書籍を買うのであれば、できるだけ新しいものを選んだ方が良いです。案外GoFに批判的な書籍の方が参考になるかもしれません。 Quora:一時期プログラミングのデザインパターンというものが大流行しましたが、現在ではどのように評価されているのでしょうか? https://jp.quora.com/%E4%B8%80%E6%99%82%E6%9C%9F%E3%83%97%E3%83%AD%E3%82%B0%E3%83%A9%E3%83%9F%E3%83%B3%E3%82%B0%E3%81%AE%E3%83%87%E3%82%B6%E3%82%A4%E3%83%B3%E3%83%91%E3%82%BF%E3%83%BC%E3%83%B3%E3%81%A8%E3%81%84%E3%81%86%E3%82%82
退会済みユーザー

退会済みユーザー

2019/12/06 02:05

なるほど、鵜呑みにはしないように用心しながら勉強してみます
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問