Files

108 lines
4.5 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Package game 定义玩法插件的统一接口与注册表。
// 房间只通过该接口驱动玩法:新增一个游戏 = 新增实现 + 注册,网关/大厅/房间零改动。
package game
import (
"encoding/json" // 标准库:动作参数以原始 JSON 透传给玩法
"fmt" // 标准库:未注册玩法时返回明确错误
"sync" // 标准库:保护注册表并发读写
)
// Meta 描述玩法的匹配与房间形态:人数与节奏差异全部由它声明。
type Meta struct {
GameID string // 游戏唯一 ID,匹配队列与房间都用它索引
MinSeats int // 最少人数:双人=2,多人=4/6...
MaxSeats int // 最多人数
Realtime bool // true=实时玩法(依赖 OnTick 驱动),false=回合制
AllowJoin bool // 是否允许开局后中途加入
TickMs int // 房间驱动周期(毫秒):0 表示用房间默认 1 秒;实时玩法设小值(如贪吃蛇 200)
}
// Action 是一次玩家动作的通用载体:房间只透传,不解析内容。
type Action struct {
Name string // 动作名,玩法内部约定(如 "place")
Payload json.RawMessage // 动作参数原样 JSON,由玩法自行解析
}
// Event 描述「发给谁、发什么」:投递由房间完成,玩法不接触连接。
type Event struct {
To []int64 // 收件人;nil 或空表示全体广播
Name string // 事件名,客户端据此分发
Payload any // 事件内容
}
// Finish 是对局结果:由玩法返回,房间负责广播与结算。
type Finish struct {
Winner int64 // 获胜用户 ID,0 表示平局
Scores map[int64]int // 各用户得分,可为空
Reason string // 结束原因:normal / opponent_leave / timeout
}
// State 是玩法私有状态:房间只做透传,具体类型由玩法在 Init 中创建。
type State any
// Game 是所有玩法必须实现的接口,房间按序调用这些方法驱动一局游戏。
type Game interface {
Meta() Meta // 元信息:人数、是否实时等
Init(seatCount int) (State, error) // 开局初始化私有状态
OnJoin(uid int64, s State) ([]Event, error) // 玩家入座
OnLeave(uid int64, s State) ([]Event, *Finish, error) // 玩家离座;可返回 Finish 判负
OnAction(uid int64, a Action, s State) ([]Event, *Finish, error) // 玩家动作;返回 nil Finish 表示未结束
OnTick(now int64, s State) ([]Event, *Finish, error) // 每秒驱动:实时玩法与超时判定
Snapshot(uid int64, s State) any // 按观众裁剪的可见状态(重连/观战用)
}
// AIAction 是 AI 玩家要执行的一次动作:带执行者,便于房间代它调用 OnAction。
type AIAction struct {
UID int64 // 执行者(AI 的 uid)
Action Action // 要执行的动作
}
// AIBrain 是可选接口:玩法实现它即可支持「AI 补位」。
// 房间在每次推进前调用一次,把返回的动作代 AI 执行;不实现该接口的玩法不会被补 AI。
type AIBrain interface {
// TickAI 返回本回合所有 AI 玩家要执行的动作(含思考延迟由玩法内部决定)。
TickAI(now int64, s State) []AIAction
}
// Factory 创建玩法实例:每局一个新实例,保证状态隔离。
type Factory func() Game
var (
mu sync.RWMutex // 保护注册表
registry = map[string]Factory{} // gameID -> 构造函数
)
// Register 注册一个玩法;同名 ID 重复注册会覆盖(便于测试替换)。
func Register(gameID string, f Factory) {
mu.Lock() // 加写锁
defer mu.Unlock() // 返回时解锁
registry[gameID] = f
}
// New 按 ID 创建玩法实例。
func New(gameID string) (Game, error) {
mu.RLock() // 加读锁
f, ok := registry[gameID] // 查注册表
mu.RUnlock() // 解锁
if !ok { // 未注册
return nil, fmt.Errorf("game %q not registered", gameID)
}
return f(), nil // 返回新实例
}
// LookupMeta 取已注册玩法的元信息(匹配器用它决定队列人数)。
func LookupMeta(gameID string) (Meta, error) {
g, err := New(gameID) // 先建实例
if err != nil { // 未注册
return Meta{}, err
}
return g.Meta(), nil // 返回元信息
}
// HasAIBrain 判断玩法是否支持 AI 补位(实现了 AIBrain 接口)。
func HasAIBrain(g Game) bool {
_, ok := g.(AIBrain)
return ok
}