From 18b8729a94ba8601a27fff7a6edd8bff4a7eb077 Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Sat, 17 Jun 2023 19:33:59 +0800 Subject: [PATCH] =?UTF-8?q?:boom:=20=E5=87=A0=E4=BD=95=E5=BA=93=E4=BC=98?= =?UTF-8?q?=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- component/components/moving2d.go | 8 +- game/builtin/aoi2d.go | 8 +- utils/g2d/coordinate.go | 25 - utils/g2d/direction.go | 26 - utils/g2d/doc.go | 2 - utils/g2d/shape.go | 821 --------------------- utils/geometry/geometry.go | 118 +++ utils/geometry/position.go | 15 + utils/geometry/rectangle.go | 12 +- utils/geometry/rectangle_test.go | 7 +- utils/geometry/shape.go | 970 +++++++++++++++++++++++++ utils/geometry/shape_search_options.go | 49 ++ utils/geometry/shape_test.go | 25 + utils/hash/math.go | 12 + utils/slice/slice.go | 13 + 15 files changed, 1226 insertions(+), 885 deletions(-) delete mode 100644 utils/g2d/coordinate.go delete mode 100644 utils/g2d/direction.go delete mode 100644 utils/g2d/doc.go delete mode 100644 utils/g2d/shape.go create mode 100644 utils/geometry/geometry.go create mode 100644 utils/geometry/shape.go create mode 100644 utils/geometry/shape_search_options.go create mode 100644 utils/geometry/shape_test.go create mode 100644 utils/hash/math.go diff --git a/component/components/moving2d.go b/component/components/moving2d.go index 03181cd..d6feb4e 100644 --- a/component/components/moving2d.go +++ b/component/components/moving2d.go @@ -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) diff --git a/game/builtin/aoi2d.go b/game/builtin/aoi2d.go index e7f66c7..a1fce9d 100644 --- a/game/builtin/aoi2d.go +++ b/game/builtin/aoi2d.go @@ -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) diff --git a/utils/g2d/coordinate.go b/utils/g2d/coordinate.go deleted file mode 100644 index 529a369..0000000 --- a/utils/g2d/coordinate.go +++ /dev/null @@ -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 -} diff --git a/utils/g2d/direction.go b/utils/g2d/direction.go deleted file mode 100644 index 94837b7..0000000 --- a/utils/g2d/direction.go +++ /dev/null @@ -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 -} diff --git a/utils/g2d/doc.go b/utils/g2d/doc.go deleted file mode 100644 index 2355893..0000000 --- a/utils/g2d/doc.go +++ /dev/null @@ -1,2 +0,0 @@ -// Package g2d 提供了大量用于2D计算的辅助函数及组件,例如图形搜索、辐射关系、适用于矩阵的计算函数等 -package g2d diff --git a/utils/g2d/shape.go b/utils/g2d/shape.go deleted file mode 100644 index b5130aa..0000000 --- a/utils/g2d/shape.go +++ /dev/null @@ -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 -} diff --git a/utils/geometry/geometry.go b/utils/geometry/geometry.go new file mode 100644 index 0000000..63ceb23 --- /dev/null +++ b/utils/geometry/geometry.go @@ -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 +} diff --git a/utils/geometry/position.go b/utils/geometry/position.go index d59a44f..c380bfc 100644 --- a/utils/geometry/position.go +++ b/utils/geometry/position.go @@ -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) +} diff --git a/utils/geometry/rectangle.go b/utils/geometry/rectangle.go index 205ad7b..d8c66df 100644 --- a/utils/geometry/rectangle.go +++ b/utils/geometry/rectangle.go @@ -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 +} diff --git a/utils/geometry/rectangle_test.go b/utils/geometry/rectangle_test.go index 8ac2672..8b1141b 100644 --- a/utils/geometry/rectangle_test.go +++ b/utils/geometry/rectangle_test.go @@ -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) diff --git a/utils/geometry/shape.go b/utils/geometry/shape.go new file mode 100644 index 0000000..6511489 --- /dev/null +++ b/utils/geometry/shape.go @@ -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 +//} diff --git a/utils/geometry/shape_search_options.go b/utils/geometry/shape_search_options.go new file mode 100644 index 0000000..7d938fd --- /dev/null +++ b/utils/geometry/shape_search_options.go @@ -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 + } +} diff --git a/utils/geometry/shape_test.go b/utils/geometry/shape_test.go new file mode 100644 index 0000000..b9d1008 --- /dev/null +++ b/utils/geometry/shape_test.go @@ -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) + } +} diff --git a/utils/hash/math.go b/utils/hash/math.go new file mode 100644 index 0000000..1c71ef6 --- /dev/null +++ b/utils/hash/math.go @@ -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 +} diff --git a/utils/slice/slice.go b/utils/slice/slice.go index 5f53385..332dceb 100644 --- a/utils/slice/slice.go +++ b/utils/slice/slice.go @@ -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] +}