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
執行結果如下: