🎨 优化单元测试及示例

This commit is contained in:
kercylan98 2023-06-19 10:52:53 +08:00
parent 1059f9c016
commit ea7ed7ea4e
4 changed files with 303 additions and 63 deletions

View File

@ -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
}
}

View File

@ -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 #
}

View File

@ -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

View File

@ -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)
}
})
}