功能新增:生成一批在特定位置不会触发任何匹配规则的成员类型、匹配策略
This commit is contained in:
parent
319bad13bd
commit
2ddf2217bd
|
@ -10,35 +10,6 @@ func PositionArrayToXY(position [2]int) (x, y int) {
|
||||||
return position[0], position[1]
|
return position[0], position[1]
|
||||||
}
|
}
|
||||||
|
|
||||||
// CoverageAreaBoundless 将一个图形覆盖范围设置为无边的
|
func PositionClone(position [2]int) [2]int {
|
||||||
// - 例如一个图形的left和top从2开始,那么将被转换到从0开始
|
return [2]int{position[0], position[1]}
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,20 @@
|
||||||
package matrix
|
package matrix
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/kercylan98/minotaur/utils/g2d"
|
||||||
"github.com/kercylan98/minotaur/utils/synchronization"
|
"github.com/kercylan98/minotaur/utils/synchronization"
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewMatch3[ItemType comparable, Item Match3Item[ItemType]](width, height int, options ...Match3Option[ItemType, Item]) *Match3[ItemType, Item] {
|
func NewMatch3[ItemType comparable, Item Match3Item[ItemType]](width, height int, options ...Match3Option[ItemType, Item]) *Match3[ItemType, Item] {
|
||||||
match3 := &Match3[ItemType, Item]{
|
match3 := &Match3[ItemType, Item]{
|
||||||
matrix: NewMatrix[Item](width, height),
|
matrix: NewMatrix[Item](width, height),
|
||||||
generators: map[ItemType]func() Item{},
|
generators: map[ItemType]func() Item{},
|
||||||
links: synchronization.NewMap[int64, map[int64]bool](),
|
links: synchronization.NewMap[int64, map[int64]bool](),
|
||||||
positions: map[int64][2]int{},
|
positions: map[int64][2]int{},
|
||||||
notNil: map[int]map[int]bool{},
|
notNil: map[int]map[int]bool{},
|
||||||
|
matchStrategy: map[int]func(matrix [][]Item) [][]Item{},
|
||||||
|
generateNotMatchRetryCount: 3,
|
||||||
}
|
}
|
||||||
for x := 0; x < width; x++ {
|
for x := 0; x < width; x++ {
|
||||||
match3.notNil[x] = map[int]bool{}
|
match3.notNil[x] = map[int]bool{}
|
||||||
|
@ -19,19 +22,28 @@ func NewMatch3[ItemType comparable, Item Match3Item[ItemType]](width, height int
|
||||||
for _, option := range options {
|
for _, option := range options {
|
||||||
option(match3)
|
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
|
return match3
|
||||||
}
|
}
|
||||||
|
|
||||||
// Match3 基于三消类游戏的二维矩阵
|
// Match3 基于三消类游戏的二维矩阵
|
||||||
// - 提供了适合三消类游戏的功能
|
// - 提供了适合三消类游戏的功能
|
||||||
type Match3[ItemType comparable, Item Match3Item[ItemType]] struct {
|
type Match3[ItemType comparable, Item Match3Item[ItemType]] struct {
|
||||||
matrix *Matrix[Item]
|
matrix *Matrix[Item]
|
||||||
guid int64 // 成员guid
|
|
||||||
generators map[ItemType]func() Item // 成员生成器
|
guid int64 // 成员guid
|
||||||
revokes []func() // 撤销记录
|
links *synchronization.Map[int64, map[int64]bool] // 成员类型相同且相连的链接
|
||||||
links *synchronization.Map[int64, map[int64]bool] // 成员类型相同且相连的链接
|
positions map[int64][2]int // 根据成员guid记录的成员位置
|
||||||
positions map[int64][2]int // 根据成员guid记录的成员位置
|
notNil map[int]map[int]bool // 特定位置是否不为空
|
||||||
notNil map[int]map[int]bool // 特定位置是否不为空
|
|
||||||
|
generators map[ItemType]func() Item // 成员生成器
|
||||||
|
matchStrategy map[int]func(matrix [][]Item) [][]Item // 匹配策略
|
||||||
|
generateNotMatchRetryCount int // 生成不匹配类型重试次数
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetHeight 获取高度
|
// GetHeight 获取高度
|
||||||
|
@ -44,42 +56,84 @@ func (slf *Match3[ItemType, Item]) GetWidth() int {
|
||||||
return slf.matrix.w
|
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 在特定位置生成特定类型的成员
|
// GenerateItem 在特定位置生成特定类型的成员
|
||||||
func (slf *Match3[ItemType, Item]) GenerateItem(x, y int, itemType ItemType) Item {
|
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 := slf.generators[itemType]()
|
||||||
item.SetGuid(slf.getNextGuid())
|
item.SetGuid(slf.getNextGuid())
|
||||||
slf.set(x, y, item)
|
slf.set(x, y, item)
|
||||||
return 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) {
|
func (slf *Match3[ItemType, Item]) set(x, y int, item Item) {
|
||||||
if old := slf.matrix.m[x][y]; slf.notNil[x][y] {
|
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
|
// 获取下一个guid
|
||||||
func (slf *Match3[ItemType, Item]) getNextGuid() int64 {
|
func (slf *Match3[ItemType, Item]) getNextGuid() int64 {
|
||||||
slf.addRevoke(func() {
|
|
||||||
slf.guid--
|
|
||||||
})
|
|
||||||
slf.guid++
|
slf.guid++
|
||||||
return slf.guid
|
return slf.guid
|
||||||
}
|
}
|
||||||
|
|
||||||
// 添加撤销记录
|
|
||||||
func (slf *Match3[ItemType, Item]) addRevoke(revoke func()) {
|
|
||||||
slf.revokes = append([]func(){revoke}, slf.revokes...)
|
|
||||||
}
|
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -8,4 +8,6 @@ type Match3Item[Type comparable] interface {
|
||||||
GetGuid() int64
|
GetGuid() int64
|
||||||
// GetType 获取成员类型
|
// GetType 获取成员类型
|
||||||
GetType() Type
|
GetType() Type
|
||||||
|
// Clone 克隆
|
||||||
|
Clone() Match3Item[Type]
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,3 +7,11 @@ func WithMatch3Generator[ItemType comparable, Item Match3Item[ItemType]](itemTyp
|
||||||
match3.generators[itemType] = generator
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -25,6 +25,13 @@ func (slf *Item[Type]) GetType() Type {
|
||||||
return slf.t
|
return slf.t
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (slf *Item[Type]) Clone() Match3Item[Type] {
|
||||||
|
return &Item[Type]{
|
||||||
|
guid: slf.guid,
|
||||||
|
t: slf.t,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestMatch3(t *testing.T) {
|
func TestMatch3(t *testing.T) {
|
||||||
var options []Match3Option[int, *Item[int]]
|
var options []Match3Option[int, *Item[int]]
|
||||||
for i := 0; i < 7; i++ {
|
for i := 0; i < 7; i++ {
|
||||||
|
|
|
@ -615,3 +615,36 @@ func GenerateShape(xys ...[2]int) [][]bool {
|
||||||
}
|
}
|
||||||
return m
|
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
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue