增加二维矩形相关辅助函数,三消类游戏实现(未完成)
This commit is contained in:
parent
06133d1071
commit
40184121ce
|
@ -1,10 +1,43 @@
|
|||
package g2d
|
||||
|
||||
import (
|
||||
"github.com/kercylan98/minotaur/utils/g2d/matrix"
|
||||
)
|
||||
|
||||
// NewMatrix 生成特定宽高的二维矩阵
|
||||
func NewMatrix[T any](width, height int) *matrix.Matrix[T] {
|
||||
return matrix.NewMatrix[T](width, height)
|
||||
// PositionToArray 将坐标转换为x、y的数组
|
||||
func PositionToArray(x, y int) [2]int {
|
||||
return [2]int{x, y}
|
||||
}
|
||||
|
||||
// PositionArrayToXY 将坐标数组转换为x和y坐标
|
||||
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) {
|
||||
for _, xy := range xys {
|
||||
x, y := PositionArrayToXY(xy)
|
||||
if x < left {
|
||||
left = x
|
||||
}
|
||||
if x > right {
|
||||
right = x
|
||||
}
|
||||
if y < top {
|
||||
top = y
|
||||
}
|
||||
if y > bottom {
|
||||
bottom = y
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
|
@ -0,0 +1,178 @@
|
|||
package matrix
|
||||
|
||||
import (
|
||||
"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{},
|
||||
}
|
||||
for x := 0; x < width; x++ {
|
||||
match3.notNil[x] = map[int]bool{}
|
||||
}
|
||||
for _, option := range options {
|
||||
option(match3)
|
||||
}
|
||||
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 // 特定位置是否不为空
|
||||
}
|
||||
|
||||
// GetHeight 获取高度
|
||||
func (slf *Match3[ItemType, Item]) GetHeight() int {
|
||||
return slf.matrix.h
|
||||
}
|
||||
|
||||
// GetWidth 获取宽度
|
||||
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
|
||||
}
|
||||
|
||||
// 设置特定位置的成员
|
||||
func (slf *Match3[ItemType, Item]) set(x, y int, item Item) {
|
||||
if old := slf.matrix.m[x][y]; slf.notNil[x][y] {
|
||||
oldGuid := old.GetGuid()
|
||||
for linkGuid := range slf.links.Get(oldGuid) {
|
||||
xy := slf.positions[linkGuid]
|
||||
slf.searchNeighbour(xy[0], xy[1], synchronization.NewMap[int64, bool](), synchronization.NewMap[int64, bool]())
|
||||
}
|
||||
slf.links.Delete(oldGuid)
|
||||
}
|
||||
|
||||
slf.notNil[x][y] = true
|
||||
slf.matrix.Set(x, y, item)
|
||||
slf.positions[item.GetGuid()] = [2]int{x, y}
|
||||
slf.searchNeighbour(x, y, synchronization.NewMap[int64, bool](), synchronization.NewMap[int64, bool]())
|
||||
}
|
||||
|
||||
func (slf *Match3[ItemType, Item]) searchNeighbour(x, y int, filter *synchronization.Map[int64, bool], childrenLinks *synchronization.Map[int64, bool]) {
|
||||
var (
|
||||
item = slf.matrix.m[x][y]
|
||||
neighboursLock sync.Mutex
|
||||
neighbours = map[int64]bool{}
|
||||
itemType = item.GetType()
|
||||
wait sync.WaitGroup
|
||||
itemGuid = item.GetGuid()
|
||||
handle = func(x, y int) bool {
|
||||
neighbour := slf.matrix.m[x][y]
|
||||
if !slf.notNil[x][y] || neighbour.GetType() != itemType {
|
||||
return false
|
||||
}
|
||||
neighbourGuid := neighbour.GetGuid()
|
||||
neighboursLock.Lock()
|
||||
neighbours[neighbourGuid] = true
|
||||
neighboursLock.Unlock()
|
||||
childrenLinks.Set(neighbourGuid, true)
|
||||
slf.searchNeighbour(x, y, filter, childrenLinks)
|
||||
return true
|
||||
}
|
||||
)
|
||||
if filter.Get(itemGuid) {
|
||||
return
|
||||
}
|
||||
filter.Set(itemGuid, true)
|
||||
wait.Add(4)
|
||||
go func() {
|
||||
for sy := y - 1; sy >= 0; sy-- {
|
||||
if !handle(x, sy) {
|
||||
break
|
||||
}
|
||||
}
|
||||
wait.Done()
|
||||
}()
|
||||
go func() {
|
||||
for sy := y + 1; sy < slf.matrix.h; sy++ {
|
||||
if !handle(x, sy) {
|
||||
break
|
||||
}
|
||||
}
|
||||
wait.Done()
|
||||
}()
|
||||
go func() {
|
||||
for sx := x - 1; sx >= 0; sx-- {
|
||||
if !handle(sx, y) {
|
||||
break
|
||||
}
|
||||
}
|
||||
wait.Done()
|
||||
}()
|
||||
go func() {
|
||||
for sx := x + 1; sx < slf.matrix.w; sx++ {
|
||||
if !handle(sx, y) {
|
||||
break
|
||||
}
|
||||
}
|
||||
wait.Done()
|
||||
}()
|
||||
wait.Wait()
|
||||
childrenLinks.Range(func(key int64, value bool) {
|
||||
neighbours[key] = value
|
||||
})
|
||||
slf.links.Set(itemGuid, neighbours)
|
||||
}
|
||||
|
||||
// 获取下一个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...)
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package matrix
|
||||
|
||||
// Match3Item 三消成员接口定义
|
||||
type Match3Item[Type comparable] interface {
|
||||
// SetGuid 设置guid
|
||||
SetGuid(guid int64)
|
||||
// GetGuid 获取guid
|
||||
GetGuid() int64
|
||||
// GetType 获取成员类型
|
||||
GetType() Type
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
package matrix
|
||||
|
||||
type Match3Option[ItemType comparable, Item Match3Item[ItemType]] func(match3 *Match3[ItemType, Item])
|
||||
|
||||
func WithMatch3Generator[ItemType comparable, Item Match3Item[ItemType]](itemType ItemType, generator func() Item) Match3Option[ItemType, Item] {
|
||||
return func(match3 *Match3[ItemType, Item]) {
|
||||
match3.generators[itemType] = generator
|
||||
}
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
package matrix
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/kercylan98/minotaur/utils/g2d"
|
||||
"github.com/kercylan98/minotaur/utils/random"
|
||||
"testing"
|
||||
)
|
||||
|
||||
type Item[Type comparable] struct {
|
||||
guid int64
|
||||
t Type
|
||||
}
|
||||
|
||||
func (slf *Item[Type]) SetGuid(guid int64) {
|
||||
slf.guid = guid
|
||||
}
|
||||
|
||||
func (slf *Item[Type]) GetGuid() int64 {
|
||||
return slf.guid
|
||||
}
|
||||
|
||||
func (slf *Item[Type]) GetType() Type {
|
||||
return slf.t
|
||||
}
|
||||
|
||||
func TestMatch3(t *testing.T) {
|
||||
var options []Match3Option[int, *Item[int]]
|
||||
for i := 0; i < 7; i++ {
|
||||
t := i + 1
|
||||
options = append(options, WithMatch3Generator[int, *Item[int]](t, func() *Item[int] {
|
||||
return &Item[int]{t: t}
|
||||
}))
|
||||
}
|
||||
var match3 = NewMatch3[int, *Item[int]](9, 9,
|
||||
options...,
|
||||
)
|
||||
|
||||
for x := 0; x < match3.GetWidth(); x++ {
|
||||
for y := 0; y < match3.GetHeight(); y++ {
|
||||
match3.GenerateItem(x, y, random.Int(1, 2))
|
||||
}
|
||||
}
|
||||
|
||||
for y := 0; y < match3.GetHeight(); y++ {
|
||||
for x := 0; x < match3.GetWidth(); x++ {
|
||||
fmt.Print(match3.matrix.m[x][y].t, " ")
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
fmt.Println()
|
||||
links := match3.links.Get(40)
|
||||
linkItem := match3.matrix.m[match3.positions[40][0]][match3.positions[40][1]]
|
||||
fmt.Println("LINK", linkItem.t, match3.positions[40])
|
||||
|
||||
for y := 0; y < match3.GetHeight(); y++ {
|
||||
for x := 0; x < match3.GetWidth(); x++ {
|
||||
item := match3.matrix.m[x][y]
|
||||
if links[item.guid] {
|
||||
fmt.Print("*", " ")
|
||||
} else {
|
||||
fmt.Print(match3.matrix.m[x][y].t, " ")
|
||||
}
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
var xys [][2]int
|
||||
for guid := range links {
|
||||
xys = append(xys, match3.positions[guid])
|
||||
}
|
||||
|
||||
for _, rect := range g2d.SearchNotRepeatFullRectangle(xys...) {
|
||||
fmt.Println(fmt.Sprintf("找到矩形: TopLeft: (%d, %d), BottomRight: (%d, %d)", rect[0][0], rect[0][1], rect[1][0], rect[1][1]))
|
||||
}
|
||||
}
|
|
@ -132,3 +132,117 @@ func MatrixShapeSearchWithXY[T any, Mark any](matrix [][]T, shapes []*shape.Shap
|
|||
|
||||
return result
|
||||
}
|
||||
|
||||
// SearchNotRepeatFullRectangle 在一组二维坐标中从大到小搜索不重复的填充满的矩形
|
||||
// - 不重复指一个位置被使用后将不会被其他矩形使用
|
||||
// - 返回值表示了匹配的形状的左上角和右下角的点坐标
|
||||
func SearchNotRepeatFullRectangle(xys ...[2]int) (result [][2][2]int) {
|
||||
left, _, top, _ := GetShapeCoverageArea(xys...)
|
||||
rectangleShape := GenerateShape(xys...)
|
||||
record := map[int]map[int]bool{}
|
||||
width := len(rectangleShape)
|
||||
height := len(rectangleShape[0])
|
||||
for x := 0; x < width; x++ {
|
||||
for y := 0; y < height; y++ {
|
||||
record[x] = map[int]bool{}
|
||||
}
|
||||
}
|
||||
|
||||
shapes := GetExpressibleRectangleBySize(width, height, 2, 2)
|
||||
for _, s := range shapes {
|
||||
x, y := 0, 0
|
||||
for {
|
||||
if x+s[0] >= width {
|
||||
x = 0
|
||||
y++
|
||||
}
|
||||
if y+s[1] >= height {
|
||||
break
|
||||
}
|
||||
points := GetRectangleFullPoints(s[0]+1, s[1]+1)
|
||||
find := 0
|
||||
for _, point := range points {
|
||||
px, py := PositionArrayToXY(point)
|
||||
ox, oy := px+x, py+y
|
||||
if record[ox][oy] || !rectangleShape[ox][oy] {
|
||||
find = 0
|
||||
break
|
||||
}
|
||||
find++
|
||||
}
|
||||
if find == len(points) {
|
||||
for _, point := range points {
|
||||
px, py := PositionArrayToXY(point)
|
||||
record[px+x][py+y] = true
|
||||
}
|
||||
result = append(result, [2][2]int{
|
||||
{x + left, y + top}, {x + left + s[0], y + top + s[1]},
|
||||
})
|
||||
}
|
||||
|
||||
x++
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
// GetRectangleFullPoints 获取一个矩形包含的所有点
|
||||
func GetRectangleFullPoints(width, height int) (result [][2]int) {
|
||||
for x := 0; x < width; x++ {
|
||||
for y := 0; y < height; y++ {
|
||||
result = append(result, [2]int{x, y})
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// GetExpressibleRectangle 获取一个宽高可表达的所有矩形形状
|
||||
// - 返回值表示了每一个矩形右下角的x,y位置(左上角始终为0, 0)
|
||||
// - 矩形尺寸由大到小
|
||||
func GetExpressibleRectangle(width, height int) (result [][2]int) {
|
||||
return GetExpressibleRectangleBySize(width, height, 1, 1)
|
||||
}
|
||||
|
||||
// GetExpressibleRectangleBySize 获取一个宽高可表达的所有特定尺寸以上的矩形形状
|
||||
// - 返回值表示了每一个矩形右下角的x,y位置(左上角始终为0, 0)
|
||||
// - 矩形尺寸由大到小
|
||||
func GetExpressibleRectangleBySize(width, height, minWidth, minHeight int) (result [][2]int) {
|
||||
if width == 0 || height == 0 {
|
||||
return nil
|
||||
}
|
||||
width--
|
||||
height--
|
||||
for {
|
||||
rightBottom := [2]int{width, height}
|
||||
result = append(result, rightBottom)
|
||||
if width == 0 && height == 0 || (width < minWidth && height < minHeight) {
|
||||
return
|
||||
}
|
||||
if width == height {
|
||||
width--
|
||||
} else if width < height {
|
||||
width++
|
||||
height--
|
||||
} else if width > height {
|
||||
width--
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// GenerateShape 生成一组二维坐标的形状
|
||||
// - 这个形状将被在一个刚好能容纳形状的矩形中表示
|
||||
// - 为true的位置表示了形状的每一个点
|
||||
func GenerateShape(xys ...[2]int) [][]bool {
|
||||
_, right, _, bottom := CoverageAreaBoundless(GetShapeCoverageArea(xys...))
|
||||
w, h := right+1, bottom+1
|
||||
m := make([][]bool, w)
|
||||
for x := 0; x < w; x++ {
|
||||
m[x] = make([]bool, h)
|
||||
}
|
||||
for _, xy := range xys {
|
||||
x, y := PositionArrayToXY(xy)
|
||||
m[x][y] = true
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
package g2d
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGetShapeCoverageArea(t *testing.T) {
|
||||
for _, xy := range GetExpressibleRectangleBySize(2, 3, 2, 2) {
|
||||
for y := 0; y < xy[1]+1; y++ {
|
||||
for x := 0; x < xy[0]+1; x++ {
|
||||
fmt.Print("0", " ")
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue