feat: 新增 fight 包,提供了回合制战斗的功能实现

This commit is contained in:
kercylan98 2023-07-31 18:08:40 +08:00
parent 39ccad4241
commit df8f6fc53e
5 changed files with 300 additions and 0 deletions

167
game/fight/round.go Normal file
View File

@ -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]
}

15
game/fight/round_camp.go Normal file
View File

@ -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
}

6
game/fight/round_data.go Normal file
View File

@ -0,0 +1,6 @@
package fight
// RoundData 回合制游戏数据
type RoundData interface {
// 占位
}

View File

@ -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
}
}

33
game/fight/round_test.go Normal file
View File

@ -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()
}