python實現撲克遊戲 - 抽鬼牌 和 21點

XGQian發表於2024-07-04

poker_games

python實現撲克遊戲 : 抽鬼牌 和 21點 - Python Implementation of Poker Games : Drawing Ghost Cards and Blackjack

poker模組

首先,定義一個撲克模組,後面的包括以後的撲克牌遊戲,都可以呼叫這個模組
這個模組可以實現:

  • 卡牌、撲克牌組
  • 發牌、洗牌
  • 玩家摸牌、出牌
    等一些撲克遊戲共性的類和方法

定義卡牌的類

屬性

  • 花色 - 黑桃、紅心、梅花、方塊 外加兩個特殊的鬼牌
    • 使用Enum將 花色 定義為列舉型別 6種花色與0到5 一一對應
    • 在牌類Card中將所有花色定義成字串 '♠♥♣♦🤡😈'
  • 面值 - ['', 'A', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K', 'joker', 'joker']

牌類Card 透過__repr__方法使列印返回的是花色值索引的字元 和 面值 索引的字元 如 '♠3'、'🤡joker'

import random
from enum import Enum
# 定義花色列舉
class Suite(Enum):
    """花色(列舉)"""
    # 定義四種花色,分別是黑桃、紅心、梅花、方塊,外加兩種鬼牌
    SPADE, HEART, CLUB, DIAMOND, JOKER, mJOKER = range(6)
# 定義牌類
class Card:
    """牌"""
    def __init__(self, suite, face):
        # 初始化牌的花色和點數
        self.suite = suite
        self.face = face
    def __repr__(self):
        # 定義一個字串表示牌的花色和點數
        suites = '♠♥♣♦🤡😈'  # 花色字串,分別對應黑桃、紅心、梅花、方塊、大鬼、小鬼
        faces = ['', 'A', '2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K', 'joker', 'joker']  # 點數字符串
        # 返回牌的花色和點數,格式為“花色+點數”
        return f'{suites[self.suite.value]}{faces[self.face]}'

定義撲克的類

屬性

  • 撲克牌組 - 可定義鬼牌數量、花色種類,面值範圍,牌組數量

方法

  • 洗牌 - 隨機打亂牌組
  • 發牌 - 返回牌組索引的一張牌
# 定義撲克類
class Poker:
    """撲克"""
    def __init__(self, joker = 0, card_num = (4,1,14), poker_num = 1):
        self.joker = joker    # 鬼牌數量 0,1,2
        self.card_num = card_num  # (花色數量, 面值開始, 面值結束, 牌組數量)
        self.poker_num = poker_num # 牌組數量
        # 初始化一副撲克牌,預設四種花色和13種點數(從A到K)
        self.cards = [
            Card(suite, face)
            for suite in [i for i in Suite][:self.card_num[0]]  # 遍歷花色列舉
            for face in range(self.card_num[1], self.card_num[2])  # 遍歷點數,從1(A)到13(K)
        ]
        # 在撲克牌組中加入鬼牌,預設是0
        for i in range(self.joker):
            self.cards.append(
                Card([i for i in Suite][4+i], 14+i)
            )
        # 牌組數量,預設1組牌
        self.cards = self.cards * poker_num

    def shuffle(self):
        """洗牌"""
        # 洗牌前,將current重置為0,表示還沒有發過牌
        self.current = 0
        # 使用random模組的shuffle函式打亂撲克牌順序
        random.shuffle(self.cards)
    def deal(self):
        """發牌"""
        # 發出當前索引的牌,並更新索引
        card = self.cards[self.current]
        self.current += 1
        return card
    @property
    def has_next(self):
        """還有沒有牌可以發"""
        # 判斷是否還有未發的牌
        return self.current < len(self.cards)

定義玩家類

屬性

  • 名字 - 用名字屬性來區分玩家
  • 手牌 - 一個列表,儲存玩家手牌

方法

  • 摸牌 - 先透過牌組類的發牌方法獲取一張牌,再將這這牌加入玩家手牌
  • 洗牌 - 隨機打亂手牌
  • 出牌 - 先判斷這張牌是否在手牌中,在就移除,不在則不做任何操作

如還需計算點數等方法,可以在具體的遊戲程式中繼承玩家類,再寫計算點數的方法

# 定義玩家類
class Player:
    """玩家"""
    def __init__(self, name):
        # 初始化玩家的名字和手牌列表
        self.name = name
        self.cards = []
    def get_one(self, card, is_hidden = False):
        """摸牌"""
        # 玩家摸到一張牌,將其加入手牌列表
        self.cards.append(card)
        # is_hidden為True,則表示該牌為暗牌
        if is_hidden:
            print(f'{self.name}:獲得一張牌')
        else:
            print(f'{self.name}:獲得一張{card}')
    def show_cards(self, is_hidden = False):
        # 顯示玩家當前的手牌
        # is_hidden為True,則表示該牌為暗牌
        if is_hidden:
            print(f'{self.name}現在的手牌是: '+' ▮ '*len(self.cards))
        else:
            print(f'{self.name}現在的手牌是: {self.cards}')
    def shuffle(self):
        """洗牌"""
        # 使用random模組的shuffle函式打亂撲克牌順序
        random.shuffle(self.cards)
        print(f'{self.name}洗了自己的手牌')
    def deal(self, card):
        """出牌"""
        if card in self.cards:
            self.cards.remove(card)
            print(f'{self.name}失去了一張牌: {card}')

可以列印檢查一下,poker模組是否可以正常工作

if __name__ == '__main__':
    # 第一個引數 鬼牌數量
    # 第二個引數 - 元組 (花色數量, 面值開始, 面值結束, 牌組數量)
    # 如 兩種花色 A-K的,外加一個鬼牌的牌組
    p = Poker(1, (2,1,14), 1)
    print(p.cards)

執行結果如下:

抽鬼牌

玩法

  • 先根據玩家數量調整撲克牌組數量 - 要確保所有玩家的手牌加起來除了鬼牌都是成對出現 (如 3個人 可以兩種花色 面值1-10 外加一張鬼牌,共21張,每人7張牌)
  • 開局 - 每個玩家分到一份牌
  • 玩家A回合
    • 從玩家B手牌中抽取一張牌,放到自己的手牌中
    • 玩家A可選擇自己手牌中兩個面值相同的牌打出,出牌次數無限制,也可選擇跳過出牌
  • 玩家B回合
    • 從玩家C手牌中抽取一張牌,放到自己的手牌中
    • 玩家B可選擇自己手牌中兩個面值相同的牌打出,出牌次數無限制,也可選擇跳過出牌
  • 玩家C回合
    • 從玩家A手牌中抽取一張牌,放到自己的手牌中
    • 玩家C可選擇自己手牌中兩個面值相同的牌打出,出牌次數無限制,也可選擇跳過出牌
  • 遊戲結束條件
    • 當某位玩家出牌後,剩餘手牌為空,則該玩家獲勝,遊戲結束
    • 當某位玩家出牌後,僅剩餘一張鬼牌,則該玩家失敗,遊戲結束

若poker模組中的玩家類無法滿足需求,可定義子類繼承原玩家類,抽鬼牌需要重寫原玩家類的摸牌方法,摸到鬼牌必須顯示

import poker
import random

# 定義玩家類
class Player(poker.Player):
    def get_one(self, card, is_hidden = False):
        """摸牌"""
        # 玩家摸到一張牌,將其加入手牌列表
        self.cards.append(card)
        if is_hidden:
            if card.face != 14:
                print(f'{self.name}:獲得一張牌')
            else:
                print(f'{self.name}:獲得一張{card}')
        else:
            print(f'{self.name}:獲得一張{card}')

初始化

先根據玩家數量調整撲克牌組數量 - 要確保所有玩家的手牌加起來除了鬼牌都是成對出現 (如 3個人 可以兩種花色 面值1-10 外加一張鬼牌,共21張,每人7張牌)

# 初始化
# 建立一個撲克物件
poker = poker.Poker(1, (2, 1, 11), 1)
# 對撲克進行洗牌
poker.shuffle()
# 建立一個名為“我”的玩家物件
gamer = Player('我')
# 建立一個名為“莊家”的玩家物件
master = Player('莊家')
pc2 = Player('PC')
print(poker.cards)

# 初始發牌
init_card = 7
for i in range(init_card):
    card1 = poker.deal()
    gamer.get_one(card1)
    card2 = poker.deal()
    master.get_one(card2, True)
    card3 = poker.deal()
    pc2.get_one(card3, True)
gamer.show_cards()
master.shuffle()
master.show_cards(True)
pc2.shuffle()
pc2.show_cards(True)
print(' 抽鬼牌 - 遊戲開始 '.center(40,'='))

定義抽牌與出牌階段的規則

# 玩家回合
def get_deal(master, gamer, ):
    print(f' {gamer.name}回合開始 '.center(40,'='))
    gamer.show_cards()
    master.show_cards(True)
    while True:
        num = input(f'請抽{master.name}的牌,輸入第幾張牌:')
        if num.isdigit() and int(num) -1 in range(len(master.cards)):
            break
        print('輸入格式錯誤!!!')
    num = int(num)
    card = master.cards[num - 1]
    master.deal(card)
    gamer.get_one(card)
    # 丟牌
    while True:
        gamer.show_cards()
        try:
            n, m = input('請丟牌,輸入第幾張和第幾張(如1 2)\n輸入 0 0 則跳過出牌:').split()
        except:
            print('輸入格式錯誤!!!')
            continue
        if n.isdigit() and m.isdigit():
            n, m = map(int, (n, m))
            if n != m:
                card1 = gamer.cards[n - 1]
                card2 = gamer.cards[m - 1]
                if card1.face == card2.face:
                    gamer.deal(card1)
                    gamer.deal(card2)
                    if len(gamer.cards) == 0:
                        print('you win')
                        return True, gamer, True
                    if len(gamer.cards) == 1 and gamer.cards[0].face == 14:
                        print('you lost')
                        return True, gamer, False
            elif n == 0 and m == 0:
                print(f'{gamer.name}跳過出牌')
                break
            elif n == m:
                print('無法出同一張牌')
        else:
            print(f'{gamer.name}跳過出牌')
            break
    print(f' {gamer.name}回合結束 '.center(40,'='))
    return False, 0

def deal_PC(pc2, master, ):
    # 莊家回合
    print(f' {master.name}回合開始 '.center(40,'='))
    pc2.show_cards()
    num = random.randint(1, len(pc2.cards))
    card = pc2.cards[num - 1]
    pc2.deal(card)
    master.get_one(card, True)
    pc2.show_cards()
    # if len(master.cards) == 0:
    #     print('winer')
    #     return True, master, True
    # if len(master.cards) == 1 and master.cards[0].face == 14:
    #     print('lost')
    #     return True, master, False
    # 丟牌
    while True:
        master.show_cards(True)
        counts = {}
        for x in [c.face for c in master.cards]:
            counts[x] = counts.get(x, 0) + 1
        key1 = ''
        for i in counts.items():
            if i[1] > 1:
                key1 = i[0]
                break
        if key1:
            n = [c.face for c in master.cards].index(key1)
            card1 = master.cards[n]
            master.deal(card1)
            n = [c.face for c in master.cards].index(key1)
            card1 = master.cards[n]
            master.deal(card1)
            if len(master.cards) == 0:
                print('winer')
                return True, master, True
            if len(master.cards) == 1 and master.cards[0].face == 14:
                print('lost')
                return True, master, False
        else:
            print(f'{master.name}回合結束')
            break
    pc2.show_cards(True)
    master.shuffle()
    master.show_cards(True)
    print(f' {master.name}回合結束 '.center(40,'='))
    return False, 0

遊戲進行

透過while True使遊戲不斷進行
輪流進行不同玩家的抽牌與出牌,直到某玩家出牌後觸發結束條件

while True:
    result = get_deal(master,gamer)
    if result[0]:
        print(result[1].name, ' win! ' if result[2] else 'lost')
        break
    result = deal_PC(pc2,master)
    if result[0]:
        print(result[1].name, ' win! ' if result[2] else 'lost')
        break
    result = deal_PC(gamer,pc2)
    if result[0]:
        print(result[1].name, ' win! ' if result[2] else 'lost')
        break
gamer.show_cards()
master.show_cards()
pc2.show_cards()

執行結果如下:

BlackJack - 21點遊戲

玩法

  • 定義 一副 4花色 A-K 無鬼牌 的撲克
  • 開局給玩家和莊家各分2張牌,一張明牌,一張暗牌
  • 點數說明:
    • 2 - 10 點數就是它們的面值
    • J Q K 點數都是10
    • A預設面值為11,若手牌有A 且 總點數大於等於21,則A面值變為1
  • 玩家回合:
    • 玩家選擇摸牌或停牌
    • 若選li擇摸牌,則給玩家發一張牌
      • 若此時玩家總點數大於21,則玩家輸,
      • 若不大於21,則可繼續選擇摸牌或停牌
    • 若選擇停牌,則玩家回合結束,進入莊家回合
  • 莊家回合:
    • 莊家選擇摸牌或停牌
    • 注:若莊家手牌總點數小於17,則莊家必須摸牌,直到大於等於17
      • 若此時莊家總點數大於21,則莊家輸,
      • 若不大於21,則可繼續選擇摸牌或停牌
    • 若選擇停牌,則莊家回合結束,進入結算
  • 結算
    • 計算玩家與莊家手牌總點數
    • 總點數離21越近,分數越高
    • 分數一樣,平局,否則分數高者贏

若poker模組中的玩家類無法滿足需求,可定義子類繼承原玩家類,21點需要再寫一個計算點數的方法

import poker
# 定義玩家類
class Player(poker.Player):
    """玩家"""
    def what_face(self):
        # 計算玩家手牌的總點數(考慮A可能作為1或者11)
        li = []
        for i in self.cards:
            if i.face > 10:
                # J, Q, K 都當作10點
                li.append(10)
            elif i.face == 1:
                # A 暫時當作11點
                li.append(11)
            else:
                # 2-10按照實際點數計算
                li.append(i.face)

        result = sum(li)

        # 如果總點數大於等於21,並且包含A(當作11點),嘗試將A改為1點,並重新計算
        while result >= 21 and (11 in li):
            li.remove(11)
            li.append(1)
            result = sum(li)

            # 返回玩家手牌的總點數
        return result

初始化

  • 定義 一副 4花色 A-K 無鬼牌 的撲克
  • 開局給玩家和莊家各分2張牌,一張明牌,一張暗牌
# 初始化
# 建立一個撲克物件
poker = poker.Poker()
# 對撲克進行洗牌
poker.shuffle()
# 建立一個名為“我”的玩家物件
gamer = Player('我')
# 建立一個名為“莊家”的玩家物件
master = Player('莊家')

# 開局
def game_start():
    # 列印遊戲開始的標題
    print(' BlackJack - 21點遊戲 '.center(40,'='))

# 為玩家發牌
def deal_start(player, is_hidden=False):
    """為玩家發牌,並列印發牌資訊"""
    # 摸一張明牌
    card1 = poker.deal()
    player.get_one(card1,is_hidden)
    # 摸一張牌,根據is_hidden引數決定是否為暗牌
    card2 = poker.deal()
    player.get_one(card2,is_hidden)

# 開局發牌
game_start()
deal_start(master, is_hidden=True)  # 為莊家發牌,莊家第一張牌為暗牌
deal_start(gamer, is_hidden=False)  # 為玩家發牌,玩家兩張牌都為明牌
print()

遊戲進行

  • 玩家回合
  • 莊家回合
  • 結算
# 計算並列印玩家和莊家的得分
def score_data(gamer: Player, master: Player, is_over: bool):
    # 計算玩家和莊家的得分(這裡只是一個簡單的示例,實際規則可能更復雜)
    gamer_score = (21 - abs(gamer.cards_face - 21)) * 100 // 21
    master_score = (21 - abs(master.cards_face - 21)) * 100 // 21
    if not is_over:
        # 判斷誰贏了這一局
        if gamer_score > master_score:
            print('玩家贏!')
        elif gamer_score == master_score:
            print('平局!')
        else:
            print('莊家贏!')
    print('\n遊戲資料:')
    print(f'{f"玩家 {gamer.cards}":<21} 點數:{gamer.cards_face} 得分:{gamer_score}')
    print(f'{f"莊家 {master.cards}":<21} 點數:{master.cards_face} 得分:{master_score}')

# 玩家回合
print(' 玩家回合 '.center(40,'='))
gamer.show_cards()  # 顯示玩家手牌
# 玩家開始摸牌或停牌
while True:
    gamer.cards_face = gamer.what_face()  # 計算玩家手牌的總點數
    master.cards_face = master.what_face()  # 計算莊家手牌的總點數(此處假設莊家的牌也可見,但通常不是)

    if gamer.cards_face > 21:
        # 如果玩家點數超過21,則玩家輸
        print('\n玩家點數超過21,玩家輸!', gamer.cards, '點數:', gamer.cards_face, )
        score_data(gamer, master, True)  # 傳入is_over=True表示遊戲結束
        break

        # 詢問玩家是否繼續摸牌
    is_ok = input('輸入1繼續 摸牌 ,輸入2或其他 停牌 : ')
    if is_ok == '1':
        gamer_card = poker.deal()
        gamer.get_one(gamer_card)
        print(f'{gamer.name}獲得一張{gamer_card}')
        gamer.show_cards()  # 更新後顯示玩家手牌
    else:
        # 玩家選擇停牌,進入莊家回合
        # 莊家回合
        print()
        print(' 莊家回合 '.center(40,'='))

        # 莊家回合的迴圈,根據莊家的點數來決定是否繼續摸牌
        while True:
            # 計算莊家的手牌總點數
            master.cards_face = master.what_face()

            # 如果莊家的點數小於17,則必須繼續摸牌
            if master.cards_face < 17:
                # 摸一張牌
                master_card = poker.deal()
                master.get_one(master_card)
                print(f'{master.name}獲得一張{master_card}')
                # 如果莊家的點數大於或等於17,則停止摸牌
            else:
                break

                # 結算
        print()

        # 檢查莊家的點數是否超過21
        if master.cards_face > 21:
            # 如果莊家點數超過21,則玩家贏
            print('\n莊家點數超過21,玩家贏!', master.cards, '莊家點數:', master.cards_face)
            score_data(gamer, master, True)  # 遊戲結束,傳入is_over=True
            break

            # 如果莊家點數沒有超過21,則進行正常的得分計算
        score_data(gamer, master, False)  # 遊戲未結束,傳入is_over=False

        # 因為莊家回合結束後,整個遊戲流程也結束了,所以這裡再次加入break來確保跳出迴圈
        break

執行結果如下:

相關文章