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 重置牌堆的扑克牌数量及顺序
|
// Reset 重置牌堆的扑克牌数量及顺序
|
||||||
func (slf *CardPile[P, C, T]) Reset() {
|
func (slf *CardPile[P, C, T]) Reset() {
|
||||||
|
slf.guid = 0
|
||||||
var cards = make([]T, 0, 54*slf.size)
|
var cards = make([]T, 0, 54*slf.size)
|
||||||
for i := 0; i < slf.size; i++ {
|
for i := 0; i < slf.size; i++ {
|
||||||
for _, joker := range slf.jokers {
|
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)
|
PlayerSeatSetEventHandle[PID comparable, P game.Player[PID], R Room] func(room R, player P, seat int)
|
||||||
// PlayerSeatCancelEventHandle 玩家座位取消事件处理函数
|
// PlayerSeatCancelEventHandle 玩家座位取消事件处理函数
|
||||||
PlayerSeatCancelEventHandle[PID comparable, P game.Player[PID], R Room] func(room R, player P, seat int)
|
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] {
|
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]
|
playerSeatSetEventRoomHandles map[int64][]PlayerSeatSetEventHandle[PID, P, R]
|
||||||
playerSeatCancelEventHandles []PlayerSeatCancelEventHandle[PID, P, R]
|
playerSeatCancelEventHandles []PlayerSeatCancelEventHandle[PID, P, R]
|
||||||
playerSeatCancelEventRoomHandles map[int64][]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) {
|
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)
|
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)
|
option(roomInfo)
|
||||||
}
|
}
|
||||||
slf.rooms.Set(room.GetGuid(), roomInfo)
|
slf.rooms.Set(room.GetGuid(), roomInfo)
|
||||||
|
slf.OnRoomCreateEvent(room, slf.GetHelper(room))
|
||||||
}
|
}
|
||||||
|
|
||||||
// ReleaseRoom 释放房间
|
// ReleaseRoom 释放房间
|
||||||
|
|
|
@ -3,6 +3,7 @@ package room
|
||||||
import (
|
import (
|
||||||
"github.com/kercylan98/minotaur/game"
|
"github.com/kercylan98/minotaur/game"
|
||||||
"github.com/kercylan98/minotaur/utils/concurrent"
|
"github.com/kercylan98/minotaur/utils/concurrent"
|
||||||
|
"github.com/kercylan98/minotaur/utils/generic"
|
||||||
"github.com/kercylan98/minotaur/utils/hash"
|
"github.com/kercylan98/minotaur/utils/hash"
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
@ -91,7 +92,7 @@ func (slf *Seat[PlayerID, P, R]) SetSeat(id PlayerID, seat int) int {
|
||||||
}()
|
}()
|
||||||
oldSeat := slf.GetSeat(id)
|
oldSeat := slf.GetSeat(id)
|
||||||
player := slf.GetPlayerWithSeat(seat)
|
player := slf.GetPlayerWithSeat(seat)
|
||||||
if player != nil {
|
if generic.IsNil(player) {
|
||||||
if oldSeat == NoSeat {
|
if oldSeat == NoSeat {
|
||||||
maxSeat := len(slf.seatSP) - 1
|
maxSeat := len(slf.seatSP) - 1
|
||||||
if seat > maxSeat {
|
if seat > maxSeat {
|
||||||
|
|
|
@ -143,6 +143,9 @@ func PushTickerMessage(srv *Server, caller func(), mark ...any) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// PushAsyncMessage 向特定服务器中推送 MessageTypeAsync 消息
|
// PushAsyncMessage 向特定服务器中推送 MessageTypeAsync 消息
|
||||||
|
// - 异步消息将在服务器的异步消息队列中进行处理,处理完成 caller 的阻塞操作后,将会通过系统消息执行 callback 函数
|
||||||
|
// - callback 函数将在异步消息处理完成后进行调用,无论过程是否产生 err,都将被执行,允许为 nil
|
||||||
|
// - 需要注意的是,为了避免并发问题,caller 函数请仅处理阻塞操作,其他操作应该在 callback 函数中进行
|
||||||
func PushAsyncMessage(srv *Server, caller func() error, callback func(err error), mark ...any) {
|
func PushAsyncMessage(srv *Server, caller func() error, callback func(err error), mark ...any) {
|
||||||
msg := srv.messagePool.Get()
|
msg := srv.messagePool.Get()
|
||||||
msg.t = MessageTypeAsync
|
msg.t = MessageTypeAsync
|
||||||
|
|
|
@ -569,7 +569,9 @@ func (slf *Server) dispatchMessage(msg *Message) {
|
||||||
}()
|
}()
|
||||||
err := handle()
|
err := handle()
|
||||||
if cb && callback != nil {
|
if cb && callback != nil {
|
||||||
callback(err)
|
PushSystemMessage(slf, func() {
|
||||||
|
callback(err)
|
||||||
|
}, "AsyncCallback")
|
||||||
} else {
|
} else {
|
||||||
log.Error("Server", log.String("MessageType", messageNames[msg.t]), log.Any("error", err), log.String("stack", string(debug.Stack())))
|
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
|
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 获取给定数组的所有组合,包括重复元素的组合
|
// Combinations 获取给定数组的所有组合,包括重复元素的组合
|
||||||
func Combinations[T any](a []T) [][]T {
|
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
|
var result [][]T
|
||||||
for i := 0; i < totalCombinations; i++ {
|
n := len(a)
|
||||||
|
for i := 0; i < (1 << n); i++ {
|
||||||
var currentCombination []T
|
var currentCombination []T
|
||||||
for j := 0; j < n; j++ {
|
for j := 0; j < n; j++ {
|
||||||
if (i & (1 << j)) != 0 {
|
if (i & (1 << j)) != 0 {
|
||||||
currentCombination = append(currentCombination, uniqueSlice[j])
|
currentCombination = append(currentCombination, a[j])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
result = append(result, currentCombination)
|
result = append(result, currentCombination)
|
||||||
|
|
|
@ -3,6 +3,7 @@ package storage_test
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"github.com/kercylan98/minotaur/utils/slice"
|
||||||
"github.com/kercylan98/minotaur/utils/storage"
|
"github.com/kercylan98/minotaur/utils/storage"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
@ -43,3 +44,13 @@ func TestData_Struct(t *testing.T) {
|
||||||
|
|
||||||
fmt.Println(string(bytes))
|
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