diff --git a/utils/g2d/g2d.go b/utils/g2d/g2d.go index e2b9dbc..00a4bca 100644 --- a/utils/g2d/g2d.go +++ b/utils/g2d/g2d.go @@ -10,35 +10,6 @@ func PositionArrayToXY(position [2]int) (x, y int) { return position[0], position[1] } -// CoverageAreaBoundless 将一个图形覆盖范围设置为无边的 -// - 例如一个图形的left和top从2开始,那么将被转换到从0开始 -func CoverageAreaBoundless(l, r, t, b int) (left, right, top, bottom int) { - differentX := 0 - l - differentY := 0 - t - left = l + differentX - right = r + differentX - top = t + differentY - bottom = b + differentY - return -} - -// GetShapeCoverageArea 获取一个图形覆盖的范围 -func GetShapeCoverageArea(xys ...[2]int) (left, right, top, bottom int) { - left, top = -1, -1 - for _, xy := range xys { - x, y := PositionArrayToXY(xy) - if x < left || left == -1 { - left = x - } - if x > right { - right = x - } - if y < top || top == -1 { - top = y - } - if y > bottom { - bottom = y - } - } - return +func PositionClone(position [2]int) [2]int { + return [2]int{position[0], position[1]} } diff --git a/utils/g2d/matrix/match3.go b/utils/g2d/matrix/match3.go index c47ac13..f497f3d 100644 --- a/utils/g2d/matrix/match3.go +++ b/utils/g2d/matrix/match3.go @@ -1,17 +1,20 @@ package matrix import ( + "github.com/kercylan98/minotaur/utils/g2d" "github.com/kercylan98/minotaur/utils/synchronization" "sync" ) func NewMatch3[ItemType comparable, Item Match3Item[ItemType]](width, height int, options ...Match3Option[ItemType, Item]) *Match3[ItemType, Item] { match3 := &Match3[ItemType, Item]{ - matrix: NewMatrix[Item](width, height), - generators: map[ItemType]func() Item{}, - links: synchronization.NewMap[int64, map[int64]bool](), - positions: map[int64][2]int{}, - notNil: map[int]map[int]bool{}, + matrix: NewMatrix[Item](width, height), + generators: map[ItemType]func() Item{}, + links: synchronization.NewMap[int64, map[int64]bool](), + positions: map[int64][2]int{}, + notNil: map[int]map[int]bool{}, + matchStrategy: map[int]func(matrix [][]Item) [][]Item{}, + generateNotMatchRetryCount: 3, } for x := 0; x < width; x++ { match3.notNil[x] = map[int]bool{} @@ -19,19 +22,28 @@ func NewMatch3[ItemType comparable, Item Match3Item[ItemType]](width, height int for _, option := range options { option(match3) } + if len(match3.generators) == 0 { + panic("please use WithMatch3Generator set at least one generation strategy") + } + if len(match3.matchStrategy) == 0 { + panic("please use WithMatch3Strategy set at least one match strategy") + } return match3 } // Match3 基于三消类游戏的二维矩阵 // - 提供了适合三消类游戏的功能 type Match3[ItemType comparable, Item Match3Item[ItemType]] struct { - matrix *Matrix[Item] - guid int64 // 成员guid - generators map[ItemType]func() Item // 成员生成器 - revokes []func() // 撤销记录 - links *synchronization.Map[int64, map[int64]bool] // 成员类型相同且相连的链接 - positions map[int64][2]int // 根据成员guid记录的成员位置 - notNil map[int]map[int]bool // 特定位置是否不为空 + matrix *Matrix[Item] + + guid int64 // 成员guid + links *synchronization.Map[int64, map[int64]bool] // 成员类型相同且相连的链接 + positions map[int64][2]int // 根据成员guid记录的成员位置 + notNil map[int]map[int]bool // 特定位置是否不为空 + + generators map[ItemType]func() Item // 成员生成器 + matchStrategy map[int]func(matrix [][]Item) [][]Item // 匹配策略 + generateNotMatchRetryCount int // 生成不匹配类型重试次数 } // GetHeight 获取高度 @@ -44,42 +56,84 @@ func (slf *Match3[ItemType, Item]) GetWidth() int { return slf.matrix.w } -// Revoke 撤销特定步数 -func (slf *Match3[ItemType, Item]) Revoke(step int) { - if step <= 0 { - return - } - if step > len(slf.revokes) { - step = len(slf.revokes) - } - for i := 0; i < step; i++ { - slf.revokes[i]() - } - slf.revokes = slf.revokes[step:] -} - -// RevokeAll 撤销全部 -func (slf *Match3[ItemType, Item]) RevokeAll() { - slf.Revoke(len(slf.revokes)) -} - -// RevokeClear 清除所有撤销记录 -func (slf *Match3[ItemType, Item]) RevokeClear() { - slf.revokes = slf.revokes[:0] -} - // GenerateItem 在特定位置生成特定类型的成员 func (slf *Match3[ItemType, Item]) GenerateItem(x, y int, itemType ItemType) Item { - slf.addRevoke(func() { - item := slf.matrix.m[x][y] - slf.set(x, y, item) - }) item := slf.generators[itemType]() item.SetGuid(slf.getNextGuid()) slf.set(x, y, item) return item } +// Predict 预言 +func (slf *Match3[ItemType, Item]) Predict() { + // TODO +} + +// GenerateItemsByNotMatch 生成一批在特定位置不会触发任何匹配规则的成员类型 +// - 这一批成员不会被加入到矩阵中,索引与位置索引相对应 +// - 无解的策略下会导致死循环 +func (slf *Match3[ItemType, Item]) GenerateItemsByNotMatch(xys ...[2]int) (result []ItemType) { + result = make([]ItemType, 0, len(xys)) + lastIndex := len(xys) - 1 + retry := 0 + backup := NewBackup(slf) +start: + { + for i, xy := range xys { + x, y := g2d.PositionArrayToXY(xy) + var match bool + for _, f := range slf.generators { + slf.set(x, y, f()) + for i := 1; i <= len(slf.matchStrategy); i++ { + if len(slf.matchStrategy[i](slf.matrix.m)) > 0 { + match = true + break + } + } + if !match { + break + } + } + if match { + if i == lastIndex { + if retry < slf.generateNotMatchRetryCount { + retry++ + result = result[:0] + backup.Restore() + goto start + } else { + panic("no solution, the matrix rule is wrong or there are matching members.") + } + } else { + result = result[:0] + backup.Restore() + goto start + } + } + result = append(result, slf.matrix.m[x][y].GetType()) + } + } + return +} + +// GetMatch 获取二维矩阵 +// - 该矩阵为克隆的,意味着任何修改都不会影响原有内容 +func (slf *Match3[ItemType, Item]) GetMatch() [][]Item { + var ( + width = slf.GetWidth() + height = slf.GetHeight() + clone = make([][]Item[ItemType], width) + ) + for x := 0; x < width; x++ { + ys := make([]Item, height) + for y := 0; y < height; y++ { + ys[y] = slf.matrix.m[x][y].Clone().(Item) + } + clone[x] = ys + } + return clone +} + // 设置特定位置的成员 func (slf *Match3[ItemType, Item]) set(x, y int, item Item) { if old := slf.matrix.m[x][y]; slf.notNil[x][y] { @@ -165,14 +219,6 @@ func (slf *Match3[ItemType, Item]) searchNeighbour(x, y int, filter *synchroniza // 获取下一个guid func (slf *Match3[ItemType, Item]) getNextGuid() int64 { - slf.addRevoke(func() { - slf.guid-- - }) slf.guid++ return slf.guid } - -// 添加撤销记录 -func (slf *Match3[ItemType, Item]) addRevoke(revoke func()) { - slf.revokes = append([]func(){revoke}, slf.revokes...) -} diff --git a/utils/g2d/matrix/match3_backup.go b/utils/g2d/matrix/match3_backup.go new file mode 100644 index 0000000..7d009c8 --- /dev/null +++ b/utils/g2d/matrix/match3_backup.go @@ -0,0 +1,60 @@ +package matrix + +import ( + "github.com/kercylan98/minotaur/utils/g2d" + "github.com/kercylan98/minotaur/utils/hash" + "github.com/kercylan98/minotaur/utils/synchronization" +) + +func NewBackup[ItemType comparable, Item Match3Item[ItemType]](match3 *Match3[ItemType, Item]) *Match3Backup[ItemType, Item] { + backup := &Match3Backup[ItemType, Item]{match3: match3} + + backup.guid = match3.guid + backup.links = synchronization.NewMap[int64, map[int64]bool]() + match3.links.Range(func(key int64, value map[int64]bool) { + backup.links.Set(key, hash.Copy(value)) + }) + backup.positions = map[int64][2]int{} + for key, value := range match3.positions { + backup.positions[key] = g2d.PositionClone(value) + } + backup.notNil = map[int]map[int]bool{} + for key, values := range match3.notNil { + var notNil = map[int]bool{} + for key, value := range values { + notNil[key] = value + } + backup.notNil[key] = notNil + } + return backup +} + +type Match3Backup[ItemType comparable, Item Match3Item[ItemType]] struct { + match3 *Match3[ItemType, Item] + + guid int64 // 成员guid + links *synchronization.Map[int64, map[int64]bool] // 成员类型相同且相连的链接 + positions map[int64][2]int // 根据成员guid记录的成员位置 + notNil map[int]map[int]bool // 特定位置是否不为空 +} + +// Restore 还原备份 +func (slf *Match3Backup[ItemType, Item]) Restore() { + slf.match3.guid = slf.guid + slf.match3.links = synchronization.NewMap[int64, map[int64]bool]() + slf.links.Range(func(key int64, value map[int64]bool) { + slf.match3.links.Set(key, hash.Copy(value)) + }) + slf.match3.positions = map[int64][2]int{} + for key, value := range slf.positions { + slf.match3.positions[key] = g2d.PositionClone(value) + } + slf.match3.notNil = map[int]map[int]bool{} + for key, values := range slf.notNil { + var notNil = map[int]bool{} + for key, value := range values { + notNil[key] = value + } + slf.match3.notNil[key] = notNil + } +} diff --git a/utils/g2d/matrix/match3_item.go b/utils/g2d/matrix/match3_item.go index 18f96ee..99f3d78 100644 --- a/utils/g2d/matrix/match3_item.go +++ b/utils/g2d/matrix/match3_item.go @@ -8,4 +8,6 @@ type Match3Item[Type comparable] interface { GetGuid() int64 // GetType 获取成员类型 GetType() Type + // Clone 克隆 + Clone() Match3Item[Type] } diff --git a/utils/g2d/matrix/match3_options.go b/utils/g2d/matrix/match3_options.go index d96285f..22414a2 100644 --- a/utils/g2d/matrix/match3_options.go +++ b/utils/g2d/matrix/match3_options.go @@ -7,3 +7,11 @@ func WithMatch3Generator[ItemType comparable, Item Match3Item[ItemType]](itemTyp match3.generators[itemType] = generator } } + +// WithMatch3Tactics 设置匹配策略 +// - 匹配策略用于匹配出对应成员 +func WithMatch3Tactics[ItemType comparable, Item Match3Item[ItemType]](tactics func(matrix [][]Item) [][]Item) Match3Option[ItemType, Item] { + return func(match3 *Match3[ItemType, Item]) { + match3.matchStrategy[len(match3.matchStrategy)+1] = tactics + } +} diff --git a/utils/g2d/matrix/match3_test.go b/utils/g2d/matrix/match3_test.go index dd54476..dff579e 100644 --- a/utils/g2d/matrix/match3_test.go +++ b/utils/g2d/matrix/match3_test.go @@ -25,6 +25,13 @@ func (slf *Item[Type]) GetType() Type { return slf.t } +func (slf *Item[Type]) Clone() Match3Item[Type] { + return &Item[Type]{ + guid: slf.guid, + t: slf.t, + } +} + func TestMatch3(t *testing.T) { var options []Match3Option[int, *Item[int]] for i := 0; i < 7; i++ { diff --git a/utils/g2d/shape.go b/utils/g2d/shape.go index 5f55a88..7e393a9 100644 --- a/utils/g2d/shape.go +++ b/utils/g2d/shape.go @@ -615,3 +615,36 @@ func GenerateShape(xys ...[2]int) [][]bool { } return m } + +// CoverageAreaBoundless 将一个图形覆盖范围设置为无边的 +// - 例如一个图形的left和top从2开始,那么将被转换到从0开始 +func CoverageAreaBoundless(l, r, t, b int) (left, right, top, bottom int) { + differentX := 0 - l + differentY := 0 - t + left = l + differentX + right = r + differentX + top = t + differentY + bottom = b + differentY + return +} + +// GetShapeCoverageArea 获取一个图形覆盖的范围 +func GetShapeCoverageArea(xys ...[2]int) (left, right, top, bottom int) { + left, top = -1, -1 + for _, xy := range xys { + x, y := PositionArrayToXY(xy) + if x < left || left == -1 { + left = x + } + if x > right { + right = x + } + if y < top || top == -1 { + top = y + } + if y > bottom { + bottom = y + } + } + return +}