要用 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()核心类设计
PokerCard:扑克牌类,封装了牌的花色、点数和计算逻辑,支持21点规则(A可算1或11)PokerGameApp:主应用类,负责界面创建、牌组管理和交互逻辑关键功能实现
self.deck存储完整牌组,self.player_hand和self.dealer_hand存储玩家/庄家手牌_calculate_hand_points方法实现智能点数计算,自动处理A的1/11点转换运行前置条件
pip install pillowdeck、player_hand、dealer_hand分别存储总牌组和玩家/庄家手牌,通过PokerCard类封装单张牌的属性和计算逻辑。你可以直接运行这段代码,体验完整的扑克牌程序功能。如果需要调整点数计算规则(比如改为德州扑克规则),只需修改_calculate_hand_points方法即可。