Ooh, yes please Koren. I've got a fairly intricate thing on the go that could do with something exactly like this.
So... about Python and L-systems (I hope it's short enough that people don't mind I post the pieces of code)
In case anyone is not familiar with thoses, L-systems are easy way to represent and construct fractals.
I'll take the Koch fractal (the snowflake) as an example...
You begin with a token or a sequence of tokens (e.g. "frrfrrf")
And you apply rules:
"f" is replaced by the sequence"flfrrflf"
"r" is replaced by "r"
"l" is replaced by "l"
Assuming you use a dict, it's easy to store rules:
Code:
Koch_rules = { "f" : "flfrrflf" }
(I won't store rules where the result is the same as the input)
You can write a generator function that take a set of rules and an iterable and behave as an iterable of tokens:
Code:
def Apply(rules, it) :
for elem in it :
if elem not in rules :
yield elem
else :
for elem in rules[elem] :
yield elem
To test:
Code:
"".join( Apply(Koch_rules, "rlrlf") )
will gives you the "rlrlflfrrflf" string
Now, to get a fractal, you have to apply the rules several times. You can use a loop, but it's better to chain iterables, since you won't have to store any string ever, the tokens will be processed automatically. You can do this insanely easily in Python:
Code:
def Fractal(it_rules, x) :
for rules in it_rules :
x = Apply(rules, x)
return x
Functional programming gurus may prefer folding, using reduce from functools module (though not as Pythonic, probably):
Code:
def Fractal(it_rules, x) :
return reduce(lambda seq, rules : Apply(rules, seq), it_rules, x)
With this function,
Code:
Apply( [Koch_rules]*6, "frrfrrf" )
will gives you the sequence of tokens that produce the 6th order Koch fractal. See that the it_rules are an iterable of rules, so you can easily define different rules for different recursion orders (for example, in plants, use different recursion rules for trunk/branches and leaves). I just applied 6 times the same Koch rules here.
Basically, you can see it as processing streams: a sequence of tokens enter a "box", and a different sequence exit the box. You just chained the boxes, put "frrfrrf" at one side, and get the stream of tokens at the other side. There's basically no cost in memory, because tokens are processed one at a time (each box will accept the next token only when it has finished with producing the resulting tokens of the previous accepted ones).
I find it really nice, and generator functions makes everything a breeze in Python, though it's hardly language-specific.
Then, to get a graphical output, you only need to process the tokens. A simple way in Python to process this kind of actions is to use the turtle module.
In the Koch fractal, "f" means "move forward", "r" means "turn right 60", "l" means "turn left 60".
So I define a set of drawing rules
Code:
Koch_draw = { "f" : (turtle.forward, [5]),
"r" : (turtle.right, [60]),
"l" : (turtle.left, [60]) }
And a function to apply them, given a sequence of tokens (Unknown tokens are ignored, but are useful to construct fractals... Also, there's two special tokens, here "a" and "p", that push and pop the position/heading of the turtle on a stack, that's especially useful with plants)
Code:
def Act(actions, it) :
import turtle
turtle.reset()
turtle.speed(0)
stack = []
for elem in it :
if elem == "a" : # push
stack.append( (turtle.position(), turtle.heading()) )
elif elem == "p" : # pop
pos, hd = stack.pop()
turtle.penup()
turtle.setposition(pos)
turtle.setheading(hd)
turtle.pendown()
elif elem in actions :
f, vargs = actions[elem]
f(*vargs)
Now, just use
Code:
Act(Koch_draw, Fractal( [Koch_rules]*4, "frrfrrf" ))
and you'll have a nice Koch fractal.
Some other examples:
A tree (I'll let you adapt it to have different sizes/color for lines):
Code:
Tree_rules = { "g" : "gf",
"x" : "gfaaxupdrxupdlx" }
Tree_draw = { "f" : (turtle.forward, [5]),
"r" : (turtle.right, [35]),
"l" : (turtle.left, [35]) }
Act(Tree_draw, Fractal( [Tree_rules]*6, "x" ))
Harter-Heighway dragon:
Code:
Dragon_rules = { "x" : "xlyf",
"y" : "fxry" }
Dragon_draw = { "f" : (turtle.forward, [5]),
"r" : (turtle.right, [90]),
"l" : (turtle.left, [90]) }
Act(Dragon_draw, Fractal( [Dragon_rules]*10, "fx" ))
Gosper curve:
Code:
Gosper_rules = { "x" : "xlyfllyfrfxrrfxfxryfl",
"y" : "rfxlyfyfllyflfxrrfxry" }
Gosper_draw = { "f" : (turtle.forward, [5]),
"r" : (turtle.right, [60]),
"l" : (turtle.left, [60]) }
Act(Gosper_draw, Fractal( [Gosper_rules]*6, "xf" ))
Sierpinski curve:
Code:
Sierp_rules = { "x" : "yfrxfry",
"y" : "xflyflx" }
Sierp_draw = { "f" : (turtle.forward, [5]),
"r" : (turtle.right, [60]),
"l" : (turtle.left, [60]) }
Act(Sierp_draw, Fractal( [Sierp_rules]*6, "xf" ))
You'll find hundreds of rulesets on the Internet, and it's easy to design your owns (though the most interesting ones are not that easy to create)
Hope it'll help. Happy coding