/go-turtle

Turtle graphics in Go

Primary LanguageGoMIT LicenseMIT

go-turtle

Turtle graphics in Go.

A couple of showcase scripts are in the samples folder.

Moving around:

sample world

A conveniently 4K shaped image:

hilbert curve times 19

And another to be safe:

dragon level 12

Turtle

A minimal Turtle agent, moving on a cartesian plane.

The orientation is in degrees. Right rotates clockwise, Left counter-clockwise.

Use it to simulate the movements of the turtle without the drawing overhead.

// create a new turtle
t := turtle.New()

// move it just like you expect
t.Forward(5)
t.Left(45)
t.Forward(5)
t.Right(45)
t.Backward(5)

// get the X, Y, Deg data as needed
fmt.Println(t.X, t.Y, t.Deg)
// 3.5355339059327378 3.5355339059327373 0

// teleport around
t.SetPos(4, 4)
t.SetHeading(120)

// vaguely nice printing
fmt.Println("T:", t)
// T: (   4.0000,    4.0000) ^  120.0000

TurtleDraw

Has the same interface of Turtle, but draws. Each TurtleDraw is attached to a World.

Create a new world to draw in: an uniform image of the requested size (width, height) with SoftBlack background is generated.

w := turtle.NewWorld(900, 600)

The background color can be set:

w := turtle.NewWorldWithColor(900, 600, turtle.Yellow)

An existing image can be used as base:

img := image.NewRGBA(image.Rect(0, 0, 900, 600))
draw.Draw(img, img.Bounds(), &image.Uniform{turtle.Cyan}, image.Point{0, 0}, draw.Src)
wi := turtle.NewWorldWithImage(img)

Create a TurtleDraw attached to the World:

// create a turtle attached to w
td := turtle.NewTurtleDraw(w)

// position/orientation
td.SetPos(100, 300)
td.SetHeading(turtle.North + 80)

// line style
td.SetColor(turtle.Blue)
td.SetSize(4)

// start drawing
td.PenDown()

// same interface as Turtle
td.Forward(100)
td.Left(160)
td.Forward(100)

Save the current image:

err := w.SaveImage("world.png")
if err != nil {
    fmt.Println("Could not save the image: ", err)
}

Close the world (there are two open internal channels).

w.Close()

// this is an error: the turtle tries to send the line
// to the world input channel that has been closed
// td.Forward(50)

You can create as many turtles as you want. The Hilbert script shows an example where multiple turtles are created and placed, and then are all controlled at once to generate the same pattern in different locations. In this way, the expensive computation to generate the Hilbert fractal instructions is only done once.

When drawing, a turtle sends the line to the world on a channel and blocks until it is done.

Instructions

A simple struct is defined to pack the information needed to carry out an action.

type CmdType byte
const (
	CmdForward CmdType = iota
	CmdBackward
	CmdLeft
	CmdRight
)

type Instruction struct {
	Cmd    CmdType
	Amount float64
}

Which can be directly sent to a turtle:

td.DoInstruction(i)

Fractals

Turtle graphics are very useful to draw fractals.

The fractal package provides some functions to draw fractals, as a Lindenmayer system.

The functions generate Instructions on a channel, that can be executed as needed.

In the sample folder, there is a script that provides a CLI to generate nice images.

Constants

A few standard colors:

Black     = color.RGBA{0, 0, 0, 255}
SoftBlack = color.RGBA{10, 10, 10, 255}
White     = color.RGBA{255, 255, 255, 255}

Red   = color.RGBA{255, 0, 0, 255}
Green = color.RGBA{0, 255, 0, 255}
Blue  = color.RGBA{0, 0, 255, 255}

Cyan    = color.RGBA{0, 255, 255, 255}
Magenta = color.RGBA{255, 0, 255, 255}
Yellow  = color.RGBA{255, 255, 0, 255}

DarkOrange = color.RGBA{150, 75, 0, 255} // It's just so warm and relaxing

Cardinal directions:

East  = 0.0
North = 90.0
West  = 180.0
South = 270.0

Implementation notes

Note on float64

A lot of inputs to the API are actually float64, so when using a variable instead of an untyped const the compiler will complain if the var is int.

So this works as the var has type float:

segLen := 150.0
td.Forward(segLen)

and this does not:

segLen := 150
td.Forward(segLen)
// cannot use segLen (variable of type int) as float64 value
// in argument to td.Forward (compile)

This works magically because in Go constants are neat.

td.Forward(150)

Drawing pixels

When drawing points of odd size, the square is centered on the position. When drawing points of even size, the square is shifted to the top and right. The red points are drawn with y=0, along the bottom border of the image.

drawing pixels of even size

To draw a single point, just call forward with 0 dist:

td.Forward(0)

Channels and line drawing

The world draws the Line it receives on the DrawLineCh channel, so you can technically draw a line directly with that skipping the turtles altogether, and everything should work.

TODO - Ideas

  • Hilbert sample!
  • More colors!

Contributing

This was a side project to another one I was doing to learn Go, so all improvements and suggestions for a better code are welcome.