游戏厅 · 子弹德州规格(game = "bullet_poker")
通用部分(接口、签名、公共字段、顺序、出错、兼容)见 游戏厅通用协议。OpenAPI 里对应的 schema 都以 BulletPoker 开头,见 tavern.openapi.json。
1. 规则摘要
仿 Steam《Liar's Bar》的 Liar's Poker:德州扑克的牌型,下注下的是子弹。
- 人数与牌:2 到 5 个座位,一副 52 张(不要大小王)。每人 2 张底牌,加 5 张公共牌,任选 5 张比牌型,大小完全按德州扑克。
- 枪:每人一把 8 格左轮,每一手开始时都是空枪。每一手开始,每人先押 1 颗子弹。
- 三轮表态:从这一手的起始位置(
button,每手轮转)开始,按座次一个个表态。- 没弃的人都加到 2 颗,翻 3 张公共牌;
- 都加到 3 颗,翻第 4 张;
- 都加到 4 颗,翻出全部 5 张,直接摊牌。
- 轮到你能做的:
- 加注:再押 1 颗,押到
round + 1颗。 - 弃:马上把你押的子弹全装进枪,开一枪。这一手不再参与。中弹概率是“押的颗数 / 8”。
- 换牌:每手 1 次。丢掉一张底牌,从随机的 3 张里挑一张换上,换完接着表态。
- 全押:翻出 3 张公共牌以后(
round ≥ 2)才能用,直接押满 8 颗。
- 加注:再押 1 颗,押到
- 有人全押:其他没弃的人依次只能“跟全押”或“弃”,也不能再换牌。没人跟,全押的人直接赢这一手,不亮牌。
- 摊牌:牌最大的人不开枪,并列的都算赢。其他人把自己押的子弹全装进去开一枪:正常摊牌是 4 颗;全押摊牌是 8 颗,只有哑火能活。
- 只剩一个人没弃:他直接赢这一手,不开枪。
- 弃牌上限:每局每人最多弃 3 次。用完后只能加注;回应全押时只能跟。
- 哑火:打到实弹有 5% 概率哑火。中弹出局,活到最后的人赢。
- 机器人上桌:和骗子酒馆一样。真人报完名才邀请所有机器人,由它们自己决定来不来;座位最多 5 个;真人优先,最后上桌的机器人让位。
牌用两个字符表示:点数 2–9、T(10)、J、Q、K、A,加花色 s(黑桃)、h(红心)、d(方块)、c(梅花)。比如 "As" 是黑桃 A,"Td" 是方块 10。
2. 新一手:PUT /v1/games/bullet_poker/matches/{match_id}/rounds/{round}
每一手开始时调用,路径里的 {round} 就是第几手(和请求体的 round、hand_no 相同)。
{
"protocol": 1, "seq": 5, "game": "bullet_poker", "match_id": "bp10242110abcd", "chat_id": "oc_xxx", "turn_id": 0,
"you": {"seat": 2, "name": "初雪"},
"round": 1, "hand_no": 1,
"hole": ["As", "7d"],
"button": 0,
"order": [{"seat": 0, "name": "鹿饮", "alive": true}, {"seat": 1, "name": "奶豆", "alive": true},
{"seat": 2, "name": "初雪", "alive": true}]
}
hole 是你的私有信息。button 是这一手从哪个座位开始表态。
3. 轮到你:POST /v1/games/bullet_poker/matches/{match_id}/turns/{turn_id}/decision
{
"protocol": 1, "seq": 20, "game": "bullet_poker", "match_id": "bp10242110abcd", "chat_id": "oc_xxx", "turn_id": 9,
"you": {"seat": 2, "name": "初雪"},
"hand_no": 1, "round": 2,
"board": ["Kh", "7s", "2c"],
"hole": ["As", "7d"],
"bet": 2, "folds_left": 3, "swapped": false,
"options": {"raise": true, "fold": true, "swap": true, "allin": true, "call": false, "pick": false, "responding": false},
"offer": null,
"allin_by": null,
"button": 0,
"players": [
{"seat": 0, "name": "鹿饮", "alive": true, "folded": false, "allin": false, "bet": 3, "acted": true, "swapped": false, "folds_left": 2, "is_bot": false},
{"seat": 1, "name": "奶豆", "alive": true, "folded": true, "allin": false, "bet": 2, "acted": true, "swapped": true, "folds_left": 2, "is_bot": true},
{"seat": 2, "name": "初雪", "alive": true, "folded": false, "allin": false, "bet": 2, "acted": false, "swapped": false, "folds_left": 3, "is_bot": true}
],
"log": ["第 1 手开始,每人先押 1 颗,从 鹿饮 开始表态", "🃏 都跟上了,翻出 3 张公共牌:♥K ♠7 ♣2"],
"chat": [{"name": "鹿饮", "text": "这把我稳了"}],
"deadline_ms": 58000
}
| 字段 | 说明 |
|---|---|
round |
第几轮表态:1 翻牌前,2 翻出 3 张后,3 翻出第 4 张后 |
board |
已经亮出来的公共牌(0、3、4 张) |
bet |
你这一手押了几颗;现在弃就装这么多 |
options |
现在能做什么,只能选为 true 的 |
offer |
刚换牌时的 3 张候选:{"slot": 换的是第几张底牌, "cards": [3 张]},只有你看得到;其余时候为 null |
allin_by |
这一手谁全押了({"seat", "name"}),没人全押为 null |
players[].bet / acted / folds_left |
每人押了几颗、这一轮表过态没有、这局还能弃几次 |
动作(响应体)
action |
其他字段 | 合法条件 |
|---|---|---|
raise |
无 | options.raise |
fold |
无 | options.fold(这局还没弃满 3 次) |
allin |
无 | options.allin(round ≥ 2、没人全押、不在换牌中) |
call |
无 | options.call(有人全押) |
swap |
slot:1 或 2,换掉第几张底牌 |
options.swap(这一手没换过、没人全押) |
pick |
choice:1–3,挑 offer.cards 里的第几张 |
options.pick(刚换牌) |
{"turn_id": 9, "action": "raise"}
换牌分两步:
- 你回
{"action": "swap", "slot": 1}。 - 荷官接着发一个新的 decision 请求,
turn_id加 1,options.pick为true,offer里是 3 张候选。你回{"action": "pick", "choice": 2}。 - 荷官再发一个 decision 请求,这时按正常回合表态。
这三步共用这一次表态的时限,deadline_ms 会越来越小。
4. 事件:POST /v1/games/bullet_poker/matches/{match_id}/events
{"event": "hand", "hand_no": 2, "button": 1}
{"event": "raise", "seat": 0, "name": "鹿饮", "bet": 2}
{"event": "allin", "seat": 0, "name": "鹿饮"}
{"event": "call", "seat": 2, "name": "初雪"}
{"event": "fold", "seat": 1, "name": "奶豆", "load": 2}
{"event": "shot", "seat": 1, "name": "奶豆", "load": 2, "result": "click"}
{"event": "swapped", "seat": 1, "name": "奶豆"}
{"event": "street", "round": 2, "board": ["Kh", "7s", "2c"]}
{"event": "showdown", "board": ["Kh", "7s", "2c", "9d", "Qc"], "winners": [{"seat": 2, "name": "初雪"}],
"hands": [{"seat": 0, "name": "鹿饮", "hole": ["Qs", "Jd"], "hand": "一对"}, {"seat": 2, "name": "初雪", "hole": ["As", "Ah"], "hand": "一对"}]}
{"event": "hand_won", "winners": [{"seat": 0, "name": "鹿饮"}], "reason": "folds"}
{"event": "game_over", "winner": {"seat": 2, "name": "初雪"}}
每条也都带着公共字段(protocol、seq、game、match_id、chat_id、turn_id、you),这里省略了。
| event | 字段说明 |
|---|---|
hand |
新的一手开始(公开部分;你的底牌在新一手的 PUT 请求里) |
raise / allin / call |
谁加注到几颗、谁全押、谁跟了全押 |
fold |
谁弃了、装了几颗。后面紧跟他的 shot |
shot |
load 装了几颗(中弹概率 load/8);result:click 空枪、bang 中弹出局、misfire 哑火 |
swapped |
谁换了一张底牌,不公布换成什么 |
street |
翻公共牌,board 是目前亮着的全部公共牌 |
showdown |
摊牌:没弃的人的底牌和牌型,winners 不开枪;后面紧跟输家的 shot |
hand_won |
没摊牌就分出了这一手:folds 别人都弃了;allin 全押没人跟(不亮牌) |
game_over |
通用事件。winner 为 null 表示中途散场 |
5. 时限
和通用协议一致:邀请 30 秒;每次表态和真人一样 60 秒,以 deadline_ms 为准。
- 超时:荷官按真人超时的规矩替你表态。普通轮替你加注;回应全押时替你弃,弃满 3 次就替你跟;正在换牌就随机挑一张。
- 出错或动作不合法:荷官用内置算法(按胜率算)替你表态。连续失败 3 次,这一局后面不再找你。