In 3 previous episodes we built a Logo interpreter and a module for evolving L Systems. Today we'll combine them to show how you can generate interesting graphics. Let's get started.

Project

I've tagged the fractal_playground with before_episode_169

We'll add the logo project to our dependencies:

{:logo, github: "knewter/logo"}
mix deps.get

OK, now we're going to just build a quick test for a module that reduces a string into a list of functions and arguments to apply against our turtle:

defmodule LogoMappingTest do
  use ExUnit.Case

  # We expect to have a list of mappings from a character to a function and
  # arguments.  Here I'll define some we'll use later.
  @mappings %{
    "A" => {:forward, [10]},
    "-" => {:right, [30]},
    "B" => {:forward, [10]},
    "+" => {:left, [30]},
  }

  # Now we want to make sure that our mapping works.
  test "maps an alphabet to logo function calls" do
    assert [
      {:forward, [10]},
      {:right, [30]},
      {:forward, [10]},
      {:left, [30]}
    ] = LogoMapping.map("A-B+", @mappings)
  end
end

Honestly, this is easily the smallest thing that could justify being a function; we're basically putting some tiny sugar on top of Enum.map. Here's the implementation anyway:

defmodule LogoMapping do
  def map(string, mappings) do
    string
    |> String.graphemes
    |> Enum.map(fn(char) ->
      mappings[char]
    end)
  end
end

So this is comically tiny. When I first did this I had a mental block and this was a larger function, in which I'd implemented map in terms of reduce manually. Once I realized what I'd done I just mocked myself for a while. Still, I like having this chunk of code wrapped up. Run the tests, and they'll pass.

Next, we're just going to implement this thing. We'll make an example script:

mkdir examples
vim examples/sierpinski.exs

And on to the show. We'll set up an L system I grabbed off of wikipedia:

defmodule Sierpinski do
  import Logo.Instance

  @rules %{
    "A" => "B-A-B",
    "B" => "A+B+A"
  }
  @axiom "A"
end

We'll build a quick function to iterate through our L System a set number of times - we're wrapping this up in a run function:

defmodule Sierpinski do
  # ...
  def run(iterations) do
    runs = LSystem.run(@axiom, @rules, steps)
    [output|_] = Enum.reverse(runs)
    IO.inspect output
  end
end

Sierpinski.run(5)

We'll map comma-big-t to run our example:

:map ,T mix run examples/sierpinski.exs<cr>

Go ahead and run it and we should get the text output of 5 runs through this L system ((( do it )))

Alright, now we want to map that string to functions that get passed to our turtle. Let's do that:

  @angle 60
  @distance 10
  @mappings %{
    "A" => {:forward, [@distance]},
    "B" => {:forward, [@distance]},
    "-" => {:right, [@angle]},
    "+" => {:left, [@angle]}
  }

  def run(steps) do
    runs = LSystem.run(@axiom, @rules, steps)
    [output|_] = Enum.reverse(runs)

    # We'll map our output to the logo functions
    moves = LogoMapping.map(output, @mappings)

    IO.puts inspect moves
  end

We can run it now, and we see some weird looking maps - that's because our arguments are all 1 character lists that can be represented as ASCII, so don't be alarmed. Now all that's left is to set the stage for our turtle, then pass these functions into it using apply:

  def run(steps) do
    runs = LSystem.run(@axiom, @rules, steps)
    [output|_] = Enum.reverse(runs)
    moves = LogoMapping.map(output, @mappings)

    # We'll start a new turtle and set him up near the center of the canvas with
    # a red pen down.
    {:ok, turtle} = Logo.Instance.start

    turtle
    |> move_to({300, 300})
    |> color({255, 0, 0})
    |> pen_down

    # We'll apply each move to our turtle
    for {fun, args} <- moves do
      apply(Logo.Instance, fun, [turtle] ++ args)
    end

    # And then all that's left is to draw him
    Logo.Window.start(turtle)
  end

Alright, run it now, and we get something really fun ((( do it )))

Let's run a few more iterations

((( increase to 6, run it, reduce distance per iteration so it fits on the canvas )))

Alright, what does 7 look like?

((( do it )))

That's obviously a sierpinski triangle, so that's fantastic.

It's also a little fun to skew it by changing the angle slightly ((( change it to 58 and run it )))

Summary

In today's episode, we combined our L Systems and our Logo interpreter to generate a sierpinski triangle. There are a lot of really fun examples you can run through on wikipedia, but some of the extra intriguing ones require the ability to push and pop the turtle's position and angle onto a stack. In an upcoming episode we'll introduce that and draw some more intricate graphics. See you soon!

Resources