From f4ffae089e7aefedeb6c0d588bdc9d3261fa59df Mon Sep 17 00:00:00 2001
From: Nahshon Unna-Tsameret
Date: Mon, 26 Apr 2021 11:19:52 +0300
Subject: [PATCH] implement bucket tool
Signed-off-by: Nahshon Unna-Tsameret
---
state/state.go | 119 ++++++++++++++++++++++++++++++++++++--------
state/state_test.go | 111 +++++++++++++++++++++++++++++++++++++++++
webapp/index.gohtml | 26 ++++++++--
3 files changed, 231 insertions(+), 25 deletions(-)
diff --git a/state/state.go b/state/state.go
index f90bd9b..67d88e5 100644
--- a/state/state.go
+++ b/state/state.go
@@ -11,6 +11,7 @@ import (
const (
penName = "pen"
eraserName = "eraser"
+ bucketName = "bucket"
)
type Canvas [][]common.Color
@@ -61,6 +62,7 @@ func NewState(canvasWidth, canvasHeight uint8) *State {
s.tools = map[string]tool{
penName: s.pen,
eraserName: s.eraser,
+ bucketName: s.bucket,
}
_ = s.Reset()
@@ -149,50 +151,123 @@ func (s *State) Paint() *Change {
return s.tools[s.toolName]()
}
-func (s *State) pen() *Change {
- px := s.paintPixel(s.color)
- if px == nil {
+func (s *State) setSinglePixelTool(color common.Color) *Change {
+ after, before := s.paintPixel(color, s.cursor.X, s.cursor.Y)
+ if after == nil {
return nil
}
+ if before != nil {
+ change := &Change{
+ Pixels: []Pixel{*before},
+ }
+ undoList.push(change)
+ }
+
return &Change{
- Pixels: []Pixel{*px},
+ Pixels: []Pixel{*after},
}
}
+func (s *State) pen() *Change {
+ return s.setSinglePixelTool(s.color)
+}
+
func (s *State) eraser() *Change {
- px := s.paintPixel(0)
- if px == nil {
- return nil
+ return s.setSinglePixelTool(0)
+}
+
+func (s State) getNeighbors(center Pixel) []Pixel {
+ color := center.Color
+ res := make([]Pixel, 0, 4)
+ if center.Y > 0 && color == s.canvas[center.Y-1][center.X] {
+ res = append(res, Pixel{X: center.X, Y: center.Y - 1, Color: color})
}
- return &Change{
- Pixels: []Pixel{*px},
+ if center.X > 0 && color == s.canvas[center.Y][center.X-1] {
+ res = append(res, Pixel{X: center.X - 1, Y: center.Y, Color: color})
}
+
+ if center.Y < s.canvasHeight-1 && color == s.canvas[center.Y+1][center.X] {
+ res = append(res, Pixel{X: center.X, Y: center.Y + 1, Color: color})
+ }
+
+ if center.X < s.canvasWidth-1 && color == s.canvas[center.Y][center.X+1] {
+ res = append(res, Pixel{X: center.X + 1, Y: center.Y, Color: color})
+ }
+
+ return res
}
-func (s *State) paintPixel(color common.Color) *Pixel {
- if s.cursor.Y >= s.canvasHeight || s.cursor.X >= s.canvasWidth {
- log.Printf("Error: Cursor (%d, %d) is out of canvas\n", s.cursor.X, s.cursor.Y)
- return nil
+func (s *State) paintNeighbors(px Pixel, after *[]Pixel, before *[]Pixel) {
+ nbs := s.getNeighbors(px)
+
+ afterPx, beforePx := s.paintPixel(s.color, px.X, px.Y)
+ if afterPx != nil {
+ *after = append(*after, *afterPx)
+
+ if beforePx != nil {
+ *before = append(*before, *beforePx)
+ }
+
+ for _, n := range nbs {
+ s.paintNeighbors(n, after, before)
+ }
}
+}
- if s.canvas[s.cursor.Y][s.cursor.X] != color {
- chng := &Change{
- Pixels: []Pixel{{X: s.cursor.X, Y: s.cursor.Y, Color: s.canvas[s.cursor.Y][s.cursor.X]}},
+func (s *State) bucket() *Change {
+ cursorPx := Pixel{
+ X: s.cursor.X,
+ Y: s.cursor.Y,
+ Color: s.canvas[s.cursor.Y][s.cursor.X],
+ }
+
+ after := make([]Pixel, 0, 10)
+ before := make([]Pixel, 0, 10)
+
+ s.paintNeighbors(cursorPx, &after, &before)
+ if len(after) > 0 {
+ undoChange := &Change{
+ Pixels: before,
}
- undoList.push(chng)
- s.canvas[s.cursor.Y][s.cursor.X] = color
- return &Pixel{
- X: s.cursor.X,
- Y: s.cursor.Y,
- Color: color,
+
+ undoList.push(undoChange)
+
+ return &Change{
+ Pixels: after,
}
}
return nil
}
+func (s *State) paintPixel(color common.Color, x, y uint8) (*Pixel, *Pixel) {
+ if y >= s.canvasHeight || x >= s.canvasWidth {
+ log.Printf("Error: Cursor (%d, %d) is out of canvas\n", x, y)
+ return nil, nil
+ }
+
+ if s.canvas[y][x] != color {
+ before := &Pixel{
+ X: x,
+ Y: y,
+ Color: s.canvas[y][x],
+ }
+
+ s.canvas[y][x] = color
+ after := &Pixel{
+ X: x,
+ Y: y,
+ Color: color,
+ }
+
+ return after, before
+ }
+
+ return nil, nil
+}
+
func (s State) CreateDisplayMessage() hat.DisplayMessage {
c := make([][]common.Color, common.WindowSize)
for y := uint8(0); y < common.WindowSize; y++ {
diff --git a/state/state_test.go b/state/state_test.go
index daee453..f112172 100644
--- a/state/state_test.go
+++ b/state/state_test.go
@@ -1,6 +1,7 @@
package state
import (
+ "reflect"
"testing"
"github.com/nunnatsa/piHatDraw/common"
@@ -364,3 +365,113 @@ func TestState_Undo(t *testing.T) {
}
}
+
+func TestBucket(t *testing.T) {
+ s := NewState(8, 8)
+ s.canvas = [][]common.Color{
+ {0, 0, 0, 0, 0, 0, 0, 0},
+ {0, 0, 0, 0, 0, 0, 0, 0},
+ {0, 0, 1, 1, 1, 1, 1, 0},
+ {0, 0, 1, 0, 0, 0, 1, 0},
+ {0, 0, 1, 0, 0, 0, 1, 0},
+ {0, 0, 1, 0, 0, 0, 1, 0},
+ {0, 0, 1, 1, 1, 1, 1, 0},
+ {0, 0, 0, 0, 0, 0, 0, 0},
+ }
+
+ s.cursor.Y = 4
+ s.cursor.X = 4
+ s.toolName = bucketName
+ s.color = 2
+ s.Paint()
+ expected := Canvas{
+ {0, 0, 0, 0, 0, 0, 0, 0},
+ {0, 0, 0, 0, 0, 0, 0, 0},
+ {0, 0, 1, 1, 1, 1, 1, 0},
+ {0, 0, 1, 2, 2, 2, 1, 0},
+ {0, 0, 1, 2, 2, 2, 1, 0},
+ {0, 0, 1, 2, 2, 2, 1, 0},
+ {0, 0, 1, 1, 1, 1, 1, 0},
+ {0, 0, 0, 0, 0, 0, 0, 0},
+ }
+
+ if !reflect.DeepEqual(expected, s.canvas) {
+ t.Errorf("should fill the square")
+ }
+
+ s.cursor.X = 2
+ s.cursor.Y = 2
+ s.color = 3
+ s.Paint()
+ expected = Canvas{
+ {0, 0, 0, 0, 0, 0, 0, 0},
+ {0, 0, 0, 0, 0, 0, 0, 0},
+ {0, 0, 3, 3, 3, 3, 3, 0},
+ {0, 0, 3, 2, 2, 2, 3, 0},
+ {0, 0, 3, 2, 2, 2, 3, 0},
+ {0, 0, 3, 2, 2, 2, 3, 0},
+ {0, 0, 3, 3, 3, 3, 3, 0},
+ {0, 0, 0, 0, 0, 0, 0, 0},
+ }
+
+ if !reflect.DeepEqual(expected, s.canvas) {
+ t.Errorf("should fill the square")
+ }
+
+ s.cursor.X = 0
+ s.cursor.Y = 0
+ s.color = 4
+ s.Paint()
+
+ expected = Canvas{
+ {4, 4, 4, 4, 4, 4, 4, 4},
+ {4, 4, 4, 4, 4, 4, 4, 4},
+ {4, 4, 3, 3, 3, 3, 3, 4},
+ {4, 4, 3, 2, 2, 2, 3, 4},
+ {4, 4, 3, 2, 2, 2, 3, 4},
+ {4, 4, 3, 2, 2, 2, 3, 4},
+ {4, 4, 3, 3, 3, 3, 3, 4},
+ {4, 4, 4, 4, 4, 4, 4, 4},
+ }
+
+ if !reflect.DeepEqual(expected, s.canvas) {
+ t.Errorf("should fill the square")
+ }
+
+ s.cursor.X = 7
+ s.cursor.Y = 7
+ s.color = 5
+ s.Paint()
+
+ expected = Canvas{
+ {5, 5, 5, 5, 5, 5, 5, 5},
+ {5, 5, 5, 5, 5, 5, 5, 5},
+ {5, 5, 3, 3, 3, 3, 3, 5},
+ {5, 5, 3, 2, 2, 2, 3, 5},
+ {5, 5, 3, 2, 2, 2, 3, 5},
+ {5, 5, 3, 2, 2, 2, 3, 5},
+ {5, 5, 3, 3, 3, 3, 3, 5},
+ {5, 5, 5, 5, 5, 5, 5, 5},
+ }
+
+ if !reflect.DeepEqual(expected, s.canvas) {
+ t.Errorf("should fill the square")
+ }
+
+ s.Undo()
+
+ expected = Canvas{
+ {4, 4, 4, 4, 4, 4, 4, 4},
+ {4, 4, 4, 4, 4, 4, 4, 4},
+ {4, 4, 3, 3, 3, 3, 3, 4},
+ {4, 4, 3, 2, 2, 2, 3, 4},
+ {4, 4, 3, 2, 2, 2, 3, 4},
+ {4, 4, 3, 2, 2, 2, 3, 4},
+ {4, 4, 3, 3, 3, 3, 3, 4},
+ {4, 4, 4, 4, 4, 4, 4, 4},
+ }
+
+ if !reflect.DeepEqual(expected, s.canvas) {
+ t.Errorf("should fill the square")
+ }
+}
diff --git a/webapp/index.gohtml b/webapp/index.gohtml
index 26a39e7..69cf4b9 100644
--- a/webapp/index.gohtml
+++ b/webapp/index.gohtml
@@ -69,6 +69,8 @@
+
+
@@ -148,8 +150,8 @@
win = data.window
}
- if (data.pen) {
- color = data.pen.color
+ if (data.color) {
+ color = data.color
}
const mt = document.getElementById("matrix")
@@ -183,12 +185,26 @@
const cursorElement = document.getElementById(cursorID)
cursorElement.style.color = reverseColor(cursorElement.style.backgroundColor)
- cursorElement.innerText = tool === "pen" ? '+' : tool === "eraser" ? 'x' : "?"
+ switch (tool) {
+ case "pen":
+ cursorElement.innerText = '+'
+ break
+ case "eraser":
+ cursorElement.innerText = 'x'
+ break
+ case "bucket":
+ cursorElement.innerText = 'o'
+ break
+ default:
+ cursorElement.innerText = '?'
+ }
if (tool === "pen") {
toolTypePen.checked = "checked"
} else if (tool === "eraser") {
toolTypeEraser.checked = "checked"
+ } else if (tool === "bucket") {
+ toolTypeBucket.checked = "checked"
}
colorPicker.value = color
@@ -211,6 +227,10 @@
toolTypeEraser.onchange = function (e) {
setTool("eraser")
}
+ const toolTypeBucket = document.getElementById("toolTypeBucket")
+ toolTypeBucket.onchange = function (e) {
+ setTool("bucket")
+ }
pickColorFromPixel = document.getElementById("pickColorFromPixel")
pickColorFromPixel.onclick = function (e) {