feat: fight 包新增 TurnBased 回合制数据结构,用于替代 fight.Round。解决并发安全问题,并且支持按照速度进行回合切换
This commit is contained in:
parent
42ab52bc66
commit
378f855992
|
@ -16,6 +16,8 @@ type RoundGameOverVerifyHandle[Data RoundData] func(round *Round[Data]) bool
|
|||
// - camps 阵营
|
||||
// - roundGameOverVerifyHandle 游戏结束验证函数
|
||||
// - options 选项
|
||||
//
|
||||
// Deprecated: 从 Minotaur 0.2.7 开始,由于设计原因已弃用,请尝试考虑使用 fight.TurnBased 进行代替
|
||||
func NewRound[Data RoundData](data Data, camps []*RoundCamp, roundGameOverVerifyHandle RoundGameOverVerifyHandle[Data], options ...RoundOption[Data]) *Round[Data] {
|
||||
mark := random.HostName()
|
||||
round := &Round[Data]{
|
||||
|
|
|
@ -0,0 +1,184 @@
|
|||
package fight
|
||||
|
||||
import (
|
||||
"github.com/kercylan98/minotaur/utils/generic"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
signalFinish = 1 + iota // 操作结束信号
|
||||
signalStop // 停止回合制信号
|
||||
)
|
||||
|
||||
// NewTurnBased 创建一个新的回合制
|
||||
// - calcNextTurnDuration 将返回下一次行动时间间隔,适用于按照速度计算下一次行动时间间隔的情况。当返回 0 时,将使用默认的行动超时时间
|
||||
func NewTurnBased[CampID, EntityID comparable, Camp generic.IdR[CampID], Entity generic.IdR[EntityID]](calcNextTurnDuration func(Camp, Entity) time.Duration) *TurnBased[CampID, EntityID, Camp, Entity] {
|
||||
tb := &TurnBased[CampID, EntityID, Camp, Entity]{
|
||||
turnBasedEvents: &turnBasedEvents[CampID, EntityID, Camp, Entity]{},
|
||||
campRel: make(map[EntityID]Camp),
|
||||
calcNextTurnDuration: calcNextTurnDuration,
|
||||
actionTimeoutHandler: func(camp Camp, entity Entity) time.Duration {
|
||||
return 0
|
||||
},
|
||||
}
|
||||
tb.controller = &TurnBasedController[CampID, EntityID, Camp, Entity]{tb: tb}
|
||||
return tb
|
||||
}
|
||||
|
||||
// TurnBased 回合制
|
||||
type TurnBased[CampID, EntityID comparable, Camp generic.IdR[CampID], Entity generic.IdR[EntityID]] struct {
|
||||
*turnBasedEvents[CampID, EntityID, Camp, Entity]
|
||||
controller *TurnBasedController[CampID, EntityID, Camp, Entity] // 控制器
|
||||
ticker *time.Ticker // 计时器
|
||||
actionWaitTicker *time.Ticker // 行动等待计时器
|
||||
actioning bool // 是否正在行动
|
||||
actionMutex sync.RWMutex // 行动锁
|
||||
entities []Entity // 所有阵容实体顺序
|
||||
campRel map[EntityID]Camp // 实体与阵营的关系
|
||||
calcNextTurnDuration func(Camp, Entity) time.Duration // 下一次行动时间间隔
|
||||
actionTimeoutHandler func(Camp, Entity) time.Duration // 行动超时时间
|
||||
|
||||
signal chan byte // 信号
|
||||
round int // 当前回合数
|
||||
currCamp Camp // 当前操作阵营
|
||||
currEntity Entity // 当前操作实体
|
||||
currActionTimeout time.Duration // 当前行动超时时间
|
||||
currStart time.Time // 当前回合开始时间
|
||||
|
||||
closeMutex sync.RWMutex // 关闭锁
|
||||
closed bool
|
||||
}
|
||||
|
||||
// Close 关闭回合制
|
||||
func (slf *TurnBased[CampID, EntityID, Camp, Entity]) Close() {
|
||||
slf.closeMutex.Lock()
|
||||
defer slf.closeMutex.Unlock()
|
||||
slf.closed = true
|
||||
}
|
||||
|
||||
// AddCamp 添加阵营
|
||||
func (slf *TurnBased[CampID, EntityID, Camp, Entity]) AddCamp(camp Camp, entity Entity, entities ...Entity) {
|
||||
for _, e := range append([]Entity{entity}, entities...) {
|
||||
slf.entities = append(slf.entities, e)
|
||||
slf.campRel[e.GetId()] = camp
|
||||
}
|
||||
}
|
||||
|
||||
// SetActionTimeout 设置行动超时时间处理函数
|
||||
// - 默认情况下行动超时时间函数将始终返回 0
|
||||
func (slf *TurnBased[CampID, EntityID, Camp, Entity]) SetActionTimeout(actionTimeoutHandler func(Camp, Entity) time.Duration) {
|
||||
if actionTimeoutHandler == nil {
|
||||
panic("actionTimeoutHandler can not be nil")
|
||||
}
|
||||
slf.actionTimeoutHandler = actionTimeoutHandler
|
||||
}
|
||||
|
||||
// Run 运行
|
||||
func (slf *TurnBased[CampID, EntityID, Camp, Entity]) Run() {
|
||||
slf.round = 1
|
||||
slf.signal = make(chan byte, 1)
|
||||
var actionDuration = make(map[EntityID]time.Duration)
|
||||
var actionSubmit = func() {
|
||||
slf.actionMutex.Lock()
|
||||
slf.actioning = false
|
||||
if slf.actionWaitTicker != nil {
|
||||
slf.actionWaitTicker.Stop()
|
||||
}
|
||||
slf.actionMutex.Unlock()
|
||||
}
|
||||
for {
|
||||
slf.closeMutex.RLock()
|
||||
if slf.closed {
|
||||
slf.closeMutex.RUnlock()
|
||||
break
|
||||
}
|
||||
slf.closeMutex.RUnlock()
|
||||
|
||||
var minDuration *time.Duration
|
||||
var delay time.Duration
|
||||
for _, entity := range slf.entities {
|
||||
camp := slf.campRel[entity.GetId()]
|
||||
next := slf.calcNextTurnDuration(camp, entity)
|
||||
accumulate := next + actionDuration[entity.GetId()]
|
||||
if minDuration == nil || accumulate < *minDuration {
|
||||
minDuration = &accumulate
|
||||
slf.currEntity = entity
|
||||
slf.currCamp = camp
|
||||
delay = next
|
||||
}
|
||||
}
|
||||
if *minDuration == 0 {
|
||||
*minDuration = 1 // 防止永远是第一对象行动
|
||||
}
|
||||
actionDuration[slf.currEntity.GetId()] = *minDuration
|
||||
if len(actionDuration) == len(slf.entities) {
|
||||
for key := range actionDuration {
|
||||
delete(actionDuration, key)
|
||||
}
|
||||
}
|
||||
|
||||
if delay > 0 {
|
||||
if slf.ticker == nil {
|
||||
slf.ticker = time.NewTicker(delay)
|
||||
} else {
|
||||
slf.ticker.Reset(delay)
|
||||
}
|
||||
<-slf.ticker.C
|
||||
}
|
||||
|
||||
// 进入回合操作阶段
|
||||
slf.currActionTimeout = slf.actionTimeoutHandler(slf.currCamp, slf.currEntity)
|
||||
slf.currStart = time.Now()
|
||||
slf.actionMutex.Lock()
|
||||
slf.actioning = true
|
||||
slf.actionMutex.Unlock()
|
||||
slf.OnTurnBasedEntitySwitchEvent(slf.controller)
|
||||
if slf.actionWaitTicker == nil {
|
||||
slf.actionWaitTicker = time.NewTicker(slf.currActionTimeout)
|
||||
} else {
|
||||
slf.actionWaitTicker.Reset(slf.currActionTimeout)
|
||||
}
|
||||
breakListen:
|
||||
for {
|
||||
select {
|
||||
case <-slf.actionWaitTicker.C:
|
||||
actionSubmit()
|
||||
slf.OnTurnBasedEntityActionTimeoutEvent(slf.controller)
|
||||
break breakListen
|
||||
case sign := <-slf.signal:
|
||||
switch sign {
|
||||
case signalFinish:
|
||||
actionSubmit()
|
||||
slf.OnTurnBasedEntityActionFinishEvent(slf.controller)
|
||||
break breakListen
|
||||
}
|
||||
}
|
||||
}
|
||||
slf.OnTurnBasedEntityActionSubmitEvent(slf.controller)
|
||||
|
||||
slf.closeMutex.Lock()
|
||||
if slf.closed {
|
||||
if slf.ticker != nil {
|
||||
slf.ticker.Stop()
|
||||
slf.ticker = nil
|
||||
}
|
||||
if slf.actionWaitTicker != nil {
|
||||
slf.actionWaitTicker.Stop()
|
||||
slf.actionWaitTicker = nil
|
||||
}
|
||||
if slf.signal != nil {
|
||||
close(slf.signal)
|
||||
slf.signal = nil
|
||||
}
|
||||
slf.closeMutex.Unlock()
|
||||
break
|
||||
}
|
||||
slf.closeMutex.Unlock()
|
||||
|
||||
if len(actionDuration) == 0 {
|
||||
slf.round++
|
||||
}
|
||||
|
||||
}
|
||||
}
|
|
@ -0,0 +1,79 @@
|
|||
package fight
|
||||
|
||||
import (
|
||||
"github.com/kercylan98/minotaur/utils/generic"
|
||||
"time"
|
||||
)
|
||||
|
||||
type TurnBasedControllerInfo[CampID, EntityID comparable, Camp generic.IdR[CampID], Entity generic.IdR[EntityID]] interface {
|
||||
// GetRound 获取当前回合数
|
||||
GetRound() int
|
||||
// GetCamp 获取当前操作阵营
|
||||
GetCamp() Camp
|
||||
// GetEntity 获取当前操作实体
|
||||
GetEntity() Entity
|
||||
// GetActionTimeoutDuration 获取当前行动超时时长
|
||||
GetActionTimeoutDuration() time.Duration
|
||||
// GetActionStartTime 获取当前行动开始时间
|
||||
GetActionStartTime() time.Time
|
||||
// GetActionEndTime 获取当前行动结束时间
|
||||
GetActionEndTime() time.Time
|
||||
// Stop 在当前回合执行完毕后停止回合进程
|
||||
Stop()
|
||||
}
|
||||
|
||||
type TurnBasedControllerAction[CampID, EntityID comparable, Camp generic.IdR[CampID], Entity generic.IdR[EntityID]] interface {
|
||||
TurnBasedControllerInfo[CampID, EntityID, Camp, Entity]
|
||||
// Finish 结束当前操作,将立即切换到下一个操作实体
|
||||
Finish()
|
||||
}
|
||||
|
||||
// TurnBasedController 回合制控制器
|
||||
type TurnBasedController[CampID, EntityID comparable, Camp generic.IdR[CampID], Entity generic.IdR[EntityID]] struct {
|
||||
tb *TurnBased[CampID, EntityID, Camp, Entity]
|
||||
}
|
||||
|
||||
// GetRound 获取当前回合数
|
||||
func (slf *TurnBasedController[CampID, EntityID, Camp, Entity]) GetRound() int {
|
||||
return slf.tb.round
|
||||
}
|
||||
|
||||
// GetCamp 获取当前操作阵营
|
||||
func (slf *TurnBasedController[CampID, EntityID, Camp, Entity]) GetCamp() Camp {
|
||||
return slf.tb.currCamp
|
||||
}
|
||||
|
||||
// GetEntity 获取当前操作实体
|
||||
func (slf *TurnBasedController[CampID, EntityID, Camp, Entity]) GetEntity() Entity {
|
||||
return slf.tb.currEntity
|
||||
}
|
||||
|
||||
// GetActionTimeoutDuration 获取当前行动超时时长
|
||||
func (slf *TurnBasedController[CampID, EntityID, Camp, Entity]) GetActionTimeoutDuration() time.Duration {
|
||||
return slf.tb.currActionTimeout
|
||||
}
|
||||
|
||||
// GetActionStartTime 获取当前行动开始时间
|
||||
func (slf *TurnBasedController[CampID, EntityID, Camp, Entity]) GetActionStartTime() time.Time {
|
||||
return slf.tb.currStart
|
||||
}
|
||||
|
||||
// GetActionEndTime 获取当前行动结束时间
|
||||
func (slf *TurnBasedController[CampID, EntityID, Camp, Entity]) GetActionEndTime() time.Time {
|
||||
return slf.tb.currStart.Add(slf.tb.currActionTimeout)
|
||||
}
|
||||
|
||||
// Finish 结束当前操作,将立即切换到下一个操作实体
|
||||
func (slf *TurnBasedController[CampID, EntityID, Camp, Entity]) Finish() {
|
||||
slf.tb.actionMutex.Lock()
|
||||
defer slf.tb.actionMutex.Unlock()
|
||||
if slf.tb.actioning {
|
||||
slf.tb.actioning = false
|
||||
slf.tb.signal <- signalFinish
|
||||
}
|
||||
}
|
||||
|
||||
// Stop 在当前回合执行完毕后停止回合进程
|
||||
func (slf *TurnBasedController[CampID, EntityID, Camp, Entity]) Stop() {
|
||||
slf.tb.Close()
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
package fight
|
||||
|
||||
import "github.com/kercylan98/minotaur/utils/generic"
|
||||
|
||||
type (
|
||||
TurnBasedEntitySwitchEventHandler[CampID, EntityID comparable, Camp generic.IdR[CampID], Entity generic.IdR[EntityID]] func(controller TurnBasedControllerAction[CampID, EntityID, Camp, Entity])
|
||||
TurnBasedEntityActionTimeoutEventHandler[CampID, EntityID comparable, Camp generic.IdR[CampID], Entity generic.IdR[EntityID]] func(controller TurnBasedControllerInfo[CampID, EntityID, Camp, Entity])
|
||||
TurnBasedEntityActionFinishEventHandler[CampID, EntityID comparable, Camp generic.IdR[CampID], Entity generic.IdR[EntityID]] func(controller TurnBasedControllerInfo[CampID, EntityID, Camp, Entity])
|
||||
TurnBasedEntityActionSubmitEventHandler[CampID, EntityID comparable, Camp generic.IdR[CampID], Entity generic.IdR[EntityID]] func(controller TurnBasedControllerInfo[CampID, EntityID, Camp, Entity])
|
||||
)
|
||||
|
||||
type turnBasedEvents[CampID, EntityID comparable, Camp generic.IdR[CampID], Entity generic.IdR[EntityID]] struct {
|
||||
entitySwitchEventHandlers []TurnBasedEntitySwitchEventHandler[CampID, EntityID, Camp, Entity]
|
||||
actionTimeoutEventHandlers []TurnBasedEntityActionTimeoutEventHandler[CampID, EntityID, Camp, Entity]
|
||||
actionFinishEventHandlers []TurnBasedEntityActionFinishEventHandler[CampID, EntityID, Camp, Entity]
|
||||
actionSubmitEventHandlers []TurnBasedEntityActionSubmitEventHandler[CampID, EntityID, Camp, Entity]
|
||||
}
|
||||
|
||||
// RegTurnBasedEntitySwitchEvent 注册回合制实体切换事件处理函数,该处理函数将在切换到实体切换为操作时机时触发
|
||||
// - 刚函数通常仅用于告知当前操作实体已经完成切换,适合做一些前置校验,但不应该在该函数中执行长时间阻塞操作
|
||||
// - 操作计时将在该函数执行完毕后开始
|
||||
//
|
||||
// 场景:
|
||||
// - 回合开始,如果该实体被标记为已死亡,则跳过该实体
|
||||
func (slf *turnBasedEvents[CampID, EntityID, Camp, Entity]) RegTurnBasedEntitySwitchEvent(handler TurnBasedEntitySwitchEventHandler[CampID, EntityID, Camp, Entity]) {
|
||||
slf.entitySwitchEventHandlers = append(slf.entitySwitchEventHandlers, handler)
|
||||
}
|
||||
|
||||
// OnTurnBasedEntitySwitchEvent 触发回合制实体切换事件
|
||||
func (slf *turnBasedEvents[CampID, EntityID, Camp, Entity]) OnTurnBasedEntitySwitchEvent(controller TurnBasedControllerAction[CampID, EntityID, Camp, Entity]) {
|
||||
for _, handler := range slf.entitySwitchEventHandlers {
|
||||
handler(controller)
|
||||
}
|
||||
}
|
||||
|
||||
// RegTurnBasedEntityActionTimeoutEvent 注册回合制实体行动超时事件处理函数,该处理函数将在实体行动超时时触发
|
||||
func (slf *turnBasedEvents[CampID, EntityID, Camp, Entity]) RegTurnBasedEntityActionTimeoutEvent(handler TurnBasedEntityActionTimeoutEventHandler[CampID, EntityID, Camp, Entity]) {
|
||||
slf.actionTimeoutEventHandlers = append(slf.actionTimeoutEventHandlers, handler)
|
||||
}
|
||||
|
||||
// OnTurnBasedEntityActionTimeoutEvent 触发回合制实体行动超时事件
|
||||
func (slf *turnBasedEvents[CampID, EntityID, Camp, Entity]) OnTurnBasedEntityActionTimeoutEvent(controller TurnBasedControllerInfo[CampID, EntityID, Camp, Entity]) {
|
||||
for _, handler := range slf.actionTimeoutEventHandlers {
|
||||
handler(controller)
|
||||
}
|
||||
}
|
||||
|
||||
// RegTurnBasedEntityActionFinishEvent 注册回合制实体行动结束事件处理函数,该处理函数将在实体行动结束时触发
|
||||
func (slf *turnBasedEvents[CampID, EntityID, Camp, Entity]) RegTurnBasedEntityActionFinishEvent(handler TurnBasedEntityActionFinishEventHandler[CampID, EntityID, Camp, Entity]) {
|
||||
slf.actionFinishEventHandlers = append(slf.actionFinishEventHandlers, handler)
|
||||
}
|
||||
|
||||
// OnTurnBasedEntityActionFinishEvent 触发回合制实体行动结束事件
|
||||
func (slf *turnBasedEvents[CampID, EntityID, Camp, Entity]) OnTurnBasedEntityActionFinishEvent(controller TurnBasedControllerInfo[CampID, EntityID, Camp, Entity]) {
|
||||
for _, handler := range slf.actionFinishEventHandlers {
|
||||
handler(controller)
|
||||
}
|
||||
}
|
||||
|
||||
// RegTurnBasedEntityActionSubmitEvent 注册回合制实体行动提交事件处理函数,该处理函数将在实体行动提交时触发
|
||||
// - 该事件将在实体以任意方式结束行动时触发,包括正常结束、超时结束等
|
||||
// - 该事件会在原本的行动结束事件之后触发
|
||||
func (slf *turnBasedEvents[CampID, EntityID, Camp, Entity]) RegTurnBasedEntityActionSubmitEvent(handler TurnBasedEntityActionSubmitEventHandler[CampID, EntityID, Camp, Entity]) {
|
||||
slf.actionSubmitEventHandlers = append(slf.actionSubmitEventHandlers, handler)
|
||||
}
|
||||
|
||||
// OnTurnBasedEntityActionSubmitEvent 触发回合制实体行动提交事件
|
||||
func (slf *turnBasedEvents[CampID, EntityID, Camp, Entity]) OnTurnBasedEntityActionSubmitEvent(controller TurnBasedControllerInfo[CampID, EntityID, Camp, Entity]) {
|
||||
for _, handler := range slf.actionSubmitEventHandlers {
|
||||
handler(controller)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
package fight_test
|
||||
|
||||
import (
|
||||
"github.com/kercylan98/minotaur/game/fight"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Camp struct {
|
||||
id string
|
||||
}
|
||||
|
||||
func (slf *Camp) GetId() string {
|
||||
return slf.id
|
||||
}
|
||||
|
||||
type Entity struct {
|
||||
id string
|
||||
speed float64
|
||||
}
|
||||
|
||||
func (slf *Entity) GetId() string {
|
||||
return slf.id
|
||||
}
|
||||
|
||||
func TestTurnBased_Run(t *testing.T) {
|
||||
tbi := fight.NewTurnBased[string, string, *Camp, *Entity](func(camp *Camp, entity *Entity) time.Duration {
|
||||
return time.Duration(float64(time.Second) / entity.speed)
|
||||
})
|
||||
|
||||
tbi.SetActionTimeout(func(camp *Camp, entity *Entity) time.Duration {
|
||||
return time.Second * 5
|
||||
})
|
||||
|
||||
tbi.RegTurnBasedEntityActionTimeoutEvent(func(controller fight.TurnBasedControllerInfo[string, string, *Camp, *Entity]) {
|
||||
t.Log("时间", time.Now().Unix(), "回合", controller.GetRound(), "阵营", controller.GetCamp().GetId(), "实体", controller.GetEntity().GetId(), "超时")
|
||||
})
|
||||
|
||||
tbi.RegTurnBasedEntitySwitchEvent(func(controller fight.TurnBasedControllerAction[string, string, *Camp, *Entity]) {
|
||||
switch controller.GetEntity().GetId() {
|
||||
case "1":
|
||||
go func() {
|
||||
time.Sleep(time.Second * 2)
|
||||
controller.Finish()
|
||||
}()
|
||||
case "2":
|
||||
controller.Stop()
|
||||
}
|
||||
t.Log("时间", time.Now().Unix(), "回合", controller.GetRound(), "阵营", controller.GetCamp().GetId(), "实体", controller.GetEntity().GetId(), "开始行动")
|
||||
})
|
||||
|
||||
tbi.AddCamp(&Camp{id: "1"}, &Entity{id: "1", speed: 1}, &Entity{id: "2", speed: 1})
|
||||
tbi.AddCamp(&Camp{id: "2"}, &Entity{id: "3", speed: 1}, &Entity{id: "4", speed: 1})
|
||||
|
||||
tbi.Run()
|
||||
}
|
Loading…
Reference in New Issue