功能新增:生成一批在特定位置不会触发任何匹配规则的成员类型、匹配策略

This commit is contained in:
kercylan 2023-06-04 20:15:20 +08:00
parent 319bad13bd
commit 2ddf2217bd
7 changed files with 206 additions and 79 deletions

View File

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

View File

@ -1,6 +1,7 @@
package matrix
import (
"github.com/kercylan98/minotaur/utils/g2d"
"github.com/kercylan98/minotaur/utils/synchronization"
"sync"
)
@ -12,6 +13,8 @@ func NewMatch3[ItemType comparable, Item Match3Item[ItemType]](width, height int
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,6 +22,12 @@ 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
}
@ -26,12 +35,15 @@ func NewMatch3[ItemType comparable, Item Match3Item[ItemType]](width, height int
// - 提供了适合三消类游戏的功能
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 // 特定位置是否不为空
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...)
}

View File

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

View File

@ -8,4 +8,6 @@ type Match3Item[Type comparable] interface {
GetGuid() int64
// GetType 获取成员类型
GetType() Type
// Clone 克隆
Clone() Match3Item[Type]
}

View File

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

View File

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

View File

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