From 40184121ceaf96662f47b862ba7e29d85071511b Mon Sep 17 00:00:00 2001 From: kercylan <61743331+kercylan98@users.noreply.github.com> Date: Sun, 4 Jun 2023 12:15:10 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=BA=8C=E7=BB=B4=E7=9F=A9?= =?UTF-8?q?=E5=BD=A2=E7=9B=B8=E5=85=B3=E8=BE=85=E5=8A=A9=E5=87=BD=E6=95=B0?= =?UTF-8?q?=EF=BC=8C=E4=B8=89=E6=B6=88=E7=B1=BB=E6=B8=B8=E6=88=8F=E5=AE=9E?= =?UTF-8?q?=E7=8E=B0=EF=BC=88=E6=9C=AA=E5=AE=8C=E6=88=90=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- utils/g2d/g2d.go | 47 ++++++-- utils/g2d/matrix/match3.go | 178 +++++++++++++++++++++++++++++ utils/g2d/matrix/match3_item.go | 11 ++ utils/g2d/matrix/match3_options.go | 9 ++ utils/g2d/matrix/match3_test.go | 76 ++++++++++++ utils/g2d/shape.go | 114 ++++++++++++++++++ utils/g2d/shape_test.go | 18 +++ 7 files changed, 446 insertions(+), 7 deletions(-) create mode 100644 utils/g2d/matrix/match3.go create mode 100644 utils/g2d/matrix/match3_item.go create mode 100644 utils/g2d/matrix/match3_options.go create mode 100644 utils/g2d/matrix/match3_test.go create mode 100644 utils/g2d/shape_test.go diff --git a/utils/g2d/g2d.go b/utils/g2d/g2d.go index 6c57570..0324fd6 100644 --- a/utils/g2d/g2d.go +++ b/utils/g2d/g2d.go @@ -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 } diff --git a/utils/g2d/matrix/match3.go b/utils/g2d/matrix/match3.go new file mode 100644 index 0000000..c47ac13 --- /dev/null +++ b/utils/g2d/matrix/match3.go @@ -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...) +} diff --git a/utils/g2d/matrix/match3_item.go b/utils/g2d/matrix/match3_item.go new file mode 100644 index 0000000..18f96ee --- /dev/null +++ b/utils/g2d/matrix/match3_item.go @@ -0,0 +1,11 @@ +package matrix + +// Match3Item 三消成员接口定义 +type Match3Item[Type comparable] interface { + // SetGuid 设置guid + SetGuid(guid int64) + // GetGuid 获取guid + GetGuid() int64 + // GetType 获取成员类型 + GetType() Type +} diff --git a/utils/g2d/matrix/match3_options.go b/utils/g2d/matrix/match3_options.go new file mode 100644 index 0000000..d96285f --- /dev/null +++ b/utils/g2d/matrix/match3_options.go @@ -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 + } +} diff --git a/utils/g2d/matrix/match3_test.go b/utils/g2d/matrix/match3_test.go new file mode 100644 index 0000000..5e28ae3 --- /dev/null +++ b/utils/g2d/matrix/match3_test.go @@ -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])) + } +} diff --git a/utils/g2d/shape.go b/utils/g2d/shape.go index 85c8ee5..6e167db 100644 --- a/utils/g2d/shape.go +++ b/utils/g2d/shape.go @@ -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 +} diff --git a/utils/g2d/shape_test.go b/utils/g2d/shape_test.go new file mode 100644 index 0000000..a4e28f1 --- /dev/null +++ b/utils/g2d/shape_test.go @@ -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() + } +}