- 人工智能
- 强化学习
- 深度学习
【免费下载链接】open_spiel
OpenSpiel is a collection of environments and algorithms for research in general reinforcement learning and search/planning in games.
导读
本文以 open_spiel/python/games/README.pokerkit_wrapper.md 为主线,系统讲解 OpenSpiel 中两个基于 PokerKit 构建的扑克游戏适配层:PokerkitWrapper与RepeatedPokerkit。读完本文你将掌握:如何在 OpenSpiel 中加载多种"预定义"扑克变体(德州扑克、限注、七张梭哈、Razz、底池限注奥马哈、短牌等)并配置其全部参数;如何通过RepeatedPokerkit在同一 episode 内连打多手牌,从而仿真现金局(cash game)与锦标赛(tournament)两种赛制;以及这两层包装背后的动作编码、观察字符串、手牌历史(PHH)等实现原理,可直接用于强化学习与博弈论算法的环境搭建。
一、背景:为什么 OpenSpiel 需要两层"包装"
OpenSpiel 原生 C++ 侧的扑克实现是universal_poker(基于 ACPC 协议),只覆盖德州扑克等少数变体。为了支持更多"预定义"的扑克游戏变体,OpenSpiel 引入了两层 Python 包装:
- PokerkitWrapper:直接包装 PokerKit 库,将 PokerKit 的
State对象适配为 OpenSpiel 的pyspiel.Game/pyspiel.State接口,目前支持 6 种变体; - RepeatedPokerkit:再包装
PokerkitWrapper(即对 PokerKit 的"双重包装"),让一个 episode 内连续打多手牌,通过盲注/底注/限注额度的**升级计划表(schedule)**与筹码管理,同时支持现金局与锦标赛仿真。
这种分层设计在源码中的体现为:open_spiel/python/games/pokerkit_wrapper.py 定义了基础包装器及其 ACPC 风格子类,并在文件末尾通过pyspiel.register_game(_GAME_TYPE, PokerkitWrapper)注册;open_spiel/python/games/repeated_pokerkit.py 依赖前者,注册名为python_repeated_pokerkit的游戏。两个模块都属于可选依赖,加载逻辑见 open_spiel/python/games/init.py:当pokerkit库不可导入时,两个模块会被置为None而不影响 OpenSpiel 主框架启动。
相关论文:Juho Kim 所著PokerKit: A Comprehensive Python Library for Fine-Grained Multivariant Poker Game Simulations,发表于IEEE Transactions on Games(DOI: 10.1109/TG.2023.3325637)。本仓库中的实现即依据该库的设计接口进行包装。
二、PokerkitWrapper:单局多变体扑克
2.1 注册名与支持的变体
PokerkitWrapper的注册短名为python_pokerkit_wrapper,其 GameType 定义在 pokerkit_wrapper.py#L178-L197:
dynamics = SEQUENTIAL(序贯决策)、chance_mode = EXPLICIT_STOCHASTIC(显式随机节点发牌)、information = IMPERFECT_INFORMATION(不完全信息)、utility = ZERO_SUM(零和)、reward_model = TERMINAL(终局结算);min_num_players = 2、max_num_players = 10(与 universal_poker 保持一致);- 提供信息状态字符串与观察字符串(
provides_information_state_string=True、provides_observation_string=True),但暂不提供张量(provides_information_state_tensor=False、provides_observation_tensor=False),使用上需依赖字符串观察。
支持的变体由SUPPORTED_VARIANT_MAP定义(pokerkit_wrapper.py#L66-L83),共 6 种:
变体名(参数variant取值) | 对应 PokerKit 类 | 说明 |
|---|---|---|
NoLimitTexasHoldem | pokerkit.NoLimitTexasHoldem | 无限注德州扑克(默认) |
NoLimitShortDeckHoldem | pokerkit.NoLimitShortDeckHoldem | 短牌德州扑克(36 张牌) |
FixedLimitTexasHoldem | pokerkit.FixedLimitTexasHoldem | 限注德州扑克(Limit Hold'em) |
FixedLimitSevenCardStud | pokerkit.FixedLimitSevenCardStud | 七张梭哈(Seven Card Stud) |
FixedLimitRazz | pokerkit.FixedLimitRazz | Razz(梭哈类,比最小牌) |
PotLimitOmahaHoldem | pokerkit.PotLimitOmahaHoldem | 底池限注奥马哈(PLO) |
其中NoLimitShortDeckHoldem使用pokerkit.Deck.SHORT_DECK_HOLDEM短牌牌堆,其余变体使用pokerkit.Deck.STANDARD标准 52 张牌堆(见 pokerkit_wrapper.py#L387-L395)。测试中对此也有验证:test_deck_size_is_correct_for_nolimit_holdem断言无限注德州为 52 张,test_shortdeck_deck断言短牌为 36 张且不含 2~5 点牌(pokerkit_wrapper_test.py)。
2.2 默认参数与完整参数表
_DEFAULT_PARAMS(pokerkit_wrapper.py#L130-L176)给出了所有可配置参数及其默认值,加载游戏时缺省参数会自动取默认值(构造逻辑见__init__中的_DEFAULT_PARAMS | params):
| 参数 | 默认值 | 类型与约束 | 适用变体 |
|---|---|---|---|
variant | "NoLimitTexasHoldem" | 字符串,必须命中SUPPORTED_VARIANT_MAP | 全部 |
num_players | 2 | 整数,2~10 | 全部 |
blinds | "5 10" | 空格分隔的字符串"<SB> <BB>";要求 SB ≥ 1、BB ≥ 2、SB ≤ BB | 有盲注的变体(Hold'em 类) |
num_streets | 4 | 整数,街道(下注轮)数量 | 全部 |
bring_in | 5 | 整数,公开底牌的强制注;必须严格小于small_bet;提供small_bet时必须提供 | 七张梭哈、Razz |
small_bet | 10 | 整数,限注变体的小注额;必须严格小于big_bet | FixedLimit 系列 |
big_bet | 20 | 整数,限注变体的大注额 | FixedLimit 系列 |
antes | "0 0" | 空格分隔字符串,前注;目前主要作为预留参数 | 全部(预留) |
stack_sizes | "2000 2000" | 空格分隔的num_players个整数,各玩家起始筹码 | 全部 |
几个重要的使用注意点(源码注释中明确给出):
- 字符串参数不要包含逗号,单值整数不要写成字符串,否则默认 pickle 序列化/反序列化往返可能出错(pokerkit_wrapper.py#L122-L129);
- 传入浮点数筹码(如
2000.0)会开启分数分池(fractional split pots);传整数则按 PokerKit 的默认方式整分; blinds中的位置语义遵循 PokerKit 的"位置(position)"而非"座位号":非单挑按钮局中,0 号玩家永远是小盲、1 号是大盲、2 号是 UTG……最后一位玩家在按钮位;梭哈类游戏没有按钮概念,庄家左邻为 0 号位(源码引用了 PokerKit 文档 的说明);- 参数存在"强耦合对"(如
bring_in/small_bet、small_bet/big_bet),若只改动其中一个而另一个保持默认,代码会打印警告日志提示可能配置错误(pokerkit_wrapper.py#L351-L376)。
源码还提供了VARIANT_PARAM_USAGE(pokerkit_wrapper.py#L109-L118),通过反射各 PokerKit 变体类的构造函数签名,告诉你每个参数实际被哪些变体使用;测试test_bring_in_usage等也印证了这一点:bring_in只用于 Razz 与七张梭哈,small_bet/big_bet用于 FixedLimit 系列。
2.3 加载游戏与基本使用
import pyspiel # 默认:无限注德州扑克,2 人,盲注 5/10,每人 2000 筹码 game = pyspiel.load_game("python_pokerkit_wrapper") # 自定义:4 人 PLO,盲注 5/10,不等筹码 game = pyspiel.load_game( "python_pokerkit_wrapper", { "variant": "PotLimitOmahaHoldem", "num_players": 4, "blinds": "5 10", "stack_sizes": "198 189 200 200", }, ) # 七张梭哈示例(无盲注,需要 bring_in + small_bet + big_bet) game = pyspiel.load_game( "python_pokerkit_wrapper", { "variant": "FixedLimitSevenCardStud", "num_players": 4, "stack_sizes": "200 200 189 164", "bring_in": 5, "small_bet": 10, "big_bet": 20, }, )状态推进与普通 OpenSpiel 游戏一致:发牌节点(chance node)通过chance_outcomes()取牌并用apply_action(card_int)发牌,玩家节点通过legal_actions()取动作。每个状态内部包装了一个pokerkit.State对象(PokerkitWrapperState,见 pokerkit_wrapper.py#L653-L663),chance 动作的编号即牌堆中每张牌的下标(0~51 或 0~35),字符串形式如"Deal Hole Card: TEN OF SPADES (Ts)"。
2.4 动作编码:Fold / Check-Call / 下注额度
动作空间被刻意设计为与 universal_poker 的kFold/kCall对齐,便于从 ACPC 实现移植代码(pokerkit_wrapper.py#L223-L248):
0= FOLD(弃牌);1= CHECK_OR_CALL,同时在特殊局面下复用为 POST_BRING_IN(这两个动作在语义上不会同时合法,因此共用编号 1 是安全的);>= 2的整数 = 加注到 X(Bet/Raise to X),其中X 表示当前街(street)内的累计投入,这与 ACPC/universal_poker 的"整手牌累计投入"语义不同,是两类实现最核心的差异。
由于动作1被保留,下注 1 个筹码在玩家只剩 1 个筹码想全下时与保留动作冲突,因此实现将该局面特殊映射为动作2,动作字符串为"Bet/Raise to 1 [ALL-IN EDGECASE]"(_ALL_IN_FOR_ONE_CHIP_EDGECASE_STRING,pokerkit_wrapper.py#L120)。源码通过强制min_bet/small_bet至少为 2 来避免除全下外出现 1 筹码下注。测试test_side_pot_with_one_chip_bet完整演示了该边界场景:P0翻牌全下 1 筹码时,合法动作是[ACTION_CHECK_OR_CALL, 2],应用动作2后内部实际执行complete_bet_or_raise_to(1)(pokerkit_wrapper_test.py)。
下注额度的合法范围由 PokerKit 给出:限注(FIXED_LIMIT)下 min/max 相同、只有一个固定额度;无限注(NO_LIMIT)与底池限注(POT_LIMIT)下枚举min_completion_betting_or_raising_to_amount到max_completion_betting_or_raising_to_amount之间的所有整数(pokerkit_wrapper.py#L717-L749)。因此num_distinct_actions上界取max(stack_sizes) + 1(0 为 fold,1 为 check,2 以上为加注额度,见_calculate_num_distinct_actions)。测试test_action_space_equals_max_stack_size验证了 4 人局最大筹码 400 时num_distinct_actions == 401。
2.5 自动流程与"不发牌"
PokerkitWrapper用一组**自动流程(automations)**接管了 PokerKit 中本需显式调用的步骤(pokerkit_wrapper.py#L52-L63):
pokerkit.Automation.ANTE_POSTING # 前注自动缴纳 pokerkit.Automation.BET_COLLECTION # 下注自动收集入池 pokerkit.Automation.BLIND_OR_STRADDLE_POSTING # 盲注/抓注自动缴纳 pokerkit.Automation.HOLE_CARDS_SHOWING_OR_MUCKING # 摊牌/盖牌自动处理 pokerkit.Automation.HAND_KILLING # 死牌自动处理 pokerkit.Automation.CHIPS_PUSHING # 筹码推入 pokerkit.Automation.CHIPS_PULLING # 筹码收回(结算)源码特别警告:不要自动化 CARD_BURNING(烧牌)。因为烧牌在 OpenSpiel 中应作为 chance 节点/显式动作处理,否则无法忠实反映随机性。为此wrapped_state_factory()在构造底层pokerkit.State时显式把每条街的card_burning_status设为False(pokerkit_wrapper.py#L579-L647),测试test_card_burning_is_disabled_everywhere也逐一断言所有 street 的烧牌状态为关闭。发牌、下注则保留为显式 chance/player 节点,保证 OpenSpiel 的树形搜索与随机模拟接口语义完整。
2.6 观察字符串与手牌历史(PHH)
PokerkitWrapperObserver(pokerkit_wrapper.py#L1291-L1470)生成按玩家视角的观察字符串,字段包括:当前街道、该街是否已发牌/抽牌、下一个行动玩家、底池、各玩家当前下注、公共牌、本玩家私有底牌、各玩家公开视角底牌(他人底牌以??打码)、各玩家起始与当前筹码。例如(来自官方 playthrough 文件 python_pokerkit_wrapper(variant=NoLimitTexasHoldem).txt.txt)):
Player: 0 ||Current Street: 0 ||Current Street discard/draw performed: False ||Next Player to act: None ||Pot(s): [] ||Bets: [10, 5] ||Board Cards: [] ||Player's Private Hole Cards: [Ts] ||Per-player Hole Cards (public view): [[??], []] ||Per-player Starting Stacks: (2000, 2000) ||Per-player Current Stacks: [1990, 1995]当 observer 以IIGObservationType(perfect_recall=True)创建时,string_from还会把该玩家的 PokerKit 手牌历史动作(PHH Actions)拼接到观察字符串末尾(pokerkit_wrapper.py#L1461-L1470)。PHH 动作通过pokerkit.HandHistory.from_game_state生成,且自动对其他玩家的底牌进行打码(如d dh p2 ????,见poker_hand_history_actions,pokerkit_wrapper.py#L1317-L1365)。测试test_observer_string_contains_phh_for_all_game_variants_iff_perfect_recall验证了完美回忆(perfect_recall)开关对 PHH 是否出现在字符串中的影响。
另外,to_struct()返回的pyspiel.pokerkit_wrapper.PokerkitStateStruct还额外提供:各玩家视角的 PHH 动作列表、ACPC 协议的per_player_acpc_logs(形如["S->", "MATCHSTATE:0:0:cc/r300r600:AcKc|/QcJcTc\r\n"])、投注历史字符串(如cc/r200r500r800)、盲注/起始筹码/底池大小等字段,并兼容UniversalPokerStateStruct的一部分字段(betting_history、player_contributions、pot_size等,pokerkit_wrapper.py#L1148-L1271),方便与既有 universal_poker 代码对接。
2.7 ACPC 风格子类:PokerkitWrapperAcpcStyle
为更贴近 C++ 侧 UniversalPoker / ACPC 的动作语义,源码提供了子类PokerkitWrapperAcpcStyle(注册名python_pokerkit_wrapper_acpc_style,pokerkit_wrapper.py#L1490-L1536),但仅支持两种变体:NoLimitTexasHoldem与FixedLimitTexasHoldem(_VARIANTS_SUPPORTING_ACPC_STYLE)。
其核心差异在于:ACPC 风格中加注动作X表示所有街道的累计投入,而基础类是"当前街道投入"。子类通过ToAcpcActionConverter逐街跟踪每个玩家的投入,在合法动作列表与动作字符串之间做双向映射(pokerkit_wrapper.py#L1801-L2074)。观察字符串与基础类完全一致(不匹配 universal_poker),但betting_history中的加注额度会换算为 ACPC 总投入口径,to_struct()中会断言换算前后仅r后的数字不同(pokerkit_wrapper.py#L1779-L1798)。与 universal_poker 对照时,源码注释建议以 PHH 手牌历史与 returns 数组为准。
三、RepeatedPokerkit:同一 episode 连打多手牌
3.1 设计目标与注册名
RepeatedPokerkit(注册名python_repeated_pokerkit,repeated_pokerkit.py#L88-L109)在PokerkitWrapper之上再包一层,让一个 OpenSpiel episode 内连续进行多手牌。每一手牌结束(底层pokerkit.State终局)后自动开始下一手:更新筹码、按规则轮转庄家(按钮)、按计划表推进盲注/限注额度/底注,直到达到max_num_hands或锦标赛产生冠军。每手牌的收益逐手累加作为该 episode 的总收益(returns()汇总_hand_returns,repeated_pokerkit.py#L1019-L1031)。
GameType 特征与包装器一致:序贯、显式随机、不完全信息、零和、终局结算;同样只提供观察字符串、不提供张量与信息状态字符串。由于嵌套pokerkit_game_params的 pickle 序列化存在问题(源码 TODO b/445192003),该游戏的 GameType 序列化测试被暂时禁用(repeated_pokerkit.py#L61-L64)。
3.2 参数表与赛制语义
RepeatedPokerkit的全部参数(repeated_pokerkit.py#L65-L86)及__init__中的校验逻辑(repeated_pokerkit.py#L336-L400)如下:
| 参数 | 默认值 | 类型与说明 |
|---|---|---|
pokerkit_game_params | 必填 | 指定底层单局游戏,必须是python_pokerkit_wrapper(...)或python_pokerkit_wrapper_acpc_style(...)形式的参数字符串/字典;其中的num_players、blinds、stack_sizes等会被本层实时改写以反映重复局当前状态 |
max_num_hands | 1 | episode 内最大手牌数(必填,锦标赛中需与盲注计划表协同设定) |
reset_stacks | False | 每手牌是否重置筹码。True= 现金局(每手重新等额买入);False= 锦标赛(筹码跨手累积、输光出局) |
rotate_dealer | True | 每手牌是否轮转庄家(对 PokerKit 而言即轮转"座位",使按钮成为最后一个 stack)。设为False时若当前庄家出局仍会强制轮转 |
blind_schedule | "" | 锦标赛盲注计划表,格式见下 |
bet_size_schedule | "" | 限注变体的注额计划表 |
bring_in_schedule | "" | 梭哈类变体的底注计划表 |
first_button_player | -1 | 第一手牌的按钮位玩家 ID;-1表示取最后一位玩家(即 PokerKit 默认解释) |
record_full_hand_histories | True | 每手结束时是否记录各玩家视角的完整 PHH 动作(_wrapped_state_hand_histories);手数/玩家数很大时内存占用高,遇内存问题可置False |
计划表(schedule)均为分号分隔的等级列表,解析函数见 repeated_pokerkit.py#L114-L225:
- 盲注计划表
blind_schedule:<盲注等级1>;<盲注等级2>;...,每个等级形如<手数>:<小盲>/<大盲>,例如"1:10/20;2:20/40;1:50/100"; - 注额计划表
bet_size_schedule:每个等级形如<手数>:<小注>/<大注>,仅适用于 FIXED_LIMIT 注额结构(否则报错); - 底注计划表
bring_in_schedule:每个等级形如<手数>:<底注>,同样仅适用于 FIXED_LIMIT 结构。
超出最后一个等级的手数沿用最后一档(update_blinds_bet_sizes_and_or_bring_in中的schedule[-1]兜底,repeated_pokerkit.py#L883-L898)。测试test_blind_schedule_progression验证了"1:10/20;2:20/40;1:50/100;1:1000/1000"在手 0、1、2、3、4+ 的盲注演进,以及最后一档1000/1000的持续沿用。
3.3 现金局与锦标赛的加载示例
现金局(每手重置筹码):
import pyspiel game = pyspiel.load_game( "python_repeated_pokerkit", { "max_num_hands": 25, "reset_stacks": True, # 现金局 "rotate_dealer": True, "pokerkit_game_params": { "name": "python_pokerkit_wrapper", "variant": "NoLimitTexasHoldem", "num_players": 4, "blinds": "5 10", "stack_sizes": "1000 1000 1000 1000", }, }, )锦标赛(筹码跨手累积,带盲注升级):
game = pyspiel.load_game( "python_repeated_pokerkit", { "max_num_hands": 100, "reset_stacks": False, # 锦标赛 "rotate_dealer": True, "blind_schedule": "10:10/20;10:15/30;10:25/50;10:50/100;10:100/200;50:200/400", "pokerkit_game_params": { "name": "python_pokerkit_wrapper", "num_players": 6, "blinds": "10 20", "stack_sizes": "2000 2000 2000 2000 2000 2000", }, }, )官方 playthrough 文件也提供了可直接对照的实例:repeated_pokerkit_nolimit_holdem_cash_game.txt(max_num_hands=25,reset_stacks=True)与 repeated_pokerkit_nolimit_holdem_tournament.txt(max_num_hands=200,reset_stacks=False)。从 tournament playthrough 的 GameInfo 可见:4 人各 200 筹码时MinUtility() = -200、MaxUtility() = 800(总筹码守恒),这正是锦标赛"总筹码不流失"语义的体现。
3.4 实现细节:座位轮转、筹码结转与出局判定
RepeatedPokerkitState(repeated_pokerkit.py#L475-L668)维护了 OpenSpiel 玩家 ID 与 PokerKit 座位之间的双向映射,以及庄家、盲注、注额、底注等跨手状态。每手结束后的流程为(_apply_action,repeated_pokerkit.py#L960-L1000):
- 记录本手各座位的收益到
_hand_returns; - 按需记录各玩家视角 PHH;
- 判断是否达到
max_num_hands(达到则终局并做最终筹码结算)或仅剩 1 名活跃玩家(锦标赛冠军产生); - 否则进入下一手:
update_stacks()将底池收益结转进筹码 →update_seat_assignments_unrotated()按筹码归零与否标记出局座位 →update_dealer()轮转庄家(若庄家出局则跳过其 ID 继续轮转)→update_seat_rotation()计算旋转量使按钮坐到最后一座位 → 按计划表推进盲注/注额/底注 →update_pokerkit_wrapper()用最新参数重新pyspiel.load_game加载底层包装器并创建新状态。
几个值得注意的语义(源码注释明确说明):
- 使用简化按钮规则而非"死按钮"(dead button)规则,即当盲注位玩家出局时跳过空位继续轮转,这是线上扑克常见做法;
- 与 RepeatedPoker(C++ 实现)不同,本实现支持筹码不足一个大盲的玩家继续参赛而非自动淘汰,因此锦标赛总奖励恒等于初始总筹码;
- 当
rotate_dealer=False时若庄家出局,仍会强制轮转到下一个未出局玩家,以保证后续座位旋转可计算; - 无按钮旋转概念的变体(如梭哈类)若设置
rotate_dealer=True会直接报错(repeated_pokerkit.py#L620-L625)。
相关测试覆盖了筹码结转(test_stacks_carried_over)、多人轮转(test_stack_rotation_multiway)、恰好归零淘汰(test_eliminates_players_at_exactly_zero_chips)、多人同时出局后的分池(test_multiway_all_but_one_players_bust_simultaneously、test_6max_3players_bust_then_split_pot)、手数上限(test_max_num_hands_limit)与终局收益(test_returns_in_terminal_state)等场景(repeated_pokerkit_test.py)。
四、安装前提与测试验证
4.1 依赖安装
两个游戏均依赖第三方 Python 库pokerkit。OpenSpiel 的依赖脚本 open_spiel/scripts/python_extra_deps.sh 中将其列入可选依赖(不同 Python 版本对应pokerkit==0.6.3或pokerkit==0.7.3),可据此执行:
pip install pokerkit未安装时,open_spiel/python/games/init.py 的 try/except 会静默地将两个模块置为None,OpenSpiel 其余功能不受影响;测试文件 pokerkit_wrapper_test.py 与 repeated_pokerkit_test.py 顶部也采用同样的 try/except 导入策略,以保证在缺少该依赖的环境下测试可正常跳过。
4.2 可验证的运行方式
加载游戏后即可用 OpenSpiel 自带工具做随机模拟验证 API 一致性:
import pyspiel game = pyspiel.load_game("python_pokerkit_wrapper") pyspiel.random_sim_test(game, num_sims=10, serialize=False, verbose=True) game2 = pyspiel.load_game( "python_repeated_pokerkit(max_num_hands=20,reset_stacks=True," "rotate_dealer=True,pokerkit_game_params=python_pokerkit_wrapper())" ) pyspiel.random_sim_test(game2, num_sims=5, serialize=False, verbose=True)random_sim_test会从初始状态随机走完整个树直到终局,并校验动作合法性、收益零和性等一致性约束。仓库中的官方 playthrough 文件(open_spiel/integration_tests/playthroughs/)为每个受支持的变体保留了逐步状态转储,例如python_pokerkit_wrapper(variant=FixedLimitRazz).txt、python_pokerkit_wrapper(variant=PotLimitOmahaHoldem).txt、python_pokerkit_wrapper_acpc_style.txt、repeated_pokerkit_plo_cash_game.txt与repeated_pokerkit_seven_card_stud_tournament.txt等,可作为理解各变体行为与调试自定义配置的参考基线。
五、总结与扩展方向
从源码结构看,这两层包装为 OpenSpiel 补齐了"多扑克变体 + 多手牌赛制"两块能力:
PokerkitWrapper解决了变体覆盖问题(德州/限注/梭哈/Razz/PLO/短牌),并通过 ACPC 风格子类兼容既有 UniversalPoker 动作语义;RepeatedPokerkit解决了赛制建模问题(现金局/锦标赛),以计划表驱动盲注与限注额升级,并内置出局、庄家轮转、筹码结转等规则。
仓库中的 TODO 也指明了可继续扩展的方向:支持任意数量盲注(当前限定恰好两个)、更紧的收益上界估计、锦标赛 payout 结构、分数分池的多人局支持,以及PokerkitWrapperAcpcStyle在特定 all-in 场景下与 UniversalPoker 的完全对齐(详见 pokerkit_wrapper.py 与 repeated_pokerkit.py 中的b/437724266、b/434776281、b/444333187等注释)。对于需要在不完全信息扑克环境中做 CFR、MCTS、DQN 等算法研究的开发者,可以直接以这两个游戏为环境起点,通过观察字符串或 PHH 手牌历史构造信息集。
- 人工智能
- 强化学习
- 深度学习
【免费下载链接】open_spiel
OpenSpiel is a collection of environments and algorithms for research in general reinforcement learning and search/planning in games.
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考