💥 几何库优化

This commit is contained in:
kercylan98 2023-06-17 19:33:59 +08:00
parent d972261164
commit 18b8729a94
15 changed files with 1226 additions and 885 deletions

View File

@ -2,7 +2,7 @@ package components
import (
"github.com/kercylan98/minotaur/component"
"github.com/kercylan98/minotaur/utils/g2d"
"github.com/kercylan98/minotaur/utils/geometry"
"sync"
"time"
)
@ -117,13 +117,13 @@ func (slf *Moving2D) handle() {
for guid, entity := range slf.entities {
entity := entity
x, y := entity.GetPosition()
angle := g2d.CalcAngle(x, y, entity.x, entity.y)
angle := geometry.CalcAngle(x, y, entity.x, entity.y)
moveTime := time.Now().UnixMilli()
interval := float64(moveTime - entity.lastMoveTime)
if interval == 0 {
continue
}
distance := g2d.CalcDistance(x, y, entity.x, entity.y)
distance := geometry.CalcDistance(x, y, entity.x, entity.y)
moveDistance := interval * (entity.GetSpeed() / (slf.timeUnit / 1000 / 1000))
if moveDistance >= distance || (x == entity.x && y == entity.y) {
entity.SetPosition(entity.x, entity.y)
@ -131,7 +131,7 @@ func (slf *Moving2D) handle() {
slf.OnPosition2DDestinationEvent(entity)
continue
} else {
nx, ny := g2d.CalculateNewCoordinate(x, y, angle, moveDistance)
nx, ny := geometry.CalculateNewCoordinate(x, y, angle, moveDistance)
entity.SetPosition(nx, ny)
entity.lastMoveTime = moveTime
slf.OnPosition2DChangeEvent(entity, x, y)

View File

@ -2,7 +2,7 @@ package builtin
import (
"github.com/kercylan98/minotaur/game"
"github.com/kercylan98/minotaur/utils/g2d"
"github.com/kercylan98/minotaur/utils/geometry"
"github.com/kercylan98/minotaur/utils/hash"
"math"
"sync"
@ -177,7 +177,7 @@ func (slf *AOI2D) refresh(entity game.AOIEntity2D) {
focus := slf.focus[guid]
for eg, e := range focus {
ex, ey := e.GetPosition()
if g2d.CalcDistance(x, y, ex, ey) > vision {
if geometry.CalcDistance(x, y, ex, ey) > vision {
delete(focus, eg)
delete(slf.focus[eg], guid)
}
@ -243,13 +243,13 @@ func (slf *AOI2D) rangeVisionAreaEntities(entity game.AOIEntity2D, handle func(g
} else {
areaY = y
}
areaDistance := g2d.CalcDistance(x, y, areaX, areaY)
areaDistance := geometry.CalcDistance(x, y, areaX, areaY)
if areaDistance <= vision {
for eg, e := range slf.areas[w][h] {
if eg == guid {
continue
}
if ex, ey := e.GetPosition(); g2d.CalcDistance(x, y, ex, ey) > vision {
if ex, ey := e.GetPosition(); geometry.CalcDistance(x, y, ex, ey) > vision {
continue
}
handle(eg, e)

View File

@ -1,25 +0,0 @@
package g2d
import "math"
// CalcDistance 计算两点之间的距离
func CalcDistance(x1, y1, x2, y2 float64) float64 {
return math.Sqrt(math.Pow(x2-x1, 2) + math.Pow(y2-y1, 2))
}
// CalcAngle 计算点2位于点1之间的角度
func CalcAngle(x1, y1, x2, y2 float64) float64 {
return math.Atan2(y2-y1, x2-x1) * 180 / math.Pi
}
// CalculateNewCoordinate 根据给定的x、y坐标、角度和距离计算新的坐标
func CalculateNewCoordinate(x, y, angle, distance float64) (newX, newY float64) {
// 将角度转换为弧度
radians := angle * math.Pi / 180.0
// 计算新的坐标
newX = x + distance*math.Cos(radians)
newY = y + distance*math.Sin(radians)
return newX, newY
}

View File

@ -1,26 +0,0 @@
package g2d
// Direction 方向
type Direction uint8
const (
DirectionUp = Direction(iota) // 上方
DirectionDown // 下方
DirectionLeft // 左方
DirectionRight // 右方
)
// CalcDirection 计算点2位于点1的方向
func CalcDirection(x1, y1, x2, y2 float64) Direction {
angle := CalcAngle(x1, y1, x2, y2)
if angle > -45 && angle < 45 {
return DirectionRight
} else if angle > 135 && angle < -135 {
return DirectionLeft
} else if angle > 45 && angle < 135 {
return DirectionUp
} else if angle > -135 && angle < -45 {
return DirectionDown
}
return 0
}

View File

@ -1,2 +0,0 @@
// Package g2d 提供了大量用于2D计算的辅助函数及组件例如图形搜索、辐射关系、适用于矩阵的计算函数等
package g2d

View File

@ -1,821 +0,0 @@
package g2d
import (
"github.com/kercylan98/minotaur/utils/geometry"
"sort"
)
// SearchNotRepeatCross 在一组二维坐标中从大到小搜索不重复交叉(十字)线
// - 不重复指一个位置被使用后将不会被其他交叉线(十字)使用
func SearchNotRepeatCross(xys ...[2]int) (result [][][2]int) {
left, _, top, _ := GetShapeCoverageArea(xys...)
rectangleShape := GenerateShape(xys...)
record := map[int]map[int]bool{}
for x := 0; x < len(rectangleShape); x++ {
for y := 0; y < len(rectangleShape[0]); y++ {
record[x] = map[int]bool{}
}
}
for _, xy := range xys {
var points [][2]int
var find = map[int]bool{}
x, y := geometry.CoordinateArrayToCoordinate(xy)
x = x + (0 - left)
y = y + (0 - top)
// 搜索四个方向
for sx := x - 1; sx >= 0; sx-- {
if !rectangleShape[sx][y] {
break
}
find[1] = true
points = append(points, [2]int{sx + left, y + top})
}
if !find[1] {
continue
}
for sx := x + 1; sx < len(rectangleShape); sx++ {
if !rectangleShape[sx][y] {
break
}
find[2] = true
points = append(points, [2]int{sx + left, y + top})
}
if !find[2] {
continue
}
for sy := y - 1; sy >= 0; sy-- {
if !rectangleShape[x][sy] {
break
}
find[3] = true
points = append(points, [2]int{x + left, sy + top})
}
if !find[3] {
continue
}
for sy := y + 1; sy < len(rectangleShape[0]); sy++ {
if !rectangleShape[x][sy] {
break
}
find[4] = true
points = append(points, [2]int{x + left, sy + top})
}
if !find[4] {
continue
}
result = append(result, append(points, [2]int{x + left, y + top}))
}
sort.Slice(result, func(i, j int) bool {
return len(result[i]) > len(result[j])
})
var notRepeat [][][2]int
for _, points := range result {
var match = true
for _, point := range points {
x, y := geometry.CoordinateArrayToCoordinate(point)
x = x + (0 - left)
y = y + (0 - top)
if record[x][y] {
match = false
break
}
record[x][y] = true
}
if match {
notRepeat = append(notRepeat, points)
}
}
return notRepeat
}
// SearchContainCross 在一组二维坐标中查找是否存在交叉(十字)线
func SearchContainCross(xys ...[2]int) bool {
left, _, top, _ := GetShapeCoverageArea(xys...)
rectangleShape := GenerateShape(xys...)
record := map[int]map[int]bool{}
for x := 0; x < len(rectangleShape); x++ {
for y := 0; y < len(rectangleShape[0]); y++ {
record[x] = map[int]bool{}
}
}
for _, xy := range xys {
var points [][2]int
var find = map[int]bool{}
x, y := geometry.CoordinateArrayToCoordinate(xy)
x = x + (0 - left)
y = y + (0 - top)
// 搜索四个方向
for sx := x - 1; sx >= 0; sx-- {
if !rectangleShape[sx][y] {
break
}
find[1] = true
points = append(points, [2]int{sx + left, y + top})
}
if !find[1] {
continue
}
for sx := x + 1; sx < len(rectangleShape); sx++ {
if !rectangleShape[sx][y] {
break
}
find[2] = true
points = append(points, [2]int{sx + left, y + top})
}
if !find[2] {
continue
}
for sy := y - 1; sy >= 0; sy-- {
if !rectangleShape[x][sy] {
break
}
find[3] = true
points = append(points, [2]int{x + left, sy + top})
}
if !find[3] {
continue
}
for sy := y + 1; sy < len(rectangleShape[0]); sy++ {
if !rectangleShape[x][sy] {
break
}
find[4] = true
points = append(points, [2]int{x + left, sy + top})
}
if !find[4] {
continue
}
return true
}
return false
}
// SearchNotRepeatStraightLine 在一组二维坐标中从大到小搜索不重复的直线
// - 最低需要长度为3
func SearchNotRepeatStraightLine(minLength int, xys ...[2]int) (result [][][2]int) {
if minLength < 3 {
return nil
}
left, _, top, _ := GetShapeCoverageArea(xys...)
rectangleShape := GenerateShape(xys...)
record := map[int]map[int]bool{}
for x := 0; x < len(rectangleShape); x++ {
for y := 0; y < len(rectangleShape[0]); y++ {
record[x] = map[int]bool{}
}
}
for _, xy := range xys {
var points [][2]int
var find = map[int]bool{}
x, y := geometry.CoordinateArrayToCoordinate(xy)
x = x + (0 - left)
y = y + (0 - top)
// 搜索四个方向
for sx := x - 1; sx >= 0; sx-- {
if !rectangleShape[sx][y] {
break
}
find[1] = true
points = append(points, [2]int{sx + left, y + top})
}
for sx := x + 1; sx < len(rectangleShape); sx++ {
if !rectangleShape[sx][y] {
break
}
find[2] = true
points = append(points, [2]int{sx + left, y + top})
}
if len(find) == 0 {
points = nil
} else if len(points) >= minLength-1 {
goto end
} else {
points = nil
}
for sy := y - 1; sy >= 0; sy-- {
if !rectangleShape[x][sy] {
break
}
find[3] = true
points = append(points, [2]int{x + left, sy + top})
}
for sy := y + 1; sy < len(rectangleShape[0]); sy++ {
if !rectangleShape[x][sy] {
break
}
find[4] = true
points = append(points, [2]int{x + left, sy + top})
}
if !find[3] && !find[4] {
continue
}
end:
{
if len(points) < minLength-1 {
continue
}
result = append(result, append(points, [2]int{x + left, y + top}))
}
}
sort.Slice(result, func(i, j int) bool {
return len(result[i]) > len(result[j])
})
var notRepeat [][][2]int
for _, points := range result {
var match = true
for _, point := range points {
x, y := geometry.CoordinateArrayToCoordinate(point)
x = x + (0 - left)
y = y + (0 - top)
if record[x][y] {
match = false
break
}
record[x][y] = true
}
if match {
notRepeat = append(notRepeat, points)
}
}
return notRepeat
}
// SearchContainStraightLine 在一组二维坐标中查找是否存在直线
func SearchContainStraightLine(minLength int, xys ...[2]int) bool {
if minLength < 3 {
return false
}
left, _, top, _ := GetShapeCoverageArea(xys...)
rectangleShape := GenerateShape(xys...)
record := map[int]map[int]bool{}
for x := 0; x < len(rectangleShape); x++ {
for y := 0; y < len(rectangleShape[0]); y++ {
record[x] = map[int]bool{}
}
}
for _, xy := range xys {
var points [][2]int
var find = map[int]bool{}
x, y := geometry.CoordinateArrayToCoordinate(xy)
x = x + (0 - left)
y = y + (0 - top)
// 搜索四个方向
for sx := x - 1; sx >= 0; sx-- {
if !rectangleShape[sx][y] {
break
}
find[1] = true
points = append(points, [2]int{sx + left, y + top})
}
for sx := x + 1; sx < len(rectangleShape); sx++ {
if !rectangleShape[sx][y] {
break
}
find[2] = true
points = append(points, [2]int{sx + left, y + top})
}
if len(find) == 0 {
points = nil
} else if len(points) >= minLength-1 {
goto end
} else {
points = nil
}
for sy := y - 1; sy >= 0; sy-- {
if !rectangleShape[x][sy] {
break
}
find[3] = true
points = append(points, [2]int{x + left, sy + top})
}
for sy := y + 1; sy < len(rectangleShape[0]); sy++ {
if !rectangleShape[x][sy] {
break
}
find[4] = true
points = append(points, [2]int{x + left, sy + top})
}
if !find[3] && !find[4] {
continue
}
end:
{
if len(points) < minLength-1 {
continue
}
return true
}
}
return false
}
// SearchNotRepeatT 在一组二维坐标中从大到小搜索不重复T型T线
func SearchNotRepeatT(minLength int, xys ...[2]int) (result [][][2]int) {
if minLength < 4 {
return nil
}
left, _, top, _ := GetShapeCoverageArea(xys...)
rectangleShape := GenerateShape(xys...)
record := map[int]map[int]bool{}
for x := 0; x < len(rectangleShape); x++ {
for y := 0; y < len(rectangleShape[0]); y++ {
record[x] = map[int]bool{}
}
}
for _, xy := range xys {
var points [][2]int
var find = map[int]bool{}
x, y := geometry.CoordinateArrayToCoordinate(xy)
x = x + (0 - left)
y = y + (0 - top)
// 搜索四个方向
for sx := x - 1; sx >= 0; sx-- {
if !rectangleShape[sx][y] {
break
}
find[1] = true
points = append(points, [2]int{sx + left, y + top})
}
for sx := x + 1; sx < len(rectangleShape); sx++ {
if !rectangleShape[sx][y] {
break
}
find[2] = true
points = append(points, [2]int{sx + left, y + top})
}
for sy := y - 1; sy >= 0; sy-- {
if !rectangleShape[x][sy] {
break
}
find[3] = true
points = append(points, [2]int{x + left, sy + top})
}
for sy := y + 1; sy < len(rectangleShape[0]); sy++ {
if !rectangleShape[x][sy] {
break
}
find[4] = true
points = append(points, [2]int{x + left, sy + top})
}
if len(find) != 3 || len(points) < minLength {
continue
}
result = append(result, append(points, [2]int{x + left, y + top}))
}
sort.Slice(result, func(i, j int) bool {
return len(result[i]) > len(result[j])
})
var notRepeat [][][2]int
for _, points := range result {
var match = true
for _, point := range points {
x, y := geometry.CoordinateArrayToCoordinate(point)
x = x + (0 - left)
y = y + (0 - top)
if record[x][y] {
match = false
break
}
record[x][y] = true
}
if match {
notRepeat = append(notRepeat, points)
}
}
return notRepeat
}
// SearchContainT 在一组二维坐标中查找是否存在T型T线
func SearchContainT(minLength int, xys ...[2]int) bool {
if minLength < 4 {
return false
}
left, _, top, _ := GetShapeCoverageArea(xys...)
rectangleShape := GenerateShape(xys...)
record := map[int]map[int]bool{}
for x := 0; x < len(rectangleShape); x++ {
for y := 0; y < len(rectangleShape[0]); y++ {
record[x] = map[int]bool{}
}
}
for _, xy := range xys {
var points [][2]int
var find = map[int]bool{}
x, y := geometry.CoordinateArrayToCoordinate(xy)
x = x + (0 - left)
y = y + (0 - top)
// 搜索四个方向
for sx := x - 1; sx >= 0; sx-- {
if !rectangleShape[sx][y] {
break
}
find[1] = true
points = append(points, [2]int{sx + left, y + top})
}
for sx := x + 1; sx < len(rectangleShape); sx++ {
if !rectangleShape[sx][y] {
break
}
find[2] = true
points = append(points, [2]int{sx + left, y + top})
}
for sy := y - 1; sy >= 0; sy-- {
if !rectangleShape[x][sy] {
break
}
find[3] = true
points = append(points, [2]int{x + left, sy + top})
}
for sy := y + 1; sy < len(rectangleShape[0]); sy++ {
if !rectangleShape[x][sy] {
break
}
find[4] = true
points = append(points, [2]int{x + left, sy + top})
}
if len(find) != 3 || len(points) < minLength-1 {
continue
}
return true
}
return false
}
// SearchNotRepeatRightAngle 在一组二维坐标中从大到小搜索不重复的直角L线
func SearchNotRepeatRightAngle(minLength int, xys ...[2]int) (result [][][2]int) {
if minLength < 3 {
return nil
}
left, _, top, _ := GetShapeCoverageArea(xys...)
rectangleShape := GenerateShape(xys...)
record := map[int]map[int]bool{}
for x := 0; x < len(rectangleShape); x++ {
for y := 0; y < len(rectangleShape[0]); y++ {
record[x] = map[int]bool{}
}
}
for _, xy := range xys {
var points [][2]int
var find = map[int]bool{}
x, y := geometry.CoordinateArrayToCoordinate(xy)
x = x + (0 - left)
y = y + (0 - top)
// 搜索四个方向
for sx := x - 1; sx >= 0; sx-- {
if !rectangleShape[sx][y] {
break
}
find[1] = true
points = append(points, [2]int{sx + left, y + top})
}
if find[1] {
goto up
}
for sx := x + 1; sx < len(rectangleShape); sx++ {
if !rectangleShape[sx][y] {
break
}
find[2] = true
points = append(points, [2]int{sx + left, y + top})
}
up:
for sy := y - 1; sy >= 0; sy-- {
if !rectangleShape[x][sy] {
break
}
find[3] = true
points = append(points, [2]int{x + left, sy + top})
}
if find[3] {
goto end
}
// down
for sy := y + 1; sy < len(rectangleShape[0]); sy++ {
if !rectangleShape[x][sy] {
break
}
find[4] = true
points = append(points, [2]int{x + left, sy + top})
}
if !find[4] {
continue
}
end:
{
if len(find) != 2 || len(points) < minLength-1 {
continue
}
result = append(result, append(points, [2]int{x + left, y + top}))
}
}
sort.Slice(result, func(i, j int) bool {
return len(result[i]) > len(result[j])
})
var notRepeat [][][2]int
for _, points := range result {
var match = true
for _, point := range points {
x, y := geometry.CoordinateArrayToCoordinate(point)
x = x + (0 - left)
y = y + (0 - top)
if record[x][y] {
match = false
break
}
record[x][y] = true
}
if match {
notRepeat = append(notRepeat, points)
}
}
return notRepeat
}
// SearchContainRightAngle 在一组二维坐标中查找是否存在直角L线
func SearchContainRightAngle(minLength int, xys ...[2]int) bool {
if minLength < 3 {
return false
}
left, _, top, _ := GetShapeCoverageArea(xys...)
rectangleShape := GenerateShape(xys...)
record := map[int]map[int]bool{}
for x := 0; x < len(rectangleShape); x++ {
for y := 0; y < len(rectangleShape[0]); y++ {
record[x] = map[int]bool{}
}
}
for _, xy := range xys {
var points [][2]int
var find = map[int]bool{}
x, y := geometry.CoordinateArrayToCoordinate(xy)
x = x + (0 - left)
y = y + (0 - top)
// 搜索四个方向
for sx := x - 1; sx >= 0; sx-- {
if !rectangleShape[sx][y] {
break
}
find[1] = true
points = append(points, [2]int{sx + left, y + top})
}
if find[1] {
goto up
}
for sx := x + 1; sx < len(rectangleShape); sx++ {
if !rectangleShape[sx][y] {
break
}
find[2] = true
points = append(points, [2]int{sx + left, y + top})
}
up:
for sy := y - 1; sy >= 0; sy-- {
if !rectangleShape[x][sy] {
break
}
find[3] = true
points = append(points, [2]int{x + left, sy + top})
}
if find[3] {
goto end
}
// down
for sy := y + 1; sy < len(rectangleShape[0]); sy++ {
if !rectangleShape[x][sy] {
break
}
find[4] = true
points = append(points, [2]int{x + left, sy + top})
}
if !find[4] {
continue
}
end:
{
if len(find) != 2 || len(points) < minLength-1 {
continue
}
return true
}
}
return false
}
// SearchNotRepeatFullRectangle 在一组二维坐标中从大到小搜索不重复的填充满的矩形
// - 不重复指一个位置被使用后将不会被其他矩形使用
// - 返回值表示了匹配的形状的左上角和右下角的点坐标
func SearchNotRepeatFullRectangle(minWidth, minHeight int, 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, minWidth, minHeight)
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 := geometry.CoordinateArrayToCoordinate(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 := geometry.CoordinateArrayToCoordinate(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
}
// SearchContainFullRectangle 在一组二维坐标中查找是否存在填充满的矩形
func SearchContainFullRectangle(minWidth, minHeight int, xys ...[2]int) bool {
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, minWidth, minHeight)
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 := geometry.CoordinateArrayToCoordinate(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 := geometry.CoordinateArrayToCoordinate(point)
record[px+x][py+y] = true
}
return true
}
x++
}
}
return false
}
// 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
}
// GetRectangleFullPointsByXY 通过开始结束坐标获取一个矩形包含的所有点
// - 例如 1,1 到 2,2 的矩形结果为 1,1 2,1 1,2 2,2
func GetRectangleFullPointsByXY(startX, startY, endX, endY int) (result [][2]int) {
for x := startX; x <= endX; x++ {
for y := startY; y <= endY; 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) {
sourceWidth := width
if width == 0 || height == 0 {
return nil
}
if width < minWidth || height < minHeight {
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 {
if width+1 == sourceWidth {
height--
} else {
width++
height--
}
} else if width > height {
width--
}
}
}
// GenerateShape 生成一组二维坐标的形状
// - 这个形状将被在一个刚好能容纳形状的矩形中表示
// - 为true的位置表示了形状的每一个点
func GenerateShape(xys ...[2]int) [][]bool {
left, r, top, b := GetShapeCoverageArea(xys...)
_, right, _, bottom := CoverageAreaBoundless(left, r, top, b)
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 := geometry.CoordinateArrayToCoordinate(xy)
m[x-(r-right)][y-(b-bottom)] = true
}
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
}

118
utils/geometry/geometry.go Normal file
View File

@ -0,0 +1,118 @@
package geometry
import (
"github.com/kercylan98/minotaur/utils/generic"
"math"
)
// Direction 方向
type Direction uint8
const (
DirectionUnknown = Direction(iota) // 未知
DirectionUp // 上方
DirectionDown // 下方
DirectionLeft // 左方
DirectionRight // 右方
)
var (
Directions = []Direction{DirectionUp, DirectionDown, DirectionLeft, DirectionRight} // 上下左右四个方向的数组
)
// GetDirectionNextWithCoordinate 获取特定方向上的下一个坐标
func GetDirectionNextWithCoordinate[V generic.Number](direction Direction, x, y V) (nx, ny V) {
switch direction {
case DirectionUp:
nx, ny = x, y-1
case DirectionDown:
nx, ny = x, y+1
case DirectionLeft:
nx, ny = x-1, y
case DirectionRight:
nx, ny = x+1, y
default:
panic("unexplained direction")
}
return
}
// GetDirectionNextWithCoordinateArray 获取特定方向上的下一个坐标
func GetDirectionNextWithCoordinateArray[V generic.Number](direction Direction, point Point[V]) Point[V] {
x, y := point.GetXY()
switch direction {
case DirectionUp:
return NewPoint(x, y-1)
case DirectionDown:
return NewPoint(x, y+1)
case DirectionLeft:
return NewPoint(x-1, y)
case DirectionRight:
return NewPoint(x+1, y)
default:
panic("unexplained direction")
}
}
// GetDirectionNextWithPos 获取位置在特定宽度和特定方向上的下一个位置
// - 需要注意的是,在左右方向时,当下一个位置不在游戏区域内时,将会返回上一行的末位置或下一行的首位置
func GetDirectionNextWithPos[V generic.Number](direction Direction, width, pos V) V {
switch direction {
case DirectionUp:
return pos - width
case DirectionDown:
return pos + width
case DirectionLeft:
return pos - 1
case DirectionRight:
return pos + 1
default:
panic("unexplained direction")
}
}
// CalcDirection 计算点2位于点1的方向
func CalcDirection[V generic.Number](x1, y1, x2, y2 V) Direction {
var oneEighty = 180
var fortyFive = 45
var oneThirtyFive = 135
var twoTwentyFive = 225
var threeFifteen = 315
var end = 360
var start = 0
angle := CalcAngle(x1, y1, x2, y2) + V(oneEighty)
if angle > V(oneThirtyFive) && angle <= V(twoTwentyFive) {
return DirectionRight
} else if (angle > V(threeFifteen) && angle <= V(end)) || (angle >= V(start) && angle <= V(fortyFive)) {
return DirectionLeft
} else if angle > V(twoTwentyFive) && angle <= V(threeFifteen) {
return DirectionUp
} else if angle > V(fortyFive) && angle <= V(oneThirtyFive) {
return DirectionDown
}
return DirectionUnknown
}
// CalcDistance 计算两点之间的距离
func CalcDistance[V generic.Number](x1, y1, x2, y2 V) V {
return V(math.Sqrt(math.Pow(float64(x2-x1), 2) + math.Pow(float64(y2-y1), 2)))
}
// CalcAngle 计算点2位于点1之间的角度
func CalcAngle[V generic.Number](x1, y1, x2, y2 V) V {
return V(math.Atan2(float64(y2-y1), float64(x2-x1)) * 180 / math.Pi)
}
// CalculateNewCoordinate 根据给定的x、y坐标、角度和距离计算新的坐标
func CalculateNewCoordinate[V generic.Number](x, y, angle, distance V) (newX, newY V) {
// 将角度转换为弧度
var pi = math.Pi
var dividend = 180.0
radians := angle * V(pi) / V(dividend)
// 计算新的坐标
newX = x + distance*V(math.Cos(float64(radians)))
newY = y + distance*V(math.Sin(float64(radians)))
return newX, newY
}

View File

@ -33,6 +33,16 @@ func (slf Point[V]) GetPos(width V) V {
return CoordinateArrayToPos(width, slf)
}
// GetOffset 获取偏移后的新坐标
func (slf Point[V]) GetOffset(x, y V) Point[V] {
return NewPoint(slf.GetX()+x, slf.GetY()+y)
}
// Equal 返回两个点是否相等
func (slf Point[V]) Equal(point Point[V]) bool {
return slf.GetX() == point.GetX() && slf.GetY() == point.GetY()
}
// Copy 复制一个点位置
func (slf Point[V]) Copy() Point[V] {
return CoordinateArrayCopy(slf)
@ -132,3 +142,8 @@ func PosToCoordinateArrayWithMulti[V generic.Number](width V, positions ...V) []
}
return result
}
// PosSameRow 返回两个顺序位置在同一宽度是否位于同一行
func PosSameRow[V generic.Number](width, pos1, pos2 V) bool {
return (pos1 / width) == (pos2 / width)
}

View File

@ -283,7 +283,7 @@ func GetRectangleFullPointsByXY[V generic.Number](startX, startY, endX, endY V)
return
}
// GetRectangleFullPoints 获取一个矩形包含的所有点
// GetRectangleFullPoints 获取一个矩形填充满后包含的所有点
func GetRectangleFullPoints[V generic.Number](width, height V) (result []Point[V]) {
for x := V(0); x < width; x++ {
for y := V(0); y < height; y++ {
@ -292,3 +292,13 @@ func GetRectangleFullPoints[V generic.Number](width, height V) (result []Point[V
}
return
}
// GetRectangleFullPos 获取一个矩形填充满后包含的所有位置
func GetRectangleFullPos[V generic.Number](width, height V) (result []V) {
size := int(width * height)
result = make([]V, 0, size)
for pos := 0; pos < size; pos++ {
result[pos] = V(pos)
}
return
}

View File

@ -1,6 +1,7 @@
package geometry_test
import (
"fmt"
"github.com/kercylan98/minotaur/utils/geometry"
. "github.com/smartystreets/goconvey/convey"
"testing"
@ -46,12 +47,14 @@ func TestCoverageAreaBoundless(t *testing.T) {
func TestGenerateShapeOnRectangle(t *testing.T) {
Convey("TestGenerateShapeOnRectangle", t, func() {
var points []geometry.Point[int]
var points geometry.Shape[int]
points = append(points, geometry.NewPoint(1, 1))
points = append(points, geometry.NewPoint(2, 1))
points = append(points, geometry.NewPoint(2, 2))
ps := geometry.GenerateShapeOnRectangle(points...)
fmt.Println(points)
ps := geometry.GenerateShapeOnRectangle(points.Points()...)
So(ps[0].GetX(), ShouldEqual, 0)
So(ps[0].GetY(), ShouldEqual, 0)

970
utils/geometry/shape.go Normal file
View File

@ -0,0 +1,970 @@
package geometry
import (
"github.com/kercylan98/minotaur/utils/generic"
"github.com/kercylan98/minotaur/utils/slice"
"math"
"sort"
)
// Shape 通过多个点表示了一个形状
type Shape[V generic.Number] []Point[V]
// Points 获取这个形状的所有点
func (slf Shape[V]) Points() []Point[V] {
return slf
}
// String 将该形状转换为可视化的字符串进行返回
func (slf Shape[V]) String() string {
var result string
left, right, top, bottom := GetShapeCoverageAreaWithCoordinateArray(slf.Points()...)
width := right - left + 1
height := bottom - top + 1
for y := top; y < top+height; y++ {
for x := left; x < left+width; x++ {
exist := false
for _, p := range slf {
if x == p.GetX() && y == p.GetY() {
exist = true
break
}
}
if exist {
result += "X "
} else {
result += "# "
}
}
result += "\r\n"
}
return result
}
// ShapeSearch 获取该形状中包含的所有图形组合及其位置
// - 需要注意的是,即便图形最终表示为相同的,但是只要位置组合顺序不同,那么也将被认定为一种图形组合
// - [[1 0] [1 1] [1 2]] 和 [[1 1] [1 0] [1 2]] 可以被视为两个图形组合
// - 返回的坐标为原始形状的坐标
//
// 可通过可选项对搜索结果进行过滤
func (slf Shape[V]) ShapeSearch(options ...ShapeSearchOption) (result []Shape[V]) {
opt := &shapeSearchOptions{upperLimit: math.MaxInt}
for _, option := range options {
option(opt)
}
var shapes []Shape[V]
switch opt.sort {
case 1:
shapes = slf.getAllGraphicCompositionWithAsc(opt)
case -1:
shapes = slf.getAllGraphicCompositionWithDesc(opt)
default:
shapes = slf.getAllGraphicComposition(opt)
}
result = shapes
if opt.deduplication {
deduplication := make(map[V]struct{})
w := V(len(slf.Points()))
var notRepeat = make([]Shape[V], 0, len(result))
for _, points := range result {
count := len(points)
if count < opt.lowerLimit || count > opt.upperLimit {
continue
}
var match = true
for _, point := range points {
pos := point.GetPos(w)
if _, exist := deduplication[pos]; exist {
match = false
break
}
deduplication[pos] = struct{}{}
}
if match {
notRepeat = append(notRepeat, points)
}
}
result = notRepeat
} else {
limit := make([]Shape[V], 0, len(result))
for _, shape := range result {
count := len(shape.Points())
if count < opt.lowerLimit || count > opt.upperLimit {
continue
}
limit = append(limit, shape)
}
result = limit
}
return
}
// getAllGraphicComposition 获取该形状中包含的所有图形组合及其位置
// - 需要注意的是,即便图形最终表示为相同的,但是只要位置组合顺序不同,那么也将被认定为一种图形组合
// - [[1 0] [1 1] [1 2]] 和 [[1 1] [1 0] [1 2]] 可以被视为两个图形组合
// - 返回的坐标为原始形状的坐标
func (slf Shape[V]) getAllGraphicComposition(opt *shapeSearchOptions) (result []Shape[V]) {
left, right, top, bottom := GetShapeCoverageAreaWithCoordinateArray(slf.Points()...)
width := right - left + 1
height := bottom - top + 1
areaWidth := width + left
areaHeight := height + top
rectangleShape := GenerateShapeOnRectangle(slf.Points()...)
records := make(map[V]struct{})
// 通过每个点扩散图形
for _, point := range slf.Points() {
// 搜索四个方向
var next = -1
var directionPoint = point
var links = Shape[V]{point}
for {
var direction Direction
next, direction = slice.NextLoop(Directions, next)
for {
directionPoint = GetDirectionNextWithCoordinateArray(direction, directionPoint)
if px, py := directionPoint.GetXY(); px < 0 || px >= areaWidth || py < 0 || py >= areaHeight {
break
}
if offsetPos := int(CoordinateArrayToPos(width, directionPoint.GetOffset(-left, -top))); offsetPos < 0 || offsetPos >= len(rectangleShape) || !rectangleShape[offsetPos].Data {
break
}
links = append(links, directionPoint)
pos := directionPoint.GetPos(areaWidth)
if _, exist := records[pos]; !exist {
result = append(result, Shape[V]{directionPoint})
records[pos] = struct{}{}
}
}
if direction == DirectionRight {
break
}
directionPoint = point
}
result = append(result, links)
}
return result
}
// getAllGraphicCompositionWithAsc 通过升序的方式获取该形状中包含的所有图形组合及其位置
// - 升序指标为图形包含的点数量
// - 其余内容可参考 getAllGraphicComposition
func (slf Shape[V]) getAllGraphicCompositionWithAsc(opt *shapeSearchOptions) (result []Shape[V]) {
result = slf.getAllGraphicComposition(opt)
sort.Slice(result, func(i, j int) bool {
return len(result[i].Points()) < len(result[j].Points())
})
return
}
// getAllGraphicCompositionWithDesc 通过降序的方式获取该形状中包含的所有图形组合及其位置
// - 降序指标为图形包含的点数量
// - 其余内容可参考 GetAllGraphicComposition
func (slf Shape[V]) getAllGraphicCompositionWithDesc(opt *shapeSearchOptions) (result []Shape[V]) {
result = slf.getAllGraphicComposition(opt)
sort.Slice(result, func(i, j int) bool {
return len(result[i].Points()) > len(result[j].Points())
})
return
}
//
//// SearchNotRepeatCross 在一组二维坐标中从大到小搜索不重复交叉(十字)线
//// - 不重复指一个位置被使用后将不会被其他交叉线(十字)使用
//func SearchNotRepeatCross[V generic.Number](findHandle func(findCount map[Direction]int, nextDirection func(direction Direction), stop func()), points []Point[V]) (result [][]Point[V]) {
// left, right, top, bottom := GetShapeCoverageAreaWithCoordinateArray(points...)
// width := right - left + 1
// height := bottom - top + 1
// size := width * height
// rectangleShape := GenerateShapeOnRectangle(points...)
// record := map[V]map[V]bool{}
// for x := V(0); x < width; x++ {
// for y := V(0); y < height; y++ {
// record[x] = map[V]bool{}
// }
// }
//
// var findCount = map[Direction]int{}
//
// for _, point := range points {
//
// var next = -1
// for {
// var direction Direction
// next, direction = slice.NextLoop(Directions, next)
// nextPoint := point
// for {
// nextPoint = GetDirectionNextWithCoordinateArray(direction, point)
// nextPos := nextPoint.GetPos(width)
// if nextPos < 0 || nextPos >= size {
// break
// }
// if rectangleShape[int(nextPos)].Data {
// findCount[direction]++
// var goToNextDirection bool
// var stop bool
// findHandle(findCount, func(direction Direction) {
// switch direction {
// case DirectionUp:
// next = -1
// case DirectionDown:
// next = 0
// case DirectionLeft:
// next = 1
// case DirectionRight:
// next = 2
// }
// goToNextDirection = true
// }, func() {
// stop = true
// })
// if stop {
// return
// }
// if goToNextDirection {
// break
// }
// } else {
// break
// }
// }
// }
//
// for _, direction := range Directions {
// for {
// nextPoint := GetDirectionNextWithCoordinateArray(direction, point)
// nextPos := nextPoint.GetPos(width)
// if nextPos < 0 || nextPos >= size {
// break
// }
// if rectangleShape[int(nextPos)].Data {
// findCount[direction]++
// } else {
// break
// }
// }
//
// // 十字至少各边需要长度1
// totalCount := hash.Sum(findCount)
// if totalCount < 4 {
// continue
// }
// }
// }
//
// for _, xy := range xys {
// var points []Point[V]
// var find = map[int]bool{}
// x, y := xy.GetXY()
// x = x + (0 - left)
// y = y + (0 - top)
// // 搜索四个方向
// for sx := x - 1; sx >= 0; sx-- {
// if !rectangleShape[int(CoordinateToPos(width, sx, y))].Data {
// break
// }
// find[1] = true
// points = append(points, NewPoint(sx+left, y+top))
// }
// if !find[1] {
// continue
// }
// for sx := x + 1; sx < V(len(rectangleShape)); sx++ {
// if !rectangleShape[int(CoordinateToPos(width, sx, y))].Data {
// break
// }
// find[2] = true
// points = append(points, NewPoint(sx+left, y+top))
// }
// if !find[2] {
// continue
// }
// for sy := y - 1; sy >= 0; sy-- {
// if !rectangleShape[int(CoordinateToPos(width, x, sy))].Data {
// break
// }
// find[3] = true
// points = append(points, NewPoint(x+left, sy+top))
// }
// if !find[3] {
// continue
// }
// for sy := y + 1; sy < V(len(rectangleShape)); sy++ {
// if !rectangleShape[int(CoordinateToPos(width, x, sy))].Data {
// break
// }
// find[4] = true
// points = append(points, NewPoint(x+left, sy+top))
// }
// if !find[4] {
// continue
// }
// result = append(result, append(points, NewPoint(x+left, y+top)))
// }
//
// sort.Slice(result, func(i, j int) bool {
// return len(result[i]) > len(result[j])
// })
//
// var notRepeat [][]Point[V]
// for _, points := range result {
// var match = true
// for _, point := range points {
// x, y := CoordinateArrayToCoordinate(point)
// x = x + (0 - left)
// y = y + (0 - top)
// if record[x][y] {
// match = false
// break
// }
// record[x][y] = true
// }
// if match {
// notRepeat = append(notRepeat, points)
// }
// }
//
// return notRepeat
//}
//
//// SearchContainCross 在一组二维坐标中查找是否存在交叉(十字)线
//func SearchContainCross(xys ...[2]int) bool {
// left, _, top, _ := GetShapeCoverageArea(xys...)
// rectangleShape := GenerateShape(xys...)
// record := map[int]map[int]bool{}
// for x := 0; x < len(rectangleShape); x++ {
// for y := 0; y < len(rectangleShape[0]); y++ {
// record[x] = map[int]bool{}
// }
// }
//
// for _, xy := range xys {
// var points [][2]int
// var find = map[int]bool{}
// x, y := CoordinateArrayToCoordinate(xy)
// x = x + (0 - left)
// y = y + (0 - top)
// // 搜索四个方向
// for sx := x - 1; sx >= 0; sx-- {
// if !rectangleShape[sx][y] {
// break
// }
// find[1] = true
// points = append(points, [2]int{sx + left, y + top})
// }
// if !find[1] {
// continue
// }
// for sx := x + 1; sx < len(rectangleShape); sx++ {
// if !rectangleShape[sx][y] {
// break
// }
// find[2] = true
// points = append(points, [2]int{sx + left, y + top})
// }
// if !find[2] {
// continue
// }
// for sy := y - 1; sy >= 0; sy-- {
// if !rectangleShape[x][sy] {
// break
// }
// find[3] = true
// points = append(points, [2]int{x + left, sy + top})
// }
// if !find[3] {
// continue
// }
// for sy := y + 1; sy < len(rectangleShape[0]); sy++ {
// if !rectangleShape[x][sy] {
// break
// }
// find[4] = true
// points = append(points, [2]int{x + left, sy + top})
// }
// if !find[4] {
// continue
// }
// return true
// }
//
// return false
//}
//
//// SearchNotRepeatStraightLine 在一组二维坐标中从大到小搜索不重复的直线
//// - 最低需要长度为3
//func SearchNotRepeatStraightLine(minLength int, xys ...[2]int) (result [][][2]int) {
// if minLength < 3 {
// return nil
// }
// left, _, top, _ := GetShapeCoverageArea(xys...)
// rectangleShape := GenerateShape(xys...)
// record := map[int]map[int]bool{}
// for x := 0; x < len(rectangleShape); x++ {
// for y := 0; y < len(rectangleShape[0]); y++ {
// record[x] = map[int]bool{}
// }
// }
//
// for _, xy := range xys {
// var points [][2]int
// var find = map[int]bool{}
// x, y := CoordinateArrayToCoordinate(xy)
// x = x + (0 - left)
// y = y + (0 - top)
// // 搜索四个方向
// for sx := x - 1; sx >= 0; sx-- {
// if !rectangleShape[sx][y] {
// break
// }
// find[1] = true
// points = append(points, [2]int{sx + left, y + top})
// }
// for sx := x + 1; sx < len(rectangleShape); sx++ {
// if !rectangleShape[sx][y] {
// break
// }
// find[2] = true
// points = append(points, [2]int{sx + left, y + top})
// }
// if len(find) == 0 {
// points = nil
// } else if len(points) >= minLength-1 {
// goto end
// } else {
// points = nil
// }
// for sy := y - 1; sy >= 0; sy-- {
// if !rectangleShape[x][sy] {
// break
// }
// find[3] = true
// points = append(points, [2]int{x + left, sy + top})
// }
// for sy := y + 1; sy < len(rectangleShape[0]); sy++ {
// if !rectangleShape[x][sy] {
// break
// }
// find[4] = true
// points = append(points, [2]int{x + left, sy + top})
// }
// if !find[3] && !find[4] {
// continue
// }
// end:
// {
// if len(points) < minLength-1 {
// continue
// }
// result = append(result, append(points, [2]int{x + left, y + top}))
// }
// }
//
// sort.Slice(result, func(i, j int) bool {
// return len(result[i]) > len(result[j])
// })
//
// var notRepeat [][][2]int
// for _, points := range result {
// var match = true
// for _, point := range points {
// x, y := CoordinateArrayToCoordinate(point)
// x = x + (0 - left)
// y = y + (0 - top)
// if record[x][y] {
// match = false
// break
// }
// record[x][y] = true
// }
// if match {
// notRepeat = append(notRepeat, points)
// }
// }
//
// return notRepeat
//}
//
//// SearchContainStraightLine 在一组二维坐标中查找是否存在直线
//func SearchContainStraightLine(minLength int, xys ...[2]int) bool {
// if minLength < 3 {
// return false
// }
// left, _, top, _ := GetShapeCoverageArea(xys...)
// rectangleShape := GenerateShape(xys...)
// record := map[int]map[int]bool{}
// for x := 0; x < len(rectangleShape); x++ {
// for y := 0; y < len(rectangleShape[0]); y++ {
// record[x] = map[int]bool{}
// }
// }
//
// for _, xy := range xys {
// var points [][2]int
// var find = map[int]bool{}
// x, y := CoordinateArrayToCoordinate(xy)
// x = x + (0 - left)
// y = y + (0 - top)
// // 搜索四个方向
// for sx := x - 1; sx >= 0; sx-- {
// if !rectangleShape[sx][y] {
// break
// }
// find[1] = true
// points = append(points, [2]int{sx + left, y + top})
// }
// for sx := x + 1; sx < len(rectangleShape); sx++ {
// if !rectangleShape[sx][y] {
// break
// }
// find[2] = true
// points = append(points, [2]int{sx + left, y + top})
// }
// if len(find) == 0 {
// points = nil
// } else if len(points) >= minLength-1 {
// goto end
// } else {
// points = nil
// }
// for sy := y - 1; sy >= 0; sy-- {
// if !rectangleShape[x][sy] {
// break
// }
// find[3] = true
// points = append(points, [2]int{x + left, sy + top})
// }
// for sy := y + 1; sy < len(rectangleShape[0]); sy++ {
// if !rectangleShape[x][sy] {
// break
// }
// find[4] = true
// points = append(points, [2]int{x + left, sy + top})
// }
// if !find[3] && !find[4] {
// continue
// }
// end:
// {
// if len(points) < minLength-1 {
// continue
// }
// return true
// }
// }
//
// return false
//}
//
//// SearchNotRepeatT 在一组二维坐标中从大到小搜索不重复T型T线
//func SearchNotRepeatT(minLength int, xys ...[2]int) (result [][][2]int) {
// if minLength < 4 {
// return nil
// }
// left, _, top, _ := GetShapeCoverageArea(xys...)
// rectangleShape := GenerateShape(xys...)
// record := map[int]map[int]bool{}
// for x := 0; x < len(rectangleShape); x++ {
// for y := 0; y < len(rectangleShape[0]); y++ {
// record[x] = map[int]bool{}
// }
// }
//
// for _, xy := range xys {
// var points [][2]int
// var find = map[int]bool{}
// x, y := CoordinateArrayToCoordinate(xy)
// x = x + (0 - left)
// y = y + (0 - top)
// // 搜索四个方向
// for sx := x - 1; sx >= 0; sx-- {
// if !rectangleShape[sx][y] {
// break
// }
// find[1] = true
// points = append(points, [2]int{sx + left, y + top})
// }
// for sx := x + 1; sx < len(rectangleShape); sx++ {
// if !rectangleShape[sx][y] {
// break
// }
// find[2] = true
// points = append(points, [2]int{sx + left, y + top})
// }
// for sy := y - 1; sy >= 0; sy-- {
// if !rectangleShape[x][sy] {
// break
// }
// find[3] = true
// points = append(points, [2]int{x + left, sy + top})
// }
// for sy := y + 1; sy < len(rectangleShape[0]); sy++ {
// if !rectangleShape[x][sy] {
// break
// }
// find[4] = true
// points = append(points, [2]int{x + left, sy + top})
// }
// if len(find) != 3 || len(points) < minLength {
// continue
// }
// result = append(result, append(points, [2]int{x + left, y + top}))
// }
//
// sort.Slice(result, func(i, j int) bool {
// return len(result[i]) > len(result[j])
// })
//
// var notRepeat [][][2]int
// for _, points := range result {
// var match = true
// for _, point := range points {
// x, y := CoordinateArrayToCoordinate(point)
// x = x + (0 - left)
// y = y + (0 - top)
// if record[x][y] {
// match = false
// break
// }
// record[x][y] = true
// }
// if match {
// notRepeat = append(notRepeat, points)
// }
// }
//
// return notRepeat
//}
//
//// SearchContainT 在一组二维坐标中查找是否存在T型T线
//func SearchContainT(minLength int, xys ...[2]int) bool {
// if minLength < 4 {
// return false
// }
// left, _, top, _ := GetShapeCoverageArea(xys...)
// rectangleShape := GenerateShape(xys...)
// record := map[int]map[int]bool{}
// for x := 0; x < len(rectangleShape); x++ {
// for y := 0; y < len(rectangleShape[0]); y++ {
// record[x] = map[int]bool{}
// }
// }
//
// for _, xy := range xys {
// var points [][2]int
// var find = map[int]bool{}
// x, y := CoordinateArrayToCoordinate(xy)
// x = x + (0 - left)
// y = y + (0 - top)
// // 搜索四个方向
// for sx := x - 1; sx >= 0; sx-- {
// if !rectangleShape[sx][y] {
// break
// }
// find[1] = true
// points = append(points, [2]int{sx + left, y + top})
// }
// for sx := x + 1; sx < len(rectangleShape); sx++ {
// if !rectangleShape[sx][y] {
// break
// }
// find[2] = true
// points = append(points, [2]int{sx + left, y + top})
// }
// for sy := y - 1; sy >= 0; sy-- {
// if !rectangleShape[x][sy] {
// break
// }
// find[3] = true
// points = append(points, [2]int{x + left, sy + top})
// }
// for sy := y + 1; sy < len(rectangleShape[0]); sy++ {
// if !rectangleShape[x][sy] {
// break
// }
// find[4] = true
// points = append(points, [2]int{x + left, sy + top})
// }
// if len(find) != 3 || len(points) < minLength-1 {
// continue
// }
// return true
// }
//
// return false
//}
//
//// SearchNotRepeatRightAngle 在一组二维坐标中从大到小搜索不重复的直角L线
//func SearchNotRepeatRightAngle(minLength int, xys ...[2]int) (result [][][2]int) {
// if minLength < 3 {
// return nil
// }
// left, _, top, _ := GetShapeCoverageArea(xys...)
// rectangleShape := GenerateShape(xys...)
// record := map[int]map[int]bool{}
// for x := 0; x < len(rectangleShape); x++ {
// for y := 0; y < len(rectangleShape[0]); y++ {
// record[x] = map[int]bool{}
// }
// }
//
// for _, xy := range xys {
// var points [][2]int
// var find = map[int]bool{}
// x, y := CoordinateArrayToCoordinate(xy)
// x = x + (0 - left)
// y = y + (0 - top)
// // 搜索四个方向
// for sx := x - 1; sx >= 0; sx-- {
// if !rectangleShape[sx][y] {
// break
// }
// find[1] = true
// points = append(points, [2]int{sx + left, y + top})
// }
// if find[1] {
// goto up
// }
// for sx := x + 1; sx < len(rectangleShape); sx++ {
// if !rectangleShape[sx][y] {
// break
// }
// find[2] = true
// points = append(points, [2]int{sx + left, y + top})
// }
// up:
// for sy := y - 1; sy >= 0; sy-- {
// if !rectangleShape[x][sy] {
// break
// }
// find[3] = true
// points = append(points, [2]int{x + left, sy + top})
// }
// if find[3] {
// goto end
// }
// // down
// for sy := y + 1; sy < len(rectangleShape[0]); sy++ {
// if !rectangleShape[x][sy] {
// break
// }
// find[4] = true
// points = append(points, [2]int{x + left, sy + top})
// }
// if !find[4] {
// continue
// }
// end:
// {
// if len(find) != 2 || len(points) < minLength-1 {
// continue
// }
// result = append(result, append(points, [2]int{x + left, y + top}))
// }
// }
//
// sort.Slice(result, func(i, j int) bool {
// return len(result[i]) > len(result[j])
// })
//
// var notRepeat [][][2]int
// for _, points := range result {
// var match = true
// for _, point := range points {
// x, y := CoordinateArrayToCoordinate(point)
// x = x + (0 - left)
// y = y + (0 - top)
// if record[x][y] {
// match = false
// break
// }
// record[x][y] = true
// }
// if match {
// notRepeat = append(notRepeat, points)
// }
// }
//
// return notRepeat
//}
//
//// SearchContainRightAngle 在一组二维坐标中查找是否存在直角L线
//func SearchContainRightAngle(minLength int, xys ...[2]int) bool {
// if minLength < 3 {
// return false
// }
// left, _, top, _ := GetShapeCoverageArea(xys...)
// rectangleShape := GenerateShape(xys...)
// record := map[int]map[int]bool{}
// for x := 0; x < len(rectangleShape); x++ {
// for y := 0; y < len(rectangleShape[0]); y++ {
// record[x] = map[int]bool{}
// }
// }
//
// for _, xy := range xys {
// var points [][2]int
// var find = map[int]bool{}
// x, y := CoordinateArrayToCoordinate(xy)
// x = x + (0 - left)
// y = y + (0 - top)
// // 搜索四个方向
// for sx := x - 1; sx >= 0; sx-- {
// if !rectangleShape[sx][y] {
// break
// }
// find[1] = true
// points = append(points, [2]int{sx + left, y + top})
// }
// if find[1] {
// goto up
// }
// for sx := x + 1; sx < len(rectangleShape); sx++ {
// if !rectangleShape[sx][y] {
// break
// }
// find[2] = true
// points = append(points, [2]int{sx + left, y + top})
// }
// up:
// for sy := y - 1; sy >= 0; sy-- {
// if !rectangleShape[x][sy] {
// break
// }
// find[3] = true
// points = append(points, [2]int{x + left, sy + top})
// }
// if find[3] {
// goto end
// }
// // down
// for sy := y + 1; sy < len(rectangleShape[0]); sy++ {
// if !rectangleShape[x][sy] {
// break
// }
// find[4] = true
// points = append(points, [2]int{x + left, sy + top})
// }
// if !find[4] {
// continue
// }
// end:
// {
// if len(find) != 2 || len(points) < minLength-1 {
// continue
// }
// return true
// }
// }
//
// return false
//}
//
//// SearchNotRepeatFullRectangle 在一组二维坐标中从大到小搜索不重复的填充满的矩形
//// - 不重复指一个位置被使用后将不会被其他矩形使用
//// - 返回值表示了匹配的形状的左上角和右下角的点坐标
//func SearchNotRepeatFullRectangle(minWidth, minHeight int, 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, minWidth, minHeight)
// 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 := CoordinateArrayToCoordinate(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 := CoordinateArrayToCoordinate(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
//}
//
//// SearchContainFullRectangle 在一组二维坐标中查找是否存在填充满的矩形
//func SearchContainFullRectangle(minWidth, minHeight int, xys ...[2]int) bool {
// 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, minWidth, minHeight)
// 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 := CoordinateArrayToCoordinate(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 := CoordinateArrayToCoordinate(point)
// record[px+x][py+y] = true
// }
// return true
// }
//
// x++
// }
// }
//
// return false
//}

View File

@ -0,0 +1,49 @@
package geometry
type shapeSearchOptions struct {
lowerLimit int
upperLimit int
sort int
deduplication bool
}
// ShapeSearchOption 图形搜索可选项,用于 Shape.ShapeSearch 搜索支持
type ShapeSearchOption func(options *shapeSearchOptions)
// WithShapeSearchDeduplication 通过去重的方式进行搜索
// - 去重方式中每个点仅会被使用一次
func WithShapeSearchDeduplication() ShapeSearchOption {
return func(options *shapeSearchOptions) {
options.deduplication = true
}
}
// WithShapeSearchPointCountLowerLimit 通过限制图形构成的最小点数进行搜索
// - 当搜索到的图形的点数量低于 lowerLimit 时,将被忽略
func WithShapeSearchPointCountLowerLimit(lowerLimit int) ShapeSearchOption {
return func(options *shapeSearchOptions) {
options.lowerLimit = lowerLimit
}
}
// WithShapeSearchPointCountUpperLimit 通过限制图形构成的最大点数进行搜索
// - 当搜索到的图形的点数量大于 upperLimit 时,将被忽略
func WithShapeSearchPointCountUpperLimit(upperLimit int) ShapeSearchOption {
return func(options *shapeSearchOptions) {
options.upperLimit = upperLimit
}
}
// WithShapeSearchAsc 通过升序的方式进行搜索
func WithShapeSearchAsc() ShapeSearchOption {
return func(options *shapeSearchOptions) {
options.sort = 1
}
}
// WithShapeSearchDesc 通过降序的方式进行搜索
func WithShapeSearchDesc() ShapeSearchOption {
return func(options *shapeSearchOptions) {
options.sort = -1
}
}

View File

@ -0,0 +1,25 @@
package geometry_test
import (
"fmt"
"github.com/kercylan98/minotaur/utils/geometry"
"testing"
)
func TestShape_Search(t *testing.T) {
var shape geometry.Shape[int]
// 生成一个L形的shape
shape = append(shape, geometry.NewPoint(1, 0))
shape = append(shape, geometry.NewPoint(1, 1))
shape = append(shape, geometry.NewPoint(1, 2))
shape = append(shape, geometry.NewPoint(2, 2))
fmt.Println(shape)
shapes := shape.ShapeSearch(geometry.WithShapeSearchAsc(), geometry.WithShapeSearchDeduplication())
for _, shape := range shapes {
fmt.Println("图形", shape.Points())
fmt.Println(shape)
}
}

12
utils/hash/math.go Normal file
View File

@ -0,0 +1,12 @@
package hash
import "github.com/kercylan98/minotaur/utils/generic"
// Sum 计算一个 map 中的 value 总和
func Sum[K comparable, V generic.Number](m map[K]V) V {
var sum V
for _, v := range m {
sum += v
}
return sum
}

View File

@ -54,3 +54,16 @@ func Move[V any](slice *[]V, index, to int) {
Del[V](slice, index)
}
}
// NextLoop 返回 i 的下一个数组成员,当 i 达到数组长度时从 0 开始
// - 当 i 为 -1 时将返回第一个元素
func NextLoop[V any](slice []V, i int) (next int, value V) {
if i == -1 {
return 0, slice[0]
}
next = i + 1
if next == len(slice) {
next = 0
}
return next, slice[next]
}