162 lines
4.2 KiB
Go
162 lines
4.2 KiB
Go
// 飞行棋在房间内的动作处理、AI/托管行动与状态快照
|
|
package room
|
|
|
|
import (
|
|
"fmt"
|
|
"time"
|
|
|
|
"nl-game-api-gin/internal/gamecore/ludo"
|
|
)
|
|
|
|
// handleLudoRollLocked 真人掷骰(需持锁)
|
|
func (r *Room) handleLudoRollLocked(seatIdx int, c *Client) {
|
|
if r.status != "playing" || r.ludoGame == nil {
|
|
c.pushError("对局未开始")
|
|
return
|
|
}
|
|
if err := r.ludoGame.Roll(seatIdx); err != nil {
|
|
c.pushError(err.Error())
|
|
return
|
|
}
|
|
r.afterLudoActionLocked()
|
|
}
|
|
|
|
// handleLudoMoveLocked 真人选棋移动(需持锁)
|
|
func (r *Room) handleLudoMoveLocked(seatIdx, plane int, c *Client) {
|
|
if r.status != "playing" || r.ludoGame == nil {
|
|
c.pushError("对局未开始")
|
|
return
|
|
}
|
|
if err := r.ludoGame.Move(seatIdx, plane); err != nil {
|
|
c.pushError(err.Error())
|
|
return
|
|
}
|
|
r.afterLudoActionLocked()
|
|
}
|
|
|
|
// aiLudoActLocked AI/托管行动:掷骰后按估值选棋(需持锁)
|
|
// escrow=true 为超时托管:使用中等难度规则决策
|
|
func (r *Room) aiLudoActLocked(seatIdx int, escrow bool) {
|
|
g := r.ludoGame
|
|
if g == nil || r.status != "playing" || g.Turn != seatIdx {
|
|
return
|
|
}
|
|
diff := r.AIDifficulty
|
|
if escrow {
|
|
diff = "medium"
|
|
}
|
|
if g.Phase == ludo.PhaseRoll {
|
|
g.Roll(seatIdx)
|
|
} else if g.Phase == ludo.PhaseMove {
|
|
if plane := ludo.AIChoosePlane(g, seatIdx, diff); plane >= 0 {
|
|
g.Move(seatIdx, plane)
|
|
}
|
|
}
|
|
r.afterLudoActionLocked()
|
|
}
|
|
|
|
// ludoAnimMs 估算一批动画事件的前端播放时长(毫秒),用于调度预留观看时间
|
|
func ludoAnimMs(events []ludo.Event) int {
|
|
ms := 0
|
|
for _, e := range events {
|
|
switch e.Kind {
|
|
case "roll":
|
|
ms += 1000
|
|
case "nomove":
|
|
ms += 600
|
|
case "takeoff":
|
|
ms += 650
|
|
case "move":
|
|
// 反弹时实际走的格数等于骰子点数,正常前进为 To-From
|
|
steps := e.To - e.From
|
|
if e.Die > steps {
|
|
steps = e.Die
|
|
}
|
|
ms += steps*150 + 150
|
|
case "jump":
|
|
ms += 650
|
|
case "fly":
|
|
ms += 750
|
|
case "capture":
|
|
ms += 950
|
|
case "arrive":
|
|
ms += 700
|
|
case "bonus":
|
|
ms += 500
|
|
case "sixpenalty":
|
|
ms += 950
|
|
}
|
|
}
|
|
if ms > 4500 {
|
|
ms = 4500
|
|
}
|
|
return ms
|
|
}
|
|
|
|
// afterLudoActionLocked 动作后的统一收尾:收集动画事件、广播战报、结算或调度下一回合(需持锁)
|
|
func (r *Room) afterLudoActionLocked() {
|
|
g := r.ludoGame
|
|
for _, line := range g.DrainLog() {
|
|
r.broadcastChatLocked(-1, "系统", "", r.replaceSeatNames(line), false)
|
|
}
|
|
// 真人全部退赛/离开:立即按进度提前结算,避免纯 AI 空跑到掷骰上限
|
|
if g.Phase != ludo.PhaseOver {
|
|
humanAlive := false
|
|
for _, s := range r.seats {
|
|
if s.UserID > 0 && g.Active[s.Index] {
|
|
humanAlive = true
|
|
}
|
|
}
|
|
if !humanAlive {
|
|
g.ForceFinish()
|
|
for _, line := range g.DrainLog() {
|
|
r.broadcastChatLocked(-1, "系统", "", r.replaceSeatNames(line), false)
|
|
}
|
|
}
|
|
}
|
|
// 收集本次动作的动画事件:递增序号、按事件估算播放时长
|
|
r.ludoEvents = g.DrainEvents()
|
|
r.ludoSeq++
|
|
r.animMs = ludoAnimMs(r.ludoEvents)
|
|
if g.Phase == ludo.PhaseOver {
|
|
winners := []int{}
|
|
desc := "对局结束"
|
|
if g.Winner >= 0 {
|
|
winners = append(winners, g.Winner)
|
|
desc = fmt.Sprintf("%s 率先完成飞行,获得胜利!", r.seats[g.Winner].Name)
|
|
}
|
|
// 结算延迟到最后一段动画播完再广播,避免飞机还没落地就弹出结果
|
|
r.turnSeq++ // 使遗留的回合超时定时器失效
|
|
if r.turnTimer != nil {
|
|
r.turnTimer.Stop()
|
|
}
|
|
delay := time.Duration(r.animMs+600) * time.Millisecond
|
|
time.AfterFunc(delay, func() {
|
|
r.mu.Lock()
|
|
defer r.mu.Unlock()
|
|
// 等待期间房间可能已被解散,不再结算与广播
|
|
if r.closed {
|
|
return
|
|
}
|
|
r.settleLocked(winners, desc)
|
|
r.broadcastStateLocked()
|
|
})
|
|
} else {
|
|
r.scheduleTurnLocked()
|
|
}
|
|
r.broadcastStateLocked()
|
|
}
|
|
|
|
// ludoStateLocked 飞行棋状态快照(信息全公开,需持锁)
|
|
func (r *Room) ludoStateLocked() map[string]any {
|
|
g := r.ludoGame
|
|
return map[string]any{
|
|
"phase": g.Phase, "turn": g.Turn, "die": g.Die,
|
|
"planes": g.Planes, "active": g.Active,
|
|
"movable": g.Movable(g.Turn), "last_move": g.LastMv,
|
|
"rolls": g.Rolls, "max_rolls": ludo.MaxRolls,
|
|
// 动作序号 + 动画事件:前端凭连续序号判断播放动画还是直接同步
|
|
"seq": r.ludoSeq, "events": r.ludoEvents,
|
|
}
|
|
}
|