Cursor实战案例-游戏与媒体-62-策略对抗小游戏:用Python开发简易的多人纸牌概率扑克游戏

发布时间:2026/9/27 16:16:01
Cursor实战案例-游戏与媒体-62-策略对抗小游戏:用Python开发简易的多人纸牌概率扑克游戏 1. 为什么用 Cursor 写多人纸牌概率扑克比手搓快三倍多人纸牌概率扑克游戏核心难点不在发牌而在“不完全信息下的胜率推演”。你手里两张底牌公共牌翻到转牌圈对手底牌未知这时候要判断跟注、加注还是弃牌靠感觉就是送筹码。用 Python 写一个简易的多人纸牌概率扑克原型把蒙特卡洛模拟塞进决策循环AI 就能基于实时胜率做理性选择。Cursor 在这个场景里的价值是帮你把“牌型判定 概率模拟 回合骨架”三块代码快速串起来。我试过纯手写光 7 选 5 的组合评估和 A-2-3-4-5 特殊顺子就调了半天用 Cursor 的对话式补全把牌型规则描述清楚它直接给出可运行的evaluate_hand_5_cards骨架你只需要核对边界条件。这篇面向想快速搭出可玩原型的开发者。你会拿到一副 52 张牌的牌堆模型、基于蒙特卡洛的胜率计算函数、三人回合下注骨架以及一套可复制的验证命令。全程纯 Python 标准库不需要额外依赖Python 3.8 以上就能跑。如果你在 Cursor 里写还能让 AI 帮你补全注释和异常分支省掉大量查文档时间。2. TaoToken 前置给 Cursor 配一个稳定的模型通道Cursor 本身是编辑器它的 AI 补全和对话能力需要背后有模型服务。如果你在 Cursor 里频繁遇到补全延迟、对话超时或者想统一管理多个模型的调用额度可以先把模型接入层配好。TaoToken 提供 OpenAI 兼容的接口Cursor 的自定义模型配置里填上 API 地址和 Key 就能用。具体操作打开 Cursor 设置找到 Models 或 OpenAI API Key 配置项。API 地址填https://taotoken.net/apiKey 从控制台生成。如果你用的是 Claude Code 这类终端工具也可以在环境变量里配ANTHROPIC_BASE_URL指向对应端点。配置完成后Cursor 的 Tab 补全和 Chat 都会走这个通道写扑克游戏时让 AI 帮你补全牌型判断逻辑会顺畅很多。需要生成 Key 的话进控制台创建注意别把 Key 硬编码进poker_game.py用环境变量读取。接入文档里有不同客户端的配置示例Cursor 和 Claude Code 的填法略有差异照着改就行。3. 可复制配置牌堆、牌型判定与蒙特卡洛胜率核心下面这份poker_game.py可以直接复制运行。它包含四个部分牌堆模型、5 张牌型评估、7 选 5 最优组合、蒙特卡洛胜率推演。代码里保留了关键注释方便你在 Cursor 里继续让 AI 扩展。#!/usr/bin/env python # -*- coding: utf-8 -*- poker_game.py 多人纸牌概率扑克原型牌堆 牌型判定 蒙特卡洛胜率 回合骨架 import collections import itertools import random SUITS [♠, ♥, ♦, ♣] RANKS { 2: 2, 3: 3, 4: 4, 5: 5, 6: 6, 7: 7, 8: 8, 9: 9, T: 10, J: 11, Q: 12, K: 13, A: 14 } class Card: def __init__(self, rank, suit): self.rank rank self.suit suit self.value RANKS[rank] def __repr__(self): return f{self.suit}{self.rank} class Deck: def __init__(self): self.cards [Card(r, s) for r in RANKS for s in SUITS] def shuffle(self): random.shuffle(self.cards) def draw(self, n1): drawn [] for _ in range(n): if self.cards: drawn.append(self.cards.pop(0)) return drawn HAND_RANK_VALUES { STRAIGHT_FLUSH: 8, FOUR_OF_A_KIND: 7, FULL_HOUSE: 6, FLUSH: 5, STRAIGHT: 4, THREE_OF_A_KIND: 3, TWO_PAIR: 2, ONE_PAIR: 1, HIGH_CARD: 0 } def evaluate_hand_5_cards(cards): sorted_cards sorted(cards, keylambda c: c.value, reverseTrue) values [c.value for c in sorted_cards] suits [c.suit for c in sorted_cards] is_flush len(set(suits)) 1 is_straight False if len(set(values)) 5: if values[0] - values[4] 4: is_straight True elif values [14, 5, 4, 3, 2]: is_straight True values [5, 4, 3, 2, 1] count_map collections.Counter(values) mc count_map.most_common() if is_flush and is_straight: return HAND_RANK_VALUES[STRAIGHT_FLUSH], [values[0]] if mc[0][1] 4: return HAND_RANK_VALUES[FOUR_OF_A_KIND], [mc[0][0], mc[1][0]] if mc[0][1] 3 and mc[1][1] 2: return HAND_RANK_VALUES[FULL_HOUSE], [mc[0][0], mc[1][0]] if is_flush: return HAND_RANK_VALUES[FLUSH], values if is_straight: return HAND_RANK_VALUES[STRAIGHT], [values[0]] if mc[0][1] 3: return HAND_RANK_VALUES[THREE_OF_A_KIND], [mc[0][0], mc[1][0], mc[2][0]] if mc[0][1] 2 and mc[1][1] 2: return HAND_RANK_VALUES[TWO_PAIR], [mc[0][0], mc[1][0], mc[2][0]] if mc[0][1] 2: return HAND_RANK_VALUES[ONE_PAIR], [mc[0][0], mc[1][0], mc[2][0], mc[3][0]] return HAND_RANK_VALUES[HIGH_CARD], values def evaluate_best_hand(hole_cards, community_cards): all_7 hole_cards community_cards best_rank -1 best_kicker [] for comb in itertools.combinations(all_7, 5): rank, kicker evaluate_hand_5_cards(list(comb)) if rank best_rank: best_rank rank best_kicker kicker elif rank best_rank and kicker best_kicker: best_kicker kicker return best_rank, best_kicker def calculate_win_rate(my_hand, community_cards, num_players, simulations2000): wins 0 known my_hand community_cards known_str {f{c.suit}{c.rank} for c in known} for _ in range(simulations): sim_deck Deck() sim_deck.cards [c for c in sim_deck.cards if f{c.suit}{c.rank} not in known_str] random.shuffle(sim_deck.cards) num_needed_comm 5 - len(community_cards) sim_comm community_cards sim_deck.draw(num_needed_comm) opponents [] for _ in range(num_players - 1): opponents.append(sim_deck.draw(2)) my_score evaluate_best_hand(my_hand, sim_comm) i_win True for opp in opponents: opp_score evaluate_best_hand(opp, sim_comm) if opp_score[0] my_score[0]: i_win False break elif opp_score[0] my_score[0] and opp_score[1] my_score[1]: i_win False break if i_win: wins 1 return wins / simulations这段代码里有两个容易写错的地方。第一是 A-2-3-4-5 特殊顺子必须单独判断values [14, 5, 4, 3, 2]并把 A 的值改成 1否则会被当成高牌。第二是蒙特卡洛模拟时要先剔除已知牌否则会出现重复发牌胜率偏高。你在 Cursor 里可以让 AI 帮你检查这两处边界。4. 多人回合骨架与运行验证有了胜率计算接下来搭一个三人回合骨架。每个玩家有底牌、筹码、是否弃牌三个状态。AI 根据实时胜率和底池赔率决定跟注、加注还是弃牌。class Player: def __init__(self, name, chips1000, is_aiTrue): self.name name self.chips chips self.is_ai is_ai self.hand [] self.is_folded False self.win_rate 0.0 def make_decision(self, current_bet, my_investment, pot, num_active, community): if self.is_folded: return FOLD, 0 self.win_rate calculate_win_rate( self.hand, community, num_active, simulations1000 ) call_cost current_bet - my_investment if call_cost 0: if self.win_rate 0.65: return RAISE, min(100, self.chips) return CHECK, 0 pot_odds call_cost / (pot call_cost) if self.win_rate pot_odds: if self.win_rate 0.75 and self.chips call_cost 100: return RAISE, call_cost 100 return CALL, min(call_cost, self.chips) else: if self.win_rate 0.35 and random.random() 0.15: return CALL, min(call_cost, self.chips) self.is_folded True return FOLD, 0 def run_game_round(): print( 多人纸牌概率扑克对局开始 ) deck Deck() deck.shuffle() human Player(玩家(你), chips1000, is_aiFalse) bob Player(AI_Bob, chips1000, is_aiTrue) alice Player(AI_Alice, chips1000, is_aiTrue) players [human, bob, alice] for p in players: p.hand deck.draw(2) print(f你的底牌: {human.hand}) community deck.draw(4) print(f当前公共牌(转牌圈): {community}) pot 60 current_bet 20 investments {p.name: 20 for p in players} human_rate calculate_win_rate(human.hand, community, 3, simulations5000) print(f你的实时胜率: {human_rate * 100:.2f}% (5000次模拟)) for p in players: if p.is_folded: continue if p.is_ai: decision, amount p.make_decision( current_bet, investments[p.name], pot, 3, community ) if decision CALL: pot amount investments[p.name] amount print(f{p.name} 跟注 {amount} (胜率 {p.win_rate*100:.1f}%)) elif decision RAISE: pot amount current_bet investments[p.name] amount investments[p.name] amount print(f{p.name} 加注 {amount} (胜率 {p.win_rate*100:.1f}%)) elif decision FOLD: print(f{p.name} 弃牌 (胜率 {p.win_rate*100:.1f}%)) elif decision CHECK: print(f{p.name} 过牌) else: if human_rate 0.40: cost current_bet - investments[p.name] pot cost investments[p.name] cost print(f你 自动跟注 {cost}) else: p.is_folded True print(你 自动弃牌) community deck.draw(1) print(f最终公共牌(河牌圈): {community}) active [p for p in players if not p.is_folded] if not active: print(所有玩家弃牌无赢家) return best_score -1 best_kicker [] winner None for p in active: rank, kicker evaluate_best_hand(p.hand, community) hand_name [k for k, v in HAND_RANK_VALUES.items() if v rank][0] print(f{p.name} | 底牌 {p.hand} | 牌型 {hand_name} | 特征 {kicker}) if rank best_score or (rank best_score and kicker best_kicker): best_score rank best_kicker kicker winner p print(f最终赢家: {winner.name}赢得底池 {pot} 筹码) if __name__ __main__: run_game_round()运行命令python poker_game.py预期输出类似 多人纸牌概率扑克对局开始 你的底牌: [♦A, ♠Q] 当前公共牌(转牌圈): [♦Q, ♣J, ♠4, ♦T] 你的实时胜率: 64.84% (5000次模拟) 你 自动跟注 0 AI_Bob 跟注 0 (胜率 58.2%) AI_Alice 弃牌 (胜率 18.5%) 最终公共牌(河牌圈): [♦Q, ♣J, ♠4, ♦T, ♦8] 玩家(你) | 底牌 [♦A, ♠Q] | 牌型 ONE_PAIR | 特征 [12, 14, 11, 10, 8] AI_Bob | 底牌 [♣9, ♠8] | 牌型 ONE_PAIR | 特征 [8, 12, 11, 10, 9] 最终赢家: 玩家(你)赢得底池 60 筹码5000 次模拟在普通笔记本上大约 0.25 秒完成。如果你在 Cursor 里跑可以把simulations调小到 500 做快速调试确认逻辑无误后再拉满。5. 本篇常见错排查5.1 翻牌前模拟卡顿公共牌为 0 时蒙特卡洛要从 50 张未知牌里抽 5 张公共牌再给每个对手发 2 张底牌路径组合爆炸。如果你把simulations设成 10000单线程 Python 会卡住两三秒。解决方法是动态调整模拟次数翻牌前用 500 次转牌圈和河牌圈用 2000 到 5000 次。胜率精度和响应速度要平衡翻牌前本来信息就模糊没必要精算。5.2 A-2-3-4-5 被误判为高牌这是最常见的牌型判定 bug。values [14, 5, 4, 3, 2]这个分支必须写在顺子判断里并且把 A 的值改成 1 再参与后续比较。如果不处理系统会把 A-2-3-4-5 当成“高牌 A”胜率计算直接崩掉。你在 Cursor 里可以让 AI 专门写一个测试用例evaluate_hand_5_cards传入[A,2,3,4,5]应该返回STRAIGHT而不是HIGH_CARD。5.3 已知牌未剔除导致胜率虚高calculate_win_rate里如果忘了把my_hand和community_cards从模拟牌堆里去掉模拟发牌会重复出现你手里的牌对手拿到这些牌的概率被错误放大你的胜率会偏高。代码里用known_str集合过滤这一步不能省。另外如果你已知部分对手底牌也要加进known_str。5.4 Cursor 补全的牌型顺序错乱让 Cursor 生成牌型判断时它有时会把同花顺和四条的顺序写反。牌型强度从高到低是同花顺、四条、葫芦、同花、顺子、三条、两对、一对、高牌。判断顺序必须按这个来先判同花顺再判四条否则四条会被同花顺覆盖。你可以在代码里加一行断言或者写个单元测试跑一遍所有牌型。6. 继续扩展与工具链建议这套原型跑通后你可以往三个方向扩展。第一把Player.make_decision里的胜率阈值做成可配置参数让不同 AI 有不同的风险偏好比如保守型 AI 胜率低于 50% 就弃牌激进型 AI 胜率 30% 也敢跟。第二加入翻牌圈、转牌圈、河牌圈的分阶段下注逻辑现在代码是直接跳到转牌圈完整流程需要补上翻牌圈的三张公共牌。第三把胜率计算改成多进程或缓存已知牌型结果提升高模拟次数下的响应速度。如果你在 Cursor 里继续开发建议把模型通道配好让 AI 帮你补全测试用例和边界分支。API 地址用https://taotoken.net/apiKey 在控制台生成后通过环境变量注入。需要长期跑编码任务或者 Agent 工作流的话可以看看 Coding Plan 的额度方案比单次调用更划算。接入文档里有 Cursor、Claude Code 等客户端的完整配置示例照着填就能用。写扑克概率游戏最深的体会是牌型判定和概率模拟这两块边界条件比主逻辑更容易出错。A-2-3-4-5、已知牌剔除、模拟次数动态调整这三个坑踩过一次就记住了。你在 Cursor 里让 AI 写代码时记得把这三条明确写进提示词能省掉大量调试时间。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询