From df8f6fc53e5bfdc481351c962f4f10a3585d3796 Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Mon, 31 Jul 2023 18:08:40 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=20fight=20=E5=8C=85?= =?UTF-8?q?=EF=BC=8C=E6=8F=90=E4=BE=9B=E4=BA=86=E5=9B=9E=E5=90=88=E5=88=B6?= =?UTF-8?q?=E6=88=98=E6=96=97=E7=9A=84=E5=8A=9F=E8=83=BD=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- game/fight/round.go | 167 ++++++++++++++++++++++++++++++++++++ game/fight/round_camp.go | 15 ++++ game/fight/round_data.go | 6 ++ game/fight/round_options.go | 79 +++++++++++++++++ game/fight/round_test.go | 33 +++++++ 5 files changed, 300 insertions(+) create mode 100644 game/fight/round.go create mode 100644 game/fight/round_camp.go create mode 100644 game/fight/round_data.go create mode 100644 game/fight/round_options.go create mode 100644 game/fight/round_test.go diff --git a/game/fight/round.go b/game/fight/round.go new file mode 100644 index 0000000..6c195ee --- /dev/null +++ b/game/fight/round.go @@ -0,0 +1,167 @@ +package fight + +import ( + "fmt" + "github.com/kercylan98/minotaur/utils/random" + "github.com/kercylan98/minotaur/utils/timer" + "time" +) + +// RoundGameOverVerifyHandle 回合制游戏结束验证函数 +type RoundGameOverVerifyHandle[Data RoundData] func(round *Round[Data]) bool + +// NewRound 创建一个新的回合制游戏 +// - data 游戏数据 +// - camps 阵营 +// - roundGameOverVerifyHandle 游戏结束验证函数 +// - options 选项 +func NewRound[Data RoundData](data Data, camps []*RoundCamp, roundGameOverVerifyHandle RoundGameOverVerifyHandle[Data], options ...RoundOption[Data]) *Round[Data] { + round := &Round[Data]{ + data: data, + camps: make(map[int][]int), + actionTimeoutTickerName: fmt.Sprintf("round_action_timeout_%s", random.HostName()), + roundGameOverVerifyHandle: roundGameOverVerifyHandle, + } + for _, camp := range camps { + round.camps[camp.campId] = camp.entities + round.campOrder = append(round.campOrder, camp.campId) + } + for _, option := range options { + option(round) + } + if round.ticker == nil { + round.ticker = timer.GetTicker(5) + } + return round +} + +// Round 回合制游戏结构 +type Round[Data RoundData] struct { + data Data // 游戏数据 + ticker *timer.Ticker // 计时器 + camps map[int][]int // 阵营 + campOrder []int // 阵营顺序 + actionTimeout time.Duration // 行动超时时间 + actionTimeoutTickerName string // 行动超时计时器名称 + round int // 回合数 + roundCount int // 回合计数 + currentCamp int // 当前行动阵营 + currentEntity int // 当前行动的阵营实体索引 + currentEndTime int64 // 当前行动结束时间 + shareAction bool // 是否共享行动(同阵营共享行动时间) + roundGameOverVerifyHandle RoundGameOverVerifyHandle[Data] // 游戏结束验证函数 + campCounterclockwise bool // 是否阵营逆时针 + entityCounterclockwise bool // 是否对象逆时针 + + swapCampEventHandles []RoundSwapCampEvent[Data] // 阵营交换事件 + swapEntityEventHandles []RoundSwapEntityEvent[Data] // 实体交换事件 + gameOverEventHandles []RoundGameOverEvent[Data] // 游戏结束事件 + changeEventHandles []RoundChangeEvent[Data] // 游戏回合变更事件 +} + +// GetData 获取游戏数据 +func (slf *Round[Data]) GetData() Data { + return slf.data +} + +// Run 运行游戏 +// - 将通过传入的 Camp 进行初始化,Camp 为一个二维数组,每个数组内的元素都是一个行动标识 +func (slf *Round[Data]) Run() { + slf.currentEntity = -1 + slf.round = 1 + slf.loop() +} + +func (slf *Round[Data]) loop() { + slf.ticker.StopTimer(slf.actionTimeoutTickerName) + if slf.roundGameOverVerifyHandle(slf) { + for _, handle := range slf.gameOverEventHandles { + handle(slf) + } + return + } else { + slf.ActionRefresh() + } + if slf.currentEntity == -1 || slf.currentEntity >= len(slf.camps[slf.currentCamp])-1 { + if !slf.campCounterclockwise { + slf.currentCamp = slf.campOrder[0] + slf.campOrder = append(slf.campOrder[1:], slf.currentCamp) + } else { + slf.currentCamp = slf.campOrder[len(slf.campOrder)-1] + slf.campOrder = append([]int{slf.currentCamp}, slf.campOrder[:len(slf.campOrder)-1]...) + } + + slf.currentEntity = -1 + slf.roundCount++ + if slf.roundCount > len(slf.camps) { + slf.round++ + slf.roundCount = 1 + for _, handle := range slf.changeEventHandles { + handle(slf) + } + } + for _, handle := range slf.swapCampEventHandles { + handle(slf, slf.currentCamp) + } + } + slf.currentEntity++ + for _, handle := range slf.swapEntityEventHandles { + if slf.entityCounterclockwise { + handle(slf, slf.currentCamp, slf.camps[slf.currentCamp][len(slf.camps[slf.currentCamp])-slf.currentEntity-1]) + } else { + handle(slf, slf.currentCamp, slf.camps[slf.currentCamp][slf.currentEntity]) + } + } +} + +// SkipCamp 跳过当前阵营剩余对象的行动 +func (slf *Round[Data]) SkipCamp() { + slf.currentEntity = -1 +} + +// ActionRefresh 刷新行动超时时间 +func (slf *Round[Data]) ActionRefresh() { + slf.currentEndTime = time.Now().Unix() + slf.ticker.After(slf.actionTimeoutTickerName, slf.actionTimeout, slf.loop) +} + +// ActionFinish 结束行动 +func (slf *Round[Data]) ActionFinish() { + slf.ticker.StopTimer(slf.actionTimeoutTickerName) + if slf.shareAction { + slf.currentEntity = -1 + } else { + slf.currentEntity++ + } + slf.loop() +} + +// GetRound 获取当前回合数 +func (slf *Round[Data]) GetRound() int { + return slf.round +} + +// AllowAction 是否允许行动 +func (slf *Round[Data]) AllowAction(camp, entity int) bool { + return (slf.currentCamp == camp && slf.currentEntity == entity) || slf.shareAction && camp == slf.currentCamp +} + +// CampAllowAction 阵容是否允许行动 +func (slf *Round[Data]) CampAllowAction(camp int) bool { + return slf.currentCamp == camp +} + +// GetCurrentCamp 获取当前行动的阵营 +func (slf *Round[Data]) GetCurrentCamp() int { + return slf.currentCamp +} + +// GetCurrentRoundProgressRate 获取当前回合进度 +func (slf *Round[Data]) GetCurrentRoundProgressRate() float64 { + return float64(slf.roundCount / len(slf.camps)) +} + +// GetCurrent 获取当前行动的阵营和对象 +func (slf *Round[Data]) GetCurrent() (camp, entity int) { + return slf.currentCamp, slf.camps[slf.currentCamp][slf.currentEntity] +} diff --git a/game/fight/round_camp.go b/game/fight/round_camp.go new file mode 100644 index 0000000..348c4fd --- /dev/null +++ b/game/fight/round_camp.go @@ -0,0 +1,15 @@ +package fight + +// NewRoundCamp 创建一个新的回合制游戏阵营 +func NewRoundCamp(campId, entity int, entities ...int) *RoundCamp { + return &RoundCamp{ + campId: campId, + entities: append([]int{entity}, entities...), + } +} + +// RoundCamp 回合制游戏阵营 +type RoundCamp struct { + campId int + entities []int +} diff --git a/game/fight/round_data.go b/game/fight/round_data.go new file mode 100644 index 0000000..01dba29 --- /dev/null +++ b/game/fight/round_data.go @@ -0,0 +1,6 @@ +package fight + +// RoundData 回合制游戏数据 +type RoundData interface { + // 占位 +} diff --git a/game/fight/round_options.go b/game/fight/round_options.go new file mode 100644 index 0000000..96a6570 --- /dev/null +++ b/game/fight/round_options.go @@ -0,0 +1,79 @@ +package fight + +import ( + "github.com/kercylan98/minotaur/utils/timer" + "time" +) + +// RoundOption 回合制游戏选项 +type RoundOption[Data RoundData] func(round *Round[Data]) + +type ( + RoundSwapCampEvent[Data RoundData] func(round *Round[Data], campId int) + RoundSwapEntityEvent[Data RoundData] func(round *Round[Data], campId, entity int) + RoundGameOverEvent[Data RoundData] func(round *Round[Data]) + RoundChangeEvent[Data RoundData] func(round *Round[Data]) +) + +// WithRoundTicker 设置游戏的计时器 +func WithRoundTicker[Data RoundData](ticker *timer.Ticker) RoundOption[Data] { + return func(round *Round[Data]) { + round.ticker = ticker + } +} + +// WithRoundActionTimeout 设置游戏的行动超时时间 +func WithRoundActionTimeout[Data RoundData](timeout time.Duration) RoundOption[Data] { + return func(round *Round[Data]) { + round.actionTimeout = timeout + } +} + +// WithRoundShareAction 设置游戏的行动是否共享 +func WithRoundShareAction[Data RoundData](share bool) RoundOption[Data] { + return func(round *Round[Data]) { + round.shareAction = share + } +} + +// WithRoundSwapCampEvent 设置游戏的阵营交换事件 +func WithRoundSwapCampEvent[Data RoundData](swapCampEventHandle RoundSwapCampEvent[Data]) RoundOption[Data] { + return func(round *Round[Data]) { + round.swapCampEventHandles = append(round.swapCampEventHandles, swapCampEventHandle) + } +} + +// WithRoundSwapEntityEvent 设置游戏的实体交换事件 +func WithRoundSwapEntityEvent[Data RoundData](swapEntityEventHandle RoundSwapEntityEvent[Data]) RoundOption[Data] { + return func(round *Round[Data]) { + round.swapEntityEventHandles = append(round.swapEntityEventHandles, swapEntityEventHandle) + } +} + +// WithRoundGameOverEvent 设置游戏的结束事件 +func WithRoundGameOverEvent[Data RoundData](gameOverEventHandle RoundGameOverEvent[Data]) RoundOption[Data] { + return func(round *Round[Data]) { + round.gameOverEventHandles = append(round.gameOverEventHandles, gameOverEventHandle) + } +} + +// WithRoundChangeEvent 设置游戏的回合变更事件 +func WithRoundChangeEvent[Data RoundData](changeEventHandle RoundChangeEvent[Data]) RoundOption[Data] { + return func(round *Round[Data]) { + round.changeEventHandles = append(round.changeEventHandles, changeEventHandle) + } +} + +// WithRoundCampCounterclockwise 设置游戏阵营逆序执行 +func WithRoundCampCounterclockwise[Data RoundData]() RoundOption[Data] { + return func(round *Round[Data]) { + round.campCounterclockwise = true + } +} + +// WithRoundEntityCounterclockwise 设置游戏实体逆序执行 +func WithRoundEntityCounterclockwise[Data RoundData]() RoundOption[Data] { + return func(round *Round[Data]) { + round.entityCounterclockwise = true + } +} diff --git a/game/fight/round_test.go b/game/fight/round_test.go new file mode 100644 index 0000000..bd42c6f --- /dev/null +++ b/game/fight/round_test.go @@ -0,0 +1,33 @@ +package fight + +import ( + "sync" + "testing" + "time" +) + +func TestName(t *testing.T) { + var wait sync.WaitGroup + var camps []*RoundCamp + camps = append(camps, NewRoundCamp(1, 1, 2, 3)) + camps = append(camps, NewRoundCamp(2, 4, 5, 6)) + camps = append(camps, NewRoundCamp(3, 7, 8, 9)) + r := NewRound("", camps, func(round *Round[string]) bool { + return round.GetRound() == 2 + }, + WithRoundActionTimeout[string](time.Second), + WithRoundSwapEntityEvent[string](func(round *Round[string], campId, entity int) { + t.Log(time.Now(), "swap entity", round.GetRound(), campId, entity) + }), + WithRoundGameOverEvent[string](func(round *Round[string]) { + t.Log(time.Now(), "game over", round.GetRound()) + wait.Done() + }), + WithRoundCampCounterclockwise[string](), + WithRoundEntityCounterclockwise[string](), + ) + + wait.Add(1) + r.Run() + wait.Wait() +}