Merge branch 'develop'

This commit is contained in:
kercylan98 2023-07-31 18:09:43 +08:00
commit 237cbd79cb
30 changed files with 1079 additions and 587 deletions

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

@ -0,0 +1,167 @@
package fight
import (
"fmt"
"github.com/kercylan98/minotaur/utils/random"
"github.com/kercylan98/minotaur/utils/timer"
"time"
)
// RoundGameOverVerifyHandle 回合制游戏结束验证函数
type RoundGameOverVerifyHandle[Data RoundData] func(round *Round[Data]) bool
// NewRound 创建一个新的回合制游戏
// - data 游戏数据
// - camps 阵营
// - roundGameOverVerifyHandle 游戏结束验证函数
// - options 选项
func NewRound[Data RoundData](data Data, camps []*RoundCamp, roundGameOverVerifyHandle RoundGameOverVerifyHandle[Data], options ...RoundOption[Data]) *Round[Data] {
round := &Round[Data]{
data: data,
camps: make(map[int][]int),
actionTimeoutTickerName: fmt.Sprintf("round_action_timeout_%s", random.HostName()),
roundGameOverVerifyHandle: roundGameOverVerifyHandle,
}
for _, camp := range camps {
round.camps[camp.campId] = camp.entities
round.campOrder = append(round.campOrder, camp.campId)
}
for _, option := range options {
option(round)
}
if round.ticker == nil {
round.ticker = timer.GetTicker(5)
}
return round
}
// Round 回合制游戏结构
type Round[Data RoundData] struct {
data Data // 游戏数据
ticker *timer.Ticker // 计时器
camps map[int][]int // 阵营
campOrder []int // 阵营顺序
actionTimeout time.Duration // 行动超时时间
actionTimeoutTickerName string // 行动超时计时器名称
round int // 回合数
roundCount int // 回合计数
currentCamp int // 当前行动阵营
currentEntity int // 当前行动的阵营实体索引
currentEndTime int64 // 当前行动结束时间
shareAction bool // 是否共享行动(同阵营共享行动时间)
roundGameOverVerifyHandle RoundGameOverVerifyHandle[Data] // 游戏结束验证函数
campCounterclockwise bool // 是否阵营逆时针
entityCounterclockwise bool // 是否对象逆时针
swapCampEventHandles []RoundSwapCampEvent[Data] // 阵营交换事件
swapEntityEventHandles []RoundSwapEntityEvent[Data] // 实体交换事件
gameOverEventHandles []RoundGameOverEvent[Data] // 游戏结束事件
changeEventHandles []RoundChangeEvent[Data] // 游戏回合变更事件
}
// GetData 获取游戏数据
func (slf *Round[Data]) GetData() Data {
return slf.data
}
// Run 运行游戏
// - 将通过传入的 Camp 进行初始化Camp 为一个二维数组,每个数组内的元素都是一个行动标识
func (slf *Round[Data]) Run() {
slf.currentEntity = -1
slf.round = 1
slf.loop()
}
func (slf *Round[Data]) loop() {
slf.ticker.StopTimer(slf.actionTimeoutTickerName)
if slf.roundGameOverVerifyHandle(slf) {
for _, handle := range slf.gameOverEventHandles {
handle(slf)
}
return
} else {
slf.ActionRefresh()
}
if slf.currentEntity == -1 || slf.currentEntity >= len(slf.camps[slf.currentCamp])-1 {
if !slf.campCounterclockwise {
slf.currentCamp = slf.campOrder[0]
slf.campOrder = append(slf.campOrder[1:], slf.currentCamp)
} else {
slf.currentCamp = slf.campOrder[len(slf.campOrder)-1]
slf.campOrder = append([]int{slf.currentCamp}, slf.campOrder[:len(slf.campOrder)-1]...)
}
slf.currentEntity = -1
slf.roundCount++
if slf.roundCount > len(slf.camps) {
slf.round++
slf.roundCount = 1
for _, handle := range slf.changeEventHandles {
handle(slf)
}
}
for _, handle := range slf.swapCampEventHandles {
handle(slf, slf.currentCamp)
}
}
slf.currentEntity++
for _, handle := range slf.swapEntityEventHandles {
if slf.entityCounterclockwise {
handle(slf, slf.currentCamp, slf.camps[slf.currentCamp][len(slf.camps[slf.currentCamp])-slf.currentEntity-1])
} else {
handle(slf, slf.currentCamp, slf.camps[slf.currentCamp][slf.currentEntity])
}
}
}
// SkipCamp 跳过当前阵营剩余对象的行动
func (slf *Round[Data]) SkipCamp() {
slf.currentEntity = -1
}
// ActionRefresh 刷新行动超时时间
func (slf *Round[Data]) ActionRefresh() {
slf.currentEndTime = time.Now().Unix()
slf.ticker.After(slf.actionTimeoutTickerName, slf.actionTimeout, slf.loop)
}
// ActionFinish 结束行动
func (slf *Round[Data]) ActionFinish() {
slf.ticker.StopTimer(slf.actionTimeoutTickerName)
if slf.shareAction {
slf.currentEntity = -1
} else {
slf.currentEntity++
}
slf.loop()
}
// GetRound 获取当前回合数
func (slf *Round[Data]) GetRound() int {
return slf.round
}
// AllowAction 是否允许行动
func (slf *Round[Data]) AllowAction(camp, entity int) bool {
return (slf.currentCamp == camp && slf.currentEntity == entity) || slf.shareAction && camp == slf.currentCamp
}
// CampAllowAction 阵容是否允许行动
func (slf *Round[Data]) CampAllowAction(camp int) bool {
return slf.currentCamp == camp
}
// GetCurrentCamp 获取当前行动的阵营
func (slf *Round[Data]) GetCurrentCamp() int {
return slf.currentCamp
}
// GetCurrentRoundProgressRate 获取当前回合进度
func (slf *Round[Data]) GetCurrentRoundProgressRate() float64 {
return float64(slf.roundCount / len(slf.camps))
}
// GetCurrent 获取当前行动的阵营和对象
func (slf *Round[Data]) GetCurrent() (camp, entity int) {
return slf.currentCamp, slf.camps[slf.currentCamp][slf.currentEntity]
}

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

@ -0,0 +1,15 @@
package fight
// NewRoundCamp 创建一个新的回合制游戏阵营
func NewRoundCamp(campId, entity int, entities ...int) *RoundCamp {
return &RoundCamp{
campId: campId,
entities: append([]int{entity}, entities...),
}
}
// RoundCamp 回合制游戏阵营
type RoundCamp struct {
campId int
entities []int
}

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

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

View File

@ -0,0 +1,79 @@
package fight
import (
"github.com/kercylan98/minotaur/utils/timer"
"time"
)
// RoundOption 回合制游戏选项
type RoundOption[Data RoundData] func(round *Round[Data])
type (
RoundSwapCampEvent[Data RoundData] func(round *Round[Data], campId int)
RoundSwapEntityEvent[Data RoundData] func(round *Round[Data], campId, entity int)
RoundGameOverEvent[Data RoundData] func(round *Round[Data])
RoundChangeEvent[Data RoundData] func(round *Round[Data])
)
// WithRoundTicker 设置游戏的计时器
func WithRoundTicker[Data RoundData](ticker *timer.Ticker) RoundOption[Data] {
return func(round *Round[Data]) {
round.ticker = ticker
}
}
// WithRoundActionTimeout 设置游戏的行动超时时间
func WithRoundActionTimeout[Data RoundData](timeout time.Duration) RoundOption[Data] {
return func(round *Round[Data]) {
round.actionTimeout = timeout
}
}
// WithRoundShareAction 设置游戏的行动是否共享
func WithRoundShareAction[Data RoundData](share bool) RoundOption[Data] {
return func(round *Round[Data]) {
round.shareAction = share
}
}
// WithRoundSwapCampEvent 设置游戏的阵营交换事件
func WithRoundSwapCampEvent[Data RoundData](swapCampEventHandle RoundSwapCampEvent[Data]) RoundOption[Data] {
return func(round *Round[Data]) {
round.swapCampEventHandles = append(round.swapCampEventHandles, swapCampEventHandle)
}
}
// WithRoundSwapEntityEvent 设置游戏的实体交换事件
func WithRoundSwapEntityEvent[Data RoundData](swapEntityEventHandle RoundSwapEntityEvent[Data]) RoundOption[Data] {
return func(round *Round[Data]) {
round.swapEntityEventHandles = append(round.swapEntityEventHandles, swapEntityEventHandle)
}
}
// WithRoundGameOverEvent 设置游戏的结束事件
func WithRoundGameOverEvent[Data RoundData](gameOverEventHandle RoundGameOverEvent[Data]) RoundOption[Data] {
return func(round *Round[Data]) {
round.gameOverEventHandles = append(round.gameOverEventHandles, gameOverEventHandle)
}
}
// WithRoundChangeEvent 设置游戏的回合变更事件
func WithRoundChangeEvent[Data RoundData](changeEventHandle RoundChangeEvent[Data]) RoundOption[Data] {
return func(round *Round[Data]) {
round.changeEventHandles = append(round.changeEventHandles, changeEventHandle)
}
}
// WithRoundCampCounterclockwise 设置游戏阵营逆序执行
func WithRoundCampCounterclockwise[Data RoundData]() RoundOption[Data] {
return func(round *Round[Data]) {
round.campCounterclockwise = true
}
}
// WithRoundEntityCounterclockwise 设置游戏实体逆序执行
func WithRoundEntityCounterclockwise[Data RoundData]() RoundOption[Data] {
return func(round *Round[Data]) {
round.entityCounterclockwise = true
}
}

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

@ -0,0 +1,33 @@
package fight
import (
"sync"
"testing"
"time"
)
func TestName(t *testing.T) {
var wait sync.WaitGroup
var camps []*RoundCamp
camps = append(camps, NewRoundCamp(1, 1, 2, 3))
camps = append(camps, NewRoundCamp(2, 4, 5, 6))
camps = append(camps, NewRoundCamp(3, 7, 8, 9))
r := NewRound("", camps, func(round *Round[string]) bool {
return round.GetRound() == 2
},
WithRoundActionTimeout[string](time.Second),
WithRoundSwapEntityEvent[string](func(round *Round[string], campId, entity int) {
t.Log(time.Now(), "swap entity", round.GetRound(), campId, entity)
}),
WithRoundGameOverEvent[string](func(round *Round[string]) {
t.Log(time.Now(), "game over", round.GetRound())
wait.Done()
}),
WithRoundCampCounterclockwise[string](),
WithRoundEntityCounterclockwise[string](),
)
wait.Add(1)
r.Run()
wait.Wait()
}

View File

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

View File

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

117
game/fsm/fsm.go Normal file
View File

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

56
game/fsm/options.go Normal file
View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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 释放房间

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -0,0 +1,5 @@
package combination
type Item interface {
// 占位
}

View File

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

View File

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

View File

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

View File

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

View File

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

40
utils/super/verify.go Normal file
View File

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

View File

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