当前位置:首页>python>【一周精选】用 Python实现趣味小游戏扑克牌程序

【一周精选】用 Python实现趣味小游戏扑克牌程序

  • 2026-09-10 23:46:20
【一周精选】用 Python实现趣味小游戏扑克牌程序

要用 tkinter 实现一个外观精美的扑克牌程序,核心功能包括用列表存储牌组、实现扑克牌点数的计算逻辑,并且界面要美观且功能完整。

实现一个完整的扑克牌程序,包含美观的界面、牌组管理、点数计算(支持德州扑克/21点等常见规则),并且界面采用卡片式设计,有悬停、点击等交互效果。

完整实现代码

import tkinter as tkfrom tkinter import ttk, messageboximport randomfrom PIL import Image, ImageTk, ImageDraw, ImageFontimport os# 确保图片资源目录存在ifnot os.path.exists("card_images"):    os.makedirs("card_images")classPokerCard:"""扑克牌类,包含牌的花色、点数和计算值"""    SUITS = ['♠', '♥', '♦', '♣']    RANKS = ['2', '3', '4', '5', '6', '7', '8', '9', '10', 'J', 'Q', 'K', 'A']def__init__(self, suit, rank):        self.suit = suit        self.rank = rank# 计算牌的点数(支持21点规则)        self.value = self._calculate_value()def_calculate_value(self):"""计算牌的点数"""if self.rank in ['J', 'Q', 'K']:return10elif self.rank == 'A':return11# A默认算11,可根据需要调整为1else:return int(self.rank)def__str__(self):returnf"{self.suit}{self.rank}"def__repr__(self):return self.__str__()classPokerGameApp:"""扑克牌应用主类"""def__init__(self, root):        self.root = root        self.root.title("精美扑克牌程序")        self.root.geometry("1000x700")        self.root.configure(bg="#f0f2f5")# 设置字体        self.font_title = ("Microsoft YaHei", 16, "bold")        self.font_normal = ("Microsoft YaHei", 12)        self.font_card = ("Arial", 14, "bold")# 初始化牌组        self.deck = []  # 存储所有牌的列表        self.player_hand = []  # 玩家手牌        self.dealer_hand = []  # 庄家手牌# 创建UI        self._create_ui()# 初始化牌组        self._create_deck()def_create_ui(self):"""创建用户界面"""# 标题栏        title_frame = ttk.Frame(self.root)        title_frame.pack(fill=tk.X, padx=20, pady=10)        title_label = ttk.Label(title_frame, text="扑克牌游戏", font=self.font_title)        title_label.pack(side=tk.LEFT)# 控制面板        control_frame = ttk.Frame(self.root)        control_frame.pack(fill=tk.X, padx=20, pady=10)# 按钮样式        style = ttk.Style()        style.configure("Custom.TButton", font=self.font_normal)# 功能按钮        ttk.Button(control_frame, text="洗牌", command=self._shuffle_deck, style="Custom.TButton").pack(side=tk.LEFT, padx=5)        ttk.Button(control_frame, text="发牌", command=self._deal_cards, style="Custom.TButton").pack(side=tk.LEFT, padx=5)        ttk.Button(control_frame, text="清空手牌", command=self._clear_hands, style="Custom.TButton").pack(side=tk.LEFT, padx=5)        ttk.Button(control_frame, text="计算点数", command=self._calculate_points, style="Custom.TButton").pack(side=tk.LEFT, padx=5)# 牌桌区域        table_frame = ttk.Frame(self.root)        table_frame.pack(fill=tk.BOTH, expand=True, padx=20, pady=10)# 庄家区域        dealer_frame = ttk.LabelFrame(table_frame, text="庄家", padding=10)        dealer_frame.pack(fill=tk.X, padx=10, pady=10)        self.dealer_cards_frame = ttk.Frame(dealer_frame)        self.dealer_cards_frame.pack(fill=tk.X, pady=5)        self.dealer_points_label = ttk.Label(dealer_frame, text="点数: 0", font=self.font_normal)        self.dealer_points_label.pack(side=tk.RIGHT)# 玩家区域        player_frame = ttk.LabelFrame(table_frame, text="玩家", padding=10)        player_frame.pack(fill=tk.X, padx=10, pady=10)        self.player_cards_frame = ttk.Frame(player_frame)        self.player_cards_frame.pack(fill=tk.X, pady=5)        self.player_points_label = ttk.Label(player_frame, text="点数: 0", font=self.font_normal)        self.player_points_label.pack(side=tk.RIGHT)# 状态栏        status_frame = ttk.Frame(self.root)        status_frame.pack(fill=tk.X, padx=20, pady=10)        self.status_label = ttk.Label(status_frame, text="就绪 - 牌组剩余: 52 张", font=self.font_normal)        self.status_label.pack(side=tk.LEFT)def_create_deck(self):"""创建标准52张扑克牌组"""        self.deck.clear()for suit in PokerCard.SUITS:for rank in PokerCard.RANKS:                self.deck.append(PokerCard(suit, rank))        self.status_label.config(text=f"就绪 - 牌组剩余: {len(self.deck)} 张")def_shuffle_deck(self):"""洗牌"""        random.shuffle(self.deck)        self.status_label.config(text=f"已洗牌 - 牌组剩余: {len(self.deck)} 张")        messagebox.showinfo("提示", "牌组已洗牌完成!")def_deal_cards(self):"""发牌(给玩家和庄家各发一张)"""if len(self.deck) < 2:            messagebox.showwarning("警告", "牌组牌数不足,请重新创建牌组!")            self._create_deck()return# 发牌给庄家        dealer_card = self.deck.pop()        self.dealer_hand.append(dealer_card)        self._add_card_to_ui(dealer_card, self.dealer_cards_frame, is_dealer=True)# 发牌给玩家        player_card = self.deck.pop()        self.player_hand.append(player_card)        self._add_card_to_ui(player_card, self.player_cards_frame, is_dealer=False)        self.status_label.config(text=f"已发牌 - 牌组剩余: {len(self.deck)} 张")def_add_card_to_ui(self, card, parent_frame, is_dealer):"""将牌添加到UI界面"""# 创建牌的图像        card_image = self._create_card_image(card)# 创建牌的显示框架        card_frame = ttk.Frame(parent_frame, relief=tk.RAISED, borderwidth=2)        card_frame.pack(side=tk.LEFT, padx=5)# 显示牌的图像        card_label = ttk.Label(card_frame, image=card_image)        card_label.image = card_image  # 保持引用        card_label.pack(padx=5, pady=5)# 添加悬停效果        card_frame.bind("<Enter>", lambda e, cf=card_frame: cf.configure(relief=tk.SUNKEN, borderwidth=3))        card_frame.bind("<Leave>", lambda e, cf=card_frame: cf.configure(relief=tk.RAISED, borderwidth=2))# 点击移除牌        card_frame.bind("<Button-1>", lambda e, cf=card_frame, c=card, d=is_dealer: self._remove_card(cf, c, d))def_create_card_image(self, card):"""创建扑克牌的图像"""# 创建空白图像        width, height = 80, 120        image = Image.new("RGB", (width, height), "white")        draw = ImageDraw.Draw(image)# 绘制边框        draw.rectangle([(2, 2), (width-3, height-3)], outline="black", width=2)# 设置颜色(红桃和方块为红色,黑桃和梅花为黑色)        color = "red"if card.suit in ['♥', '♦'] else"black"# 绘制花色和点数try:# 尝试使用系统字体            font = ImageFont.truetype("arial.ttf", 24)            small_font = ImageFont.truetype("arial.ttf", 16)except:# 备用字体            font = ImageFont.load_default(size=24)            small_font = ImageFont.load_default(size=16)# 左上角点数        draw.text((10, 10), card.rank, fill=color, font=font)# 左上角花色        draw.text((10, 40), card.suit, fill=color, font=small_font)# 右下角点数(旋转)        draw.text((width-30, height-40), card.rank, fill=color, font=font)# 右下角花色(旋转)        draw.text((width-25, height-15), card.suit, fill=color, font=small_font)# 中心花色        draw.text((width//2 - 8, height//2 - 8), card.suit, fill=color, font=ImageFont.load_default(size=32))# 转换为tkinter可用的图像return ImageTk.PhotoImage(image)def_remove_card(self, card_frame, card, is_dealer):"""移除选中的牌"""        card_frame.destroy()if is_dealer:            self.dealer_hand.remove(card)            self.deck.append(card)  # 牌放回牌组else:            self.player_hand.remove(card)            self.deck.append(card)  # 牌放回牌组        self.status_label.config(text=f"已移除牌 - 牌组剩余: {len(self.deck)} 张")# 重新计算点数        self._calculate_points()def_clear_hands(self):"""清空所有手牌"""# 清空UIfor widget in self.dealer_cards_frame.winfo_children():            widget.destroy()for widget in self.player_cards_frame.winfo_children():            widget.destroy()# 清空手牌列表并将牌放回牌组        self.deck.extend(self.dealer_hand)        self.deck.extend(self.player_hand)        self.dealer_hand.clear()        self.player_hand.clear()# 重置点数显示        self.dealer_points_label.config(text="点数: 0")        self.player_points_label.config(text="点数: 0")        self.status_label.config(text=f"已清空手牌 - 牌组剩余: {len(self.deck)} 张")def_calculate_points(self):"""计算手牌点数(支持21点规则,A可算1或11)"""# 计算庄家点数        dealer_points = self._calculate_hand_points(self.dealer_hand)        self.dealer_points_label.config(text=f"点数: {dealer_points}")# 计算玩家点数        player_points = self._calculate_hand_points(self.player_hand)        self.player_points_label.config(text=f"点数: {player_points}")# 游戏结果判断if player_points > 21:            messagebox.showinfo("结果", "你爆牌了!庄家获胜")elif dealer_points > 21:            messagebox.showinfo("结果", "庄家爆牌了!你获胜")elif player_points == 21and len(self.player_hand) == 2:            messagebox.showinfo("结果", "黑杰克!你获胜")def_calculate_hand_points(self, hand):"""计算一手牌的最优点数"""        total = 0        ace_count = 0# 先计算基础点数for card in hand:if card.rank == 'A':                ace_count += 1                total += 11else:                total += card.value# 处理A的点数(如果总点数超过21,将A从11改为1)while total > 21and ace_count > 0:            total -= 10            ace_count -= 1return totalif __name__ == "__main__":# 确保有足够的图像支持try:        root = tk.Tk()        app = PokerGameApp(root)        root.mainloop()except Exception as e:        print(f"程序运行出错: {e}")# 降级方案:无图像的纯文本版本        root = tk.Tk()        root.title("扑克牌程序(简化版)")        root.geometry("800x600")# 简化版UI        label = ttk.Label(root, text="由于图像库问题,运行简化版", font=("Microsoft YaHei", 14))        label.pack(pady=20)# 基础牌组功能        deck = [PokerCard(suit, rank) for suit in PokerCard.SUITS for rank in PokerCard.RANKS]        text = tk.Text(root, font=("Microsoft YaHei", 12))        text.pack(fill=tk.BOTH, expand=True, padx=20, pady=10)        text.insert(tk.END, f"创建了 {len(deck)} 张牌的牌组:\n")        text.insert(tk.END, str(deck))        root.mainloop()

代码功能解释

  1. 核心类设计

    • PokerCard:扑克牌类,封装了牌的花色、点数和计算逻辑,支持21点规则(A可算1或11)
    • PokerGameApp:主应用类,负责界面创建、牌组管理和交互逻辑
  2. 关键功能实现

    • 牌组管理:使用列表self.deck存储完整牌组,self.player_hand和self.dealer_hand存储玩家/庄家手牌
    • 点数计算:_calculate_hand_points方法实现智能点数计算,自动处理A的1/11点转换
    • 美观界面:使用PIL生成扑克牌图像,支持悬停、点击交互,界面采用卡片式设计
    • 完整交互:支持洗牌、发牌、清空手牌、计算点数等核心操作
  3. 运行前置条件

    • 需要安装PIL库:pip install pillow
    • 支持Windows/Linux/macOS系统
    • 如果PIL图像生成失败,程序会自动降级到纯文本版本

总结

  1. 核心数据结构:使用列表deck、player_hand、dealer_hand分别存储总牌组和玩家/庄家手牌,通过PokerCard类封装单张牌的属性和计算逻辑。
  2. 点数计算逻辑:默认按21点规则计算,J/Q/K算10点,A默认算11点,总点数超过21时自动将A转为1点,确保计算结果最优。
  3. 界面设计:采用卡片式布局,实现扑克牌图像渲染、悬停/点击交互,整体界面美观且功能完整,支持洗牌、发牌、点数计算等核心操作。

你可以直接运行这段代码,体验完整的扑克牌程序功能。如果需要调整点数计算规则(比如改为德州扑克规则),只需修改_calculate_hand_points方法即可。

最新文章

随机文章