From ea7ed7ea4e72ba7065a199872a597f722f597755 Mon Sep 17 00:00:00 2001 From: kercylan98 Date: Mon, 19 Jun 2023 10:52:53 +0800 Subject: [PATCH] =?UTF-8?q?:art:=20=E4=BC=98=E5=8C=96=E5=8D=95=E5=85=83?= =?UTF-8?q?=E6=B5=8B=E8=AF=95=E5=8F=8A=E7=A4=BA=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- utils/geometry/shape.go | 132 +++++++++++++++++-------- utils/geometry/shape_examle_test.go | 104 +++++++++++++++++++ utils/geometry/shape_search_options.go | 10 ++ utils/geometry/shape_test.go | 120 +++++++++++++++++----- 4 files changed, 303 insertions(+), 63 deletions(-) create mode 100644 utils/geometry/shape_examle_test.go diff --git a/utils/geometry/shape.go b/utils/geometry/shape.go index ef5a051..f7f17e3 100644 --- a/utils/geometry/shape.go +++ b/utils/geometry/shape.go @@ -1,16 +1,49 @@ package geometry import ( + "bytes" + "fmt" "github.com/kercylan98/minotaur/utils/generic" "github.com/kercylan98/minotaur/utils/slice" "math" "sort" + "strings" ) var ( ShapeStringHasBorder = false // 控制 Shape.String 是否拥有边界 ) +// NewShape 通过多个点生成一个形状进行返回 +func NewShape[V generic.SignedNumber](points ...Point[V]) Shape[V] { + return points +} + +// NewShapeWithString 通过字符串将指定 rune 转换为点位置生成形状进行返回 +// - 每个点的顺序从上到下,从左到右 +func NewShapeWithString[V generic.SignedNumber](rows []string, point rune) (shape Shape[V]) { + var width int + for _, row := range rows { + length := len(row) + if length > width { + width = length + } + } + for y := 0; y < len(rows); y++ { + runes := bytes.Runes([]byte(rows[y])) + for x := 0; x < width; x++ { + if x >= len(runes) { + break + } + if point == runes[x] { + shape = append(shape, NewPoint(V(x), V(y))) + } + } + } + + return shape +} + // Shape 通过多个点表示了一个形状 type Shape[V generic.SignedNumber] []Point[V] @@ -19,6 +52,11 @@ func (slf Shape[V]) Points() []Point[V] { return slf } +// PointCount 获取这个形状的点数量 +func (slf Shape[V]) PointCount() int { + return len(slf) +} + // String 将该形状转换为可视化的字符串进行返回 func (slf Shape[V]) String() string { var result string @@ -41,9 +79,18 @@ func (slf Shape[V]) String() string { result += "# " } } - result += "\r\n" + result = result[:len(result)-1] + result += "\n" } } else { + if left < 0 { + left += -left + right += -left + } + if top < 0 { + top += -top + bottom += -top + } for y := V(0); y < top+height; y++ { for x := V(0); x < left+width; x++ { exist := false @@ -59,10 +106,11 @@ func (slf Shape[V]) String() string { result += "# " } } - result += "\r\n" + result = result[:len(result)-1] + result += "\n" } } - return result + return fmt.Sprintf("%v\n%s", slf.Points(), strings.TrimSuffix(result, "\n")) } // ShapeSearch 获取该形状中包含的所有图形组合及其位置 @@ -72,7 +120,7 @@ func (slf Shape[V]) String() string { // // 可通过可选项对搜索结果进行过滤 func (slf Shape[V]) ShapeSearch(options ...ShapeSearchOption) (result []Shape[V]) { - opt := &shapeSearchOptions{upperLimit: math.MaxInt, rectangleMaxWidth: math.MaxInt, rectangleMaxHeight: math.MaxInt} + opt := newShapeSearchOptions() opt.directionCountUpper = map[Direction]int{} for _, d := range DirectionUDLR { opt.directionCountUpper[d] = math.MaxInt @@ -143,7 +191,7 @@ func (slf Shape[V]) getAllGraphicComposition(opt *shapeSearchOptions) (result [] height := bottom - top + 1 areaWidth := width + left areaHeight := height + top - rectangleShape := GenerateShapeOnRectangle(slf.Points()...) + rectangleShape := GenerateShapeOnRectangleWithCoordinate(slf.Points()...) records := make(map[V]struct{}) var match = func(links Shape[V], directionCount map[Direction]int, count int) bool { @@ -229,53 +277,55 @@ func (slf Shape[V]) getAllGraphicComposition(opt *shapeSearchOptions) (result [] // 搜索四个方向 var next = -1 var directionPoint = point - var links = Shape[V]{point} + var links = Shape[V]{} + var linkRecord = map[V]struct{}{} var directionCount = map[Direction]int{} var count = 0 for i, directions := range [][]Direction{DirectionUDLR, DirectionLRUD} { 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 - } - offset := directionPoint.GetOffset(-left, -top) - if offset.Negative() { - break - } - offsetPos := int(offset.GetPos(width)) - if offsetPos < 0 || offsetPos >= len(rectangleShape) || !rectangleShape[offsetPos].Data { - break - } - links = append(links, directionPoint) - directionCount[direction]++ - count++ - match(links, directionCount, count) + 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 + } + offset := directionPoint.GetOffset(-left, -top) + if offset.OutOf(V(0), V(0), width, height) || !rectangleShape[int(offset.GetX())][int(offset.GetY())] { + break + } + recordPos := directionPoint.GetPos(areaWidth) + if _, exist := linkRecord[recordPos]; !exist { + linkRecord[recordPos] = struct{}{} + links = append(links, directionPoint) + directionCount[direction]++ + count++ + match(links, directionCount, count) + } - pos := directionPoint.GetPos(areaWidth) - if _, exist := records[pos]; !exist { - result = append(result, Shape[V]{directionPoint}) - records[pos] = struct{}{} + pos := directionPoint.GetPos(areaWidth) + if _, exist := records[pos]; !exist && opt.directionCount < 1 { + result = append(result, Shape[V]{directionPoint}) + records[pos] = struct{}{} + } } - } - - finish := false - switch i { - case 0: - if direction == DirectionRight { - finish = true + finish := false + switch i { + case 0: + if direction == DirectionRight { + finish = true + } + case 1: + if direction == DirectionDown { + finish = true + } } - case 1: - if direction == DirectionDown { - finish = true + if finish { + break } + directionPoint = point } - if finish { - break - } - directionPoint = point } } diff --git a/utils/geometry/shape_examle_test.go b/utils/geometry/shape_examle_test.go new file mode 100644 index 0000000..cca5dcc --- /dev/null +++ b/utils/geometry/shape_examle_test.go @@ -0,0 +1,104 @@ +package geometry_test + +import ( + "fmt" + "github.com/kercylan98/minotaur/utils/geometry" +) + +func ExampleNewShape() { + shape := geometry.NewShape[int]( + geometry.NewPoint(3, 0), + geometry.NewPoint(3, 1), + geometry.NewPoint(3, 2), + geometry.NewPoint(3, 3), + geometry.NewPoint(4, 3), + ) + + fmt.Println(shape) + + // Output: + // [[3 0] [3 1] [3 2] [3 3] [4 3]] + // X # + // X # + // X # + // X X +} + +func ExampleNewShapeWithString() { + shape := geometry.NewShapeWithString[int]([]string{ + "###X###", + "###X###", + "###X###", + "###XX##", + }, 'X') + + fmt.Println(shape) + + // Output: [[3 0] [3 1] [3 2] [3 3] [4 3]] + // X # + // X # + // X # + // X X +} + +func ExampleShape_Points() { + shape := geometry.NewShapeWithString[int]([]string{ + "###X###", + "##XXX##", + }, 'X') + + points := shape.Points() + + fmt.Println(points) + + // Output: + // [[3 0] [2 1] [3 1] [4 1]] +} + +func ExampleShape_PointCount() { + shape := geometry.NewShapeWithString[int]([]string{ + "###X###", + "##XXX##", + }, 'X') + + fmt.Println(shape.PointCount()) + + // Output: + // 4 +} + +func ExampleShape_String() { + shape := geometry.NewShapeWithString[int]([]string{ + "###X###", + "##XXX##", + }, 'X') + + fmt.Println(shape) + + // Output: + // [[3 0] [2 1] [3 1] [4 1]] + // # X # + // X X X +} + +func ExampleShape_ShapeSearch() { + shape := geometry.NewShapeWithString[int]([]string{ + "###X###", + "##XXX##", + "###X###", + }, 'X') + + shapes := shape.ShapeSearch( + geometry.WithShapeSearchDeduplication(), + geometry.WithShapeSearchDesc(), + ) + for _, shape := range shapes { + fmt.Println(shape) + } + + // Output: + // [[3 0] [3 2] [2 1] [4 1] [3 1]] + // # X # + // X X X + // # X # +} diff --git a/utils/geometry/shape_search_options.go b/utils/geometry/shape_search_options.go index 8bdd41b..ffc14b5 100644 --- a/utils/geometry/shape_search_options.go +++ b/utils/geometry/shape_search_options.go @@ -1,5 +1,15 @@ package geometry +import "math" + +func newShapeSearchOptions() *shapeSearchOptions { + return &shapeSearchOptions{ + upperLimit: math.MaxInt, + rectangleMaxWidth: math.MaxInt, + rectangleMaxHeight: math.MaxInt, + } +} + type shapeSearchOptions struct { lowerLimit int upperLimit int diff --git a/utils/geometry/shape_test.go b/utils/geometry/shape_test.go index 525c08a..971fab9 100644 --- a/utils/geometry/shape_test.go +++ b/utils/geometry/shape_test.go @@ -3,30 +3,106 @@ package geometry_test import ( "fmt" "github.com/kercylan98/minotaur/utils/geometry" + . "github.com/smartystreets/goconvey/convey" "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, 1)) - shape = append(shape, geometry.NewPoint(2, 2)) - shape = append(shape, geometry.NewPoint(1, 3)) - geometry.ShapeStringHasBorder = true - - fmt.Println("形状:") - fmt.Println(shape) - - shapes := shape.ShapeSearch( - geometry.WithShapeSearchDesc(), - geometry.WithShapeSearchRectangleLowerLimit(2, 2), - ) - - for _, shape := range shapes { - fmt.Println("搜索", shape.Points()) +func TestNewShape(t *testing.T) { + Convey("TestNewShape", t, func() { + shape := geometry.NewShape[int]( + geometry.NewPoint(3, 0), + geometry.NewPoint(3, 1), + geometry.NewPoint(3, 2), + geometry.NewPoint(3, 3), + geometry.NewPoint(4, 3), + ) fmt.Println(shape) - } + points := shape.Points() + count := shape.PointCount() + So(count, ShouldEqual, 5) + So(points[0], ShouldEqual, geometry.NewPoint(3, 0)) + So(points[1], ShouldEqual, geometry.NewPoint(3, 1)) + So(points[2], ShouldEqual, geometry.NewPoint(3, 2)) + So(points[3], ShouldEqual, geometry.NewPoint(3, 3)) + So(points[4], ShouldEqual, geometry.NewPoint(4, 3)) + }) +} + +func TestNewShapeWithString(t *testing.T) { + Convey("TestNewShapeWithString", t, func() { + shape := geometry.NewShapeWithString[int]([]string{ + "###X###", + "###X###", + "###X###", + "###XX##", + }, 'X') + + points := shape.Points() + count := shape.PointCount() + So(count, ShouldEqual, 5) + So(points[0], ShouldEqual, geometry.NewPoint(3, 0)) + So(points[1], ShouldEqual, geometry.NewPoint(3, 1)) + So(points[2], ShouldEqual, geometry.NewPoint(3, 2)) + So(points[3], ShouldEqual, geometry.NewPoint(3, 3)) + So(points[4], ShouldEqual, geometry.NewPoint(4, 3)) + }) +} + +func TestShape_Points(t *testing.T) { + Convey("TestShape_Points", t, func() { + shape := geometry.NewShapeWithString[int]([]string{ + "###X###", + "##XXX##", + }, 'X') + + points := shape.Points() + So(points[0], ShouldEqual, geometry.NewPoint(3, 0)) + So(points[1], ShouldEqual, geometry.NewPoint(2, 1)) + So(points[2], ShouldEqual, geometry.NewPoint(3, 1)) + So(points[3], ShouldEqual, geometry.NewPoint(4, 1)) + }) +} + +func TestShape_PointCount(t *testing.T) { + Convey("TestShape_PointCount", t, func() { + shape := geometry.NewShapeWithString[int]([]string{ + "###X###", + "##XXX##", + }, 'X') + + So(shape.PointCount(), ShouldEqual, 4) + }) +} + +func TestShape_String(t *testing.T) { + Convey("TestShape_String", t, func() { + shape := geometry.NewShapeWithString[int]([]string{ + "###X###", + "##XXX##", + }, 'X') + + str := shape.String() + + So(str, ShouldEqual, "[[3 0] [2 1] [3 1] [4 1]]\n# X #\nX X X") + }) +} + +func TestShape_Search(t *testing.T) { + Convey("TestShape_Search", t, func() { + shape := geometry.NewShapeWithString[int]([]string{ + "###X###", + "##XXX##", + "###X###", + }, 'X') + + shapes := shape.ShapeSearch( + geometry.WithShapeSearchDeduplication(), + geometry.WithShapeSearchDesc(), + ) + So(len(shapes), ShouldEqual, 1) + for _, shape := range shapes { + So(shape.PointCount(), ShouldEqual, 5) + } + }) + }