🎨 优化单元测试及示例
This commit is contained in:
parent
1059f9c016
commit
ea7ed7ea4e
|
@ -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
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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 #
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue