---
# System prepended metadata

title: 製作有趣的撲克牌遊戲python
tags: [python]

---

![R](https://hackmd.io/_uploads/rkX9jDsNA.jpg)

一個基本的撲克遊戲模擬，包括玩家（Player）、莊家（Dealer）和賭徒（Gambler）三個類別。它通過讀取 CSV 文件中的數據來初始化玩家，然後進行多輪遊戲直到莊家或賭徒沒有足夠的錢為止。以下是程式碼的詳細解釋：

### **導入字典**
```
from card import Card
from deck import DeckOfCards
from csv import reader
```
### **生成一副牌**
```
FACES = ['2','3','4','5','6','7','8','9','10','J','Q','K','A']
SUITS = ['♠','♥','♣','♦']
CARDS = [(suit, face) for suit in SUITS for face in FACES]
```
### **排序手牌**
```
for i in range(numplayer):
    handfaces[i].sort(key=lambda x: FACES.index(x))
    handsuits[i].sort(key=lambda x: SUITS.index(x))
```

![OIP (1)](https://hackmd.io/_uploads/BJTuW_iN0.jpg)


### **建立第一種類別**
```
class Player:
    def __init__(self, money, default_bet, name):
        self.__money = money
        self.__default_bet = default_bet
        self.name = name

    def get_money(self):
        return self.__money

    def get_default_bet(self):
        return self.__default_bet

    def place_bet(self, bet):
        if bet > self.__money:
            print(f"{self.name}的錢不足以進行此賭注。")
        self.__money -= bet
        return bet

    def receive_winnings(self, amount):
        self.__money += amount

```

Player 類別包含了玩家的基本屬性和方法，包括錢數、默認賭注、下注、收取獎金和判斷牌型的方法。


![OIP](https://hackmd.io/_uploads/B1fiZ_oNC.jpg)


### **建立第二種類別**

```
class Dealer(Player):
    def __init__(self, money, default_bet, name, limit):
        super().__init__(money, default_bet, name)
        self.limit = limit
```
### **設計函數**
當莊家（Dealer）與賭徒（Gambler）進行一局遊戲時，會調用 game 函數。這個函數負責發牌、評估牌型並決定勝負。以下是 game 函數的詳細解釋：

#### 發牌
```
    def game(self, gambler):
        dealer_hand_faces, dealer_hand_suits = get_deal_cards(1)
        gambler_hand_faces, gambler_hand_suits = get_deal_cards(1)

        dealer_hand_faces = dealer_hand_faces[0]
        dealer_hand_suits = dealer_hand_suits[0]
        gambler_hand_faces = gambler_hand_faces[0]
        gambler_hand_suits = gambler_hand_suits[0]

        print(f"{self.name} 的手牌: {dealer_hand_faces} {dealer_hand_suits}")
        print(f"{gambler.name} 的手牌: {gambler_hand_faces} {gambler_hand_suits}")
```
#### 評估牌型
```
        dealer_best_hand, dealer_high_card = Player.evaluate_hand(dealer_hand_faces, dealer_hand_suits)
        gambler_best_hand, gambler_high_card = Player.evaluate_hand(gambler_hand_faces, gambler_hand_suits)
```
這裡調用了 Player 類中的 evaluate_hand 函數，評估莊家和賭徒的牌型，返回牌型等級（rank）和最高的牌（high_card）。

#### 打印牌型
```
        hand_types = {
            8: "同花順",
            7: "鐵支",
            6: "葫蘆",
            5: "同花",
            4: "順子",
            3: "三條",
            2: "兩對",
            1: "一對",
            0: "沒有特殊牌型"
        }

        print(f"{self.name} 的牌型為{hand_types[dealer_best_hand]}")
        print(f"{gambler.name} 的牌型為{hand_types[gambler_best_hand]}")
```
#### 判斷勝負
```
        if dealer_best_hand > gambler_best_hand:
            print(f"{self.name} 獲勝！")
            gambler.place_bet(gambler.get_default_bet())
            self.receive_winnings(gambler.get_default_bet())
        elif dealer_best_hand < gambler_best_hand:
            print(f"{gambler.name} 獲勝！")
            self.place_bet(self.get_default_bet())
            gambler.receive_winnings(self.get_default_bet())
        else:
            if dealer_best_hand == 0 and gambler_best_hand == 0:
                print("平局！")
            else:
                if Card.FACES.index(dealer_high_card) > Card.FACES.index(gambler_high_card):
                    print(f"{self.name} 獲勝！")
                    gambler.place_bet(gambler.get_default_bet())
                    self.receive_winnings(gambler.get_default_bet())
                elif Card.FACES.index(dealer_high_card) < Card.FACES.index(gambler_high_card):
                    print(f"{gambler.name} 獲勝！")
                    self.place_bet(self.get_default_bet())
                    gambler.receive_winnings(self.get_default_bet())
                else:
                    print("平局！")
```
這段代碼決定莊家和賭徒之間的勝負：

如果莊家的牌型等級（dealer_best_hand）大於賭徒的牌型等級（gambler_best_hand），莊家獲勝。賭徒下注，並且莊家獲得這個賭注的金額。
如果賭徒的牌型等級大於莊家的牌型等級，賭徒獲勝。莊家下注，並且賭徒獲得這個賭注的金額。
如果兩者的牌型等級相同，進一步比較兩者最高的牌（high_card）來決定勝負。
如果兩者最高的牌也相同，則平局。
Dealer 繼承自 Player，增加了一個 limit 屬性，並定義了 game 方法，用於與賭徒進行對賭。

![螢幕擷取畫面 2024-06-04 003258](https://hackmd.io/_uploads/H1EIxOo40.png)

###  **建立第三種類別**
```
class Gambler(Player):
    def __init__(self, money, default_bet, name, budget):
        super().__init__(money, default_bet, name)
        self.budget = budget

    def get_budget(self):
        return self.budget

```
Gambler 繼承自 Player，增加了一個 budget 屬性。


### **建立牌堆/洗牌/抽牌**
```
def get_deal_cards(playernum):
    deck_of_cards = DeckOfCards()
    deck_of_cards.shuffle()
    handfaces = [[0] * 5 for _ in range(playernum)]
    handsuits = [[0] * 5 for _ in range(playernum)]
            
    for i in range(playernum):
        for j in range(5):
            card = deck_of_cards.deal_card()
            handfaces[i][j] = card.face
            handsuits[i][j] = card.suit
        handfaces[i].sort(key=lambda x: Card.FACES.index(x))
        
    return handfaces, handsuits

```
這個函數用於生成並洗牌，然後發牌給每個玩家。

### **初始化玩家列表**
```
players = []

with open('players.csv', 'r') as my_file:
    file_csv = reader(my_file)
    header = next(file_csv, None)
    
    for row in file_csv:
        try:
            name = row[1]
            money = int(row[2])
            default_bet = int(row[4])
            if 'budget' in header:
                budget = int(row[3])
                player = Gambler(money, default_bet, name, budget)
            else:
                limit = int(row[3])
                player = Dealer(money, default_bet, name, limit)
            players.append(player)
        except ValueError as e:
            print(f"處理行 {row} 時出錯: {e}")
            continue

```
這段代碼從 CSV 文件讀取玩家數據並初始化 Dealer 和 Gambler 對象。

### **主遊戲邏輯**
```
def main():
    rounds = 100
    dealer = players[0]
    gamblers = players[1:]
    roundnum = 1
    
    for _ in range(rounds):
        print(f"第{roundnum}輪")
        roundnum += 1
        if dealer.get_money() <= 0:
            print("莊家沒錢了！遊戲結束。")
            break
        
        remaining_gamblers = []
        for gambler in gamblers:
            if gambler.get_money() > 0:
                dealer.game(gambler)
                if gambler.get_money() > 0:
                    remaining_gamblers.append(gambler)
        
        gamblers = remaining_gamblers
        if not gamblers:
            print("沒有玩家有足夠的錢進行遊戲。莊家獲勝！")
            break

    winner = max(players, key=lambda player: player.get_money())
    print(f"獲勝者是 {winner.name}，擁有 ${winner.get_money()}")
    
if __name__ == "__main__":
    main()

```
主遊戲邏輯模擬了多輪撲克遊戲，直到莊家或賭徒沒有足夠的錢為止，最後打印出擁有最多錢的玩家。

![OIP (2)](https://hackmd.io/_uploads/SkS1MujER.jpg)

### **定義牌型**
#### one pair
```
    def one_pair(handfaces):
        for j in range(4):
            if handfaces[j] == handfaces[j + 1]:
                return True, handfaces[j + 1]
        return False, None
```
#### Two pair
```
    def two_pair(handfaces):
        if handfaces[0] == handfaces[1] and handfaces[2] == handfaces[3]:
            return True, handfaces[3]
        elif handfaces[1] == handfaces[2] and handfaces[3] == handfaces[4]:
            return True, handfaces[4]
        elif handfaces[0] == handfaces[1] and handfaces[3] == handfaces[4]:
            return True, handfaces[4]
        return False, None
```
#### three of a kind
```
    def three_of_a_kind(handfaces):
        for j in range(3):
            if handfaces[j] == handfaces[j+1] == handfaces[j+2]:
                return True, handfaces[j+2]
        return False, None
```    
#### A straight
```
    def a_straight(handfaces):
        if all(Card.FACES.index(handfaces[i]) == Card.FACES.index(handfaces[i - 1]) + 1 for i in range(1, 5)):
            return True, handfaces[4]
        return False, None
```
#### A flush
``` 
    def a_flush(handsuits):
        if len(set(handsuits)) == 1:
            return True, None
        return False, None
``` 
#### A full house 
```  
    def a_full_house(handfaces):
        if handfaces[0] == handfaces[1] == handfaces[2] and handfaces[3] == handfaces[4]:
            return True, handfaces[2]
        elif handfaces[0] == handfaces[1] and handfaces[2] == handfaces[3] == handfaces[4]:
            return True, handfaces[4]
        return False, None
``` 
#### Four of a kind 
``` 
    def four_of_a_kind(handfaces):
        for j in range(2):
            if handfaces[j] == handfaces[j+1] == handfaces[j+2] == handfaces[j+3]:
                return True, handfaces[j+3]
        return False, None
``` 
#### straight flush
``` 
    def straight_flush(handfaces, handsuits):
        is_straight, high_card = Player.a_straight(handfaces)
        is_flush, _ = Player.a_flush(handsuits)
        if is_straight and is_flush:
            return True, high_card
        return False, None
```

### 判斷牌型
```
    def evaluate_hand(handfaces, handsuits):
        hand_rankings = [
            (Player.straight_flush, 8),
            (Player.four_of_a_kind, 7),
            (Player.a_full_house, 6),
            (Player.a_flush, 5),
            (Player.a_straight, 4),
            (Player.three_of_a_kind, 3),
            (Player.two_pair, 2),
            (Player.one_pair, 1)
        ]
        
        for func, rank in hand_rankings:
            if func == Player.straight_flush:
                result, high_card = func(handfaces, handsuits)
            else:
                result, high_card = func(handfaces)
                
            if result:
                return rank, high_card

        return 0, handfaces[4]
```
### 解釋如何區分莊家與賭客
在你的程式碼中，區分莊家與賭客的關鍵在於 CSV 文件的標題行是否包含 budget 欄位。這是通過檢查標題行中是否存在 budget 欄位來實現的。

具體步驟
讀取 CSV 文件：

首先，使用 csv.reader 來解析 CSV 文件的內容，並讀取文件的標題行。
檢查標題行：

使用 if 'budget' in header: 來檢查標題行是否包含 budget 欄位。
如果存在 budget 欄位，則表示這個 CSV 文件包含賭客的數據。
如果不存在 budget 欄位，則表示這個 CSV 文件包含莊家的數據。
創建對應的玩家實例：

根據檢查結果，創建對應的 Gambler 或 Dealer 類的實例，並將其添加到玩家列表中。
代碼示例
```
with open('players.csv', 'r') as my_file:
    file_csv = reader(my_file)
    header = next(file_csv, None)  # 讀取標題行
    
    for row in file_csv:
        try:
            name = row[1]
            money = int(row[2])
            default_bet = int(row[4])
            if 'budget' in header:
                # 如果標題行包含 'budget'，表示這是賭客
                budget = int(row[3])
                player = Gambler(money, default_bet, name, budget)
            else:
                # 如果標題行不包含 'budget'，表示這是莊家
                limit = int(row[3])
                player = Dealer(money, default_bet, name, limit)
            players.append(player)
        except ValueError as e:
            print(f"處理行 {row} 時出錯: {e}")
            continue  # 跳過無效數據行
```

更具體的示例
假設 players.csv 文件的內容如下：

賭客與莊家混合情況
```
type,name,money,budget,default_bet
Gambler,John,1000,500,50
Dealer,Alice,5000,1000,100
```
在這種情況下，程式會檢查標題行並發現 budget 欄位，因此會根據每一行的數據創建賭客或莊家的實例。

第一行數據 (Gambler,John,1000,500,50) 會創建一個 Gambler 類的實例，因為 type 是 Gambler 且存在 budget 欄位。
第二行數據 (Dealer,Alice,5000,1000,100) 會創建一個 Dealer 類的實例，因為 type 是 Dealer 且存在 budget 欄位。
只有莊家
```
type,name,money,limit,default_bet
Dealer,Bob,2000,1500,100
```

在這種情況下，程式會檢查標題行並發現沒有 budget 欄位，因此會根據每一行的數據創建莊家的實例。

第一行數據 (Dealer,Bob,2000,1500,100) 會創建一個 Dealer 類的實例，因為 type 是 Dealer 且存在 limit 欄位。

總結
這種方法通過檢查 CSV 文件的標題行來區分賭客和莊家，並據此創建對應的類實例。這使得程式能夠靈活地處理不同類型的玩家數據，並確保每個玩家都有正確的屬性和行為。

### 解釋remaining_gamblers
remaining_gamblers 變數在你的程式碼中用來追蹤在每輪遊戲結束後仍有足夠金錢繼續遊戲的賭客。這個變數有助於確保只有那些仍有資金的賭客才能參加下一輪遊戲。

使用場景
在每一輪遊戲結束後，需要判斷哪些賭客還有錢繼續進行下一輪遊戲，這時候就需要 remaining_gamblers 變數來記錄這些賭客。這樣可以防止那些已經輸光錢的賭客繼續參加遊戲。

代碼片段
```
remaining_gamblers = []
for gambler in gamblers:
    if gambler.get_money() > 0:
        dealer.game(gambler)
        if gambler.get_money() > 0:
            remaining_gamblers.append(gambler)
gamblers = remaining_gamblers
```

詳細解釋
初始化 remaining_gamblers：

首先，創建一個空的 remaining_gamblers 列表，用來存放仍有資金的賭客。
迭代賭客列表：

遍歷當前的 gamblers 列表，對每一個賭客進行檢查和遊戲操作。
檢查賭客的資金：

對於每一個賭客，首先檢查他是否還有錢（gambler.get_money() > 0）。
如果賭客還有錢，則讓莊家和賭客進行一場遊戲（dealer.game(gambler)）。
更新 remaining_gamblers 列表：

在每場遊戲結束後，再次檢查賭客是否還有錢。如果賭客還有錢，則將其添加到 remaining_gamblers 列表中。
更新 gamblers 列表：

最後，將 gamblers 列表更新為 remaining_gamblers 列表，這樣在下一輪遊戲中只會包含那些仍有資金的賭客。
範例
假設有三個賭客 A、B 和 C，他們的初始金額分別為 100、50 和 0。進行一輪遊戲後，結果如下：

A 剩下 80
B 剩下 0
C 本來就沒有錢
在這種情況下，remaining_gamblers 會只包含 A，因為 B 和 C 都沒有錢繼續遊戲。接下來的 gamblers 列表也只會包含 A。

這樣，每輪結束後 remaining_gamblers 確保只包含那些仍有資金的賭客，從而防止無錢的賭客參與後續遊戲。

### 小結
這個程式碼展示了一個基本的撲克遊戲模擬，包括初始化玩家、發牌、判斷牌型、進行對賭和計算最終的贏家。這個遊戲的核心是牌型判斷和玩家之間的對賭邏輯。