diff --git a/utils/g2d/dp/distribution_pattern.go b/utils/g2d/dp/distribution_pattern.go new file mode 100644 index 0000000..c080504 --- /dev/null +++ b/utils/g2d/dp/distribution_pattern.go @@ -0,0 +1,108 @@ +package dp + +import ( + "github.com/kercylan98/minotaur/utils/g2d" + "github.com/kercylan98/minotaur/utils/hash" +) + +// NewDistributionPattern 构建一个分布图实例 +func NewDistributionPattern[Item any](sameKindVerifyHandle func(itemA, itemB Item) bool) *DistributionPattern[Item] { + return &DistributionPattern[Item]{ + links: map[int]map[int]Item{}, + sameKindVerifyHandle: sameKindVerifyHandle, + } +} + +// DistributionPattern 分布图 +type DistributionPattern[Item any] struct { + matrix []Item + links map[int]map[int]Item + sameKindVerifyHandle func(itemA, itemB Item) bool + width int + usePos bool +} + +// LoadMatrix 通过二维矩阵加载分布图 +// - 通过该函数加载的分布图使用的矩阵是复制后的矩阵,因此无法直接通过刷新(Refresh)来更新分布关系 +// - 需要通过直接刷新的方式请使用 LoadMatrixWithPos +func (slf *DistributionPattern[Item]) LoadMatrix(matrix [][]Item) { + slf.LoadMatrixWithPos(g2d.MatrixToPosMatrix(matrix)) + slf.usePos = false +} + +// LoadMatrixWithPos 通过二维矩阵加载分布图 +func (slf *DistributionPattern[Item]) LoadMatrixWithPos(width int, matrix []Item) { + slf.width = width + slf.matrix = matrix + slf.usePos = true + for k := range slf.links { + delete(slf.links, k) + } + for pos, item := range slf.matrix { + slf.buildRelationships(pos, item) + } +} + +// Refresh 刷新特定位置的分布关系 +// - 由于 LoadMatrix 的矩阵是复制后的矩阵,所以任何外部的改动都不会影响到分布图的变化,在这种情况下,刷新将没有任何意义 +// - 需要通过直接刷新的方式请使用 LoadMatrixWithPos 加载矩阵,或者通过 RefreshWithItem 函数进行刷新 +func (slf *DistributionPattern[Item]) Refresh(pos int) { + if !slf.usePos { + return + } + links, exist := slf.links[pos] + if !exist { + slf.buildRelationships(pos, slf.matrix[pos]) + return + } + temp := hash.Copy(links) + for tp := range links { + delete(slf.links, tp) + } + for tp, target := range temp { + slf.buildRelationships(tp, target) + } +} + +// RefreshWithItem 通过特定的成员刷新特定位置的分布关系 +// - 如果矩阵通过 LoadMatrixWithPos 加载,将会重定向至 Refresh +func (slf *DistributionPattern[Item]) RefreshWithItem(pos int, item Item) { + if slf.usePos { + slf.Refresh(pos) + return + } + + slf.matrix[pos] = item + links, exist := slf.links[pos] + if !exist { + slf.buildRelationships(pos, slf.matrix[pos]) + return + } + temp := hash.Copy(links) + for tp := range links { + delete(slf.links, tp) + } + for tp, target := range temp { + slf.buildRelationships(tp, target) + } +} + +// 构建关系 +func (slf *DistributionPattern[Item]) buildRelationships(pos int, item Item) { + links, exist := slf.links[pos] + if !exist { + links = map[int]Item{pos: item} + slf.links[pos] = links + } + + for _, tp := range g2d.GetAdjacentCoordinatesWithPos(slf.matrix, slf.width, pos) { + target := slf.matrix[tp] + if _, exist := links[tp]; exist || !slf.sameKindVerifyHandle(item, target) { + continue + } + + slf.links[tp] = links + links[tp] = target + slf.buildRelationships(tp, target) + } +} diff --git a/utils/g2d/dp/distribution_pattern_test.go b/utils/g2d/dp/distribution_pattern_test.go new file mode 100644 index 0000000..7a5851b --- /dev/null +++ b/utils/g2d/dp/distribution_pattern_test.go @@ -0,0 +1,20 @@ +package dp + +import ( + "fmt" + "testing" +) + +func TestNewDistributionPattern(t *testing.T) { + + dp := NewDistributionPattern[int](func(itemA, itemB int) bool { + return itemA == itemB + }) + + matrix := []int{1, 1, 2, 2, 2, 2, 1, 2, 2} + dp.LoadMatrixWithPos(3, matrix) + + for pos, link := range dp.links { + fmt.Println(pos, link, fmt.Sprintf("%p", link)) + } +} diff --git a/utils/g2d/g2d.go b/utils/g2d/g2d.go index 309b869..bcff311 100644 --- a/utils/g2d/g2d.go +++ b/utils/g2d/g2d.go @@ -90,3 +90,16 @@ func PosToCoordinateX(width, pos int) int { func PosToCoordinateY(width, pos int) int { return pos / width } + +// MatrixToPosMatrix 将二维矩阵转换为顺序的二维矩阵 +func MatrixToPosMatrix[V any](matrix [][]V) (width int, posMatrix []V) { + width = len(matrix) + height := len(matrix[0]) + posMatrix = make([]V, width*height) + for x := 0; x < width; x++ { + for y := 0; y < height; y++ { + posMatrix[CoordinateToPos(width, x, y)] = matrix[x][y] + } + } + return +} diff --git a/utils/g2d/radiation_pattern.go b/utils/g2d/radiation_pattern.go deleted file mode 100644 index 248297f..0000000 --- a/utils/g2d/radiation_pattern.go +++ /dev/null @@ -1,186 +0,0 @@ -package g2d - -import ( - "github.com/kercylan98/minotaur/utils/hash" - "github.com/kercylan98/minotaur/utils/synchronization" - "sync" -) - -func NewRadiationPattern[ItemType comparable, Item RadiationPatternItem[ItemType]](matrix [][]Item, options ...RadiationPatternOption[ItemType, Item]) *RadiationPattern[ItemType, Item] { - var clone = make([][]Item, len(matrix)) - for x := 0; x < len(matrix); x++ { - ys := make([]Item, len(matrix[0])) - for y := 0; y < len(matrix[0]); y++ { - ys[y] = matrix[x][y] - } - clone[x] = ys - } - rp := &RadiationPattern[ItemType, Item]{ - matrix: clone, - links: synchronization.NewMap[int64, map[int64]bool](), - positions: map[int64][2]int{}, - nils: map[int]map[int]bool{}, - } - for _, option := range options { - option(rp) - } - for x := 0; x < len(matrix); x++ { - rp.nils[x] = map[int]bool{} - } - for x := 0; x < len(matrix); x++ { - for y := 0; y < len(matrix[0]); y++ { - item := matrix[x][y] - if rp.excludes[item.GetType()] { - continue - } - rp.positions[item.GetGuid()] = CoordinateToCoordinateArray(x, y) - rp.searchNeighbour(x, y, synchronization.NewMap[int64, bool](), synchronization.NewMap[int64, bool]()) - } - } - return rp -} - -// RadiationPattern 辐射图数据结构 -// - 辐射图用于将一个二维数组里相邻的所有类型相同的成员进行标注 -type RadiationPattern[ItemType comparable, Item RadiationPatternItem[ItemType]] struct { - matrix [][]Item - links *synchronization.Map[int64, map[int64]bool] // 成员类型相同且相连的链接 - positions map[int64][2]int // 根据成员guid记录的成员位置 - nils map[int]map[int]bool // 空位置 - excludes map[ItemType]bool // 排除建立关系的类型 -} - -// GetLinks 获取特定成员能够辐射到的所有成员 -func (slf *RadiationPattern[ItemType, Item]) GetLinks(guid int64) []int64 { - return hash.KeyToSlice(slf.links.Get(guid)) -} - -// GetLinkPositions 获取特定成员能够辐射到的所有成员位置 -func (slf *RadiationPattern[ItemType, Item]) GetLinkPositions(guid int64) [][2]int { - links := slf.links.Get(guid) - var result = make([][2]int, 0, len(links)) - for g := range links { - result = append(result, slf.positions[g]) - } - return result -} - -// GetPosition 获取特定成员的位置 -func (slf *RadiationPattern[ItemType, Item]) GetPosition(guid int64) [2]int { - return slf.positions[guid] -} - -// Remove 移除特定位置的辐射信息 -func (slf *RadiationPattern[ItemType, Item]) Remove(x, y int) { - old := slf.matrix[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) - delete(slf.positions, oldGuid) - slf.nils[x][y] = true -} - -// Refresh 刷新特定位置成员并且更新其辐射信息 -func (slf *RadiationPattern[ItemType, Item]) Refresh(x, y int, item Item) { - if slf.excludes[item.GetType()] { - return - } - slf.Remove(x, y) - - slf.nils[x][y] = false - slf.matrix[x][y] = item - slf.positions[item.GetGuid()] = CoordinateToCoordinateArray(x, y) - slf.searchNeighbour(x, y, synchronization.NewMap[int64, bool](), synchronization.NewMap[int64, bool]()) -} - -// RefreshBySwap 通过交换的方式刷新两个成员的辐射信息 -func (slf *RadiationPattern[ItemType, Item]) RefreshBySwap(x1, y1, x2, y2 int, item1, item2 Item) { - var xys = [][2]int{CoordinateToCoordinateArray(x1, y1), CoordinateToCoordinateArray(x2, y2)} - for _, xy := range xys { - x, y := CoordinateArrayToCoordinate(xy) - slf.Remove(x, y) - } - for i, item := range []Item{item1, item2} { - if slf.excludes[item.GetType()] { - continue - } - x, y := CoordinateArrayToCoordinate(xys[i]) - slf.nils[x][y] = false - slf.matrix[x][y] = item - slf.positions[item.GetGuid()] = CoordinateToCoordinateArray(x, y) - slf.searchNeighbour(x, y, synchronization.NewMap[int64, bool](), synchronization.NewMap[int64, bool]()) - } -} - -func (slf *RadiationPattern[ItemType, Item]) searchNeighbour(x, y int, filter *synchronization.Map[int64, bool], childrenLinks *synchronization.Map[int64, bool]) { - var item = slf.matrix[x][y] - if slf.excludes[item.GetType()] { - return - } - var ( - 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[x][y] - nt := neighbour.GetType() - if slf.excludes[nt] || nt != itemType || slf.nils[x][y] { - 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 < len(slf.matrix[0]); 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 < len(slf.matrix); 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) -} diff --git a/utils/g2d/radiation_pattern_item.go b/utils/g2d/radiation_pattern_item.go deleted file mode 100644 index 7949067..0000000 --- a/utils/g2d/radiation_pattern_item.go +++ /dev/null @@ -1,6 +0,0 @@ -package g2d - -type RadiationPatternItem[Type comparable] interface { - GetGuid() int64 - GetType() Type -} diff --git a/utils/g2d/radiation_pattern_options.go b/utils/g2d/radiation_pattern_options.go deleted file mode 100644 index c3ed618..0000000 --- a/utils/g2d/radiation_pattern_options.go +++ /dev/null @@ -1,14 +0,0 @@ -package g2d - -type RadiationPatternOption[ItemType comparable, Item RadiationPatternItem[ItemType]] func(rp *RadiationPattern[ItemType, Item]) - -func WithRadiationPatternExclude[ItemType comparable, Item RadiationPatternItem[ItemType]](itemType ...ItemType) RadiationPatternOption[ItemType, Item] { - return func(rp *RadiationPattern[ItemType, Item]) { - if rp.excludes == nil { - rp.excludes = map[ItemType]bool{} - } - for _, t := range itemType { - rp.excludes[t] = true - } - } -}