aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--pkg/field/blank.go97
-rw-r--r--pkg/field/blank_test.go346
-rw-r--r--pkg/field/field.go4
-rw-r--r--pkg/field/utils.go38
4 files changed, 465 insertions, 20 deletions
diff --git a/pkg/field/blank.go b/pkg/field/blank.go
new file mode 100644
index 0000000..2c30b6e
--- /dev/null
+++ b/pkg/field/blank.go
@@ -0,0 +1,97 @@
+package field
+
+func (f *Field) Blank(x, y int) {
+ if !f.isIn(x, y) {
+ // outside the field, nothing to do
+ return
+ }
+ l := &f.lines[y-f.y]
+ if x < l.x || x >= l.x+l.l {
+ // outside the current line's columns
+ return
+ }
+ if x > l.x && x < l.x+l.l-1 {
+ // just set the value
+ l.columns[x-l.x] = ' '
+ return
+ }
+ // do we need to trim this line?
+ if l.l == 1 {
+ // this was the last character on the line
+ if y == f.y {
+ // We need to trim the leading lines
+ leadingLines := 1
+ for i := 1; i < f.ly; i++ {
+ if f.lines[i].l == 0 {
+ leadingLines++
+ } else {
+ break
+ }
+ }
+ f.y += leadingLines
+ f.ly -= leadingLines
+ lines := make([]Line, f.ly)
+ for i := 0; i < f.ly; i++ {
+ lines[i] = f.lines[leadingLines+i]
+ }
+ f.lines = lines
+ } else if y == f.y+f.ly-1 {
+ // We need to trim the trailing lines
+ trailingLines := 1
+ for i := f.ly + f.y - 2; i >= 0; i-- {
+ if f.lines[i].l == 0 {
+ trailingLines++
+ } else {
+ break
+ }
+ }
+ f.ly -= trailingLines
+ f.lines = f.lines[:f.ly]
+ } else {
+ // it was a line in the middle
+ l.l = 0
+ l.columns = make([]int, 0)
+ }
+ } else if x == l.x {
+ // We need to remove leading spaces
+ leadingSpaces := 1
+ for i := 1; i < l.l; i++ {
+ if l.columns[i] == ' ' {
+ leadingSpaces++
+ } else {
+ break
+ }
+ }
+ l.x += leadingSpaces
+ l.l -= leadingSpaces
+ columns := make([]int, l.l)
+ for i := 0; i < l.l; i++ {
+ columns[i] = l.columns[leadingSpaces+i]
+ }
+ l.columns = columns
+ } else if x == l.l+l.x-1 {
+ // we need to remove trailing spaces
+ trailingSpaces := 1
+ for i := l.l - 2; i >= 0; i-- {
+ if l.columns[i] == ' ' {
+ trailingSpaces++
+ } else {
+ break
+ }
+ }
+ l.l -= trailingSpaces
+ l.columns = l.columns[:l.l]
+ }
+ // we now need to find the new field limits
+ f.x = f.lines[0].x
+ x2 := f.lines[0].l + f.lines[0].x
+ for i := 1; i < f.ly; i++ {
+ if f.x > f.lines[i].x {
+ f.x = f.lines[i].x
+ }
+ if x2 < f.lines[i].x+f.lines[i].l {
+ x2 = f.lines[i].x + f.lines[i].l
+ }
+ }
+ f.lx = x2 - f.x
+}
diff --git a/pkg/field/blank_test.go b/pkg/field/blank_test.go
new file mode 100644
index 0000000..260c50b
--- /dev/null
+++ b/pkg/field/blank_test.go
@@ -0,0 +1,346 @@
+package field
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/require"
+)
+
+func TestBlankInside(t *testing.T) {
+ input := Field{
+ x: 0,
+ y: 0,
+ lx: 3,
+ ly: 1,
+ lines: []Line{
+ Line{x: 0, l: 3, columns: []int{'@', 'a', 'b'}},
+ },
+ }
+ expected := Field{
+ x: 0,
+ y: 0,
+ lx: 3,
+ ly: 1,
+ lines: []Line{
+ Line{x: 0, l: 3, columns: []int{'@', ' ', 'b'}},
+ },
+ }
+ // Test cases
+ testCases := []struct {
+ name string
+ input *Field
+ inputX int
+ inputY int
+ expected *Field
+ }{
+ {"inside", &input, 1, 0, &expected},
+ }
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ tc.input.Blank(tc.inputX, tc.inputY)
+ require.Equal(t, tc.expected, tc.input)
+ })
+ }
+}
+
+func TestBlankInsideLine(t *testing.T) {
+ input := Field{
+ x: 0,
+ y: 0,
+ lx: 3,
+ ly: 3,
+ lines: []Line{
+ Line{x: 0, l: 3, columns: []int{'@', 'a', 'b'}},
+ Line{x: 0, l: 1, columns: []int{'d'}},
+ Line{x: 0, l: 1, columns: []int{'c'}},
+ },
+ }
+ expected := Field{
+ x: 0,
+ y: 0,
+ lx: 3,
+ ly: 3,
+ lines: []Line{
+ Line{x: 0, l: 3, columns: []int{'@', 'a', 'b'}},
+ Line{columns: []int{}},
+ Line{x: 0, l: 1, columns: []int{'c'}},
+ },
+ }
+ // Test cases
+ testCases := []struct {
+ name string
+ input *Field
+ inputX int
+ inputY int
+ expected *Field
+ }{
+ {"inside", &input, 0, 1, &expected},
+ }
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ tc.input.Blank(tc.inputX, tc.inputY)
+ require.Equal(t, tc.expected, tc.input)
+ })
+ }
+}
+
+func TestBlankOutside(t *testing.T) {
+ input := Field{
+ x: 0,
+ y: 0,
+ lx: 1,
+ ly: 1,
+ lines: []Line{
+ Line{x: 0, l: 1, columns: []int{'@'}},
+ },
+ }
+ expected := Field{
+ x: 0,
+ y: 0,
+ lx: 1,
+ ly: 1,
+ lines: []Line{
+ Line{x: 0, l: 1, columns: []int{'@'}},
+ },
+ }
+ // Test cases
+ testCases := []struct {
+ name string
+ input *Field
+ inputX int
+ inputY int
+ expected *Field
+ }{
+ {"xappend", &input, 1, 0, &expected},
+ {"xprepend", &input, -1, 0, &expected},
+ {"yappend", &input, 0, 1, &expected},
+ {"yprepend", &input, 0, -1, &expected},
+ }
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ tc.input.Blank(tc.inputX, tc.inputY)
+ require.Equal(t, tc.expected, tc.input)
+ })
+ }
+}
+
+func TestBlankOutsideLine(t *testing.T) {
+ input := Field{
+ x: -1,
+ y: 0,
+ lx: 3,
+ ly: 2,
+ lines: []Line{
+ Line{x: -1, l: 3, columns: []int{'@', '@', '@'}},
+ Line{x: 0, l: 1, columns: []int{'@'}},
+ },
+ }
+ expected := Field{
+ x: -1,
+ y: 0,
+ lx: 3,
+ ly: 2,
+ lines: []Line{
+ Line{x: -1, l: 3, columns: []int{'@', '@', '@'}},
+ Line{x: -0, l: 1, columns: []int{'@'}},
+ },
+ }
+ // Test cases
+ testCases := []struct {
+ name string
+ input *Field
+ inputX int
+ inputY int
+ expected *Field
+ }{
+ {"xappend", &input, 1, 1, &expected},
+ {"xprepend", &input, -1, 1, &expected},
+ }
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ tc.input.Blank(tc.inputX, tc.inputY)
+ require.Equal(t, tc.expected, tc.input)
+ })
+ }
+}
+
+func TestBlankLineTrim(t *testing.T) {
+ expected := Field{
+ x: -1,
+ y: 0,
+ lx: 3,
+ ly: 1,
+ lines: []Line{
+ Line{x: -1, l: 3, columns: []int{'@', '@', '@'}},
+ },
+ }
+ bottomCenter := Field{
+ x: -1,
+ y: 0,
+ lx: 3,
+ ly: 3,
+ lines: []Line{
+ Line{x: -1, l: 3, columns: []int{'@', '@', '@'}},
+ Line{},
+ Line{x: 0, l: 1, columns: []int{'@'}},
+ },
+ }
+ bottomLeft := Field{
+ x: -4,
+ y: 0,
+ lx: 6,
+ ly: 3,
+ lines: []Line{
+ Line{x: -1, l: 3, columns: []int{'@', '@', '@'}},
+ Line{},
+ Line{x: -4, l: 1, columns: []int{'@'}},
+ },
+ }
+ bottomRight := Field{
+ x: -1,
+ y: 0,
+ lx: 6,
+ ly: 3,
+ lines: []Line{
+ Line{x: -1, l: 3, columns: []int{'@', '@', '@'}},
+ Line{},
+ Line{x: 4, l: 1, columns: []int{'@'}},
+ },
+ }
+ topCenter := Field{
+ x: -1,
+ y: -2,
+ lx: 3,
+ ly: 3,
+ lines: []Line{
+ Line{x: 0, l: 1, columns: []int{'@'}},
+ Line{},
+ Line{x: -1, l: 3, columns: []int{'@', '@', '@'}},
+ },
+ }
+ topLeft := Field{
+ x: -4,
+ y: -2,
+ lx: 6,
+ ly: 3,
+ lines: []Line{
+ Line{x: -4, l: 1, columns: []int{'@'}},
+ Line{},
+ Line{x: -1, l: 3, columns: []int{'@', '@', '@'}},
+ },
+ }
+ topRight := Field{
+ x: -1,
+ y: -2,
+ lx: 6,
+ ly: 3,
+ lines: []Line{
+ Line{x: 4, l: 1, columns: []int{'@'}},
+ Line{},
+ Line{x: -1, l: 3, columns: []int{'@', '@', '@'}},
+ },
+ }
+ // Test cases
+ testCases := []struct {
+ name string
+ input *Field
+ inputX int
+ inputY int
+ expected *Field
+ }{
+ {"bottomCenter", &bottomCenter, 0, 2, &expected},
+ {"bottomLeft", &bottomLeft, -4, 2, &expected},
+ {"bottomRight", &bottomRight, 4, 2, &expected},
+ {"topCenter", &topCenter, 0, -2, &expected},
+ {"topLeft", &topLeft, -4, -2, &expected},
+ {"topRight", &topRight, 4, -2, &expected},
+ }
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ tc.input.Blank(tc.inputX, tc.inputY)
+ require.Equal(t, tc.expected, tc.input)
+ })
+ }
+}
+
+func TestBlankcolumnsTrim(t *testing.T) {
+ expectedBottom := Field{
+ x: -1,
+ y: 0,
+ lx: 3,
+ ly: 2,
+ lines: []Line{
+ Line{x: -1, l: 3, columns: []int{'@', '@', '@'}},
+ Line{x: 0, l: 1, columns: []int{'@'}},
+ },
+ }
+ expectedTop := Field{
+ x: -1,
+ y: -1,
+ lx: 3,
+ ly: 2,
+ lines: []Line{
+ Line{x: 0, l: 1, columns: []int{'@'}},
+ Line{x: -1, l: 3, columns: []int{'@', '@', '@'}},
+ },
+ }
+ bottomLeft := Field{
+ x: -4,
+ y: 0,
+ lx: 6,
+ ly: 2,
+ lines: []Line{
+ Line{x: -1, l: 3, columns: []int{'@', '@', '@'}},
+ Line{x: -4, l: 5, columns: []int{'@', ' ', ' ', ' ', '@'}},
+ },
+ }
+ bottomRight := Field{
+ x: -1,
+ y: 0,
+ lx: 6,
+ ly: 2,
+ lines: []Line{
+ Line{x: -1, l: 3, columns: []int{'@', '@', '@'}},
+ Line{x: 0, l: 5, columns: []int{'@', ' ', ' ', ' ', '@'}},
+ },
+ }
+ topLeft := Field{
+ x: -4,
+ y: -1,
+ lx: 6,
+ ly: 2,
+ lines: []Line{
+ Line{x: -4, l: 5, columns: []int{'@', ' ', ' ', ' ', '@'}},
+ Line{x: -1, l: 3, columns: []int{'@', '@', '@'}},
+ },
+ }
+ topRight := Field{
+ x: -1,
+ y: -1,
+ lx: 6,
+ ly: 2,
+ lines: []Line{
+ Line{x: 0, l: 5, columns: []int{'@', ' ', ' ', ' ', '@'}},
+ Line{x: -1, l: 3, columns: []int{'@', '@', '@'}},
+ },
+ }
+ // Test cases
+ testCases := []struct {
+ name string
+ input *Field
+ inputX int
+ inputY int
+ expected *Field
+ }{
+ {"bottomLeft", &bottomLeft, -4, 1, &expectedBottom},
+ {"bottomRight", &bottomRight, 4, 1, &expectedBottom},
+ {"topLeft", &topLeft, -4, -1, &expectedTop},
+ {"topRight", &topRight, 4, -1, &expectedTop},
+ }
+ for _, tc := range testCases {
+ t.Run(tc.name, func(t *testing.T) {
+ tc.input.Blank(tc.inputX, tc.inputY)
+ require.Equal(t, tc.expected, tc.input)
+ })
+ }
+}
diff --git a/pkg/field/field.go b/pkg/field/field.go
index fbe987a..a31f4da 100644
--- a/pkg/field/field.go
+++ b/pkg/field/field.go
@@ -41,8 +41,8 @@ func Load(fd io.Reader) (*Field, error) {
}
if l.l > 0 {
f.ly++
- if f.lx < l.l {
- f.lx = l.l
+ if f.lx-f.x < l.l-l.x {
+ f.lx = l.l - l.x + f.x
}
f.lines = append(f.lines, *l)
}
diff --git a/pkg/field/utils.go b/pkg/field/utils.go
index 1bd2b2b..c513d96 100644
--- a/pkg/field/utils.go
+++ b/pkg/field/utils.go
@@ -15,6 +15,10 @@ func (f Field) isIn(x, y int) bool {
}
func (f *Field) Set(x, y, v int) {
+ if v == ' ' {
+ f.Blank(x, y)
+ return
+ }
if y >= f.y {
if y < f.y+f.ly {
l := &f.lines[y-f.y]
@@ -29,13 +33,13 @@ func (f *Field) Set(x, y, v int) {
l.columns[x-l.x] = v
} else {
// append columns
- newL := l.l + x - l.x
+ newL := x - l.x + 1
for i := l.l; i < newL-1; i++ {
l.columns = append(l.columns, ' ')
}
l.columns = append(l.columns, v)
l.l = newL
- if f.lx < l.l-l.x {
+ if f.lx-f.x < l.l-l.x {
f.lx = l.l - l.x
}
}
@@ -44,18 +48,18 @@ func (f *Field) Set(x, y, v int) {
newL := l.l + l.x - x
c := make([]int, newL)
c[0] = v
- for i := 0; i < l.x-x; i++ {
- c[i+1] = ' '
+ for i := 1; i < l.x-x; i++ {
+ c[i] = ' '
}
- for j := 0; j < l.l; j++ {
- c[j+l.x-x] = l.columns[j]
+ for i := l.x - x; i < newL; i++ {
+ c[i] = l.columns[i-l.x+x]
}
l.columns = c
l.x = x
l.l = newL
if f.x > x {
+ f.lx = f.lx + f.x - x
f.x = x
- f.lx = newL
}
}
} else {
@@ -67,9 +71,10 @@ func (f *Field) Set(x, y, v int) {
f.lines = append(f.lines, Line{x: x, l: 1, columns: []int{v}})
f.ly = newLy
if f.x > x {
- f.lx += f.x - x
+ f.lx = f.lx + f.x - x
f.x = x
- } else if f.lx-f.x < x {
+ }
+ if f.lx-f.x < x {
f.lx = x - f.x
}
}
@@ -77,21 +82,18 @@ func (f *Field) Set(x, y, v int) {
// prepend lines
newLy := f.ly + f.y - y
lines := make([]Line, newLy)
- lines[0] = Line{
- x: x,
- l: 1,
- columns: []int{v},
- }
- for j := 0; j < f.ly; j++ {
- lines[j+f.y-y] = f.lines[j]
+ lines[0] = Line{x: x, l: 1, columns: []int{v}}
+ for j := f.y - y; j < newLy; j++ {
+ lines[j] = f.lines[j-f.y+y]
}
f.lines = lines
f.y = y
f.ly = newLy
if f.x > x {
- f.lx += f.x - x
+ f.lx = f.lx + f.x - x
f.x = x
- } else if f.lx-f.x < x {
+ }
+ if f.lx-f.x < x {
f.lx = x - f.x + 1
}
}