Skip to content

Commit

Permalink
implement bucket tool
Browse files Browse the repository at this point in the history
Signed-off-by: Nahshon Unna-Tsameret <nahsh.ut@gmail.com>
  • Loading branch information
nunnatsa committed Apr 29, 2021
1 parent e925ee8 commit f4ffae0
Show file tree
Hide file tree
Showing 3 changed files with 231 additions and 25 deletions.
119 changes: 97 additions & 22 deletions state/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
const (
penName = "pen"
eraserName = "eraser"
bucketName = "bucket"
)

type Canvas [][]common.Color
Expand Down Expand Up @@ -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()
Expand Down Expand Up @@ -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++ {
Expand Down
111 changes: 111 additions & 0 deletions state/state_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package state

import (
"reflect"
"testing"

"github.com/nunnatsa/piHatDraw/common"
Expand Down Expand Up @@ -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")
}
}
26 changes: 23 additions & 3 deletions webapp/index.gohtml
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@
<label for="toolTypePen">Pen</label>
<input type="radio" id="toolTypeEraser" name="toolType" value=false/>
<label for="toolTypeEraser">Eraser</label>
<input type="radio" id="toolTypeBucket" name="toolType" value=false/>
<label for="toolTypeBucket">Bucket</label>
</p>
<p>
<input id="pickColorFromPixel" type="button" value="Get current pixel color">
Expand Down Expand Up @@ -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")
Expand Down Expand Up @@ -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
Expand All @@ -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) {
Expand Down

0 comments on commit f4ffae0

Please sign in to comment.