德 州 扑 克 AI PPCA2014 July 2014 PPCA2014 的 大 作 业 是 实 现 一 个 德 州 扑 克 的 AI 我 们 会 不 定 期 在 线 进 行 测 试, 能 够 适 应 不 同 局 势 赢 得 尽 量 多 的 筹 码 的 AI 会 得 到 更 高 的 分 数 本 文 将 介 绍 本 次 大 作 业 采 用 的 德 州 扑 克 规 则 ( 第 1 节 ), 评 测 系 统 的 使 用 ( 第 2 节 ), 和 本 次 大 作 业 的 具 体 要 求 ( 第 3 节 ) 本 次 大 作 业 的 想 法 来 源 于 贾 枭 学 长, 评 测 系 统 的 框 架 是 他 完 成 的, 在 此 特 别 致 谢 贾 枭 学 长 1 游 戏 规 则 德 州 扑 克 使 用 大 小 王 除 外 的 一 副 牌, 共 52 张 玩 家 根 据 手 中 2 张 底 牌 和 场 上 5 张 公 共 牌 组 合 出 的 最 好 牌 型 决 定 胜 负 每 一 局 的 流 程 如 下 : 1. 洗 牌 2. 担 任 小 盲 注 和 大 盲 注 的 玩 家 下 盲 注 (blinds) 3. 庄 家 (dealer) 为 每 人 发 两 张 底 牌 (hole card) 4. 翻 牌 前 的 一 轮 下 注 (preflop) 5. 销 一 张 牌, 翻 三 张 公 共 牌 (flop) 6. 第 二 轮 下 注 7. 销 一 张 牌, 翻 一 张 公 共 牌 (turn) 8. 第 三 轮 下 注 9. 销 一 张 牌, 翻 一 张 公 共 牌 (river) 10. 第 四 轮 下 注 11. 若 场 上 还 剩 至 少 两 名 玩 家 未 盖 牌, 则 他 们 需 要 展 示 手 牌 比 较 大 小 (showdown) 1
12. 分 配 彩 金 一 局 游 戏 中 至 少 有 2 名 玩 家, 庄 家 由 玩 家 轮 流 担 任, 沿 顺 时 针 方 向 轮 换, 小 盲 注 是 庄 家 顺 时 针 方 向 下 一 个 玩 家, 大 盲 注 是 小 盲 注 顺 时 针 方 向 的 下 一 个 玩 家 ( 在 2 人 局 中 就 是 庄 家 ) 大 盲 注 是 小 盲 注 的 2 倍, 小 盲 注 将 随 游 戏 进 行 增 加, 目 前 每 3 局 增 加 一 次, 小 盲 注 的 大 小 依 次 为 1,2,5,10,20,50,100,200,500, 共 27 局 玩 家 在 初 始 时 将 获 得 一 样 多 的 筹 码, 目 前 为 1000 当 一 名 玩 家 手 中 筹 码 变 为 0 时, 他 就 出 局 了, 他 只 能 观 察 之 后 的 比 赛 当 场 上 只 剩 一 名 玩 家 或 打 满 27 局 时, 游 戏 结 束 第 一 轮 下 注 从 大 盲 注 顺 时 针 方 向 的 下 一 名 玩 家 开 始 沿 顺 时 针 方 向 进 行, 大 小 盲 注 在 此 时 不 算 已 经 下 注, 但 是 大 小 盲 注 计 入 第 一 轮 的 彩 池 后 三 轮 下 注 都 从 庄 家 的 下 一 名 玩 家 开 始 当 一 轮 彩 池 中 没 有 筹 码 时, 玩 家 可 以 选 择 过 牌 (check), 盖 牌 (fold), 或 加 注 (raise) 当 有 彩 池 中 已 有 筹 码 时, 玩 家 可 以 选 择 盖 牌, 跟 注 (call), 或 加 注 本 次 大 作 业 采 用 的 是 无 限 下 注 德 州 扑 克 (no-limit), 加 注 时 只 需 要 不 少 于 当 轮 上 一 个 加 注 的 数 量, 如 果 玩 家 是 当 轮 第 一 个 下 注 的, 他 的 下 注 至 少 与 大 盲 注 相 同 如 果 一 个 玩 家 在 想 要 跟 注 或 加 注 时, 他 拥 有 的 筹 码 不 足 最 低 限 额, 则 他 依 然 可 以 完 成 通 过 全 押 (all-in) 进 行 跟 注 或 加 注 盖 牌 的 玩 家 将 损 失 本 局 游 戏 中 投 入 的 所 有 筹 码, 不 再 参 与 本 局 游 戏 当 以 下 所 有 条 件 满 足 时 一 轮 下 注 结 束 : 每 个 未 盖 牌 的 玩 家 均 行 动 过 除 了 全 押 的 玩 家, 所 有 未 盖 牌 的 玩 家 下 注 都 相 同 下 一 个 行 动 的 玩 家 恰 好 是 上 一 个 加 注 的 玩 家, 或 者 场 上 只 有 一 名 玩 家 未 盖 牌, 或 者 所 有 玩 家 选 择 过 牌 最 终, 各 个 彩 池 分 给 该 彩 池 贡 献 者 牌 型 最 大 的 玩 家 若 牌 型 相 同, 则 比 较 次 要 牌 依 次 类 推 如 果 一 个 彩 池 的 贡 献 者 中 有 多 人 牌 一 样 大, 则 该 彩 池 由 他 们 平 分, 若 彩 池 中 筹 码 数 不 能 被 除 尽, 则 余 数 被 分 给 该 彩 池 胜 者 中 顺 时 针 方 向 最 靠 近 庄 家 的 一 个 牌 型 规 则 可 参 考 Wikipedia:Texas Hold em. 目 前 我 们 的 规 则 和 通 常 的 德 州 扑 克 有 一 些 不 一 致 : 两 人 局 的 盲 注 依 然 按 照 正 常 局 算 All-in 的 加 注 被 当 做 加 注 在 第 一 轮 加 注 中, 若 彩 池 中 贡 献 最 多 的 玩 家 的 下 注 等 于 大 盲 注 的 大 小 时, 大 盲 注 需 要 跟 注 0 个 筹 码 而 不 是 过 牌 可 能 还 有 其 他 不 一 致 之 处, 发 现 后 请 指 出 2
2 评 测 系 统 评 测 系 统 由 client 和 server 组 成, 大 作 业 只 需 要 实 现 client 中 Player 类 的 函 数 即 可 评 测 时, 请 运 行 client, 连 接 指 定 的 服 务 器 进 行 评 测 下 面 主 要 介 绍 client 的 运 行 环 境 编 译 方 法 和 为 实 现 AI 提 供 的 接 口 2.1 运 行 环 境 和 编 译 方 法 评 测 系 统 可 以 在 Linux,Windows,Mac OS 上 运 行, 源 代 码 公 开 在 github 上 要 获 取 源 代 码, 在 终 端 中 运 行 :(windows 下 请 自 行 安 装 git) 1 g i t c l o n e https : / / github. com/ zzy7896321 /holdem. g i t 评 测 系 统 需 要 Boost C++ Library, 请 自 行 下 载 编 译 holdem/client 目 录 下 提 供 了 Makefile 和 Makefile-mingw 请 Makefile 中 的 AI 变 量 改 为 AI 实 现 的 文 件 名, 比 如 AI 实 现 在 example.cpp 中, 需 要 把 AI 设 置 为 example 在 Linux 系 统 上, 正 常 情 况 下 1 make 即 可 编 译 在 Windows (MinGW) 系 统 上, 需 要 先 设 置 好 MinGW GCC 的 环 境 变 量, 将 Makefilemingw 中 BOOST_PATH 改 为 boost 的 根 目 录 另 外, 由 于 MinGW 的 一 些 bugs, 可 能 需 要 对 它 提 供 的 头 文 件 进 行 修 改, 参 见 Link 1 1 mingw32 make f Makefile mingw 在 Cygwin 上 可 能 无 法 正 常 编 译 评 测 系 统 需 要 支 持 C++11 的 编 译 器, 已 知 g++ 4.8 或 更 高 的 版 本 可 以 使 用 目 前 已 在 以 下 环 境 中 成 功 编 译 : Linux Mint 15 32bit, g++ 4.8.2 Linux Ubuntu 14.04 64bit, g++ 4.8.3 Windows 7 32bit, MinGW g++ 4.8.1 Windows 8 64bit, MinGW g++ 4.8.1 (to be tested) Mac OS 运 行 client, 其 中 <ip> 是 server 的 ip 地 址,port 是 端 口 3
1. / c l i e n t <ip> <port> example.cpp 是 一 个 简 单 的 UI, 各 个 阶 段 会 提 示 用 户 输 入 决 策, 仅 仅 作 为 测 试 程 序 2.2 实 现 AI 的 接 口 Player 类 中 的 以 下 函 数 需 要 实 现, 若 额 外 的 存 储 空 间, 可 在 Player.h 中 自 行 加 入 所 需 的 成 员 变 量 std::string Player::login_name(); 返 回 你 的 AI 的 名 字, 将 在 与 服 务 器 建 立 连 接 时 调 用 ( 在 调 用 init 之 前 ) void Player::login_name(std::string name); 服 务 器 接 受 name 作 为 你 的 AI 的 名 字, 这 个 名 字 可 能 与 之 前 login_name() 的 不 同 void Player::init(); 初 始 化 函 数 init 将 在 确 认 与 服 务 器 连 接 成 功, 收 到 玩 家 列 表 之 后 被 调 用 void Player::destroy(); destroy 将 在 Player 的 析 构 函 数 中 调 用 decision_type Player::preflop(); 返 回 第 一 轮 下 注 时 AI 的 决 定, 可 能 在 同 一 轮 中 被 多 次 调 用 decision_type Player::flop(); 返 回 第 二 轮 下 注 时 AI 的 决 定, 可 能 在 同 一 轮 中 被 多 次 调 用 decision_type Player::turn(); 返 回 第 三 轮 下 注 时 AI 的 决 定, 可 能 在 同 一 轮 中 被 多 次 调 用 decision_type Player::river(); 返 回 第 四 轮 下 注 时 AI 的 决 定, 可 能 在 同 一 轮 中 被 多 次 调 用 hand_type Player::showdown(); 返 回 AI 决 定 展 示 的 5 张 牌,5 张 牌 应 当 在 自 己 的 2 张 手 牌 和 5 张 公 共 牌 中 void game_end(); 一 局 比 赛 结 束 的 时 候 调 用,AI 可 以 进 行 赛 后 统 计, 以 便 提 供 之 后 比 赛 使 用 的 数 据 decision_type 定 义 为 : 4
1 enum DECISION_VALUE { CHECK, FOLD, CALL, RAISE } ; 2 typedef std : : pair <DECISION_VALUE, int> decision_type ; 其 中,CHECK,FOLD 和 CALL 作 为 决 定 时, 第 二 个 分 量 将 被 忽 略 RAISE 作 为 决 定 时, 第 二 个 分 量 表 示 在 跟 注 基 础 上, 还 要 加 注 的 大 小 比 如 场 上 本 轮 最 大 下 注 为 4, 自 己 现 在 已 经 下 了 2, 那 么 <RAISE, 4> 表 示, 跟 注 到 4 的 基 础 上 再 加 注 4, 自 己 的 下 注 将 变 为 8 可 以 调 用 make_decision 函 数 返 回 适 当 的 决 定 比 如, 1 return make_ decision (CHECK) ; 2 return make_ decision (CALL) ; 3 return make_ decision (FOLD) ; 4 return make_ decision ( RAISE, 4) ; hand_type 和 card_type 定 义 为 : 1 typedef std : : pair <char, char> card_type ; 2 typedef s t d : : array<card_type, 5> hand_type ; 其 中 card_type 两 个 分 量 分 别 表 示 牌 的 大 小 (2 3 4 5 6 7 8 9 T J Q K A) 和 牌 的 种 类 (S H D C) hand_type 是 一 个 大 小 为 5 的 数 组, 存 放 5 张 要 展 示 的 手 牌 其 他 的 定 义 详 见 common.h 比 赛 信 息 可 以 通 过 query 成 员 的 成 员 函 数 查 询, 目 前 提 供 以 下 查 询 函 数 : 函 数 原 型 说 明 整 个 游 戏 的 信 息, 从 init 开 始 可 以 调 用 const std::string& name_of(int player); 返 回 编 号 为 player 的 玩 家 的 名 字 int number_of_player(); 玩 家 总 数 int my_id(); 自 己 的 编 号 int initial_chips(); 初 始 的 筹 码 数 const std::vector<int>& chips(); 存 放 玩 家 筹 码 数 的 vector, 下 标 为 玩 家 编 号 int chips(int player); 返 回 编 号 为 player 的 玩 家 的 筹 码 数 单 局 游 戏 的 信 息, 可 以 在 preflop 到 game_end 函 数 中 调 用 int number_of_participants(); 本 局 游 戏 中 玩 家 数 量 bool out_of_game(); 自 己 是 否 已 经 出 局 int dealer(); 本 局 的 庄 家 编 号 int blind(); 本 局 小 盲 注 大 小 const card_type* hole_cards(); 返 回 存 放 自 己 的 底 牌 的 数 组, 大 小 为 2 const std::vector<card_type>& community_cards(); 返 回 存 有 公 共 牌 的 数 组, 大 小 可 能 为 0,3,4, 或 5 const std::vector<pot>& pots(); 返 回 存 放 有 彩 池 的 数 组, 一 轮 下 注 可 能 产 生 多 个 彩 池, 不 同 轮 贡 献 者 相 同 的 彩 池 不 合 并, 详 见 pot.h 一 轮 下 注 的 信 息, 仅 在 本 轮 调 用 有 效 const std::vector< std::pair<int, int> >& bets(); 本 轮 下 注 的 情 况,pair 中 两 个 分 量 分 别 为 下 注 玩 家 编 号 和 下 注 的 大 小, 一 个 玩 家 可 能 多 次 出 现 5
const std::vector<player_status>& 存 放 玩 家 下 注 状 态 的 数 组, PLAYER_STATUS player_statue(); 可 能 取 值 NOT_ACTIONED, BET, CHECKED, FOLDED, 下 标 为 玩 家 编 号 PLAYER_STATUS player_status(int player); 返 回 编 号 为 player 的 玩 家 的 下 注 状 态 const std::vector<int>& current_bets(); 存 放 本 轮 每 个 玩 家 的 下 注 数 量 的 数 组, 下 标 为 玩 家 编 号 int current_bets(int player); 返 回 编 号 为 player 的 玩 家 在 本 轮 下 注 的 数 量 单 局 比 赛 的 统 计 信 息, 可 以 从 上 一 局 的 game_end 开 始 到 下 一 局 game_end 之 前 调 用 const std::vector<handinfo>& hands(); 返 回 存 有 上 一 局 参 与 showdown 的 玩 家 手 牌, HANDINFO 参 见 common.h const std::vector< <std::vector< std::pair<int, int> > >& 返 回 上 一 局 每 个 彩 池 分 配 的 情 况,pair 的 分 量 分 别 是 won_chips_in_pots(); 玩 家 编 号 和 玩 家 从 该 彩 池 赢 得 筹 码 的 数 量 const std::vector<int>& 返 回 上 一 局 游 戏 每 个 玩 家 赢 得 的 筹 码 数 chips_won_in_last_game(); 3 作 业 要 求 实 现 一 个 能 正 常 运 行 的 AI, 并 且 有 合 理 的 运 行 时 间 尽 可 能 地 赢 得 更 多 的 筹 码, 并 且 能 适 应 不 同 类 型 的 玩 家 在 线 测 试 时, 会 提 供 server 的 ip 地 址 和 端 口, 大 约 8 至 9 人 一 组 最 终 提 交 包 括 AI 实 现 的 源 代 码 和 一 份 报 告, 报 告 需 要 简 要 阐 述 你 的 算 法 和 独 到 之 处 评 分 会 根 据 在 线 测 试 成 绩 提 交 的 代 码 和 报 告 综 合 确 定 6