【Python版】なぜ，あなたはJavaでオブジェクト指向開発ができないのか

## 概要

オブジェクト指向再学習のために「なぜ，あなたはJavaでオブジェクト指向開発ができないのか」を購入したが、Java・Rubyでしか[公式サンプル](http://gihyo.jp/book/2005/4-7741-2222-X/support)がなかったのでPythonで作成してみました。
（あくまで参考程度にしてください。私も初心者です。）

## Lesson 5 まで（ジャンケンプログラム）

### 全体の処理進行

```python:object_janken.py
from judge import *
from player import *
from tactics import *

# 審判（斎藤さん）のインスタンス生成
saito = Judge()

# プレイヤー１（村田さん）の生成
murata_tactics = RandomTactics()
murata = Player("村田さん",murata_tactics)

# プレイヤー２（山田さん）の生成
yamada_tactics = StoneOnlyTactics()
yamada = Player("山田さん",yamada_tactics)
# yamada = Player_STONE("山田さん")

# 村田さんと山田さんをプレイヤーとしてジャンケンを開始する
saito.start_janken(murata, yamada)
```
### プレイヤークラス

```python:player.py
class Player:

    # ジャンケンの手を表す定数
    STONE = 0  # グー
    SCISSORS = 1  # チョキ
    PAPER = 2  # パー

    def __init__(self, name, tactics):
        self.name = name
        self.win_count = 0
        self.tactics = tactics

    # ジャンケンの手を出す。
    def show_hand(self):
        hand = self.tactics.read_tactics()
        return hand

    # 審判から勝敗を聞く。勝ったら、引数 result は true
    def notify_result(self,result):
        if (result):
            self.win_count += 1

    # 自分の勝った回数を答える。
    def get_win_count(self):
        return self.win_count

    # 自分の名前を答える。
    def get_name(self):
        return self.name

# 継承し子クラス作成
class Player_STONE(Player):
    def show_hand(self):
        return self.STONE
```

### ジャッジクラス

```python:judge.py
class Judge:

    # ジャンケンを開始する。
    def start_janken(self, player1, player2):
        # ジャンケンの開始を宣言する
        print("【ジャンケン開始】\n")

        # ジャンケンを3回行う
        for cnt in range(3):
            # 何回戦目か表示する
            print("【", (cnt + 1), "回戦目】")

            # プレイヤーの手を見て、どちらが勝ちかを判定する。
            winner = self.judge_janken(player1, player2)

            if (winner != None):
                # 勝者を表示する
                print("\n", winner.get_name(), "が勝ちました!\n")
                # 勝ったプレイヤーへ結果を伝える
                winner.notify_result(True)
            else:
                # 引き分けの場合
                print("\n引き分けです！\n")

        # ジャンケンの終了を宣言する
        print("【ジャンケン終了】\n")

        # 最終的な勝者を判定する
        final_winner = self.judge_final_winner(player1, player2)
        print(player1.get_win_count(), " 対 ", player2.get_win_count(), "で")
        if (final_winner != None):
            print(final_winner.get_name(), "の勝ちです！\n")
        else:
            print("引き分けです！\n")

    # プレイヤーの手を見て、どちらが勝ちかを判定する。
    def judge_janken(self,player1, player2):
        winner = None

        hand_dict = {0: "STONE",
                     1: "SCISSORS",
                     2: "PAPER"}

        # プレイヤーの手を出す
        player1hand = player1.show_hand()
        player2hand = player2.show_hand()
        player1hand_name = hand_dict[player1hand]
        player2hand_name = hand_dict[player2hand]

        # それぞれの手を表示する
        print(f"{player1hand_name} vs. {player2hand_name} \n")

        # プレイヤー１が勝つ場合
        if (player1hand_name == "STONE" and player2hand_name == "SCISSORS") or \
            (player1hand_name == "SCISSORS" and player2hand_name == "PAPER") or \
            (player1hand_name == "PAPER" and player2hand_name == "STONE") :
            winner = player1

        # プレイヤー２が勝つ場合
        if (player1hand_name == "STONE" and player2hand_name == "PAPER") or \
            (player1hand_name == "SCISSORS" and player2hand_name == "STONE") or \
            (player1hand_name == "PAPER" and player2hand_name == "SCISSORS") :
            winner = player2

        # どちらでもない場合は引き分け(nilを返す)
        return winner

    # 最終的な勝者を判定する。
    def judge_final_winner(self,player1, player2):
        final_winner = None

        # 勝ち数を聞く
        player1_win_count = player1.get_win_count()
        player2_win_count = player2.get_win_count()

        # 勝者を決定
        if (player1_win_count > player2_win_count):
            final_winner = player1
        elif(player1_win_count < player2_win_count):
            final_winner = player2
        else:
            final_winner = None

        return final_winner
```

### 戦略クラス

```python:tactics.py
class RandomTactics():

    def read_tactics(self):

        # 0.0以上3.0未満の小数として乱数を得る
        random_num = rm.random()* 3.0
        if (random_num < 1.0):
          hand = Player.STONE
        elif (random_num < 2.0):
          hand = Player.SCISSORS
        elif (random_num < 3.0):
          hand = Player.PAPER

        # 決定した手を戻り値として返す
        return hand


#==グーが大好き！」戦略クラス。
class StoneOnlyTactics():

  def read_tactics(self):
    # 必ずグーを出す
    return Player.STONE
```

## Lesson 6（ババ抜きプログラム）

### カードクラス

```python:card.py

class Card():

    # ジョーカーを表す定数
    JOKER = 0
    # スペードを表す定数
    SUIT_SPADE = 1
    # ダイヤを表す定数
    SUIT_DIAMOND = 2
    # クラブを表す定数
    SUIT_CLUB = 3
    # ハートを表す定数
    SUIT_HEART = 4

    def __init__(self,suit,number):
        self.suit = suit
        self.number = number

    def getNumber(self):
        return self.number

    # toString関数をオーバーライド
    def toString(self):

        # JOKERの場合
        if self.suit == 0:
            card_code = "JK"

        # JOKER以外のカード
        else:
            suit_dict = {"SUIT_SPADE":"S", "SUIT_DIAMOND":"D",
                         "SUIT_CLUB":"C","SUIT_HEART":"H"}
            number_dict = {1:"A", 2:"2", 3:"3", 4:"4", 5:"5",
                           6:"6", 7:"7", 8:"8", 9:"9", 10:"T", 11:"J", 12:"Q", 13:"K"}
            card_code = suit_dict[self.suit] + number_dict[self.number]

        return card_code
```

### 手札クラス

```python:hand.py

import random as rm

class Hand():

    # card クラスをリストとして保持
    def __init__(self,list):
        self.hand = list

    # カードを追加
    def add_card(self,card):
        self.hand.append(card)

    # カードを引く（先頭）
    def pick_card(self):
        picked_card = self.hand.pop(0)
        return picked_card

    # 所持枚数を返す
    def getNumberOfCard(self):
        return len(self.hand)

    # 手札をシャッフル
    def shuffle(self):
        # カードをランダムに抜き取って最後に加える動作を繰り返す
        number = self.getNumberOfCard()
        for i in range(number):
            pos = int(rm.random() * number)
            picked_card = self.hand.pop(pos)
            self.hand.append(picked_card)

    # 同じ数のカードを探し、配列で戻す
    def find_same_number_card(self):

        same_cards = None

        # 最後に追加されたカードの数字を取得する
        last_added_card = self.hand[-1]
        last_added_card_num = last_added_card.number

        for index in range(len(self.hand)-1):

            card = self.hand[index]
            if card.number == last_added_card_num:

                # 最後に追加されたカードと同じカードが見つかった場合
                # 見つかった組み合わせをsameCardsに格納し、ループを抜ける
                same_cards = [self.hand.pop(-1),self.hand.pop(index)]
                break

        return same_cards

    # 手札にあるカードを文字列で表現する。
    def toString(self):
        hand_cards = ""
        for card in self.hand:
            hand_cards += card.toString() + " "
        return hand_cards
```

### テーブルクラス

```python:table.py
class Table():

    def __init__(self):
        self.disposed_cards = []

    # カードを捨てる。
    def dispose_card(self,dispose_list):
        for card in dispose_list:
            print(f"{card.toString()} を捨てました")
            self.disposed_cards.append(card)

    # 捨てられたカードを文字列で表現する。
    def toString(self):
        disposed_cards = ""
        for card in self.disposed_cards:
            disposed_cards += card.toString() + " "
        return disposed_cards
```

### プレイヤークラス

```python:player.py

class Player():

    def __init__(self,name,hand,master,table):
        self.hand = hand
        self.name = name
        self.master = master
        self.table = table

    # 順番を指名する（自分の番）
    def play(self,next_player):

        # 次のプレイヤーに手札を出してもらう
        next_hand = next_player.show_hand()

        # 相手の手札からカードを一枚引く
        picked_card = next_hand.pick_card()

        # 引いた結果を表示
        print(self.name, "：", next_player.name, "さんから ", picked_card.toString()," を引きました\n")

        # 引いたカードを自分の手札に加え、同じ数のカードがあったら捨てる
        self.deal_card(picked_card)

        # 手札がゼロになったかどうか調べる
        if self.hand.getNumberOfCard() == 0:
            # 進行役に上がりを宣言する
            self.master.declare_win(self)
        else:
            # 現在の手札を表示する
            print(self.name, "：残りの手札は ",self.hand.getNumberOfCard(), "です\n")

    # 手札をシャッフルして見せる
    def show_hand(self):
        if self.hand.getNumberOfCard() == 1:
            self.master.declare_win(self)
        self.hand.shuffle()
        return self.hand

    # カードを受け取る
    def receive_card(self,card):
        self.deal_card(card)

    # 同じカードがあったら捨てる
    def deal_card(self,card):

        # カードを自分の手札に加える
        self.hand.add_card(card)

        # 今加えたカードと同じカードを探す
        same_cards = self.hand.find_same_number_card()

        # 同じカードの組み合わせが存在した場合
        if (same_cards != None):
            # テーブルへカードを捨てる
            self.table.dispose_card(same_cards)

    # プレイヤー名を返す
    def toString(self):
        return self.name
```

### マスタークラス

```python:master.py

class Master():

    def __init__(self,player_list):
        self.player_list = player_list

    def prepare_game(self,hand):
        print("【カードを配ります】\n")

        # トランプをシャッフルする
        hand.shuffle()

        # トランプの枚数を取得する
        number_of_cards = hand.getNumberOfCard()

        # プレイヤーの人数を取得する
        number_of_players = len(self.player_list)

        for index in range(number_of_cards):

            # カードから一枚引く
            card = hand.pick_card()

            # 各プレイヤーに順番にカードを配る
            player = self.player_list[index % number_of_players]
            player.receive_card(card)


    # ゲームを開始する。
    def start_game(self):
        print("\n【ばば抜きを開始します】\n")

        # プレイヤーの人数を取得する
        count = 0
        while len(self.player_list) > 1:
            player_index = count % len(self.player_list)
            next_player_index = (count + 1) % len(self.player_list)

            # 指名するプレイヤーの取得
            player = self.player_list[player_index]
            # 次のプレイヤーの取得
            next_player = self.player_list[next_player_index]

            # プレイヤーを指名する
            print("\n", player.name, "さんの番です --- ({})".format(count), "\n")
            player.play(next_player)
            count += 1

        # プレイヤーが上がって残り1名になるとループを抜ける
        print("【ばば抜きを終了しました】\n")


    # 上がりを宣言する。
    def declare_win(self,winner):
        # 上がったプレイヤー
        print(winner.name, "さんが上がりました！\n")

        # 上がったプレイヤーをリストからはずす
        self.player_list.remove(winner)

        # 残りプレイヤーが１人になった時は敗者を表示する
        if len(self.player_list) == 1:
            loser = self.player_list[0]
            print(loser.name, "さんの負けです！\n")


    # ゲームに参加するプレイヤーを登録する。
    def register_player(self,player):
        self.player_list.append(player)
```

### 実行プログラム

```python:old_maid.py

from card import *
from hand import *
from master import *
from player import *
from table import *

# ばば抜きプログラム。


# 53枚のトランプを生成する。
def create_trump():
    trump = Hand([])

    # 各スート13枚のカードを生成する
    for i in range(13):
        number = i + 1
        trump.add_card(Card("SUIT_CLUB", number))
        trump.add_card(Card("SUIT_DIAMOND", number))
        trump.add_card(Card("SUIT_HEART", number))
        trump.add_card(Card("SUIT_SPADE", number))

    # ジョーカーの作成
    trump.add_card(Card(0, 0))

    return trump

# 進行役の生成
master = Master([])

# 場の生成
field = Table()

# プレイヤーの生成
murata = Player("村田", Hand([]), master, field)
yamada = Player("山田", Hand([]), master, field)
saito = Player("斎藤", Hand([]),master, field)

# 進行役へプレイヤーを登録
master.register_player(murata)
master.register_player(yamada)
master.register_player(saito)

# トランプを生成する
trump = create_trump()

# ゲームの準備をする
master.prepare_game(trump)

# ゲームを開始する
master.start_game()
```

## フレームワーク化（７並べ）

・読み飛ばしたため、省略。気をつけないといけないのは以下の２点

### 抽象クラス

・pythonで抽象クラスを作成する際はABC(Abstract Base Classes)をインポートし、抽象クラスを継承する必要がある
→参考：[PythonのABC - 抽象クラスとダック・タイピング](https://qiita.com/kaneshin/items/269bc5f156d86f8a91c4) 

### 値渡し
・テーブルクラスがテーブル情報を返すときに値渡しにする必要がある

```python:table.py
class FantanTable(Table):

    # テーブルはnumpyで作成
    matrix = np.full((4,13),"..")

    def getCard(self):
        return self.matrix.copy()
```