From 0038f88327dacd8d1d973de0f09992852402881c Mon Sep 17 00:00:00 2001 From: Julien Dessaux Date: Sun, 26 Sep 2021 19:29:00 +0200 Subject: Fixes some mycology test suite errors --- pkg/field/blank.go | 97 ++++++++++++++ pkg/field/blank_test.go | 346 ++++++++++++++++++++++++++++++++++++++++++++++++ pkg/field/field.go | 4 +- pkg/field/utils.go | 38 +++--- 4 files changed, 465 insertions(+), 20 deletions(-) create mode 100644 pkg/field/blank.go create mode 100644 pkg/field/blank_test.go 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 } } -- cgit v1.2.3