texas-poker-core
v1.4.32
Published
德州扑克核心功能
Readme
Texas-Poker-Core
无服务、无持久化的 德州扑克(Texas Hold’em)对局引擎:房间与座位、盲注与角色、发牌、行动轮次、阶段推进、摊牌比牌、奖池与边池分配等规则均在库内完成。
不包含:账号系统、WebSocket/HTTP、数据库、匹配、UI、节拍与 sleep;这些由业务层解释 领域事件 并驱动 pendingFlowOps 队列实现。
- 入口类:
Texas— 组装Pool、Dealer、Controller、Room;状态变更写入事件缓冲,由业务drainDomainEvents()取出后落库 / 推送。 - 指令入口:玩家行动统一走
dispatchCommand(TableCommand)(自愿下注、超时、离场弃牌、入座大盲等)。 - 节奏解耦:进街与交权进入
pendingFlowOps(stage_advance|turn_handoff),业务按产品节拍调用applyPendingStageAdvance/flushPendingTurnHandoff(单测可flushAllPendingFlowOps一次排空)。 - 错误模型:规则或状态不满足时
TexasError(TexasCoreErrorCode) fail-fast;message为默认中文,业务可用code+payload做 i18n。
详细 API 清单:CORE_API.md。与参考服务端的一整局事件流:docs/integration-core-wish-event-flow.md。
安装与构建
npm install texas-poker-core
# 或 pnpm / yarn类型定义见包内 types/;本地开发见仓库 package.json 中的 build、test 脚本。
核心对象一览
| 对象 | 职责 |
| ------------ | --------------------------------------------------------------------------------------------------------------------------------- |
| Texas | 会话门面:setPlayerRoles / dealCards / start / dispatchCommand / drainDomainEvents / 消费 pendingFlowOps / reset 等 |
| Room | 成员加入/观战/入座/离座、initialRoles(末 lockSeats)/ rotateRoles(不锁座)、RoomStatus(seats_open / seats_locked) |
| Dealer | 盲注、庄家、角色顺序、发牌(通常经 Texas / Room 访问) |
| Controller | 一手牌 HandLifecycle、当前街 stage、activePlayer、事件缓冲与 pendingFlowOps 队列 |
| Pool | 奖池与支付(texas.settle() 时 pool.pay(),并缓冲 PotAwarded) |
| Player | 单座筹码、手牌、允许行动集合;勿直接调 check/bet/…,统一经 Texas#dispatchCommand |
HandLifecycle(控制器状态)
与「房间是否锁座」不同,这是 当前这一手 在引擎里的阶段:
| 状态 | 含义 |
| ---------------- | ---------------------------------------------------------- |
| idle | 无进行中的手牌;上一手已 reset 后、下一手 start 前 |
| in_hand | 本手进行中 |
| in_hand_paused | 暂停 |
| between_hands | 本手已结束,尚未 reset;可做摊牌展示、结算入库等 |
| aborted | 预留 |
开下一手:Texas.start() 要求 controller.status === 'idle' 且 Room.status === 'seats_locked',因此本手结束后需先 texas.settle()(按需)、再 texas.reset()(会 unlockSeats),再按需 texas.rotateRolesForNewHand()(仅移庄、不锁座);局间可 seat/remove 并 reArrangeRoles();下一手开盘前(如倒计时 2s)由业务 texas.lockSeats(),再 setPlayerRoles('rearrange')(若需 RolesAssigned)、dealCards()、start()。
离座 / 入座:Room.seat / watch / remove(已入座者)仅在 Room.status === 'seats_open' 时允许(initialRoles 末会锁座;rotateRoles 不锁座;Texas.reset() 会 unlockSeats();下一手前再由业务 lockSeats())。
典型对局流程(使用手册)
以下为常见顺序;具体校验与错误码以运行时 TexasError 为准。
约定:下列 setPlayerRoles / dealCards / start / dispatchCommand 等会 同步返回 本步 TexasDomainEvent[](等价于随即 drainDomainEvents());若你自行多次调用 Core 再统一 drain,也可用 texas.drainDomainEvents() 取出缓冲。
1. 创建牌桌
import { Texas, type TableCommand } from 'texas-poker-core'
const texas = new Texas({
user: { id: 1, name: '房主' },
lowestBetAmount: 20,
maximumCountOfPlayers: 9,
initialChips: 2000
})
// 业务侧:try/catch TexasError,写日志 / 监控 / 映射 HTTP 状态码2. 注册用户与入座
const p2 = texas.createPlayer({ id: 2, name: '玩家2' })
texas.room.join(p2)
texas.room.seat(p2)
// join = 进房(默认观战);seat = 上桌,须 seats_open(一手收尾 reset 后)3. 锁座、分配角色、发牌
const events1 = texas.setPlayerRoles('initial')
// 首局:Room.initialRoles(定庄 + setOthers + lockSeats)
// 或 'rearrange'(仅 reArrangeRoles,须已有庄);可选 { buttonUserId }
await interpret(events1) // 业务:落库 / WS;下同
const events2 = texas.dealCards()
// → HoleCardsDealt(byUserId 含全员手牌;出站前按 viewer 过滤)
// 批量 seat/remove 后:Dealer 已各调 reArrangeRoles;可再 texas.reArrangeRoles() 统一推角色
// (不缓冲 RolesAssigned,需自行读 dealer 各席 role)4. 开始本手
// 要求:至少 2 人 on-set、seats_locked、controller.idle
const events3 = texas.start()
// → HandStarted、BlindsPosted、PotUpdated…;队列入队首人 turn_handoff(尚无 TurnOffered)
await drainAndConsumePendingFlow(texas, events3)
// 生产:先解释 events3,再按节拍 applyPendingStageAdvance / flushPendingTurnHandoff 并 drain 新事件
// 单测:texas.flushAllPendingFlowOps() 一次排空
// 有待贴「入座大盲」队列时,用原子 API 替代 start + 循环 PostBigBlind:
// texas.startPreflopWithJoiningBigBlinds([userId, ...])5. 玩家行动
当前行动方:texas.controller.activePlayer(须已消费队头 turn_handoff,否则 status 非 active,dispatchCommand 会被拒)。
const cmd: TableCommand = { type: 'Call', playerId: 2 }
const events = texas.dispatchCommand(cmd)
// 自愿:Fold | Check | Call | Bet | Raise | AllIn
// 超时:FoldDueToTimeout | CheckDueToTimeout(须当前行动方)
// 离场:FoldDueToLeave(须当前行动方;TurnEnded.reason === 'leave')
// 入座大盲:PostBigBlind(翻前、本街尚未入池;金额由 stakes 推导)
await drainAndConsumePendingFlow(texas, events)轮到谁、允许哪些操作:解释缓冲中的 TurnOffered(allowedActions、restrict)。
行动结果:PlayerActed + 紧邻 PotUpdated;交权结束:TurnEnded。
6. 本手结束与清理
终局时缓冲会出现 HandEnded(outcome、pokesRevealed、bestPokes 等)。业务通常在解释该事件时:
const awarded = texas.settle() // pool.pay() → PotAwarded
await interpret(awarded)
texas.reset() // pool + dealer + controller → idle,unlockSeats
// 下一手:reset → rotateRolesForNewHand → …局间 seat/reArrange… → lockSeats
// → setPlayerRoles('rearrange')? → dealCards → start() 或 startPreflopWithJoiningBigBlinds7. 业务侧最小循环(示意)
import type { Texas, TexasDomainEvent } from 'texas-poker-core'
async function drainAndConsumePendingFlow(
texas: Texas,
firstBatch: TexasDomainEvent[]
) {
await interpret(firstBatch)
while (texas.getPendingFlowOps().length > 0) {
await sleep(actionRequiredMs) // 产品节拍,Core 内无 sleep
const head = texas.getPendingFlowOps()[0]
const more =
head.kind === 'stage_advance'
? texas.applyPendingStageAdvance()
: texas.flushPendingTurnHandoff()
await interpret(more)
if (head.kind === 'stage_advance' && texas.getPendingFlowOps().length > 0) {
await sleep(stageChangedMs)
}
}
}单测可省略 sleep,在每次 dispatchCommand 后调用 texas.flushAllPendingFlowOps()。
领域事件(TexasDomainEvent)
类型定义:src/domain/handDomainEvents.ts(包内从 texas-poker-core 导出 TexasDomainEvent / HandDomainEvent)。
本手事件均带 handId(start 时分配,如 h1)与单调 seq,供 (matchId, handId, seq) 幂等落库与回放。
| 事件 | 典型触发时机 |
| ------------------------ | ---------------------------------------------------------------------------------- |
| RolesAssigned | setPlayerRoles |
| HoleCardsDealt | dealCards |
| HandStarted | start / startPreflopWithJoiningBigBlinds |
| PostedJoiningBigBlinds | 入座大盲汇总(startPreflopWithJoiningBigBlinds 至少一人入账时;PostBigBlind 每次一条) |
| BlindsPosted | 桌 SB/BB 贴盲后 |
| PlayerActed | 自愿行动 / 超时行动(盲注路径不发) |
| PotUpdated | 每次入池后(紧跟对应 PlayerActed 或盲注行) |
| StageAdvanced | 消费 stage_advance:正常进街或跑马揭示 |
| TurnOffered | 消费 turn_handoff:getControl 后 |
| TurnEnded | 行动方交权结束(reason: action | timeout | leave 等) |
| PotAwarded | settle() |
| HandEnded | 独赢弃牌或摊牌/跑马结束 |
不再提供 onGameEnd / onAction / onPreAction 等实例回调;节奏由业务解释事件 + 消费 pendingFlowOps 完成。
回放与持久化辅助:toPersistedDomainEventRows、projectCompositeReadModel、interpret 等见包导出与 docs/replay-app-server-core-coordination.md。
TableCommand(dispatchCommand)
| type | 说明 |
| ----------------------------------------------------- | -------------------------------------------------------------- |
| Fold / Check / Call / Bet / Raise / AllIn | 自愿行动;Bet/Raise 带 amount / additionalAmount |
| FoldDueToTimeout / CheckDueToTimeout | 计时到期;须当前行动方 |
| FoldDueToLeave | 当前行动方离场弃牌;可先 canFoldDueToLeave(userId) |
| PostBigBlind | 翻前补入座大盲;可选 allowWhenCurrentActor(服务端可信队列) |
JSON 解析:parseTableCommandFromJson / parseTableCommandFromUnknown。
Texas 会话 API 速查
| 方法 | 说明 |
| --------------------------------------------------- | ---------------------------------------------------------- |
| drainDomainEvents() | 取出并清空事件缓冲(无副作用) |
| dispatchCommand(cmd) | 玩家行动唯一推荐入口;返回本步事件 |
| getPendingFlowOps() | 流程队列快照(stage_advance | turn_handoff) |
| applyPendingStageAdvance() | 消费队头进街/跑马;队头不对抛 CTRL_FLOW_PENDING_MISMATCH |
| flushPendingTurnHandoff() | 消费队头交权 → TurnOffered;队头不对则静默 return |
| flushAllPendingFlowOps() | 无 sleep 排空队列(单测/批处理) |
| setPlayerRoles / dealCards / start / settle | 返回本步领域事件;start 后须消费队列才有 TurnOffered |
| startPreflopWithJoiningBigBlinds(ids) | 含入座大盲的原子开局 |
| rotateRolesForNewHand() | reset 后局间移庄,不锁座 |
| reArrangeRoles() | 按庄位重算角色,不写 RolesAssigned |
| lockSeats / unlockSeats | 显式锁座;reset() 会 unlockSeats |
| removePlayerByIdAsSystem | 系统级踢人(可绕过 seats_locked) |
| canFoldDueToLeave / end | 离场弃牌预判 / 强制结束到 between_hands |
Room 常用 API
join/joinMany:进房(观战席);人数达maximumCountOfPlayers(hang+on-set合计)时拒绝seat/seatById:上桌(须seats_open)watch/watchById:回观战(须seats_open)remove/removeById:离房;仅观战(hang)锁座时也可离房;已入座须seats_open(房主需业务先setOwner再 remove)initialRoles:定庄 + 盲位并 lockSeats;rotateRoles:局间移庄,不 lock(下一手前由业务lockSeats)lockSeats/unlockSeats:也可经texas.lockSeats()/texas.unlockSeats()setOwner/setOwnerById/getPlayerById/getPlayersBySeatStatus等
引擎与调试
Texas.configureEngine({
simulation: {
// 单测/集成脚本:如 immediateDefaultActionOnTurn、allowSingleSeatedPlayer 等
}
})
Texas.resetEngineContext() // jest.setup 已 beforeEach 调用导出中还包含牌型/阶段/行动枚举、读模型 reducer(reduceCommunityBoardFromDomainEvents 等)、createStandardDeckPokes、rankSignatureToDisplayGroups、isFatalTexasErrorCode、texasErrorCategory 等。
更多文档
| 文档 | 内容 | | -------------------------------------------------------------------------------------------------------- | ------------------------------------ | | CORE_API.md | 对外 API 与不变量摘要 | | docs/integration-core-wish-event-flow.md | 与参考服务端的整局事件流、节拍、落库 | | docs/refactor-maintainer-reference.md | 队列语义与维护速查 | | docs/replay-app-server-core-coordination.md | 回放磁带与 App/Server 配合 | | docs/How to refactor to be side-effect-free/ | 事件化架构与路线图 |
发布记录
1.0.11
保留 types 声明文件中的注释
1.0.12
导出其他类成员以及一些类型定义
1.0.13
将导出语句移动到 index 文件中
1.0.15
readme 中增加 api 文档地址
1.0.16
导出 action, role 相关的 type 以及 enum
1.0.17
上传忘记构建的内容
1.0.18
增加 User 类型字段
1.0.19
class Room 增加 function
1.0.21
room 增加方法 getPlayerById
1.0.22
class room 修改方法 getPlayerById 的返回值类型
1.0.23
完善 room api
1.0.24
test
1.0.25
test again
1.0.26
somthing went wrong
1.0.30
...
1.0.31
修复重复加入房间的问题
1.0.32
...
1.0.33
fix
1.0.34
修复 getUserInfo
1.0.39
调整 Room 的部分 api
1.0.40
add room => getPlayersBySeatStatus
1.0.41
add room => change the return type of getPlayersBySeatStatus to array
1.0.42
增加 log 信息
1.1.1
room.remove
1.1.2
main.end
1.1.3
人数小于 2 无法开始
1.1.4
reset room status
1.1.5
type Suit js Doc
1.1.6
导出扑克牌花色 type, map
1.1.7
...
1.1.8
修复一些问题
1.1.9
api change
1.1.10
新增游戏结束事件, 阶段推进事件
1.1.11
修复一些问题
1.1.12
无法找到版本问题
1.1.13
修复很多问题
1.1.14
模拟集成测试
1.1.15
修复默认下注行为的推送
1.1.16
修复很多问题
1.1.18
修复问题
1.1.19
修复问题
1.1.20
调整 api
1.1.21
增加一些事件监听方法
1.1.22
change api
1.1.23
change api
1.1.24
修复 onAction 的回调参数
1.1.25
增加 texas.reset api
1.1.26
修改 pool 值不同步的问题
1.1.26
修改 pool 值不同步的问题
1.1.27
解决玩家入座/离席时导致的玩家 role 未更新的问题
1.1.28
超时默认行为的记录
1.1.29
增加部分行为 可行动前的校验
1.1.30
手动重置游戏状态
1.1.31
增加 Log 日志
1.1.32
增加翻牌圈的参数 => onAction
1.1.33
增加 stage map 的导出
1.1.33
增加 stage map 的导出
1.1.34
修改导出方式
1.1.35
增加 poke formatter 导出
1.1.36
修复问题
1.1.37
修复许多问题
1.1.38
默认下注行为不触发回调
1.1.38
默认下注行为不触发回调
1.1.39
修复问题
1.1.39
修复问题
1.1.40
删除不必要的逻辑
1.1.41
重构 api
1.1.42
class 不使用 public 语法
1.1.43
玩家行动完后移交控制权
1.1.44
bufix
1.1.45
将游戏进程 main 函数封装为类
1.1.46
抛出错误时, 需要在 texas 实例上感知
1.1.47
推送最新版本
1.1.48
修复 player.actionble 判断错误
1.2.1
完善 texas.start 逻辑
1.2.2
完善 player.checkIfCanAct 的逻辑
1.2.3
测试 ncu
1.2.5
补充使用文档
1.2.6
修复行动前下注金额错误计算逻辑
1.2.7
不使用 core.js
1.2.8
增加 deck.core 下部分函数导出
1.2.9
使用枚举值重构 action&stage; 调整 presentation 的计算方式, 增加转换 Int 的方法, 便于以后数据库索引
1.3.0
1.3.1
重命名不合理的变量名称, 以防后期使用使混淆
1.3.2
新增角色枚举
1.4.1
重命名变量
1.4.2
分离职责到业务层
1.4.3
导出 fatal code 判断方法
1.4.4
增加一些单元测试
1.4.5
修正结算规则, 以摊开的公共牌数量为准
1.4.6
修复结算时的底牌计算错误&role-assign 增加 actionIndex 字段
1.4.7
增加类型到处
1.4.8
texas 实例新增 beforeStageAdvance,beforeNextPlayerTurn
1.4.9
修复 min,max 逻辑错误
1.4.10
增加新一轮重拍角色方法
1.4.11
修改游戏结算时的逻辑遗漏
1.4.12
修复 wager 局后未清空的问题
1.4.13
gameEnd 事件增加 pokesRevealed 字段用于入库
1.4.14
fix: 修复结算金额分配异常
1.4.15
允许玩家全押时下注所有筹码
1.4.16
允许玩家全押时下注所有筹码
1.4.17
对局一结束就允许离开
1.4.18
更新使用文档
1.4.19
修复 player reset 字段遗漏
1.4.21
引入领域事件&增加回放功能
1.4.22
增加外部设置玩家手牌&结算信息的方法
1.4.24
HandEnded/HandSettlement快照增加bestRankSignature,与桌上最强bestPokes[0]对齐- 导出
rankSignatureToDisplayGroups;顺子 wheel 在rankSignatureToRanks中 A 置于末位
1.4.25
- 导出
createStandardDeckPokes,生成标准 52 张牌(与Deck建牌顺序一致)
1.4.26
- 版本发布
1.4.27
reject modulo bias
1.4.28
贴盲与 game start 原子化
1.4.29
修复 post bb 玩家过牌导致的异常
1.4.30
同步 README 接入文档
1.4.31
增加跑马摊牌事件
1.4.32
增加 RunoutHandsRevealed 跑马亮底牌事件;无人须贴入座大盲时不发 PostedJoiningBigBlinds 空事件
