From 997bbbe2bc8e3feb10ad0f87bad93027a769b7fb Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Sat, 29 Jul 2023 10:03:43 +0800 Subject: [PATCH 01/16] =?UTF-8?q?feat:=20super=20=E5=8C=85=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=20Verify=20=E5=87=BD=E6=95=B0=EF=BC=8C=E9=80=82?= =?UTF-8?q?=E7=94=A8=E4=BA=8E=E4=B8=9A=E5=8A=A1=E6=9D=A1=E4=BB=B6=E6=A0=A1?= =?UTF-8?q?=E9=AA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- utils/super/verify.go | 40 ++++++++++++++++++++++++++++++ utils/super/verify_example_test.go | 29 ++++++++++++++++++++++ 2 files changed, 69 insertions(+) create mode 100644 utils/super/verify.go create mode 100644 utils/super/verify_example_test.go diff --git a/utils/super/verify.go b/utils/super/verify.go new file mode 100644 index 0000000..4a757ba --- /dev/null +++ b/utils/super/verify.go @@ -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 +} diff --git a/utils/super/verify_example_test.go b/utils/super/verify_example_test.go new file mode 100644 index 0000000..e29a8e0 --- /dev/null +++ b/utils/super/verify_example_test.go @@ -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 +} From ac43963a864a74c499450838ed7f1d8c53700826 Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Sat, 29 Jul 2023 10:55:29 +0800 Subject: [PATCH 02/16] =?UTF-8?q?feat:=20maths=20=E5=8C=85=E6=94=AF?= =?UTF-8?q?=E6=8C=81=E5=A5=87=E5=81=B6=E6=95=B0=E5=88=A4=E6=96=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- utils/maths/math.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/utils/maths/math.go b/utils/maths/math.go index 7312daf..441f23c 100644 --- a/utils/maths/math.go +++ b/utils/maths/math.go @@ -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) +} From 48d9c1131627087b39456b9f376d3148942ad259 Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Sat, 29 Jul 2023 15:15:35 +0800 Subject: [PATCH 03/16] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=20combination?= =?UTF-8?q?=20=E5=8C=85=EF=BC=8C=E7=94=A8=E4=BA=8E=E6=95=B0=E7=BB=84?= =?UTF-8?q?=E7=BB=84=E5=90=88=E7=AD=9B=E9=80=89=EF=BC=88=E6=8A=BD=E7=A6=BB?= =?UTF-8?q?=E8=87=AA=20poker=20=E5=8C=85=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 可根据评估函数筛选出最优、最差等组合,天然支持优先级筛选。 - 适用于提示出牌、最优解等内容,例如:扑克牌、麻将等 --- utils/combination/combination.go | 113 +++++++++++++ utils/combination/combination_options.go | 15 ++ utils/combination/combination_test.go | 50 ++++++ utils/combination/filters.go | 1 + utils/combination/item.go | 4 + utils/combination/matcher.go | 86 ++++++++++ utils/combination/matcher_options.go | 199 +++++++++++++++++++++++ 7 files changed, 468 insertions(+) create mode 100644 utils/combination/combination.go create mode 100644 utils/combination/combination_options.go create mode 100644 utils/combination/combination_test.go create mode 100644 utils/combination/filters.go create mode 100644 utils/combination/item.go create mode 100644 utils/combination/matcher.go create mode 100644 utils/combination/matcher_options.go diff --git a/utils/combination/combination.go b/utils/combination/combination.go new file mode 100644 index 0000000..35b9aa6 --- /dev/null +++ b/utils/combination/combination.go @@ -0,0 +1,113 @@ +package combination + +import ( + "github.com/kercylan98/minotaur/utils/random" +) + +// NewCombination 创建一个新的组合器 +func NewCombination[T Item](options ...CombinationOption[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 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 score := matcher.evaluate(matcherWorst); score < worst || worst == -1 { + worst = score + name = n + result = matcherWorst + } + } + return +} diff --git a/utils/combination/combination_options.go b/utils/combination/combination_options.go new file mode 100644 index 0000000..7edb62f --- /dev/null +++ b/utils/combination/combination_options.go @@ -0,0 +1,15 @@ +package combination + +// CombinationOption 组合器选项 +type CombinationOption[T Item] func(*Combination[T]) + +// WithCombinationEvaluation 设置组合评估函数 +// - 用于对组合进行评估,返回一个分值的评价函数 +// - 通过该选项将设置所有匹配器的默认评估函数为该函数 +// - 通过匹配器选项 WithMatcherEvaluation 可以覆盖该默认评估函数 +// - 默认的评估函数将返回一个随机数 +func WithCombinationEvaluation[T Item](evaluate func(items []T) float64) CombinationOption[T] { + return func(c *Combination[T]) { + c.evaluate = evaluate + } +} diff --git a/utils/combination/combination_test.go b/utils/combination/combination_test.go new file mode 100644 index 0000000..d2f4c77 --- /dev/null +++ b/utils/combination/combination_test.go @@ -0,0 +1,50 @@ +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.WithCombinationEvaluation(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}, + } + + name, result := combine.Best(cards) + fmt.Println("best:", name) + for _, item := range result { + fmt.Println(item) + } +} diff --git a/utils/combination/filters.go b/utils/combination/filters.go new file mode 100644 index 0000000..185bc35 --- /dev/null +++ b/utils/combination/filters.go @@ -0,0 +1 @@ +package combination diff --git a/utils/combination/item.go b/utils/combination/item.go new file mode 100644 index 0000000..86dbc2e --- /dev/null +++ b/utils/combination/item.go @@ -0,0 +1,4 @@ +package combination + +type Item interface { +} diff --git a/utils/combination/matcher.go b/utils/combination/matcher.go new file mode 100644 index 0000000..570b221 --- /dev/null +++ b/utils/combination/matcher.go @@ -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 +} diff --git a/utils/combination/matcher_options.go b/utils/combination/matcher_options.go new file mode 100644 index 0000000..9b521c6 --- /dev/null +++ b/utils/combination/matcher_options.go @@ -0,0 +1,199 @@ +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 设置匹配器评估函数 +// - 用于对组合进行评估,返回一个分值的评价函数 +// - 通过该选项将覆盖匹配器的默认(WithCombinationEvaluation)评估函数 +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 + } + ms := slice.Combinations(slice.SubWithCheck(items, group, func(a, b T) bool { return reflect.DeepEqual(a, b) })) + for i := 0; i < len(ms); i++ { + ts := make(map[E][]T) + for _, t := range ms[i] { + tt := getType(t) + ts[tt] = append(ts[tt], t) + } + for _, cs := range ts { + if len(cs) == m { + combinations = append(combinations, slice.Merge(group, cs)) + } + } + } + } + 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.Combinations(slice.SubWithCheck(items, group, func(a, b T) bool { return reflect.DeepEqual(a, b) })) + for i := 0; i < len(ms); i++ { + is := ms[i] + if len(is) == m { + combinations = append(combinations, slice.Merge(group, is)) + } + } + } + return combinations + }) + } +} From 57936b2b25426055de409659b5f5a2a018f9031e Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Sat, 29 Jul 2023 15:26:32 +0800 Subject: [PATCH 04/16] =?UTF-8?q?other:=20=E4=BC=98=E5=8C=96=20combination?= =?UTF-8?q?=20=E5=8C=85=E5=91=BD=E5=90=8D=EF=BC=8C=E5=88=A0=E9=99=A4?= =?UTF-8?q?=E6=97=A0=E7=94=A8=E6=96=87=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- utils/combination/combination.go | 2 +- utils/combination/combination_options.go | 8 ++++---- utils/combination/combination_test.go | 2 +- utils/combination/filters.go | 1 - utils/combination/item.go | 1 + utils/combination/matcher_options.go | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) delete mode 100644 utils/combination/filters.go diff --git a/utils/combination/combination.go b/utils/combination/combination.go index 35b9aa6..de1064c 100644 --- a/utils/combination/combination.go +++ b/utils/combination/combination.go @@ -5,7 +5,7 @@ import ( ) // NewCombination 创建一个新的组合器 -func NewCombination[T Item](options ...CombinationOption[T]) *Combination[T] { +func NewCombination[T Item](options ...Option[T]) *Combination[T] { combination := &Combination[T]{ matchers: make(map[string]*Matcher[T]), } diff --git a/utils/combination/combination_options.go b/utils/combination/combination_options.go index 7edb62f..da3428f 100644 --- a/utils/combination/combination_options.go +++ b/utils/combination/combination_options.go @@ -1,14 +1,14 @@ package combination -// CombinationOption 组合器选项 -type CombinationOption[T Item] func(*Combination[T]) +// Option 组合器选项 +type Option[T Item] func(*Combination[T]) -// WithCombinationEvaluation 设置组合评估函数 +// WithEvaluation 设置组合评估函数 // - 用于对组合进行评估,返回一个分值的评价函数 // - 通过该选项将设置所有匹配器的默认评估函数为该函数 // - 通过匹配器选项 WithMatcherEvaluation 可以覆盖该默认评估函数 // - 默认的评估函数将返回一个随机数 -func WithCombinationEvaluation[T Item](evaluate func(items []T) float64) CombinationOption[T] { +func WithEvaluation[T Item](evaluate func(items []T) float64) Option[T] { return func(c *Combination[T]) { c.evaluate = evaluate } diff --git a/utils/combination/combination_test.go b/utils/combination/combination_test.go index d2f4c77..c113294 100644 --- a/utils/combination/combination_test.go +++ b/utils/combination/combination_test.go @@ -12,7 +12,7 @@ type Poker struct { } func TestCombination_Best(t *testing.T) { - combine := combination.NewCombination(combination.WithCombinationEvaluation(func(items []*Poker) float64 { + combine := combination.NewCombination(combination.WithEvaluation(func(items []*Poker) float64 { var total float64 for _, item := range items { total += float64(item.Point) diff --git a/utils/combination/filters.go b/utils/combination/filters.go deleted file mode 100644 index 185bc35..0000000 --- a/utils/combination/filters.go +++ /dev/null @@ -1 +0,0 @@ -package combination diff --git a/utils/combination/item.go b/utils/combination/item.go index 86dbc2e..de8381d 100644 --- a/utils/combination/item.go +++ b/utils/combination/item.go @@ -1,4 +1,5 @@ package combination type Item interface { + // 占位 } diff --git a/utils/combination/matcher_options.go b/utils/combination/matcher_options.go index 9b521c6..63fa30c 100644 --- a/utils/combination/matcher_options.go +++ b/utils/combination/matcher_options.go @@ -12,7 +12,7 @@ type MatcherOption[T Item] func(matcher *Matcher[T]) // WithMatcherEvaluation 设置匹配器评估函数 // - 用于对组合进行评估,返回一个分值的评价函数 -// - 通过该选项将覆盖匹配器的默认(WithCombinationEvaluation)评估函数 +// - 通过该选项将覆盖匹配器的默认(WithEvaluation)评估函数 func WithMatcherEvaluation[T Item](evaluate func(items []T) float64) MatcherOption[T] { return func(m *Matcher[T]) { m.evaluate = evaluate From abd1db55860a26d3cad0d12e7cf7aa66304e852a Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Sat, 29 Jul 2023 16:01:09 +0800 Subject: [PATCH 05/16] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=20combination?= =?UTF-8?q?=20=E5=8C=85=20NCarryM=20=E6=80=A7=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- utils/combination/combination.go | 6 +++++ utils/combination/combination_test.go | 13 +++++++++- utils/combination/matcher_options.go | 35 +++++++++++++++------------ utils/slice/slice.go | 5 ++-- utils/storage/data_test.go | 11 +++++++++ 5 files changed, 51 insertions(+), 19 deletions(-) diff --git a/utils/combination/combination.go b/utils/combination/combination.go index de1064c..f4af74f 100644 --- a/utils/combination/combination.go +++ b/utils/combination/combination.go @@ -88,6 +88,9 @@ func (slf *Combination[T]) Best(items []T) (name string, result []T) { 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 @@ -103,6 +106,9 @@ func (slf *Combination[T]) Worst(items []T) (name string, result []T) { 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 diff --git a/utils/combination/combination_test.go b/utils/combination/combination_test.go index c113294..43470b0 100644 --- a/utils/combination/combination_test.go +++ b/utils/combination/combination_test.go @@ -40,9 +40,20 @@ func TestCombination_Best(t *testing.T) { {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.Best(cards) + name, result := combine.Worst(cards) fmt.Println("best:", name) for _, item := range result { fmt.Println(item) diff --git a/utils/combination/matcher_options.go b/utils/combination/matcher_options.go index 63fa30c..2954147 100644 --- a/utils/combination/matcher_options.go +++ b/utils/combination/matcher_options.go @@ -147,18 +147,24 @@ func WithMatcherNCarryM[T Item, E generic.Ordered](n, m int, getType func(item T if len(group) != n { continue } - ms := slice.Combinations(slice.SubWithCheck(items, group, func(a, b T) bool { return reflect.DeepEqual(a, b) })) - for i := 0; i < len(ms); i++ { - ts := make(map[E][]T) - for _, t := range ms[i] { - tt := getType(t) - ts[tt] = append(ts[tt], t) - } - for _, cs := range ts { - if len(cs) == m { - combinations = append(combinations, slice.Merge(group, cs)) + 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 @@ -185,12 +191,9 @@ func WithMatcherNCarryIndependentM[T Item, E generic.Ordered](n, m int, getType if len(group) != n { continue } - ms := slice.Combinations(slice.SubWithCheck(items, group, func(a, b T) bool { return reflect.DeepEqual(a, b) })) - for i := 0; i < len(ms); i++ { - is := ms[i] - if len(is) == m { - combinations = append(combinations, slice.Merge(group, is)) - } + 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 diff --git a/utils/slice/slice.go b/utils/slice/slice.go index f477a1b..756f458 100644 --- a/utils/slice/slice.go +++ b/utils/slice/slice.go @@ -1,6 +1,7 @@ package slice import ( + "fmt" "math/rand" "reflect" ) @@ -223,10 +224,10 @@ func Combinations[T any](a []T) [][]T { n := len(a) // 去除重复元素,保留唯一元素 - uniqueSet := make(map[uintptr]bool) + uniqueSet := make(map[string]bool) uniqueSlice := make([]T, 0, n) for _, val := range a { - ptr := reflect.ValueOf(val).Pointer() + ptr := fmt.Sprintf("%p", val) if !uniqueSet[ptr] { uniqueSet[ptr] = true uniqueSlice = append(uniqueSlice, val) diff --git a/utils/storage/data_test.go b/utils/storage/data_test.go index a5b3d11..b8c1aa0 100644 --- a/utils/storage/data_test.go +++ b/utils/storage/data_test.go @@ -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)) +} From 1297ae7a8f246f8929131b299ba6cfcffc585c4e Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Sat, 29 Jul 2023 16:42:57 +0800 Subject: [PATCH 06/16] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=20server=20?= =?UTF-8?q?=E5=BC=82=E6=AD=A5=E6=B6=88=E6=81=AF=E7=9A=84=20callback=20?= =?UTF-8?q?=E7=9A=84=E5=B9=B6=E5=8F=91=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 将 callback 转为系统消息的方式执行 --- server/server.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/server/server.go b/server/server.go index 76212d1..e7ec63a 100644 --- a/server/server.go +++ b/server/server.go @@ -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()))) } From 2482d2e7f0dcfd3bea2be2474102dcd7b10d6da5 Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Sat, 29 Jul 2023 16:46:10 +0800 Subject: [PATCH 07/16] =?UTF-8?q?docs:=20server.PushAsyncMessage=20?= =?UTF-8?q?=E6=B3=A8=E6=84=8F=E4=BA=8B=E9=A1=B9=E8=A1=A5=E5=85=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- server/message.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/server/message.go b/server/message.go index 6bac3ae..4dac411 100644 --- a/server/message.go +++ b/server/message.go @@ -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 From 03028b1a41567b2a9bfa1f4c4f8d4d5e6cc4264c Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Sat, 29 Jul 2023 17:13:12 +0800 Subject: [PATCH 08/16] =?UTF-8?q?perf:=20=E4=BC=98=E5=8C=96=20slice.Combin?= =?UTF-8?q?ations=20=E6=95=88=E7=8E=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- utils/slice/slice.go | 21 +++------------------ 1 file changed, 3 insertions(+), 18 deletions(-) diff --git a/utils/slice/slice.go b/utils/slice/slice.go index 756f458..70d6e12 100644 --- a/utils/slice/slice.go +++ b/utils/slice/slice.go @@ -1,7 +1,6 @@ package slice import ( - "fmt" "math/rand" "reflect" ) @@ -221,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[string]bool) - uniqueSlice := make([]T, 0, n) - for _, val := range a { - ptr := fmt.Sprintf("%p", val) - 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) From cee067e246942024acf44261a3e7d549b4b85b7a Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Sat, 29 Jul 2023 18:01:12 +0800 Subject: [PATCH 09/16] =?UTF-8?q?fix:=20=E7=8A=B6=E6=80=81=E6=9C=BA=20fsm?= =?UTF-8?q?=20=E5=8C=85=E5=90=8D=E4=BF=AE=E5=A4=8D=EF=BC=8C=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- game/fms/fsm_state.go | 38 -------------------------------------- game/{fms => fsm}/fsm.go | 15 +++++++++++---- game/fsm/fsm_state.go | 38 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 49 insertions(+), 42 deletions(-) delete mode 100644 game/fms/fsm_state.go rename game/{fms => fsm}/fsm.go (67%) create mode 100644 game/fsm/fsm_state.go diff --git a/game/fms/fsm_state.go b/game/fms/fsm_state.go deleted file mode 100644 index 15441c7..0000000 --- a/game/fms/fsm_state.go +++ /dev/null @@ -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) -} diff --git a/game/fms/fsm.go b/game/fsm/fsm.go similarity index 67% rename from game/fms/fsm.go rename to game/fsm/fsm.go index c281f12..8056262 100644 --- a/game/fms/fsm.go +++ b/game/fsm/fsm.go @@ -1,40 +1,47 @@ -package fms +package fsm import ( "fmt" ) +// NewFSM 创建一个新的状态机 func NewFSM[State comparable, Data any](data Data) *FSM[State, Data] { return &FSM[State, Data]{ - states: map[State]*FSMState[State, Data]{}, + states: map[State]*State[State, Data]{}, data: data, } } +// FSM 状态机 type FSM[State comparable, Data any] struct { current State data Data - states map[State]*FSMState[State, Data] + states map[State]*State[State, Data] } +// Update 触发当前状态 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]) { +// Register 注册状态 +func (slf *FSM[State, Data]) Register(state *State[State, Data]) { slf.states[state.GetState()] = state } +// Unregister 反注册状态 func (slf *FSM[State, Data]) Unregister(state State) { delete(slf.states, state) } +// HasState 检查状态机是否存在特定状态 func (slf *FSM[State, Data]) HasState(state State) bool { _, has := slf.states[state] return has } +// Change 改变状态机状态到新的状态 func (slf *FSM[State, Data]) Change(state State) { current := slf.states[slf.current] current.Exit(slf.data) diff --git a/game/fsm/fsm_state.go b/game/fsm/fsm_state.go new file mode 100644 index 0000000..4d73fd8 --- /dev/null +++ b/game/fsm/fsm_state.go @@ -0,0 +1,38 @@ +package fsm + +type ( + StateEnterHandle[Data any] func(data Data) + StateUpdateHandle[Data any] func(data Data) + StateExitHandle[Data any] func(data Data) +) + +func NewFSMState[S comparable, Data any](state S, enter StateEnterHandle[Data], update StateUpdateHandle[Data], exit StateExitHandle[Data]) *State[S, Data] { + return &State[S, Data]{ + enter: enter, + update: update, + exit: exit, + } +} + +type State[S comparable, Data any] struct { + state S + enter StateEnterHandle[Data] + update StateUpdateHandle[Data] + exit StateExitHandle[Data] +} + +func (slf *State[S, Data]) GetState() S { + return slf.state +} + +func (slf *State[S, Data]) Enter(data Data) { + slf.enter(data) +} + +func (slf *State[S, Data]) Update(data Data) { + slf.update(data) +} + +func (slf *State[S, Data]) Exit(data Data) { + slf.exit(data) +} From 87c66954a3ea1215b587aa3a22b464e6d2066321 Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Sat, 29 Jul 2023 18:06:40 +0800 Subject: [PATCH 10/16] =?UTF-8?q?feat:=20room=20=E5=8C=85=E6=96=B0?= =?UTF-8?q?=E5=A2=9E=E6=88=BF=E9=97=B4=E5=88=9B=E5=BB=BA=E4=BA=8B=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- game/room/events.go | 15 +++++++++++++++ game/room/manager.go | 1 + 2 files changed, 16 insertions(+) diff --git a/game/room/events.go b/game/room/events.go index b3bb33e..dd4c134 100644 --- a/game/room/events.go +++ b/game/room/events.go @@ -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) + // RoomCreateEventHandle 房间创建事件处理函数 + RoomCreateEventHandle[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 []RoomCreateEventHandle[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 RoomCreateEventHandle[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) + } +} diff --git a/game/room/manager.go b/game/room/manager.go index 6678e66..4dbf46f 100644 --- a/game/room/manager.go +++ b/game/room/manager.go @@ -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 释放房间 From de76411726854f0f11ffad405ded8dc5e1b89ec4 Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Sat, 29 Jul 2023 18:09:06 +0800 Subject: [PATCH 11/16] =?UTF-8?q?fix:=20=E7=8A=B6=E6=80=81=E6=9C=BA=20Stat?= =?UTF-8?q?e=20=E5=90=8D=E7=A7=B0=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- game/fsm/fsm.go | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/game/fsm/fsm.go b/game/fsm/fsm.go index 8056262..61e1589 100644 --- a/game/fsm/fsm.go +++ b/game/fsm/fsm.go @@ -5,44 +5,44 @@ import ( ) // NewFSM 创建一个新的状态机 -func NewFSM[State comparable, Data any](data Data) *FSM[State, Data] { - return &FSM[State, Data]{ - states: map[State]*State[State, Data]{}, +func NewFSM[S comparable, Data any](data Data) *FSM[S, Data] { + return &FSM[S, Data]{ + states: map[S]*State[S, Data]{}, data: data, } } // FSM 状态机 -type FSM[State comparable, Data any] struct { - current State +type FSM[S comparable, Data any] struct { + current S data Data - states map[State]*State[State, Data] + states map[S]*State[S, Data] } // Update 触发当前状态 -func (slf *FSM[State, Data]) Update() { +func (slf *FSM[S, Data]) Update() { state := slf.states[slf.current] state.Update(slf.data) } // Register 注册状态 -func (slf *FSM[State, Data]) Register(state *State[State, Data]) { +func (slf *FSM[S, Data]) Register(state *State[S, Data]) { slf.states[state.GetState()] = state } // Unregister 反注册状态 -func (slf *FSM[State, Data]) Unregister(state State) { +func (slf *FSM[S, Data]) Unregister(state S) { delete(slf.states, state) } // HasState 检查状态机是否存在特定状态 -func (slf *FSM[State, Data]) HasState(state State) bool { +func (slf *FSM[S, Data]) HasState(state S) bool { _, has := slf.states[state] return has } // Change 改变状态机状态到新的状态 -func (slf *FSM[State, Data]) Change(state State) { +func (slf *FSM[S, Data]) Change(state S) { current := slf.states[slf.current] current.Exit(slf.data) From 8b929212303e020db4476842566449f7a3b605fc Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Mon, 31 Jul 2023 10:01:10 +0800 Subject: [PATCH 12/16] =?UTF-8?q?revert:=20=E7=A7=BB=E9=99=A4=20poker=20?= =?UTF-8?q?=E5=8C=85=E7=9A=84=20matcher=EF=BC=8C=E6=94=B9=E4=B8=BA?= =?UTF-8?q?=E4=BD=BF=E7=94=A8=20combination=20=E5=8C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- game/poker/matcher.go | 52 ------- game/poker/matcher_filter.go | 42 ------ game/poker/matcher_options.go | 271 ---------------------------------- game/poker/matcher_test.go | 117 --------------- 4 files changed, 482 deletions(-) delete mode 100644 game/poker/matcher.go delete mode 100644 game/poker/matcher_filter.go delete mode 100644 game/poker/matcher_options.go delete mode 100644 game/poker/matcher_test.go diff --git a/game/poker/matcher.go b/game/poker/matcher.go deleted file mode 100644 index e76d2c6..0000000 --- a/game/poker/matcher.go +++ /dev/null @@ -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 -} diff --git a/game/poker/matcher_filter.go b/game/poker/matcher_filter.go deleted file mode 100644 index 2047a89..0000000 --- a/game/poker/matcher_filter.go +++ /dev/null @@ -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 -} diff --git a/game/poker/matcher_options.go b/game/poker/matcher_options.go deleted file mode 100644 index 0045329..0000000 --- a/game/poker/matcher_options.go +++ /dev/null @@ -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 - }) - } -} diff --git a/game/poker/matcher_test.go b/game/poker/matcher_test.go deleted file mode 100644 index 346fd88..0000000 --- a/game/poker/matcher_test.go +++ /dev/null @@ -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())) - } -} From 0fad0417c7cbd27a228b199c58c209de71ebbb0f Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Mon, 31 Jul 2023 12:05:02 +0800 Subject: [PATCH 13/16] =?UTF-8?q?refactor:=20fsm=20=E5=8C=85=E7=8A=B6?= =?UTF-8?q?=E6=80=81=E6=9C=BA=E4=BA=8B=E4=BB=B6=E4=BC=98=E5=8C=96=EF=BC=8C?= =?UTF-8?q?=E6=96=B0=E5=A2=9E=E9=83=A8=E5=88=86=E8=8E=B7=E5=8F=96=E7=8A=B6?= =?UTF-8?q?=E6=80=81=E6=9C=BA=E4=BF=A1=E6=81=AF=E7=9A=84=E5=87=BD=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- game/fsm/fsm.go | 104 +++++++++++++++++++++++++++++++++--------- game/fsm/fsm_state.go | 38 --------------- game/fsm/options.go | 56 +++++++++++++++++++++++ game/room/events.go | 8 ++-- 4 files changed, 143 insertions(+), 63 deletions(-) delete mode 100644 game/fsm/fsm_state.go create mode 100644 game/fsm/options.go diff --git a/game/fsm/fsm.go b/game/fsm/fsm.go index 61e1589..2bc462e 100644 --- a/game/fsm/fsm.go +++ b/game/fsm/fsm.go @@ -2,54 +2,116 @@ package fsm import ( "fmt" + "github.com/kercylan98/minotaur/utils/hash" ) // NewFSM 创建一个新的状态机 -func NewFSM[S comparable, Data any](data Data) *FSM[S, Data] { - return &FSM[S, Data]{ - states: map[S]*State[S, Data]{}, +func NewFSM[State comparable, Data any](data Data) *FSM[State, Data] { + return &FSM[State, Data]{ + states: map[State]struct{}{}, data: data, } } // FSM 状态机 -type FSM[S comparable, Data any] struct { - current S +type FSM[State comparable, Data any] struct { + prev *State + current *State data Data - states map[S]*State[S, 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[S, Data]) Update() { - state := slf.states[slf.current] - state.Update(slf.data) +func (slf *FSM[State, Data]) Update() { + if slf.current == nil { + return + } + for _, event := range slf.updateEventHandles[*slf.current] { + event(slf) + } } // Register 注册状态 -func (slf *FSM[S, Data]) Register(state *State[S, Data]) { - slf.states[state.GetState()] = state +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[S, Data]) Unregister(state S) { +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[S, Data]) HasState(state S) bool { - _, has := slf.states[state] - return has +func (slf *FSM[State, Data]) HasState(state State) bool { + return hash.Exist(slf.states, state) } // Change 改变状态机状态到新的状态 -func (slf *FSM[S, Data]) Change(state S) { - current := slf.states[slf.current] - current.Exit(slf.data) +func (slf *FSM[State, Data]) Change(state State) { + if !slf.IsZero() { + for _, event := range slf.exitBeforeEventHandles[*slf.current] { + event(slf) + } + } - next := slf.states[state] - if next == nil { + 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)) } - next.Enter(slf.data) + 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 } diff --git a/game/fsm/fsm_state.go b/game/fsm/fsm_state.go deleted file mode 100644 index 4d73fd8..0000000 --- a/game/fsm/fsm_state.go +++ /dev/null @@ -1,38 +0,0 @@ -package fsm - -type ( - StateEnterHandle[Data any] func(data Data) - StateUpdateHandle[Data any] func(data Data) - StateExitHandle[Data any] func(data Data) -) - -func NewFSMState[S comparable, Data any](state S, enter StateEnterHandle[Data], update StateUpdateHandle[Data], exit StateExitHandle[Data]) *State[S, Data] { - return &State[S, Data]{ - enter: enter, - update: update, - exit: exit, - } -} - -type State[S comparable, Data any] struct { - state S - enter StateEnterHandle[Data] - update StateUpdateHandle[Data] - exit StateExitHandle[Data] -} - -func (slf *State[S, Data]) GetState() S { - return slf.state -} - -func (slf *State[S, Data]) Enter(data Data) { - slf.enter(data) -} - -func (slf *State[S, Data]) Update(data Data) { - slf.update(data) -} - -func (slf *State[S, Data]) Exit(data Data) { - slf.exit(data) -} diff --git a/game/fsm/options.go b/game/fsm/options.go new file mode 100644 index 0000000..259453b --- /dev/null +++ b/game/fsm/options.go @@ -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) + } +} diff --git a/game/room/events.go b/game/room/events.go index dd4c134..8a27232 100644 --- a/game/room/events.go +++ b/game/room/events.go @@ -21,8 +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) - // RoomCreateEventHandle 房间创建事件处理函数 - RoomCreateEventHandle[PID comparable, P game.Player[PID], R Room] func(room R, helper *Helper[PID, P, R]) + // 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] { @@ -57,7 +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 []RoomCreateEventHandle[PID, P, R] + roomCreateEventHandles []CreateEventHandle[PID, P, R] } func (slf *event[PID, P, R]) unReg(guid int64) { @@ -254,7 +254,7 @@ func (slf *event[PID, P, R]) OnPlayerSeatCancelEvent(room R, player P, seat int) } // RegRoomCreateEvent 房间创建时将立即执行被注册的事件处理函数 -func (slf *event[PID, P, R]) RegRoomCreateEvent(handle RoomCreateEventHandle[PID, P, R]) { +func (slf *event[PID, P, R]) RegRoomCreateEvent(handle CreateEventHandle[PID, P, R]) { slf.roomCreateEventHandles = append(slf.roomCreateEventHandles, handle) } From 4dddd1422bc00f40be43050f53cd7525f9a73341 Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Mon, 31 Jul 2023 12:05:42 +0800 Subject: [PATCH 14/16] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E6=B3=9B?= =?UTF-8?q?=E5=9E=8B=E5=AF=B9=E8=B1=A1=20player=20=E4=B8=8D=E8=83=BD?= =?UTF-8?q?=E5=88=A4=E6=96=AD=20nil=20=E7=9A=84=E8=A1=A8=E8=BE=BE=E5=BC=8F?= =?UTF-8?q?=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- game/room/seat.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/game/room/seat.go b/game/room/seat.go index 0a3a78c..5878991 100644 --- a/game/room/seat.go +++ b/game/room/seat.go @@ -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 { From 39ccad42411774058e14a520fcfc16960e22a9f5 Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Mon, 31 Jul 2023 16:22:04 +0800 Subject: [PATCH 15/16] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E7=89=8C?= =?UTF-8?q?=E5=A0=86=E9=87=8D=E7=BD=AE=E6=97=B6=E4=B8=8D=E4=BC=9A=E9=87=8D?= =?UTF-8?q?=E7=BD=AE=20guid=20=E7=9A=84=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- game/poker/card_pile.go | 1 + 1 file changed, 1 insertion(+) diff --git a/game/poker/card_pile.go b/game/poker/card_pile.go index 5388bfc..145cdd5 100644 --- a/game/poker/card_pile.go +++ b/game/poker/card_pile.go @@ -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 { From df8f6fc53e5bfdc481351c962f4f10a3585d3796 Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Mon, 31 Jul 2023 18:08:40 +0800 Subject: [PATCH 16/16] =?UTF-8?q?feat:=20=E6=96=B0=E5=A2=9E=20fight=20?= =?UTF-8?q?=E5=8C=85=EF=BC=8C=E6=8F=90=E4=BE=9B=E4=BA=86=E5=9B=9E=E5=90=88?= =?UTF-8?q?=E5=88=B6=E6=88=98=E6=96=97=E7=9A=84=E5=8A=9F=E8=83=BD=E5=AE=9E?= =?UTF-8?q?=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- game/fight/round.go | 167 ++++++++++++++++++++++++++++++++++++ game/fight/round_camp.go | 15 ++++ game/fight/round_data.go | 6 ++ game/fight/round_options.go | 79 +++++++++++++++++ game/fight/round_test.go | 33 +++++++ 5 files changed, 300 insertions(+) create mode 100644 game/fight/round.go create mode 100644 game/fight/round_camp.go create mode 100644 game/fight/round_data.go create mode 100644 game/fight/round_options.go create mode 100644 game/fight/round_test.go diff --git a/game/fight/round.go b/game/fight/round.go new file mode 100644 index 0000000..6c195ee --- /dev/null +++ b/game/fight/round.go @@ -0,0 +1,167 @@ +package fight + +import ( + "fmt" + "github.com/kercylan98/minotaur/utils/random" + "github.com/kercylan98/minotaur/utils/timer" + "time" +) + +// RoundGameOverVerifyHandle 回合制游戏结束验证函数 +type RoundGameOverVerifyHandle[Data RoundData] func(round *Round[Data]) bool + +// NewRound 创建一个新的回合制游戏 +// - data 游戏数据 +// - camps 阵营 +// - roundGameOverVerifyHandle 游戏结束验证函数 +// - options 选项 +func NewRound[Data RoundData](data Data, camps []*RoundCamp, roundGameOverVerifyHandle RoundGameOverVerifyHandle[Data], options ...RoundOption[Data]) *Round[Data] { + round := &Round[Data]{ + data: data, + camps: make(map[int][]int), + actionTimeoutTickerName: fmt.Sprintf("round_action_timeout_%s", random.HostName()), + roundGameOverVerifyHandle: roundGameOverVerifyHandle, + } + for _, camp := range camps { + round.camps[camp.campId] = camp.entities + round.campOrder = append(round.campOrder, camp.campId) + } + for _, option := range options { + option(round) + } + if round.ticker == nil { + round.ticker = timer.GetTicker(5) + } + return round +} + +// Round 回合制游戏结构 +type Round[Data RoundData] struct { + data Data // 游戏数据 + ticker *timer.Ticker // 计时器 + camps map[int][]int // 阵营 + campOrder []int // 阵营顺序 + actionTimeout time.Duration // 行动超时时间 + actionTimeoutTickerName string // 行动超时计时器名称 + round int // 回合数 + roundCount int // 回合计数 + currentCamp int // 当前行动阵营 + currentEntity int // 当前行动的阵营实体索引 + currentEndTime int64 // 当前行动结束时间 + shareAction bool // 是否共享行动(同阵营共享行动时间) + roundGameOverVerifyHandle RoundGameOverVerifyHandle[Data] // 游戏结束验证函数 + campCounterclockwise bool // 是否阵营逆时针 + entityCounterclockwise bool // 是否对象逆时针 + + swapCampEventHandles []RoundSwapCampEvent[Data] // 阵营交换事件 + swapEntityEventHandles []RoundSwapEntityEvent[Data] // 实体交换事件 + gameOverEventHandles []RoundGameOverEvent[Data] // 游戏结束事件 + changeEventHandles []RoundChangeEvent[Data] // 游戏回合变更事件 +} + +// GetData 获取游戏数据 +func (slf *Round[Data]) GetData() Data { + return slf.data +} + +// Run 运行游戏 +// - 将通过传入的 Camp 进行初始化,Camp 为一个二维数组,每个数组内的元素都是一个行动标识 +func (slf *Round[Data]) Run() { + slf.currentEntity = -1 + slf.round = 1 + slf.loop() +} + +func (slf *Round[Data]) loop() { + slf.ticker.StopTimer(slf.actionTimeoutTickerName) + if slf.roundGameOverVerifyHandle(slf) { + for _, handle := range slf.gameOverEventHandles { + handle(slf) + } + return + } else { + slf.ActionRefresh() + } + if slf.currentEntity == -1 || slf.currentEntity >= len(slf.camps[slf.currentCamp])-1 { + if !slf.campCounterclockwise { + slf.currentCamp = slf.campOrder[0] + slf.campOrder = append(slf.campOrder[1:], slf.currentCamp) + } else { + slf.currentCamp = slf.campOrder[len(slf.campOrder)-1] + slf.campOrder = append([]int{slf.currentCamp}, slf.campOrder[:len(slf.campOrder)-1]...) + } + + slf.currentEntity = -1 + slf.roundCount++ + if slf.roundCount > len(slf.camps) { + slf.round++ + slf.roundCount = 1 + for _, handle := range slf.changeEventHandles { + handle(slf) + } + } + for _, handle := range slf.swapCampEventHandles { + handle(slf, slf.currentCamp) + } + } + slf.currentEntity++ + for _, handle := range slf.swapEntityEventHandles { + if slf.entityCounterclockwise { + handle(slf, slf.currentCamp, slf.camps[slf.currentCamp][len(slf.camps[slf.currentCamp])-slf.currentEntity-1]) + } else { + handle(slf, slf.currentCamp, slf.camps[slf.currentCamp][slf.currentEntity]) + } + } +} + +// SkipCamp 跳过当前阵营剩余对象的行动 +func (slf *Round[Data]) SkipCamp() { + slf.currentEntity = -1 +} + +// ActionRefresh 刷新行动超时时间 +func (slf *Round[Data]) ActionRefresh() { + slf.currentEndTime = time.Now().Unix() + slf.ticker.After(slf.actionTimeoutTickerName, slf.actionTimeout, slf.loop) +} + +// ActionFinish 结束行动 +func (slf *Round[Data]) ActionFinish() { + slf.ticker.StopTimer(slf.actionTimeoutTickerName) + if slf.shareAction { + slf.currentEntity = -1 + } else { + slf.currentEntity++ + } + slf.loop() +} + +// GetRound 获取当前回合数 +func (slf *Round[Data]) GetRound() int { + return slf.round +} + +// AllowAction 是否允许行动 +func (slf *Round[Data]) AllowAction(camp, entity int) bool { + return (slf.currentCamp == camp && slf.currentEntity == entity) || slf.shareAction && camp == slf.currentCamp +} + +// CampAllowAction 阵容是否允许行动 +func (slf *Round[Data]) CampAllowAction(camp int) bool { + return slf.currentCamp == camp +} + +// GetCurrentCamp 获取当前行动的阵营 +func (slf *Round[Data]) GetCurrentCamp() int { + return slf.currentCamp +} + +// GetCurrentRoundProgressRate 获取当前回合进度 +func (slf *Round[Data]) GetCurrentRoundProgressRate() float64 { + return float64(slf.roundCount / len(slf.camps)) +} + +// GetCurrent 获取当前行动的阵营和对象 +func (slf *Round[Data]) GetCurrent() (camp, entity int) { + return slf.currentCamp, slf.camps[slf.currentCamp][slf.currentEntity] +} diff --git a/game/fight/round_camp.go b/game/fight/round_camp.go new file mode 100644 index 0000000..348c4fd --- /dev/null +++ b/game/fight/round_camp.go @@ -0,0 +1,15 @@ +package fight + +// NewRoundCamp 创建一个新的回合制游戏阵营 +func NewRoundCamp(campId, entity int, entities ...int) *RoundCamp { + return &RoundCamp{ + campId: campId, + entities: append([]int{entity}, entities...), + } +} + +// RoundCamp 回合制游戏阵营 +type RoundCamp struct { + campId int + entities []int +} diff --git a/game/fight/round_data.go b/game/fight/round_data.go new file mode 100644 index 0000000..01dba29 --- /dev/null +++ b/game/fight/round_data.go @@ -0,0 +1,6 @@ +package fight + +// RoundData 回合制游戏数据 +type RoundData interface { + // 占位 +} diff --git a/game/fight/round_options.go b/game/fight/round_options.go new file mode 100644 index 0000000..96a6570 --- /dev/null +++ b/game/fight/round_options.go @@ -0,0 +1,79 @@ +package fight + +import ( + "github.com/kercylan98/minotaur/utils/timer" + "time" +) + +// RoundOption 回合制游戏选项 +type RoundOption[Data RoundData] func(round *Round[Data]) + +type ( + RoundSwapCampEvent[Data RoundData] func(round *Round[Data], campId int) + RoundSwapEntityEvent[Data RoundData] func(round *Round[Data], campId, entity int) + RoundGameOverEvent[Data RoundData] func(round *Round[Data]) + RoundChangeEvent[Data RoundData] func(round *Round[Data]) +) + +// WithRoundTicker 设置游戏的计时器 +func WithRoundTicker[Data RoundData](ticker *timer.Ticker) RoundOption[Data] { + return func(round *Round[Data]) { + round.ticker = ticker + } +} + +// WithRoundActionTimeout 设置游戏的行动超时时间 +func WithRoundActionTimeout[Data RoundData](timeout time.Duration) RoundOption[Data] { + return func(round *Round[Data]) { + round.actionTimeout = timeout + } +} + +// WithRoundShareAction 设置游戏的行动是否共享 +func WithRoundShareAction[Data RoundData](share bool) RoundOption[Data] { + return func(round *Round[Data]) { + round.shareAction = share + } +} + +// WithRoundSwapCampEvent 设置游戏的阵营交换事件 +func WithRoundSwapCampEvent[Data RoundData](swapCampEventHandle RoundSwapCampEvent[Data]) RoundOption[Data] { + return func(round *Round[Data]) { + round.swapCampEventHandles = append(round.swapCampEventHandles, swapCampEventHandle) + } +} + +// WithRoundSwapEntityEvent 设置游戏的实体交换事件 +func WithRoundSwapEntityEvent[Data RoundData](swapEntityEventHandle RoundSwapEntityEvent[Data]) RoundOption[Data] { + return func(round *Round[Data]) { + round.swapEntityEventHandles = append(round.swapEntityEventHandles, swapEntityEventHandle) + } +} + +// WithRoundGameOverEvent 设置游戏的结束事件 +func WithRoundGameOverEvent[Data RoundData](gameOverEventHandle RoundGameOverEvent[Data]) RoundOption[Data] { + return func(round *Round[Data]) { + round.gameOverEventHandles = append(round.gameOverEventHandles, gameOverEventHandle) + } +} + +// WithRoundChangeEvent 设置游戏的回合变更事件 +func WithRoundChangeEvent[Data RoundData](changeEventHandle RoundChangeEvent[Data]) RoundOption[Data] { + return func(round *Round[Data]) { + round.changeEventHandles = append(round.changeEventHandles, changeEventHandle) + } +} + +// WithRoundCampCounterclockwise 设置游戏阵营逆序执行 +func WithRoundCampCounterclockwise[Data RoundData]() RoundOption[Data] { + return func(round *Round[Data]) { + round.campCounterclockwise = true + } +} + +// WithRoundEntityCounterclockwise 设置游戏实体逆序执行 +func WithRoundEntityCounterclockwise[Data RoundData]() RoundOption[Data] { + return func(round *Round[Data]) { + round.entityCounterclockwise = true + } +} diff --git a/game/fight/round_test.go b/game/fight/round_test.go new file mode 100644 index 0000000..bd42c6f --- /dev/null +++ b/game/fight/round_test.go @@ -0,0 +1,33 @@ +package fight + +import ( + "sync" + "testing" + "time" +) + +func TestName(t *testing.T) { + var wait sync.WaitGroup + var camps []*RoundCamp + camps = append(camps, NewRoundCamp(1, 1, 2, 3)) + camps = append(camps, NewRoundCamp(2, 4, 5, 6)) + camps = append(camps, NewRoundCamp(3, 7, 8, 9)) + r := NewRound("", camps, func(round *Round[string]) bool { + return round.GetRound() == 2 + }, + WithRoundActionTimeout[string](time.Second), + WithRoundSwapEntityEvent[string](func(round *Round[string], campId, entity int) { + t.Log(time.Now(), "swap entity", round.GetRound(), campId, entity) + }), + WithRoundGameOverEvent[string](func(round *Round[string]) { + t.Log(time.Now(), "game over", round.GetRound()) + wait.Done() + }), + WithRoundCampCounterclockwise[string](), + WithRoundEntityCounterclockwise[string](), + ) + + wait.Add(1) + r.Run() + wait.Wait() +}