Merge branch 'develop'
This commit is contained in:
commit
237cbd79cb
|
@ -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]
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
package fight
|
||||
|
||||
// RoundData 回合制游戏数据
|
||||
type RoundData interface {
|
||||
// 占位
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
|
@ -1,48 +0,0 @@
|
|||
package fms
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func NewFSM[State comparable, Data any](data Data) *FSM[State, Data] {
|
||||
return &FSM[State, Data]{
|
||||
states: map[State]*FSMState[State, Data]{},
|
||||
data: data,
|
||||
}
|
||||
}
|
||||
|
||||
type FSM[State comparable, Data any] struct {
|
||||
current State
|
||||
data Data
|
||||
states map[State]*FSMState[State, Data]
|
||||
}
|
||||
|
||||
func (slf *FSM[State, Data]) Update() {
|
||||
state := slf.states[slf.current]
|
||||
state.Update(slf.data)
|
||||
}
|
||||
|
||||
func (slf *FSM[State, Data]) Register(state *FSMState[State, Data]) {
|
||||
slf.states[state.GetState()] = state
|
||||
}
|
||||
|
||||
func (slf *FSM[State, Data]) Unregister(state State) {
|
||||
delete(slf.states, state)
|
||||
}
|
||||
|
||||
func (slf *FSM[State, Data]) HasState(state State) bool {
|
||||
_, has := slf.states[state]
|
||||
return has
|
||||
}
|
||||
|
||||
func (slf *FSM[State, Data]) Change(state State) {
|
||||
current := slf.states[slf.current]
|
||||
current.Exit(slf.data)
|
||||
|
||||
next := slf.states[state]
|
||||
if next == nil {
|
||||
panic(fmt.Errorf("FSM object is attempting to switch to an invalid / undefined state: %v", state))
|
||||
}
|
||||
|
||||
next.Enter(slf.data)
|
||||
}
|
|
@ -1,38 +0,0 @@
|
|||
package fms
|
||||
|
||||
type (
|
||||
FSMStateEnterHandle[Data any] func(data Data)
|
||||
FSMStateUpdateHandle[Data any] func(data Data)
|
||||
FSMStateExitHandle[Data any] func(data Data)
|
||||
)
|
||||
|
||||
func NewFSMState[State comparable, Data any](state State, enter FSMStateEnterHandle[Data], update FSMStateUpdateHandle[Data], exit FSMStateExitHandle[Data]) *FSMState[State, Data] {
|
||||
return &FSMState[State, Data]{
|
||||
enter: enter,
|
||||
update: update,
|
||||
exit: exit,
|
||||
}
|
||||
}
|
||||
|
||||
type FSMState[State comparable, Data any] struct {
|
||||
state State
|
||||
enter FSMStateEnterHandle[Data]
|
||||
update FSMStateUpdateHandle[Data]
|
||||
exit FSMStateExitHandle[Data]
|
||||
}
|
||||
|
||||
func (slf *FSMState[State, Data]) GetState() State {
|
||||
return slf.state
|
||||
}
|
||||
|
||||
func (slf *FSMState[State, Data]) Enter(data Data) {
|
||||
slf.enter(data)
|
||||
}
|
||||
|
||||
func (slf *FSMState[State, Data]) Update(data Data) {
|
||||
slf.update(data)
|
||||
}
|
||||
|
||||
func (slf *FSMState[State, Data]) Exit(data Data) {
|
||||
slf.exit(data)
|
||||
}
|
|
@ -0,0 +1,117 @@
|
|||
package fsm
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/kercylan98/minotaur/utils/hash"
|
||||
)
|
||||
|
||||
// NewFSM 创建一个新的状态机
|
||||
func NewFSM[State comparable, Data any](data Data) *FSM[State, Data] {
|
||||
return &FSM[State, Data]{
|
||||
states: map[State]struct{}{},
|
||||
data: data,
|
||||
}
|
||||
}
|
||||
|
||||
// FSM 状态机
|
||||
type FSM[State comparable, Data any] struct {
|
||||
prev *State
|
||||
current *State
|
||||
data Data
|
||||
states map[State]struct{}
|
||||
|
||||
enterBeforeEventHandles map[State][]func(state *FSM[State, Data])
|
||||
enterAfterEventHandles map[State][]func(state *FSM[State, Data])
|
||||
updateEventHandles map[State][]func(state *FSM[State, Data])
|
||||
exitBeforeEventHandles map[State][]func(state *FSM[State, Data])
|
||||
exitAfterEventHandles map[State][]func(state *FSM[State, Data])
|
||||
}
|
||||
|
||||
// Update 触发当前状态
|
||||
func (slf *FSM[State, Data]) Update() {
|
||||
if slf.current == nil {
|
||||
return
|
||||
}
|
||||
for _, event := range slf.updateEventHandles[*slf.current] {
|
||||
event(slf)
|
||||
}
|
||||
}
|
||||
|
||||
// Register 注册状态
|
||||
func (slf *FSM[State, Data]) Register(state State, options ...Option[State, Data]) {
|
||||
slf.states[state] = struct{}{}
|
||||
for _, option := range options {
|
||||
option(slf, state)
|
||||
}
|
||||
}
|
||||
|
||||
// Unregister 反注册状态
|
||||
func (slf *FSM[State, Data]) Unregister(state State) {
|
||||
if !slf.HasState(state) {
|
||||
return
|
||||
}
|
||||
delete(slf.states, state)
|
||||
delete(slf.enterBeforeEventHandles, state)
|
||||
delete(slf.enterAfterEventHandles, state)
|
||||
delete(slf.updateEventHandles, state)
|
||||
delete(slf.exitBeforeEventHandles, state)
|
||||
delete(slf.exitAfterEventHandles, state)
|
||||
}
|
||||
|
||||
// HasState 检查状态机是否存在特定状态
|
||||
func (slf *FSM[State, Data]) HasState(state State) bool {
|
||||
return hash.Exist(slf.states, state)
|
||||
}
|
||||
|
||||
// Change 改变状态机状态到新的状态
|
||||
func (slf *FSM[State, Data]) Change(state State) {
|
||||
if !slf.IsZero() {
|
||||
for _, event := range slf.exitBeforeEventHandles[*slf.current] {
|
||||
event(slf)
|
||||
}
|
||||
}
|
||||
|
||||
slf.prev = slf.current
|
||||
slf.current = &state
|
||||
|
||||
if !slf.PrevIsZero() {
|
||||
for _, event := range slf.exitAfterEventHandles[*slf.prev] {
|
||||
event(slf)
|
||||
}
|
||||
}
|
||||
|
||||
if !slf.HasState(state) {
|
||||
panic(fmt.Errorf("FSM object is attempting to switch to an invalid / undefined state: %v", state))
|
||||
}
|
||||
|
||||
for _, event := range slf.enterBeforeEventHandles[*slf.current] {
|
||||
event(slf)
|
||||
}
|
||||
|
||||
for _, event := range slf.enterAfterEventHandles[*slf.current] {
|
||||
event(slf)
|
||||
}
|
||||
}
|
||||
|
||||
// Current 获取当前状态
|
||||
func (slf *FSM[State, Data]) Current() (state State) {
|
||||
if slf.current == nil {
|
||||
return
|
||||
}
|
||||
return *slf.current
|
||||
}
|
||||
|
||||
// GetData 获取状态机数据
|
||||
func (slf *FSM[State, Data]) GetData() Data {
|
||||
return slf.data
|
||||
}
|
||||
|
||||
// IsZero 检查状态机是否无状态
|
||||
func (slf *FSM[State, Data]) IsZero() bool {
|
||||
return slf.current == nil
|
||||
}
|
||||
|
||||
// PrevIsZero 检查状态机上一个状态是否无状态
|
||||
func (slf *FSM[State, Data]) PrevIsZero() bool {
|
||||
return slf.prev == nil
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
package fsm
|
||||
|
||||
type Option[State comparable, Data any] func(fsm *FSM[State, Data], state State)
|
||||
|
||||
// WithEnterBeforeEvent 设置状态进入前的回调
|
||||
// - 在首次设置状态时,状态机本身的当前状态为零值状态
|
||||
func WithEnterBeforeEvent[State comparable, Data any](fn func(state *FSM[State, Data])) Option[State, Data] {
|
||||
return func(fsm *FSM[State, Data], state State) {
|
||||
if fsm.enterBeforeEventHandles == nil {
|
||||
fsm.enterBeforeEventHandles = map[State][]func(state *FSM[State, Data]){}
|
||||
}
|
||||
fsm.enterBeforeEventHandles[state] = append(fsm.enterBeforeEventHandles[state], fn)
|
||||
}
|
||||
}
|
||||
|
||||
// WithEnterAfterEvent 设置状态进入后的回调
|
||||
func WithEnterAfterEvent[State comparable, Data any](fn func(state *FSM[State, Data])) Option[State, Data] {
|
||||
return func(fsm *FSM[State, Data], state State) {
|
||||
if fsm.enterAfterEventHandles == nil {
|
||||
fsm.enterAfterEventHandles = map[State][]func(state *FSM[State, Data]){}
|
||||
}
|
||||
fsm.enterAfterEventHandles[state] = append(fsm.enterAfterEventHandles[state], fn)
|
||||
}
|
||||
}
|
||||
|
||||
// WithUpdateEvent 设置状态内刷新的回调
|
||||
func WithUpdateEvent[State comparable, Data any](fn func(state *FSM[State, Data])) Option[State, Data] {
|
||||
return func(fsm *FSM[State, Data], state State) {
|
||||
if fsm.updateEventHandles == nil {
|
||||
fsm.updateEventHandles = map[State][]func(state *FSM[State, Data]){}
|
||||
}
|
||||
fsm.updateEventHandles[state] = append(fsm.updateEventHandles[state], fn)
|
||||
}
|
||||
}
|
||||
|
||||
// WithExitBeforeEvent 设置状态退出前的回调
|
||||
// - 该阶段状态机的状态为退出前的状态,而非新的状态
|
||||
func WithExitBeforeEvent[State comparable, Data any](fn func(state *FSM[State, Data])) Option[State, Data] {
|
||||
return func(fsm *FSM[State, Data], state State) {
|
||||
if fsm.exitBeforeEventHandles == nil {
|
||||
fsm.exitBeforeEventHandles = map[State][]func(state *FSM[State, Data]){}
|
||||
}
|
||||
fsm.exitBeforeEventHandles[state] = append(fsm.exitBeforeEventHandles[state], fn)
|
||||
}
|
||||
}
|
||||
|
||||
// WithExitAfterEvent 设置状态退出后的回调
|
||||
// - 该阶段状态机的状态为新的状态,而非退出前的状态
|
||||
func WithExitAfterEvent[State comparable, Data any](fn func(state *FSM[State, Data])) Option[State, Data] {
|
||||
return func(fsm *FSM[State, Data], state State) {
|
||||
if fsm.exitAfterEventHandles == nil {
|
||||
fsm.exitAfterEventHandles = map[State][]func(state *FSM[State, Data]){}
|
||||
}
|
||||
fsm.exitAfterEventHandles[state] = append(fsm.exitAfterEventHandles[state], fn)
|
||||
}
|
||||
}
|
|
@ -57,6 +57,7 @@ func (slf *CardPile[P, C, T]) GetCard(guid int64) T {
|
|||
|
||||
// Reset 重置牌堆的扑克牌数量及顺序
|
||||
func (slf *CardPile[P, C, T]) Reset() {
|
||||
slf.guid = 0
|
||||
var cards = make([]T, 0, 54*slf.size)
|
||||
for i := 0; i < slf.size; i++ {
|
||||
for _, joker := range slf.jokers {
|
||||
|
|
|
@ -1,52 +0,0 @@
|
|||
package poker
|
||||
|
||||
import (
|
||||
"github.com/kercylan98/minotaur/utils/generic"
|
||||
"github.com/kercylan98/minotaur/utils/hash"
|
||||
)
|
||||
|
||||
// NewMatcher 创建一个新的匹配器
|
||||
// - evaluate: 用于评估一组扑克牌的分值,分值最高的组合将被选中
|
||||
func NewMatcher[P, C generic.Number, T Card[P, C]]() *Matcher[P, C, T] {
|
||||
matcher := &Matcher[P, C, T]{
|
||||
filter: map[string]*MatcherFilter[P, C, T]{},
|
||||
}
|
||||
return matcher
|
||||
}
|
||||
|
||||
// Matcher 匹配器
|
||||
// - 用于匹配扑克牌型,筛选分组等
|
||||
type Matcher[P, C generic.Number, T Card[P, C]] struct {
|
||||
filter map[string]*MatcherFilter[P, C, T]
|
||||
sort []string
|
||||
}
|
||||
|
||||
// RegType 注册一个新的牌型
|
||||
// - name: 牌型名称
|
||||
// - evaluate: 用于评估一组扑克牌的分值,分值最高的组合将被选中
|
||||
// - options: 牌型选项
|
||||
func (slf *Matcher[P, C, T]) RegType(name string, evaluate func([]T) int64, options ...MatcherOption[P, C, T]) *Matcher[P, C, T] {
|
||||
if hash.Exist(slf.filter, name) {
|
||||
panic("exist of the same type")
|
||||
}
|
||||
filter := &MatcherFilter[P, C, T]{
|
||||
evaluate: evaluate,
|
||||
}
|
||||
for _, option := range options {
|
||||
option(filter)
|
||||
}
|
||||
slf.filter[name] = filter
|
||||
slf.sort = append(slf.sort, name)
|
||||
return slf
|
||||
}
|
||||
|
||||
// Group 将一组扑克牌按照匹配器的规则分组,并返回最佳组合及其牌型名称
|
||||
func (slf *Matcher[P, C, T]) Group(cards []T) (name string, result []T) {
|
||||
for _, n := range slf.sort {
|
||||
result = slf.filter[n].group(cards)
|
||||
if len(result) > 0 {
|
||||
return n, result
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
|
@ -1,42 +0,0 @@
|
|||
package poker
|
||||
|
||||
import "github.com/kercylan98/minotaur/utils/generic"
|
||||
|
||||
type MatcherFilter[P, C generic.Number, T Card[P, C]] struct {
|
||||
evaluate func([]T) int64
|
||||
handles []func(cards []T) [][]T
|
||||
asc bool
|
||||
}
|
||||
|
||||
func (slf *MatcherFilter[P, C, T]) AddHandle(handle func(cards []T) [][]T) {
|
||||
slf.handles = append(slf.handles, handle)
|
||||
}
|
||||
|
||||
func (slf *MatcherFilter[P, C, T]) group(cards []T) []T {
|
||||
var bestCombination = cards
|
||||
|
||||
for _, handle := range slf.handles {
|
||||
var bestScore int64
|
||||
filteredCombinations := handle(bestCombination)
|
||||
if len(filteredCombinations) == 0 {
|
||||
return []T{}
|
||||
}
|
||||
for _, combination := range filteredCombinations {
|
||||
score := slf.evaluate(combination)
|
||||
if slf.asc {
|
||||
if score < bestScore || bestCombination == nil {
|
||||
bestCombination = combination
|
||||
bestScore = score
|
||||
}
|
||||
} else {
|
||||
if score > bestScore || bestCombination == nil {
|
||||
bestCombination = combination
|
||||
bestScore = score
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return bestCombination
|
||||
}
|
|
@ -1,271 +0,0 @@
|
|||
package poker
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/kercylan98/minotaur/utils/generic"
|
||||
"github.com/kercylan98/minotaur/utils/slice"
|
||||
"reflect"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// MatcherOption 匹配器选项
|
||||
type MatcherOption[P, C generic.Number, T Card[P, C]] func(matcher *MatcherFilter[P, C, T])
|
||||
|
||||
// WithMatcherScoreAsc 通过升序评估分数创建匹配器
|
||||
// - 用于评估一组扑克牌的分值,分值最低的组合将被选中
|
||||
// - 默认为分数最高的组合将被选中
|
||||
func WithMatcherScoreAsc[P, C generic.Number, T Card[P, C]]() MatcherOption[P, C, T] {
|
||||
return func(matcher *MatcherFilter[P, C, T]) {
|
||||
matcher.asc = true
|
||||
}
|
||||
}
|
||||
|
||||
// WithMatcherLeastLength 通过匹配最小长度的扑克牌创建匹配器
|
||||
// - length: 牌型的长度,表示需要匹配的扑克牌最小数量
|
||||
func WithMatcherLeastLength[P, C generic.Number, T Card[P, C]](length int) MatcherOption[P, C, T] {
|
||||
return func(matcher *MatcherFilter[P, C, T]) {
|
||||
matcher.AddHandle(func(cards []T) [][]T {
|
||||
var combinations [][]T
|
||||
combinations = slice.LimitedCombinations(cards, length, len(cards))
|
||||
return combinations
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// WithMatcherLength 通过匹配长度的扑克牌创建匹配器
|
||||
// - length: 牌型的长度,表示需要匹配的扑克牌数量
|
||||
func WithMatcherLength[P, C generic.Number, T Card[P, C]](length int) MatcherOption[P, C, T] {
|
||||
return func(matcher *MatcherFilter[P, C, T]) {
|
||||
matcher.AddHandle(func(cards []T) [][]T {
|
||||
var combinations [][]T
|
||||
combinations = slice.LimitedCombinations(cards, length, length)
|
||||
return combinations
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// WithMatcherContinuity 通过匹配连续的扑克牌创建匹配器
|
||||
func WithMatcherContinuity[P, C generic.Number, T Card[P, C]]() MatcherOption[P, C, T] {
|
||||
return func(matcher *MatcherFilter[P, C, T]) {
|
||||
matcher.AddHandle(func(cards []T) [][]T {
|
||||
var combinations [][]T
|
||||
n := len(cards)
|
||||
|
||||
if n <= 0 {
|
||||
return combinations
|
||||
}
|
||||
|
||||
// 对扑克牌按点数进行排序
|
||||
sort.Slice(cards, func(i, j int) bool {
|
||||
return cards[i].GetPoint() < cards[j].GetPoint()
|
||||
})
|
||||
|
||||
// 查找连续的牌型组合
|
||||
for i := 0; i < n; i++ {
|
||||
combination := []T{cards[i]}
|
||||
for j := i + 1; j < n; j++ {
|
||||
if cards[j].GetPoint()-combination[len(combination)-1].GetPoint() == 1 {
|
||||
combination = append(combination, cards[j])
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
if len(combination) >= 2 {
|
||||
combinations = append(combinations, combination)
|
||||
}
|
||||
}
|
||||
|
||||
return combinations
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// WithMatcherContinuityPointOrder 通过匹配连续的扑克牌创建匹配器,与 WithMatcherContinuity 不同的是,该选项将按照自定义的点数顺序进行匹配
|
||||
func WithMatcherContinuityPointOrder[P, C generic.Number, T Card[P, C]](order map[P]int) MatcherOption[P, C, T] {
|
||||
return func(matcher *MatcherFilter[P, C, T]) {
|
||||
var getOrder = func(card T) P {
|
||||
if v, ok := order[card.GetPoint()]; ok {
|
||||
return P(v)
|
||||
}
|
||||
return card.GetPoint()
|
||||
}
|
||||
matcher.AddHandle(func(cards []T) [][]T {
|
||||
var combinations [][]T
|
||||
n := len(cards)
|
||||
|
||||
if n <= 0 {
|
||||
return combinations
|
||||
}
|
||||
|
||||
// 对扑克牌按点数进行排序
|
||||
sort.Slice(cards, func(i, j int) bool {
|
||||
return getOrder(cards[i]) < getOrder(cards[j])
|
||||
})
|
||||
|
||||
// 查找连续的牌型组合
|
||||
for i := 0; i < n; i++ {
|
||||
combination := []T{cards[i]}
|
||||
for j := i + 1; j < n; j++ {
|
||||
if getOrder(cards[j])-getOrder(combination[len(combination)-1]) == 1 {
|
||||
combination = append(combination, cards[j])
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
if len(combination) >= 2 {
|
||||
combinations = append(combinations, combination)
|
||||
}
|
||||
}
|
||||
|
||||
return combinations
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// WithMatcherFlush 通过匹配同花的扑克牌创建匹配器
|
||||
func WithMatcherFlush[P, C generic.Number, T Card[P, C]]() MatcherOption[P, C, T] {
|
||||
return func(matcher *MatcherFilter[P, C, T]) {
|
||||
matcher.AddHandle(func(cards []T) [][]T {
|
||||
var combinations [][]T
|
||||
|
||||
groups := GroupByColor[P, C, T](cards...)
|
||||
for _, group := range groups {
|
||||
combinations = append(combinations, slice.Combinations(group)...)
|
||||
}
|
||||
|
||||
return combinations
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// WithMatcherTie 通过匹配相同点数的扑克牌创建匹配器
|
||||
func WithMatcherTie[P, C generic.Number, T Card[P, C]]() MatcherOption[P, C, T] {
|
||||
return func(matcher *MatcherFilter[P, C, T]) {
|
||||
matcher.AddHandle(func(cards []T) [][]T {
|
||||
var combinations [][]T
|
||||
groups := GroupByPoint[P, C, T](cards...)
|
||||
for _, group := range groups {
|
||||
for _, ts := range slice.Combinations(group) {
|
||||
combinations = append(combinations, ts)
|
||||
}
|
||||
}
|
||||
return combinations
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// WithMatcherTieCount 通过匹配相同点数的特定数量的扑克牌创建匹配器
|
||||
// - count: 牌型中相同点数的牌的数量
|
||||
func WithMatcherTieCount[P, C generic.Number, T Card[P, C]](count int) MatcherOption[P, C, T] {
|
||||
return func(matcher *MatcherFilter[P, C, T]) {
|
||||
matcher.AddHandle(func(cards []T) [][]T {
|
||||
var combinations [][]T
|
||||
groups := GroupByPoint[P, C, T](cards...)
|
||||
for _, group := range groups {
|
||||
if len(group) < count {
|
||||
continue
|
||||
}
|
||||
for _, ts := range slice.Combinations(group) {
|
||||
if len(ts) == count {
|
||||
combinations = append(combinations, ts)
|
||||
}
|
||||
}
|
||||
}
|
||||
return combinations
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// WithMatcherTieCountNum 通过匹配相同点数的特定数量的扑克牌创建匹配器
|
||||
// - count: 牌型中相同点数的牌的数量
|
||||
// - num: 牌型中相同点数的牌的数量
|
||||
func WithMatcherTieCountNum[P, C generic.Number, T Card[P, C]](count, num int) MatcherOption[P, C, T] {
|
||||
return func(matcher *MatcherFilter[P, C, T]) {
|
||||
matcher.AddHandle(func(cards []T) [][]T {
|
||||
var combinations [][]T
|
||||
cs := slice.LimitedCombinations(cards, count*num, count*num)
|
||||
var pointCount = make(map[P]int)
|
||||
for _, group := range cs {
|
||||
var ok = false
|
||||
for _, t := range group {
|
||||
pointCount[t.GetPoint()]++
|
||||
if len(pointCount) == 2 {
|
||||
var matchCount = true
|
||||
for _, n := range pointCount {
|
||||
if n != num {
|
||||
matchCount = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if matchCount {
|
||||
ok = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
if ok {
|
||||
combinations = append(combinations, group)
|
||||
}
|
||||
for point := range pointCount {
|
||||
delete(pointCount, point)
|
||||
}
|
||||
}
|
||||
return combinations
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// WithMatcherNCarryM 通过匹配N带相同点数M的扑克牌创建匹配器
|
||||
// - n: 需要匹配的主牌数量
|
||||
// - m: 需要匹配的附加牌数量
|
||||
func WithMatcherNCarryM[P, C generic.Number, T Card[P, C]](n, m int) MatcherOption[P, C, T] {
|
||||
return func(matcher *MatcherFilter[P, C, T]) {
|
||||
matcher.AddHandle(func(cards []T) [][]T {
|
||||
var combinations [][]T
|
||||
groups := GroupByPoint[P, C, T](cards...)
|
||||
for _, group := range groups {
|
||||
if len(group) != n {
|
||||
continue
|
||||
}
|
||||
ms := slice.Combinations(slice.SubWithCheck(cards, group, func(a, b T) bool { return reflect.DeepEqual(a, b) }))
|
||||
for i := 0; i < len(ms); i++ {
|
||||
ts := GroupByPoint[P, C, T](ms[i]...)
|
||||
for _, cs := range ts {
|
||||
if len(cs) == m {
|
||||
combinations = append(combinations, slice.Merge(group, cs))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return combinations
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// WithMatcherNCarryMSingle 通过匹配N带M的扑克牌创建匹配器
|
||||
// - n: 需要匹配的主牌数量
|
||||
// - m: 需要匹配的附加牌数量
|
||||
func WithMatcherNCarryMSingle[P, C generic.Number, T Card[P, C]](n, m int) MatcherOption[P, C, T] {
|
||||
return func(matcher *MatcherFilter[P, C, T]) {
|
||||
matcher.AddHandle(func(cards []T) [][]T {
|
||||
var combinations [][]T
|
||||
groups := GroupByPoint[P, C, T](cards...)
|
||||
for _, group := range groups {
|
||||
if len(group) != n {
|
||||
continue
|
||||
}
|
||||
ms := slice.Combinations(slice.SubWithCheck(cards, group, func(a, b T) bool { return reflect.DeepEqual(a, b) }))
|
||||
for i := 0; i < len(ms); i++ {
|
||||
ts := ms[i]
|
||||
if len(ts) == m {
|
||||
combinations = append(combinations, slice.Merge(group, ts))
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(combinations) > 0 {
|
||||
fmt.Println(len(combinations))
|
||||
}
|
||||
return combinations
|
||||
})
|
||||
}
|
||||
}
|
|
@ -1,117 +0,0 @@
|
|||
package poker_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/kercylan98/minotaur/game/poker"
|
||||
"github.com/kercylan98/minotaur/utils/generic"
|
||||
"github.com/kercylan98/minotaur/utils/slice"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
type Card[P, C generic.Number] struct {
|
||||
guid int64
|
||||
point P
|
||||
color C
|
||||
}
|
||||
|
||||
func (slf *Card[P, C]) GetGuid() int64 {
|
||||
return slf.guid
|
||||
}
|
||||
|
||||
func (slf *Card[P, C]) GetPoint() P {
|
||||
return slf.point
|
||||
}
|
||||
|
||||
func (slf *Card[P, C]) GetColor() C {
|
||||
return slf.color
|
||||
}
|
||||
|
||||
func TestMatcher_Group(t *testing.T) {
|
||||
|
||||
evaluate := func(cards []*Card[int, int]) int64 {
|
||||
score := int64(0)
|
||||
for _, card := range cards {
|
||||
if card.point == 1 {
|
||||
score += 14
|
||||
continue
|
||||
}
|
||||
score += int64(card.GetPoint())
|
||||
}
|
||||
return score
|
||||
}
|
||||
|
||||
matcher := poker.NewMatcher[int, int, *Card[int, int]]()
|
||||
//matcher.RegType("三条", evaluate,
|
||||
// poker.WithMatcherNCarryMSingle[*Card](3, 2))
|
||||
matcher.RegType("皇家同花顺", evaluate,
|
||||
poker.WithMatcherFlush[int, int, *Card[int, int]](),
|
||||
poker.WithMatcherContinuityPointOrder[int, int, *Card[int, int]](map[int]int{1: 14}),
|
||||
poker.WithMatcherLength[int, int, *Card[int, int]](5),
|
||||
).RegType("同花顺", evaluate,
|
||||
poker.WithMatcherFlush[int, int, *Card[int, int]](),
|
||||
poker.WithMatcherContinuityPointOrder[int, int, *Card[int, int]](map[int]int{1: 14}),
|
||||
poker.WithMatcherLeastLength[int, int, *Card[int, int]](3),
|
||||
).RegType("四条", evaluate,
|
||||
poker.WithMatcherTieCount[int, int, *Card[int, int]](4),
|
||||
).RegType("葫芦", evaluate,
|
||||
poker.WithMatcherNCarryM[int, int, *Card[int, int]](3, 2),
|
||||
).RegType("顺子", evaluate,
|
||||
poker.WithMatcherContinuityPointOrder[int, int, *Card[int, int]](map[int]int{1: 14}),
|
||||
poker.WithMatcherLength[int, int, *Card[int, int]](5),
|
||||
).RegType("三条", evaluate,
|
||||
poker.WithMatcherNCarryMSingle[int, int, *Card[int, int]](3, 2),
|
||||
).RegType("两对", evaluate,
|
||||
poker.WithMatcherTieCountNum[int, int, *Card[int, int]](2, 2),
|
||||
).RegType("一对", evaluate,
|
||||
poker.WithMatcherTieCount[int, int, *Card[int, int]](2),
|
||||
).RegType("高牌", evaluate,
|
||||
poker.WithMatcherTieCount[int, int, *Card[int, int]](1),
|
||||
)
|
||||
|
||||
var pub = []*Card[int, int]{
|
||||
{point: 4, color: 3},
|
||||
{point: 5, color: 2},
|
||||
{point: 6, color: 1},
|
||||
{point: 6, color: 2},
|
||||
{point: 13, color: 2},
|
||||
}
|
||||
|
||||
var pri = []*Card[int, int]{
|
||||
{point: 1, color: 1},
|
||||
{point: 1, color: 2},
|
||||
{point: 4, color: 3},
|
||||
{point: 5, color: 4},
|
||||
}
|
||||
|
||||
var start = time.Now()
|
||||
var usePub, usePri = slice.LimitedCombinations(pub, 3, 3), slice.LimitedCombinations(pri, 2, 2)
|
||||
|
||||
var topResult []*Card[int, int]
|
||||
var topScore int64
|
||||
var topName string
|
||||
var source []*Card[int, int]
|
||||
for _, handCards := range usePri {
|
||||
for _, pubCards := range usePub {
|
||||
cards := append(handCards, pubCards...)
|
||||
name, result := matcher.Group(cards)
|
||||
score := evaluate(result)
|
||||
if score > topScore || topResult == nil {
|
||||
topScore = score
|
||||
topResult = result
|
||||
topName = name
|
||||
source = cards
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Println("time:", time.Since(start))
|
||||
fmt.Println("result:", topName)
|
||||
for _, card := range topResult {
|
||||
fmt.Println(fmt.Sprintf("Point: %d Color: %d", card.GetPoint(), card.GetColor()))
|
||||
}
|
||||
fmt.Println("source:", topScore)
|
||||
for _, card := range source {
|
||||
fmt.Println(fmt.Sprintf("Point: %d Color: %d", card.GetPoint(), card.GetColor()))
|
||||
}
|
||||
}
|
|
@ -21,6 +21,8 @@ type (
|
|||
PlayerSeatSetEventHandle[PID comparable, P game.Player[PID], R Room] func(room R, player P, seat int)
|
||||
// PlayerSeatCancelEventHandle 玩家座位取消事件处理函数
|
||||
PlayerSeatCancelEventHandle[PID comparable, P game.Player[PID], R Room] func(room R, player P, seat int)
|
||||
// CreateEventHandle 房间创建事件处理函数
|
||||
CreateEventHandle[PID comparable, P game.Player[PID], R Room] func(room R, helper *Helper[PID, P, R])
|
||||
)
|
||||
|
||||
func newEvent[PID comparable, P game.Player[PID], R Room]() *event[PID, P, R] {
|
||||
|
@ -55,6 +57,7 @@ type event[PID comparable, P game.Player[PID], R Room] struct {
|
|||
playerSeatSetEventRoomHandles map[int64][]PlayerSeatSetEventHandle[PID, P, R]
|
||||
playerSeatCancelEventHandles []PlayerSeatCancelEventHandle[PID, P, R]
|
||||
playerSeatCancelEventRoomHandles map[int64][]PlayerSeatCancelEventHandle[PID, P, R]
|
||||
roomCreateEventHandles []CreateEventHandle[PID, P, R]
|
||||
}
|
||||
|
||||
func (slf *event[PID, P, R]) unReg(guid int64) {
|
||||
|
@ -249,3 +252,15 @@ func (slf *event[PID, P, R]) OnPlayerSeatCancelEvent(room R, player P, seat int)
|
|||
handle(room, player, seat)
|
||||
}
|
||||
}
|
||||
|
||||
// RegRoomCreateEvent 房间创建时将立即执行被注册的事件处理函数
|
||||
func (slf *event[PID, P, R]) RegRoomCreateEvent(handle CreateEventHandle[PID, P, R]) {
|
||||
slf.roomCreateEventHandles = append(slf.roomCreateEventHandles, handle)
|
||||
}
|
||||
|
||||
// OnRoomCreateEvent 房间创建时将立即执行被注册的事件处理函数
|
||||
func (slf *event[PID, P, R]) OnRoomCreateEvent(room R, helper *Helper[PID, P, R]) {
|
||||
for _, handle := range slf.roomCreateEventHandles {
|
||||
handle(room, helper)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -51,6 +51,7 @@ func (slf *Manager[PID, P, R]) CreateRoom(room R, options ...Option[PID, P, R])
|
|||
option(roomInfo)
|
||||
}
|
||||
slf.rooms.Set(room.GetGuid(), roomInfo)
|
||||
slf.OnRoomCreateEvent(room, slf.GetHelper(room))
|
||||
}
|
||||
|
||||
// ReleaseRoom 释放房间
|
||||
|
|
|
@ -3,6 +3,7 @@ package room
|
|||
import (
|
||||
"github.com/kercylan98/minotaur/game"
|
||||
"github.com/kercylan98/minotaur/utils/concurrent"
|
||||
"github.com/kercylan98/minotaur/utils/generic"
|
||||
"github.com/kercylan98/minotaur/utils/hash"
|
||||
"sync"
|
||||
)
|
||||
|
@ -91,7 +92,7 @@ func (slf *Seat[PlayerID, P, R]) SetSeat(id PlayerID, seat int) int {
|
|||
}()
|
||||
oldSeat := slf.GetSeat(id)
|
||||
player := slf.GetPlayerWithSeat(seat)
|
||||
if player != nil {
|
||||
if generic.IsNil(player) {
|
||||
if oldSeat == NoSeat {
|
||||
maxSeat := len(slf.seatSP) - 1
|
||||
if seat > maxSeat {
|
||||
|
|
|
@ -143,6 +143,9 @@ func PushTickerMessage(srv *Server, caller func(), mark ...any) {
|
|||
}
|
||||
|
||||
// PushAsyncMessage 向特定服务器中推送 MessageTypeAsync 消息
|
||||
// - 异步消息将在服务器的异步消息队列中进行处理,处理完成 caller 的阻塞操作后,将会通过系统消息执行 callback 函数
|
||||
// - callback 函数将在异步消息处理完成后进行调用,无论过程是否产生 err,都将被执行,允许为 nil
|
||||
// - 需要注意的是,为了避免并发问题,caller 函数请仅处理阻塞操作,其他操作应该在 callback 函数中进行
|
||||
func PushAsyncMessage(srv *Server, caller func() error, callback func(err error), mark ...any) {
|
||||
msg := srv.messagePool.Get()
|
||||
msg.t = MessageTypeAsync
|
||||
|
|
|
@ -569,7 +569,9 @@ func (slf *Server) dispatchMessage(msg *Message) {
|
|||
}()
|
||||
err := handle()
|
||||
if cb && callback != nil {
|
||||
callback(err)
|
||||
PushSystemMessage(slf, func() {
|
||||
callback(err)
|
||||
}, "AsyncCallback")
|
||||
} else {
|
||||
log.Error("Server", log.String("MessageType", messageNames[msg.t]), log.Any("error", err), log.String("stack", string(debug.Stack())))
|
||||
}
|
||||
|
|
|
@ -0,0 +1,119 @@
|
|||
package combination
|
||||
|
||||
import (
|
||||
"github.com/kercylan98/minotaur/utils/random"
|
||||
)
|
||||
|
||||
// NewCombination 创建一个新的组合器
|
||||
func NewCombination[T Item](options ...Option[T]) *Combination[T] {
|
||||
combination := &Combination[T]{
|
||||
matchers: make(map[string]*Matcher[T]),
|
||||
}
|
||||
for _, option := range options {
|
||||
option(combination)
|
||||
}
|
||||
if combination.evaluate == nil {
|
||||
combination.evaluate = func(items []T) float64 {
|
||||
return random.Float64()
|
||||
}
|
||||
}
|
||||
return combination
|
||||
}
|
||||
|
||||
// Combination 用于从多个匹配器内提取组合的数据结构
|
||||
type Combination[T Item] struct {
|
||||
evaluate func([]T) float64
|
||||
matchers map[string]*Matcher[T]
|
||||
priority []string
|
||||
}
|
||||
|
||||
// NewMatcher 添加一个新的匹配器
|
||||
func (slf *Combination[T]) NewMatcher(name string, options ...MatcherOption[T]) *Combination[T] {
|
||||
if _, exist := slf.matchers[name]; exist {
|
||||
panic("exist of the same matcher")
|
||||
}
|
||||
var matcher = &Matcher[T]{
|
||||
evaluate: slf.evaluate,
|
||||
}
|
||||
for _, option := range options {
|
||||
option(matcher)
|
||||
}
|
||||
slf.matchers[name] = matcher
|
||||
slf.priority = append(slf.priority, name)
|
||||
return slf
|
||||
}
|
||||
|
||||
// AddMatcher 添加一个匹配器
|
||||
func (slf *Combination[T]) AddMatcher(name string, matcher *Matcher[T]) *Combination[T] {
|
||||
if _, exist := slf.matchers[name]; exist {
|
||||
panic("exist of the same matcher")
|
||||
}
|
||||
slf.matchers[name] = matcher
|
||||
slf.priority = append(slf.priority, name)
|
||||
return slf
|
||||
}
|
||||
|
||||
// RemoveMatcher 移除一个匹配器
|
||||
func (slf *Combination[T]) RemoveMatcher(name string) *Combination[T] {
|
||||
delete(slf.matchers, name)
|
||||
for i := 0; i < len(slf.priority); i++ {
|
||||
if slf.priority[i] == name {
|
||||
slf.priority = append(slf.priority[:i], slf.priority[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
return slf
|
||||
}
|
||||
|
||||
// Combinations 从一组数据中提取所有符合匹配器规则的组合
|
||||
func (slf *Combination[T]) Combinations(items []T) (result [][]T) {
|
||||
for _, n := range slf.priority {
|
||||
result = append(result, slf.matchers[n].Combinations(items)...)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// CombinationsToName 从一组数据中提取所有符合匹配器规则的组合,并返回匹配器名称
|
||||
func (slf *Combination[T]) CombinationsToName(items []T) (result map[string][][]T) {
|
||||
result = make(map[string][][]T)
|
||||
for _, n := range slf.priority {
|
||||
result[n] = append(result[n], slf.matchers[n].Combinations(items)...)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// Best 从一组数据中提取符合匹配器规则的最佳组合
|
||||
func (slf *Combination[T]) Best(items []T) (name string, result []T) {
|
||||
var best float64 = -1
|
||||
for _, n := range slf.priority {
|
||||
matcher := slf.matchers[n]
|
||||
matcherBest := matcher.Best(items)
|
||||
if len(matcherBest) == 0 {
|
||||
continue
|
||||
}
|
||||
if score := matcher.evaluate(matcherBest); score > best || best == -1 {
|
||||
best = score
|
||||
name = n
|
||||
result = matcherBest
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// Worst 从一组数据中提取符合匹配器规则的最差组合
|
||||
func (slf *Combination[T]) Worst(items []T) (name string, result []T) {
|
||||
var worst float64 = -1
|
||||
for _, n := range slf.priority {
|
||||
matcher := slf.matchers[n]
|
||||
matcherWorst := matcher.Worst(items)
|
||||
if len(matcherWorst) == 0 {
|
||||
continue
|
||||
}
|
||||
if score := matcher.evaluate(matcherWorst); score < worst || worst == -1 {
|
||||
worst = score
|
||||
name = n
|
||||
result = matcherWorst
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package combination
|
||||
|
||||
// Option 组合器选项
|
||||
type Option[T Item] func(*Combination[T])
|
||||
|
||||
// WithEvaluation 设置组合评估函数
|
||||
// - 用于对组合进行评估,返回一个分值的评价函数
|
||||
// - 通过该选项将设置所有匹配器的默认评估函数为该函数
|
||||
// - 通过匹配器选项 WithMatcherEvaluation 可以覆盖该默认评估函数
|
||||
// - 默认的评估函数将返回一个随机数
|
||||
func WithEvaluation[T Item](evaluate func(items []T) float64) Option[T] {
|
||||
return func(c *Combination[T]) {
|
||||
c.evaluate = evaluate
|
||||
}
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
package combination_test
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/kercylan98/minotaur/utils/combination"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type Poker struct {
|
||||
Point int
|
||||
Color int
|
||||
}
|
||||
|
||||
func TestCombination_Best(t *testing.T) {
|
||||
combine := combination.NewCombination(combination.WithEvaluation(func(items []*Poker) float64 {
|
||||
var total float64
|
||||
for _, item := range items {
|
||||
total += float64(item.Point)
|
||||
}
|
||||
return total
|
||||
}))
|
||||
|
||||
combine.NewMatcher("炸弹",
|
||||
combination.WithMatcherSame[*Poker, int](4, func(item *Poker) int {
|
||||
return item.Point
|
||||
}),
|
||||
).NewMatcher("三带一",
|
||||
combination.WithMatcherNCarryM[*Poker, int](3, 1, func(item *Poker) int {
|
||||
return item.Point
|
||||
}),
|
||||
)
|
||||
|
||||
var cards = []*Poker{
|
||||
{Point: 2, Color: 1},
|
||||
{Point: 2, Color: 2},
|
||||
{Point: 2, Color: 3},
|
||||
{Point: 3, Color: 4},
|
||||
{Point: 4, Color: 1},
|
||||
{Point: 4, Color: 2},
|
||||
{Point: 5, Color: 3},
|
||||
{Point: 6, Color: 4},
|
||||
{Point: 7, Color: 1},
|
||||
{Point: 8, Color: 2},
|
||||
{Point: 9, Color: 3},
|
||||
{Point: 10, Color: 4},
|
||||
{Point: 11, Color: 1},
|
||||
{Point: 12, Color: 2},
|
||||
{Point: 13, Color: 3},
|
||||
{Point: 10, Color: 3},
|
||||
{Point: 11, Color: 2},
|
||||
{Point: 12, Color: 1},
|
||||
{Point: 13, Color: 4},
|
||||
{Point: 10, Color: 2},
|
||||
}
|
||||
|
||||
name, result := combine.Worst(cards)
|
||||
fmt.Println("best:", name)
|
||||
for _, item := range result {
|
||||
fmt.Println(item)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
package combination
|
||||
|
||||
type Item interface {
|
||||
// 占位
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
package combination
|
||||
|
||||
import (
|
||||
"github.com/kercylan98/minotaur/utils/random"
|
||||
)
|
||||
|
||||
// NewMatcher 创建一个新的匹配器
|
||||
func NewMatcher[T Item](options ...MatcherOption[T]) *Matcher[T] {
|
||||
matcher := &Matcher[T]{
|
||||
filter: make([]func(items []T) [][]T, 0),
|
||||
}
|
||||
for _, option := range options {
|
||||
option(matcher)
|
||||
}
|
||||
if matcher.evaluate == nil {
|
||||
matcher.evaluate = func(items []T) float64 {
|
||||
return random.Float64()
|
||||
}
|
||||
}
|
||||
return matcher
|
||||
}
|
||||
|
||||
// Matcher 用于从一组数据内提取组合的数据结构
|
||||
type Matcher[T Item] struct {
|
||||
evaluate func(items []T) float64 // 用于对组合进行评估,返回一个分值的评价函数
|
||||
filter []func(items []T) [][]T // 用于对组合进行筛选的函数
|
||||
}
|
||||
|
||||
// AddFilter 添加一个筛选器
|
||||
// - 筛选器用于对组合进行筛选,返回一个二维数组,每个数组内的元素都是一个组合
|
||||
func (slf *Matcher[T]) AddFilter(filter func(items []T) [][]T) {
|
||||
slf.filter = append(slf.filter, filter)
|
||||
}
|
||||
|
||||
// Combinations 从一组数据中提取所有符合筛选器规则的组合
|
||||
func (slf *Matcher[T]) Combinations(items []T) [][]T {
|
||||
var combinations = [][]T{items}
|
||||
for _, handle := range slf.filter {
|
||||
combinations = append(combinations, handle(items)...)
|
||||
}
|
||||
return combinations
|
||||
}
|
||||
|
||||
// Best 从一组数据中提取符筛选器规则的最佳组合
|
||||
func (slf *Matcher[T]) Best(items []T) []T {
|
||||
var bestCombination = items
|
||||
|
||||
for _, handle := range slf.filter {
|
||||
var bestScore float64 = -1
|
||||
filteredCombinations := handle(bestCombination)
|
||||
if len(filteredCombinations) == 0 {
|
||||
return nil
|
||||
}
|
||||
for _, combination := range filteredCombinations {
|
||||
score := slf.evaluate(combination)
|
||||
if score > bestScore || bestScore == -1 {
|
||||
bestCombination = combination
|
||||
bestScore = score
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return bestCombination
|
||||
}
|
||||
|
||||
// Worst 从一组数据中提取符筛选器规则的最差组合
|
||||
func (slf *Matcher[T]) Worst(items []T) []T {
|
||||
var worstCombination = items
|
||||
|
||||
for _, handle := range slf.filter {
|
||||
var worstScore float64 = -1
|
||||
filteredCombinations := handle(worstCombination)
|
||||
if len(filteredCombinations) == 0 {
|
||||
return nil
|
||||
}
|
||||
for _, combination := range filteredCombinations {
|
||||
score := slf.evaluate(combination)
|
||||
if score < worstScore || worstScore == -1 {
|
||||
worstCombination = combination
|
||||
worstScore = score
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return worstCombination
|
||||
}
|
|
@ -0,0 +1,202 @@
|
|||
package combination
|
||||
|
||||
import (
|
||||
"github.com/kercylan98/minotaur/utils/generic"
|
||||
"github.com/kercylan98/minotaur/utils/slice"
|
||||
"reflect"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// MatcherOption 匹配器选项
|
||||
type MatcherOption[T Item] func(matcher *Matcher[T])
|
||||
|
||||
// WithMatcherEvaluation 设置匹配器评估函数
|
||||
// - 用于对组合进行评估,返回一个分值的评价函数
|
||||
// - 通过该选项将覆盖匹配器的默认(WithEvaluation)评估函数
|
||||
func WithMatcherEvaluation[T Item](evaluate func(items []T) float64) MatcherOption[T] {
|
||||
return func(m *Matcher[T]) {
|
||||
m.evaluate = evaluate
|
||||
}
|
||||
}
|
||||
|
||||
// WithMatcherLeastLength 通过匹配最小长度的组合创建匹配器
|
||||
// - length: 组合的长度,表示需要匹配的组合最小数量
|
||||
func WithMatcherLeastLength[T Item](length int) MatcherOption[T] {
|
||||
return func(m *Matcher[T]) {
|
||||
m.AddFilter(func(items []T) [][]T {
|
||||
return slice.LimitedCombinations(items, length, len(items))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// WithMatcherLength 通过匹配长度的组合创建匹配器
|
||||
// - length: 组合的长度,表示需要匹配的组合数量
|
||||
func WithMatcherLength[T Item](length int) MatcherOption[T] {
|
||||
return func(m *Matcher[T]) {
|
||||
m.AddFilter(func(items []T) [][]T {
|
||||
return slice.LimitedCombinations(items, length, length)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// WithMatcherMostLength 通过匹配最大长度的组合创建匹配器
|
||||
// - length: 组合的长度,表示需要匹配的组合最大数量
|
||||
func WithMatcherMostLength[T Item](length int) MatcherOption[T] {
|
||||
return func(m *Matcher[T]) {
|
||||
m.AddFilter(func(items []T) [][]T {
|
||||
return slice.LimitedCombinations(items, 1, length)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// WithMatcherIntervalLength 通过匹配长度区间的组合创建匹配器
|
||||
// - min: 组合的最小长度,表示需要匹配的组合最小数量
|
||||
// - max: 组合的最大长度,表示需要匹配的组合最大数量
|
||||
func WithMatcherIntervalLength[T Item](min, max int) MatcherOption[T] {
|
||||
return func(m *Matcher[T]) {
|
||||
m.AddFilter(func(items []T) [][]T {
|
||||
return slice.LimitedCombinations(items, min, max)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// WithMatcherContinuity 通过匹配连续的组合创建匹配器
|
||||
// - index: 用于获取组合中元素的索引值,用于判断是否连续
|
||||
func WithMatcherContinuity[T Item, Index generic.Number](getIndex func(item T) Index) MatcherOption[T] {
|
||||
return func(m *Matcher[T]) {
|
||||
m.AddFilter(func(items []T) [][]T {
|
||||
var combinations [][]T
|
||||
n := len(items)
|
||||
|
||||
if n <= 0 {
|
||||
return combinations
|
||||
}
|
||||
|
||||
sort.Slice(items, func(i, j int) bool {
|
||||
return getIndex(items[i]) < getIndex(items[j])
|
||||
})
|
||||
|
||||
for i := 0; i < n; i++ {
|
||||
combination := []T{items[i]}
|
||||
for j := i + 1; j < n; j++ {
|
||||
if getIndex(items[j])-getIndex(combination[len(combination)-1]) == Index(1) {
|
||||
combination = append(combination, items[j])
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
if len(combination) >= 2 {
|
||||
combinations = append(combinations, combination)
|
||||
}
|
||||
}
|
||||
|
||||
return combinations
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// WithMatcherSame 通过匹配相同的组合创建匹配器
|
||||
// - count: 组合中相同元素的数量,当 count <= 0 时,表示相同元素的数量不限
|
||||
// - getType: 用于获取组合中元素的类型,用于判断是否相同
|
||||
func WithMatcherSame[T Item, E generic.Ordered](count int, getType func(item T) E) MatcherOption[T] {
|
||||
return func(m *Matcher[T]) {
|
||||
m.AddFilter(func(items []T) [][]T {
|
||||
var combinations [][]T
|
||||
groups := slice.LimitedCombinations(items, count, count)
|
||||
for _, items := range groups {
|
||||
if count > 0 && len(items) != count {
|
||||
continue
|
||||
}
|
||||
var e E
|
||||
var init = true
|
||||
var same = true
|
||||
for _, item := range items {
|
||||
if init {
|
||||
init = false
|
||||
e = getType(item)
|
||||
} else if getType(item) != e {
|
||||
same = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if same {
|
||||
combinations = append(combinations, items)
|
||||
}
|
||||
}
|
||||
return combinations
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// WithMatcherNCarryM 通过匹配 N 携带 M 的组合创建匹配器
|
||||
// - n: 组合中元素的数量,表示需要匹配的组合数量,n 的类型需要全部相同
|
||||
// - m: 组合中元素的数量,表示需要匹配的组合数量,m 的类型需要全部相同
|
||||
// - getType: 用于获取组合中元素的类型,用于判断是否相同
|
||||
func WithMatcherNCarryM[T Item, E generic.Ordered](n, m int, getType func(item T) E) MatcherOption[T] {
|
||||
return func(matcher *Matcher[T]) {
|
||||
matcher.AddFilter(func(items []T) [][]T {
|
||||
var combinations [][]T
|
||||
|
||||
groups := make(map[E][]T)
|
||||
for _, item := range items {
|
||||
itemType := getType(item)
|
||||
groups[itemType] = append(groups[itemType], item)
|
||||
}
|
||||
|
||||
for _, group := range groups {
|
||||
if len(group) != n {
|
||||
continue
|
||||
}
|
||||
other := slice.SubWithCheck(items, group, func(a, b T) bool { return reflect.DeepEqual(a, b) })
|
||||
ms := slice.LimitedCombinations(other, m, m)
|
||||
for _, otherGroup := range ms {
|
||||
var t E
|
||||
var init = true
|
||||
var same = true
|
||||
for _, item := range otherGroup {
|
||||
if init {
|
||||
init = false
|
||||
t = getType(item)
|
||||
} else if getType(item) != t {
|
||||
same = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if same {
|
||||
combinations = append(combinations, slice.Merge(group, otherGroup))
|
||||
}
|
||||
}
|
||||
}
|
||||
return combinations
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// WithMatcherNCarryIndependentM 通过匹配 N 携带独立 M 的组合创建匹配器
|
||||
// - n: 组合中元素的数量,表示需要匹配的组合数量,n 的类型需要全部相同
|
||||
// - m: 组合中元素的数量,表示需要匹配的组合数量,m 的类型无需全部相同
|
||||
// - getType: 用于获取组合中元素的类型,用于判断是否相同
|
||||
func WithMatcherNCarryIndependentM[T Item, E generic.Ordered](n, m int, getType func(item T) E) MatcherOption[T] {
|
||||
return func(matcher *Matcher[T]) {
|
||||
matcher.AddFilter(func(items []T) [][]T {
|
||||
var combinations [][]T
|
||||
|
||||
groups := make(map[E][]T)
|
||||
for _, item := range items {
|
||||
itemType := getType(item)
|
||||
groups[itemType] = append(groups[itemType], item)
|
||||
}
|
||||
|
||||
for _, group := range groups {
|
||||
if len(group) != n {
|
||||
continue
|
||||
}
|
||||
ms := slice.LimitedCombinations(slice.SubWithCheck(items, group, func(a, b T) bool { return reflect.DeepEqual(a, b) }), m, m)
|
||||
for _, otherGroup := range ms {
|
||||
combinations = append(combinations, slice.Merge(group, otherGroup))
|
||||
}
|
||||
}
|
||||
return combinations
|
||||
})
|
||||
}
|
||||
}
|
|
@ -194,3 +194,13 @@ func JoinNumbers[V generic.Number](num1 V, n ...V) V {
|
|||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// IsOdd 返回 n 是否为奇数
|
||||
func IsOdd[V generic.Integer](n V) bool {
|
||||
return 0 != (int64(n) & 1)
|
||||
}
|
||||
|
||||
// IsEven 返回 n 是否为偶数
|
||||
func IsEven[V generic.Integer](n V) bool {
|
||||
return 0 == (int64(n) & 1)
|
||||
}
|
||||
|
|
|
@ -220,27 +220,13 @@ func GetIndexAny[V any](slice []V, values V) int {
|
|||
|
||||
// Combinations 获取给定数组的所有组合,包括重复元素的组合
|
||||
func Combinations[T any](a []T) [][]T {
|
||||
n := len(a)
|
||||
|
||||
// 去除重复元素,保留唯一元素
|
||||
uniqueSet := make(map[uintptr]bool)
|
||||
uniqueSlice := make([]T, 0, n)
|
||||
for _, val := range a {
|
||||
ptr := reflect.ValueOf(val).Pointer()
|
||||
if !uniqueSet[ptr] {
|
||||
uniqueSet[ptr] = true
|
||||
uniqueSlice = append(uniqueSlice, val)
|
||||
}
|
||||
}
|
||||
|
||||
n = len(uniqueSlice) // 去重后的数组长度
|
||||
totalCombinations := 1 << n // 2的n次方
|
||||
var result [][]T
|
||||
for i := 0; i < totalCombinations; i++ {
|
||||
n := len(a)
|
||||
for i := 0; i < (1 << n); i++ {
|
||||
var currentCombination []T
|
||||
for j := 0; j < n; j++ {
|
||||
if (i & (1 << j)) != 0 {
|
||||
currentCombination = append(currentCombination, uniqueSlice[j])
|
||||
currentCombination = append(currentCombination, a[j])
|
||||
}
|
||||
}
|
||||
result = append(result, currentCombination)
|
||||
|
|
|
@ -3,6 +3,7 @@ package storage_test
|
|||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"github.com/kercylan98/minotaur/utils/slice"
|
||||
"github.com/kercylan98/minotaur/utils/storage"
|
||||
"testing"
|
||||
)
|
||||
|
@ -43,3 +44,13 @@ func TestData_Struct(t *testing.T) {
|
|||
|
||||
fmt.Println(string(bytes))
|
||||
}
|
||||
|
||||
func TestData_Handle(t *testing.T) {
|
||||
var is []int
|
||||
for i := 1; i <= 23; i++ {
|
||||
is = append(is, i)
|
||||
}
|
||||
|
||||
res := slice.LimitedCombinations(is, 5, 5)
|
||||
fmt.Println("Count:", len(res))
|
||||
}
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
package super
|
||||
|
||||
// Verify 对特定表达式进行校验,当表达式不成立时,将执行 handle
|
||||
func Verify[V any](handle func(V)) *VerifyHandle[V] {
|
||||
return &VerifyHandle[V]{handle: handle}
|
||||
}
|
||||
|
||||
// VerifyHandle 校验句柄
|
||||
type VerifyHandle[V any] struct {
|
||||
handle func(V)
|
||||
v V
|
||||
hit bool
|
||||
}
|
||||
|
||||
// PreCase 先决校验用例,当 expression 成立时,将跳过 caseHandle 的执行,直接执行 handle 并返回 false
|
||||
// - 常用于对前置参数的空指针校验,例如当 a 为 nil 时,不执行 a.B(),而是直接返回 false
|
||||
func (slf *VerifyHandle[V]) PreCase(expression func() bool, value V, caseHandle func(verify *VerifyHandle[V]) bool) bool {
|
||||
if expression() {
|
||||
slf.handle(value)
|
||||
return false
|
||||
}
|
||||
return caseHandle(slf)
|
||||
}
|
||||
|
||||
// Case 校验用例,当 expression 成立时,将忽略后续 Case,并将在 Do 时执行 handle,返回 false
|
||||
func (slf *VerifyHandle[V]) Case(expression bool, value V) *VerifyHandle[V] {
|
||||
if !slf.hit && expression {
|
||||
slf.v = value
|
||||
slf.hit = true
|
||||
}
|
||||
return slf
|
||||
}
|
||||
|
||||
// Do 执行校验,当校验失败时,将执行 handle,并返回 false
|
||||
func (slf *VerifyHandle[V]) Do() bool {
|
||||
if slf.hit {
|
||||
slf.handle(slf.v)
|
||||
}
|
||||
return !slf.hit
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
package super_test
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/kercylan98/minotaur/utils/super"
|
||||
)
|
||||
|
||||
func ExampleVerify() {
|
||||
var getId = func() int { return 1 }
|
||||
var n *super.VerifyHandle[int]
|
||||
|
||||
super.Verify(func(err error) {
|
||||
fmt.Println(err)
|
||||
}).Case(getId() == 1, errors.New("id can't be 1")).
|
||||
Do()
|
||||
|
||||
super.Verify(func(err error) {
|
||||
fmt.Println(err)
|
||||
}).PreCase(func() bool {
|
||||
return n == nil
|
||||
}, errors.New("n can't be nil"), func(verify *super.VerifyHandle[error]) bool {
|
||||
return verify.Do()
|
||||
})
|
||||
|
||||
// Output:
|
||||
// id can't be 1
|
||||
// n can't be nil
|
||||
}
|
Loading…
Reference in New Issue