From 2b37461b54ceaf4485943941833d4f7e9648e22f Mon Sep 17 00:00:00 2001 From: Timon Ringwald Date: Thu, 1 Oct 2020 13:14:36 +0200 Subject: [PATCH 1/9] initial commit --- buffer.go | 100 +++++++++++++++++++++++++++++++++++++++++++++ buffer_sub_test.go | 16 ++++++++ go.mod | 3 ++ utils.go | 19 +++++++++ 4 files changed, 138 insertions(+) create mode 100644 buffer.go create mode 100644 buffer_sub_test.go create mode 100644 go.mod create mode 100644 utils.go diff --git a/buffer.go b/buffer.go new file mode 100644 index 0000000..af46f28 --- /dev/null +++ b/buffer.go @@ -0,0 +1,100 @@ +package buf2d + +import ( + "strings" +) + +// Buffer is a 2-dimensional rune buffer +type Buffer struct { + data [][]rune + width int + height int + parent *Buffer +} + +// NewBuffer makes a new buffer with the given dimensions +func NewBuffer(width, height int) *Buffer { + b := make([][]rune, width) + for x := range b { + b[x] = make([]rune, height) + for y := range b[x] { + b[x][y] = ' ' + } + } + + return &Buffer{ + data: b, + width: width, + height: height, + parent: nil, + } +} + +func (b *Buffer) Set(x, y int, c rune) { + b.data[x][y] = c +} + +func (b *Buffer) Get(x, y int) rune { + return b.data[x][y] +} + +func (b *Buffer) Size() (w, h int) { + return b.width, b.height +} + +func (b *Buffer) Width() int { + return b.width +} + +func (b *Buffer) Height() int { + return b.height +} + +// Draw calls drawFunc for every rune in this buffer +func (b *Buffer) Draw(drawFunc func(x, y int, c rune)) { + for x, row := range b.data { + for y, char := range row { + drawFunc(x, y, char) + } + } +} + +func (b *Buffer) String() string { + s := new(strings.Builder) + for ri, row := range b.data { + for _, char := range row { + s.WriteRune(char) + } + if ri != len(b.data)-1 { + s.WriteRune('\n') + } + } + return s.String() +} + +// Sub returns a buffer which is completely contained in this buffer +// Modifying the main buffer or the sub buffer will modify the other one as well +// This method can be used recursively +// If the given dimensions don't fit in the parent buffer, it will be truncated +func (b *Buffer) Sub(x, y, w, h int) *Buffer { + // sanitize inputs + x = limit(x, 0, b.width-1) + y = limit(y, 0, b.height-1) + w = limit(w, 1, b.width-x) + h = limit(h, 1, b.height-y) + + // make slice references + data := make([][]rune, w) + for dx := x; dx < x+w-1; dx++ { + row := b.data[dx] + data[dx] = row[y : y+w] + } + + // make buffer + return &Buffer{ + data: data, + width: w, + height: h, + parent: b, + } +} diff --git a/buffer_sub_test.go b/buffer_sub_test.go new file mode 100644 index 0000000..c5df613 --- /dev/null +++ b/buffer_sub_test.go @@ -0,0 +1,16 @@ +package buf2d + +import ( + "fmt" + "testing" +) + +func TestSub(t *testing.T) { + b := NewBuffer(10, 10) + s := b.Sub(1, 1, b.Width()-1, b.Height()-1) + b.Set(5, 5, 'a') + s.Set(5, 5, 'b') + + fmt.Println(b) + fmt.Println(s) +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..42c54d9 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module git.tordarus.net/tordarus/buf2d + +go 1.15 diff --git a/utils.go b/utils.go new file mode 100644 index 0000000..0c3a776 --- /dev/null +++ b/utils.go @@ -0,0 +1,19 @@ +package buf2d + +func limit(v, min, max int) int { + return getmax(getmin(v, max), min) +} + +func getmax(x, y int) int { + if x > y { + return x + } + return y +} + +func getmin(x, y int) int { + if x < y { + return x + } + return y +} From 060c43069079fdc0eba79443fac5ee7b94bd26b8 Mon Sep 17 00:00:00 2001 From: Timon Ringwald Date: Thu, 1 Oct 2020 13:37:58 +0200 Subject: [PATCH 2/9] fixed wrong axes --- buffer.go | 32 ++++++++++++++++---------------- buffer_sub_test.go | 4 +++- 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/buffer.go b/buffer.go index af46f28..f5e80d8 100644 --- a/buffer.go +++ b/buffer.go @@ -14,11 +14,11 @@ type Buffer struct { // NewBuffer makes a new buffer with the given dimensions func NewBuffer(width, height int) *Buffer { - b := make([][]rune, width) - for x := range b { - b[x] = make([]rune, height) - for y := range b[x] { - b[x][y] = ' ' + b := make([][]rune, height) + for y := range b { + b[y] = make([]rune, width) + for x := range b[y] { + b[y][x] = ' ' } } @@ -31,11 +31,11 @@ func NewBuffer(width, height int) *Buffer { } func (b *Buffer) Set(x, y int, c rune) { - b.data[x][y] = c + b.data[y][x] = c } func (b *Buffer) Get(x, y int) rune { - return b.data[x][y] + return b.data[y][x] } func (b *Buffer) Size() (w, h int) { @@ -52,8 +52,8 @@ func (b *Buffer) Height() int { // Draw calls drawFunc for every rune in this buffer func (b *Buffer) Draw(drawFunc func(x, y int, c rune)) { - for x, row := range b.data { - for y, char := range row { + for y, col := range b.data { + for x, char := range col { drawFunc(x, y, char) } } @@ -61,11 +61,11 @@ func (b *Buffer) Draw(drawFunc func(x, y int, c rune)) { func (b *Buffer) String() string { s := new(strings.Builder) - for ri, row := range b.data { - for _, char := range row { + for ci, col := range b.data { + for _, char := range col { s.WriteRune(char) } - if ri != len(b.data)-1 { + if ci != len(b.data)-1 { s.WriteRune('\n') } } @@ -84,10 +84,10 @@ func (b *Buffer) Sub(x, y, w, h int) *Buffer { h = limit(h, 1, b.height-y) // make slice references - data := make([][]rune, w) - for dx := x; dx < x+w-1; dx++ { - row := b.data[dx] - data[dx] = row[y : y+w] + data := make([][]rune, h) + for dy := y; dy < y+h-1; dy++ { + col := b.data[dy] + data[dy] = col[x : x+w] } // make buffer diff --git a/buffer_sub_test.go b/buffer_sub_test.go index c5df613..df27cd1 100644 --- a/buffer_sub_test.go +++ b/buffer_sub_test.go @@ -2,15 +2,17 @@ package buf2d import ( "fmt" + "strings" "testing" ) func TestSub(t *testing.T) { b := NewBuffer(10, 10) s := b.Sub(1, 1, b.Width()-1, b.Height()-1) - b.Set(5, 5, 'a') + b.Set(5, 1, 'a') s.Set(5, 5, 'b') fmt.Println(b) + fmt.Println(strings.Repeat("-", 20)) fmt.Println(s) } From 85dbadcaff3203afd8f3f9d1438e38b0edf1c8de Mon Sep 17 00:00:00 2001 From: Timon Ringwald Date: Thu, 1 Oct 2020 13:56:05 +0200 Subject: [PATCH 3/9] WriteString method added --- buffer.go | 12 ++++++++++-- buffer_sub_test.go => buffer_test.go | 1 + write_string.go | 15 +++++++++++++++ 3 files changed, 26 insertions(+), 2 deletions(-) rename buffer_sub_test.go => buffer_test.go (87%) create mode 100644 write_string.go diff --git a/buffer.go b/buffer.go index f5e80d8..6726aaf 100644 --- a/buffer.go +++ b/buffer.go @@ -1,6 +1,7 @@ package buf2d import ( + "fmt" "strings" ) @@ -30,22 +31,27 @@ func NewBuffer(width, height int) *Buffer { } } +// Set sets the rune at position (x,y) to c func (b *Buffer) Set(x, y int, c rune) { b.data[y][x] = c } +// Get returns the rune at position (x,y) func (b *Buffer) Get(x, y int) rune { return b.data[y][x] } +// Size returns width and height of b func (b *Buffer) Size() (w, h int) { return b.width, b.height } +// Width returns the width of b func (b *Buffer) Width() int { return b.width } +// Height returns the height of b func (b *Buffer) Height() int { return b.height } @@ -85,11 +91,13 @@ func (b *Buffer) Sub(x, y, w, h int) *Buffer { // make slice references data := make([][]rune, h) - for dy := y; dy < y+h-1; dy++ { - col := b.data[dy] + for dy := 0; dy < h; dy++ { + col := b.data[y+dy] data[dy] = col[x : x+w] } + fmt.Println(data) + // make buffer return &Buffer{ data: data, diff --git a/buffer_sub_test.go b/buffer_test.go similarity index 87% rename from buffer_sub_test.go rename to buffer_test.go index df27cd1..dea498f 100644 --- a/buffer_sub_test.go +++ b/buffer_test.go @@ -11,6 +11,7 @@ func TestSub(t *testing.T) { s := b.Sub(1, 1, b.Width()-1, b.Height()-1) b.Set(5, 1, 'a') s.Set(5, 5, 'b') + b.WriteString("Hello world", 1, 2) fmt.Println(b) fmt.Println(strings.Repeat("-", 20)) diff --git a/write_string.go b/write_string.go new file mode 100644 index 0000000..8424846 --- /dev/null +++ b/write_string.go @@ -0,0 +1,15 @@ +package buf2d + +import "fmt" + +// WriteString writes a whole string to the buffer at position (x,y) +// no word wrap is applied at all. If the string does not fit, it will be truncated +func (b *Buffer) WriteString(str string, x, y int) { + for dx, r := range str { + if x+dx >= b.width { + return + } + fmt.Println(x+dx, y, string(r)) + b.Set(x+dx, y, r) + } +} From f72c9e7aa3f66fb0b5c33a112327eec9690c5809 Mon Sep 17 00:00:00 2001 From: Timon Ringwald Date: Thu, 1 Oct 2020 13:59:38 +0200 Subject: [PATCH 4/9] removed logs --- buffer.go | 3 --- write_string.go | 3 --- 2 files changed, 6 deletions(-) diff --git a/buffer.go b/buffer.go index 6726aaf..6425b0d 100644 --- a/buffer.go +++ b/buffer.go @@ -1,7 +1,6 @@ package buf2d import ( - "fmt" "strings" ) @@ -96,8 +95,6 @@ func (b *Buffer) Sub(x, y, w, h int) *Buffer { data[dy] = col[x : x+w] } - fmt.Println(data) - // make buffer return &Buffer{ data: data, diff --git a/write_string.go b/write_string.go index 8424846..81858c7 100644 --- a/write_string.go +++ b/write_string.go @@ -1,7 +1,5 @@ package buf2d -import "fmt" - // WriteString writes a whole string to the buffer at position (x,y) // no word wrap is applied at all. If the string does not fit, it will be truncated func (b *Buffer) WriteString(str string, x, y int) { @@ -9,7 +7,6 @@ func (b *Buffer) WriteString(str string, x, y int) { if x+dx >= b.width { return } - fmt.Println(x+dx, y, string(r)) b.Set(x+dx, y, r) } } From 011bf8e6164c0ce4913bffe419fa7aa363b1bc84 Mon Sep 17 00:00:00 2001 From: Timon Ringwald Date: Thu, 1 Oct 2020 14:14:43 +0200 Subject: [PATCH 5/9] added Fill method --- write_string.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/write_string.go b/write_string.go index 81858c7..aa046b0 100644 --- a/write_string.go +++ b/write_string.go @@ -10,3 +10,12 @@ func (b *Buffer) WriteString(str string, x, y int) { b.Set(x+dx, y, r) } } + +// Fill fills the whole buffer with c +func (b *Buffer) Fill(c rune) { + for _, col := range b.data { + for ri := range col { + col[ri] = c + } + } +} From d5ee06e128b7545644b9f580ceab75fbc1322248 Mon Sep 17 00:00:00 2001 From: Timon Ringwald Date: Thu, 1 Oct 2020 14:46:28 +0200 Subject: [PATCH 6/9] fixed unicode chars printed wrong in WriteString --- write_string.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/write_string.go b/write_string.go index aa046b0..dd7412e 100644 --- a/write_string.go +++ b/write_string.go @@ -3,11 +3,13 @@ package buf2d // WriteString writes a whole string to the buffer at position (x,y) // no word wrap is applied at all. If the string does not fit, it will be truncated func (b *Buffer) WriteString(str string, x, y int) { - for dx, r := range str { - if x+dx >= b.width { + dx := x + for _, r := range str { + if dx >= b.width { return } - b.Set(x+dx, y, r) + b.Set(dx, y, r) + dx++ } } From 88a6a466bfa71e7d86144d08a1a15fdbe5e833b3 Mon Sep 17 00:00:00 2001 From: Timon Ringwald Date: Thu, 1 Oct 2020 15:08:17 +0200 Subject: [PATCH 7/9] WriteMultiLineString added --- write_string.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/write_string.go b/write_string.go index dd7412e..dbacf9e 100644 --- a/write_string.go +++ b/write_string.go @@ -1,5 +1,7 @@ package buf2d +import "strings" + // WriteString writes a whole string to the buffer at position (x,y) // no word wrap is applied at all. If the string does not fit, it will be truncated func (b *Buffer) WriteString(str string, x, y int) { @@ -13,6 +15,20 @@ func (b *Buffer) WriteString(str string, x, y int) { } } +// WriteMultiLineString writes a multi-line string to the buffer at position (x,y) +// no word wrap is applied at all. If a line does not fit horizontally, it will be truncated +// All lines which do not fit vertically will be truncated as well +func (b *Buffer) WriteMultiLineString(str string, x, y int) { + lines := strings.Split(str, "\n") + for dy, line := range lines { + if dy >= b.height { + return + } + + b.WriteString(line, x, y+dy) + } +} + // Fill fills the whole buffer with c func (b *Buffer) Fill(c rune) { for _, col := range b.data { From fbf90504b837b6e982b6b508e7491a47896ced1b Mon Sep 17 00:00:00 2001 From: Timon Ringwald Date: Thu, 1 Oct 2020 15:54:08 +0200 Subject: [PATCH 8/9] fixed panic in Set method --- buffer.go | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/buffer.go b/buffer.go index 6425b0d..6b83501 100644 --- a/buffer.go +++ b/buffer.go @@ -30,9 +30,17 @@ func NewBuffer(width, height int) *Buffer { } } +func (b *Buffer) x(x int) int { + return limit(x, 0, b.width-1) +} + +func (b *Buffer) y(y int) int { + return limit(y, 0, b.height-1) +} + // Set sets the rune at position (x,y) to c func (b *Buffer) Set(x, y int, c rune) { - b.data[y][x] = c + b.data[b.y(y)][b.x(x)] = c } // Get returns the rune at position (x,y) From 7d181490080f8ed56b246dcb5b0e8f299405b078 Mon Sep 17 00:00:00 2001 From: Tordarus Date: Fri, 15 Jan 2021 21:40:09 +0100 Subject: [PATCH 9/9] generic interface implementation. ready for generic update --- buffer.go | 36 +++++++++++++++++++++--------------- buffer_test.go | 4 ++-- utils.go | 21 +++++++++++++++++++++ write_string.go | 9 ++++++--- 4 files changed, 50 insertions(+), 20 deletions(-) diff --git a/buffer.go b/buffer.go index 6b83501..352a9e8 100644 --- a/buffer.go +++ b/buffer.go @@ -1,12 +1,13 @@ package buf2d import ( + "fmt" "strings" ) // Buffer is a 2-dimensional rune buffer type Buffer struct { - data [][]rune + data [][]fmt.Stringer width int height int parent *Buffer @@ -14,11 +15,11 @@ type Buffer struct { // NewBuffer makes a new buffer with the given dimensions func NewBuffer(width, height int) *Buffer { - b := make([][]rune, height) + b := make([][]fmt.Stringer, height) for y := range b { - b[y] = make([]rune, width) + b[y] = make([]fmt.Stringer, width) for x := range b[y] { - b[y][x] = ' ' + b[y][x] = spaceStringer } } @@ -38,13 +39,18 @@ func (b *Buffer) y(y int) int { return limit(y, 0, b.height-1) } -// Set sets the rune at position (x,y) to c -func (b *Buffer) Set(x, y int, c rune) { +// Set sets the fmt.Stringer at position (x,y) to c +func (b *Buffer) Set(x, y int, c fmt.Stringer) { b.data[b.y(y)][b.x(x)] = c } -// Get returns the rune at position (x,y) -func (b *Buffer) Get(x, y int) rune { +// SetRune sets the fmt.Stringer at position (x,y) to rn +func (b *Buffer) SetRune(x, y int, rn rune) { + b.data[b.y(y)][b.x(x)] = newStringerFromRune(rn) +} + +// Get returns the fmt.Stringer at position (x,y) +func (b *Buffer) Get(x, y int) fmt.Stringer { return b.data[y][x] } @@ -63,11 +69,11 @@ func (b *Buffer) Height() int { return b.height } -// Draw calls drawFunc for every rune in this buffer -func (b *Buffer) Draw(drawFunc func(x, y int, c rune)) { +// ForEach calls f for every rune in this buffer +func (b *Buffer) ForEach(f func(x, y int, c fmt.Stringer)) { for y, col := range b.data { - for x, char := range col { - drawFunc(x, y, char) + for x, v := range col { + f(x, y, v) } } } @@ -75,8 +81,8 @@ func (b *Buffer) Draw(drawFunc func(x, y int, c rune)) { func (b *Buffer) String() string { s := new(strings.Builder) for ci, col := range b.data { - for _, char := range col { - s.WriteRune(char) + for _, v := range col { + s.WriteString(fmt.Sprintf("%v", v)) } if ci != len(b.data)-1 { s.WriteRune('\n') @@ -97,7 +103,7 @@ func (b *Buffer) Sub(x, y, w, h int) *Buffer { h = limit(h, 1, b.height-y) // make slice references - data := make([][]rune, h) + data := make([][]fmt.Stringer, h) for dy := 0; dy < h; dy++ { col := b.data[y+dy] data[dy] = col[x : x+w] diff --git a/buffer_test.go b/buffer_test.go index dea498f..f1e96aa 100644 --- a/buffer_test.go +++ b/buffer_test.go @@ -9,8 +9,8 @@ import ( func TestSub(t *testing.T) { b := NewBuffer(10, 10) s := b.Sub(1, 1, b.Width()-1, b.Height()-1) - b.Set(5, 1, 'a') - s.Set(5, 5, 'b') + b.SetRune(5, 1, 'a') + s.SetRune(5, 5, 'b') b.WriteString("Hello world", 1, 2) fmt.Println(b) diff --git a/utils.go b/utils.go index 0c3a776..7473b6c 100644 --- a/utils.go +++ b/utils.go @@ -1,5 +1,7 @@ package buf2d +import "fmt" + func limit(v, min, max int) int { return getmax(getmin(v, max), min) } @@ -17,3 +19,22 @@ func getmin(x, y int) int { } return y } + +type stringerImpl string + +func (s *stringerImpl) String() string { + return string(*s) +} + +func newStringer(str string) fmt.Stringer { + var impl stringerImpl = stringerImpl(str) + return &impl +} + +func newStringerFromRune(rn rune) fmt.Stringer { + return newStringer(string(rn)) +} + +var ( + spaceStringer = newStringer(" ") +) diff --git a/write_string.go b/write_string.go index dbacf9e..9df495c 100644 --- a/write_string.go +++ b/write_string.go @@ -1,6 +1,9 @@ package buf2d -import "strings" +import ( + "fmt" + "strings" +) // WriteString writes a whole string to the buffer at position (x,y) // no word wrap is applied at all. If the string does not fit, it will be truncated @@ -10,7 +13,7 @@ func (b *Buffer) WriteString(str string, x, y int) { if dx >= b.width { return } - b.Set(dx, y, r) + b.Set(dx, y, newStringerFromRune(r)) dx++ } } @@ -30,7 +33,7 @@ func (b *Buffer) WriteMultiLineString(str string, x, y int) { } // Fill fills the whole buffer with c -func (b *Buffer) Fill(c rune) { +func (b *Buffer) Fill(c fmt.Stringer) { for _, col := range b.data { for ri := range col { col[ri] = c