Files
nl-game-api/internal/room/room_monopoly.go
2026-08-14 13:17:03 +08:00

208 lines
5.7 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.
// 大富翁在房间内的动作处理、AI/托管行动与状态快照
package room
import (
"fmt"
"strings"
"time"
"nl-game-api-gin/internal/gamecore/monopoly"
)
// replaceSeatNames 把战报中的 P0-P3 座位占位符替换为玩家昵称(需持锁)
func (r *Room) replaceSeatNames(line string) string {
for i := len(r.seats) - 1; i >= 0; i-- {
line = strings.ReplaceAll(line, fmt.Sprintf("P%d", i), r.seats[i].Name)
}
return line
}
// handleMonoRollLocked 真人掷骰(需持锁)
func (r *Room) handleMonoRollLocked(seatIdx int, c *Client) {
if r.status != "playing" || r.monoGame == nil {
c.pushError("对局未开始")
return
}
if err := r.monoGame.Roll(seatIdx); err != nil {
c.pushError(err.Error())
return
}
r.afterMonoActionLocked()
}
// handleMonoBuyLocked 真人购买决策(需持锁)
func (r *Room) handleMonoBuyLocked(seatIdx int, buy bool, c *Client) {
if r.status != "playing" || r.monoGame == nil {
c.pushError("对局未开始")
return
}
if err := r.monoGame.Buy(seatIdx, buy); err != nil {
c.pushError(err.Error())
return
}
r.afterMonoActionLocked()
}
// handleMonoUpgradeLocked 真人建筑升级决策(需持锁)
func (r *Room) handleMonoUpgradeLocked(seatIdx int, up bool, c *Client) {
if r.status != "playing" || r.monoGame == nil {
c.pushError("对局未开始")
return
}
if err := r.monoGame.Upgrade(seatIdx, up); err != nil {
c.pushError(err.Error())
return
}
r.afterMonoActionLocked()
}
// aiMonoActLocked AI/托管行动:按阶段掷骰、购买或升级(需持锁)
// escrow=true 为超时托管:只掷骰、不替真人买地/升级
func (r *Room) aiMonoActLocked(seatIdx int, escrow bool) {
g := r.monoGame
if g == nil || r.status != "playing" || g.Turn != seatIdx {
return
}
switch g.Phase {
case monopoly.PhaseRoll:
g.Roll(seatIdx)
case monopoly.PhaseBuy:
buy := false
if !escrow {
buy = monopoly.AIShouldBuy(g, seatIdx, r.AIDifficulty)
}
g.Buy(seatIdx, buy)
case monopoly.PhaseUpgrade:
up := false
if !escrow {
up = monopoly.AIShouldUpgrade(g, seatIdx, r.AIDifficulty)
}
g.Upgrade(seatIdx, up)
}
r.afterMonoActionLocked()
}
// monoAnimMs 估算一批动画事件的前端播放时长(毫秒),用于调度预留观看时间
func monoAnimMs(events []monopoly.Event, boardLen int) int {
ms := 0
for _, e := range events {
switch e.Kind {
case "roll":
ms += 1100
case "move":
steps := e.To - e.From
if steps <= 0 {
steps += boardLen
}
ms += steps*170 + 200
case "teleport":
ms += 900
case "chance":
ms += 1700
case "cash":
ms += 300
case "buy":
ms += 900
case "upgrade":
ms += 900
case "bankrupt":
ms += 900
case "rest":
ms += 400
}
}
if ms > 5000 {
ms = 5000
}
return ms
}
// afterMonoActionLocked 动作后的统一收尾:收集动画事件、广播战报、结算或调度下一回合(需持锁)
func (r *Room) afterMonoActionLocked() {
g := r.monoGame
for _, line := range g.DrainLog() {
r.broadcastChatLocked(-1, "系统", "", r.replaceSeatNames(line), false)
}
// 真人全部破产/退出:立即按资产结算,避免纯 AI 空跑到轮数上限
if g.Phase != monopoly.PhaseOver {
humanAlive := false
for _, s := range r.seats {
if s.UserID > 0 && !g.Players[s.Index].Out {
humanAlive = true
}
}
if !humanAlive {
g.ForceFinish()
for _, line := range g.DrainLog() {
r.broadcastChatLocked(-1, "系统", "", r.replaceSeatNames(line), false)
}
}
}
// 收集本次动作的动画事件:替换昵称占位符、递增序号、按事件估算播放时长
evs := g.DrainEvents()
for i := range evs {
evs[i].Text = r.replaceSeatNames(evs[i].Text)
}
r.monoEvents = evs
r.monoSeq++
r.animMs = monoAnimMs(evs, len(g.Tiles))
if g.Phase == monopoly.PhaseOver {
names := []string{}
for _, w := range g.Winners {
names = append(names, r.seats[w].Name)
}
winners, desc := g.Winners, fmt.Sprintf("对局结束,%s 获胜!", strings.Join(names, "、"))
// 结算延迟到最后一段动画播完再广播,避免棋子还没走完就弹出结果
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()
}
// monoStateLocked 大富翁状态快照(信息全公开,各座位一致,需持锁)
func (r *Room) monoStateLocked() map[string]any {
g := r.monoGame
players := make([]map[string]any, len(g.Players))
for i, p := range g.Players {
players[i] = map[string]any{
"seat": p.Seat, "money": p.Money, "pos": p.Pos,
"out": p.Out, "skip": p.Skip, "assets": g.AssetsOf(i),
}
}
st := map[string]any{
"phase": g.Phase, "turn": g.Turn, "dice": g.Dice,
"round": g.Round, "max_rounds": monopoly.MaxRounds,
"tiles": g.Tiles, "players": players,
// 动作序号 + 动画事件:前端凭连续序号判断播放动画还是直接同步
"seq": r.monoSeq, "events": r.monoEvents,
}
// 购买/升级阶段附带待决策地块信息kind 区分弹哪种卡片)
if g.Phase == monopoly.PhaseBuy && g.Pending >= 0 {
t := g.Tiles[g.Pending]
st["pending"] = map[string]any{"kind": "buy", "tile": t.Idx, "name": t.Name, "price": t.Price}
}
if g.Phase == monopoly.PhaseUpgrade && g.Pending >= 0 {
t := g.Tiles[g.Pending]
st["pending"] = map[string]any{
"kind": "upgrade", "tile": t.Idx, "name": t.Name,
"price": monopoly.UpgradeCost(t), "level": t.Level,
}
}
return st
}