支持多人数对局与 AI 补位:玩法声明人数范围、匹配分队列与超时补 AI、房间玩家名册
This commit is contained in:
+27
-21
@@ -68,16 +68,17 @@ type Room struct {
|
||||
lastActive time.Time // 最近活跃时间,用于空闲回收
|
||||
finish *game.Finish // 对局结果
|
||||
tickInterval time.Duration // 事件循环驱动周期:实时玩法按 Meta.TickMs 加速
|
||||
roster []dto.PlayerBrief // 玩家名册(昵称/头像/座位):客户端据此渲染,不含任何 AI 标记
|
||||
}
|
||||
|
||||
// newRoom 创建房间并启动事件循环。
|
||||
func newRoom(id, gameID string, hub *wsgateway.Hub, onClosed func(string, []int64)) (*Room, error) {
|
||||
func newRoom(id, gameID string, seats int, roster []dto.PlayerBrief, hub *wsgateway.Hub, onClosed func(string, []int64)) (*Room, error) {
|
||||
g, err := game.New(gameID) // 按 ID 建玩法实例
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
meta := g.Meta() // 取玩法元信息
|
||||
st, err := g.Init(meta.MinSeats) // 按最少人数初始化状态
|
||||
meta := g.Meta() // 取玩法元信息
|
||||
st, err := g.Init(seats) // 按本局实际人数初始化状态
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -89,19 +90,20 @@ func newRoom(id, gameID string, hub *wsgateway.Hub, onClosed func(string, []int6
|
||||
}
|
||||
|
||||
r := &Room{
|
||||
id: id, // 房间 ID
|
||||
gameID: gameID, // 游戏 ID
|
||||
g: g, // 玩法实例
|
||||
state: st, // 玩法状态
|
||||
seats: make([]int64, meta.MinSeats), // 按人数建座位表
|
||||
members: make(map[int64]*member), // 成员表
|
||||
status: StatusWaiting, // 初始为等待
|
||||
reqCh: make(chan message, 64), // 请求缓冲队列
|
||||
closed: make(chan struct{}), // 关闭信号
|
||||
hub: hub, // 下发通道
|
||||
onClosed: onClosed, // 关闭回调
|
||||
lastActive: time.Now(), // 初始活跃时间
|
||||
tickInterval: tickInterval, // 驱动周期(实时玩法更快)
|
||||
id: id, // 房间 ID
|
||||
gameID: gameID, // 游戏 ID
|
||||
g: g, // 玩法实例
|
||||
state: st, // 玩法状态
|
||||
seats: make([]int64, seats), // 按本局人数建座位表
|
||||
members: make(map[int64]*member), // 成员表
|
||||
status: StatusWaiting, // 初始为等待
|
||||
reqCh: make(chan message, 64), // 请求缓冲队列
|
||||
closed: make(chan struct{}), // 关闭信号
|
||||
hub: hub, // 下发通道
|
||||
onClosed: onClosed, // 关闭回调
|
||||
lastActive: time.Now(), // 初始活跃时间
|
||||
roster: roster, // 玩家名册(含 AI,对外无标识)
|
||||
tickInterval: tickInterval, // 驱动周期(实时玩法更快)
|
||||
}
|
||||
go r.run() // 启动单 goroutine 事件循环
|
||||
return r, nil
|
||||
@@ -247,9 +249,9 @@ func (r *Room) handleJoin(uid int64) error {
|
||||
return errors.New("room is full")
|
||||
}
|
||||
|
||||
r.seats[seat] = uid // 占座
|
||||
r.members[uid] = &member{uid: uid, seat: seat, online: true} // 登记成员
|
||||
r.lastActive = time.Now() // 刷新活跃时间
|
||||
r.seats[seat] = uid // 占座
|
||||
r.members[uid] = &member{uid: uid, seat: seat, online: true, ready: game.IsAIUID(uid)} // 登记成员(AI 入座即自动准备)
|
||||
r.lastActive = time.Now() // 刷新活跃时间
|
||||
|
||||
events, err := r.g.OnJoin(uid, r.state) // 通知玩法
|
||||
if err != nil { // 玩法拒绝(如人数已满)
|
||||
@@ -384,7 +386,7 @@ func (r *Room) finishGame(f *game.Finish) {
|
||||
"winner", f.Winner, "reason", f.Reason)
|
||||
|
||||
r.broadcast(wsgateway.TypeRoomFinished, dto.WSRoomFinishedPayload{ // 广播结算
|
||||
RoomID: r.id, GameID: r.gameID, Winner: f.Winner, Scores: f.Scores, Reason: f.Reason,
|
||||
RoomID: r.id, GameID: r.gameID, Winner: f.Winner, Players: r.roster, Scores: f.Scores, Reason: f.Reason,
|
||||
})
|
||||
|
||||
go func() { // 延时关闭,给客户端留出查看结算的时间
|
||||
@@ -428,6 +430,10 @@ func (r *Room) tick(now time.Time) {
|
||||
|
||||
// 实时玩法驱动:每秒把时间交给玩法(回合制玩法可实现为空)。
|
||||
if r.status == StatusPlaying {
|
||||
r.driveAI(now.Unix()) // 先让 AI 玩家行动(对外不可感知)
|
||||
if r.status != StatusPlaying { // AI 行动后可能已分出胜负
|
||||
return
|
||||
}
|
||||
events, finish, err := r.g.OnTick(now.Unix(), r.state) // 调用玩法 tick
|
||||
if err != nil { // 玩法内部错误
|
||||
slog.Error("room game tick error", "room_id", r.id, "error", err)
|
||||
@@ -483,7 +489,7 @@ func (r *Room) broadcastState() {
|
||||
snap := r.g.Snapshot(uid, r.state) // 玩法按观众裁剪
|
||||
data, err := wsgateway.Encode(wsgateway.TypeRoomState, 0, dto.WSRoomStatePayload{
|
||||
RoomID: r.id, GameID: r.gameID, Status: string(r.status), Seats: r.seats,
|
||||
Ready: r.readyIDs(), State: snap,
|
||||
Ready: r.readyIDs(), State: snap, Players: r.roster,
|
||||
})
|
||||
if err != nil { // 编码失败
|
||||
slog.Error("room encode state failed", "room_id", r.id, "error", err)
|
||||
|
||||
Reference in New Issue
Block a user