Barnsley's Fern

I recently watched the Numberphile video entitled 'Chaos Game', and in that they showed how Sierpinski's triangle could be made from random numbers. But at the end of the video, they showed how this could be applied to other things to get different fractal results. The example given was Barnsley's fern, and how a simple set of rules, along with an element of randomness, can produce incredibly beautiful and complex designs.

So, to satisfy my curiosity in this, I researched a little more, and found the different transformations that need doing, and their respective probabilities. From there I set out to implement this using Python, and then the Pygame module to display the result. Here is my attempt at creating Barnsley's Fern, and I'd just like a review of the small piece of code that I have to see how it could be improved.

import sys, pygame, random
from pygame.locals import *

pygame.init()

WIDTH  = 400
HEIGHT = 600
SCREEN = pygame.display.set_mode((WIDTH,HEIGHT))

pygame.display.set_caption('Barnsley\'s Fern')

####------Colours------####
DARKGREEN  = (  0,  64,   0)
LIGHTGREEN = (  0, 166,  17)
####-------------------####

def convert_point(tup):
tup    = list(tup)
tup = (tup + 2.5)  * ((WIDTH  - 20) /  5)
tup = HEIGHT - tup * ((HEIGHT - 20) / 10) - 10

return tuple(tup)

def f1(point):
x =  0
y =  0.16 * point

return x, y

def f2(point):
x =  0.85 * point + 0.04 * point
y = -0.04 * point + 0.85 * point + 1.6

return x, y

def f3(point):
x =  0.20 * point - 0.26 * point
y =  0.23 * point + 0.22 * point + 1.6

return x, y

def f4(point):
x = -0.15 * point + 0.28 * point
y =  0.26 * point + 0.24 * point + 0.44

return x, y

def main():
point = (0, 0)
SCREEN.fill(DARKGREEN)

while True:
SCREEN.fill(LIGHTGREEN, (convert_point(point), (1, 1)))

rand = random.random()
if   rand <= 0.01: point = f1(point)
elif rand >  0.01 and rand <= 0.86: point = f2(point)
elif rand >  0.86 and rand <= 0.93: point = f3(point)
elif rand >  0.95 and rand <= 1.00: point = f4(point)

events = pygame.event.get()
for event in events:
if event.type == QUIT:
pygame.quit()
sys.exit()
if event.type == KEYDOWN:
if event.key == K_ESCAPE:
pygame.quit()
sys.exit()

pygame.display.update()

if __name__ == '__main__':
main()

• Trust the math. In elif clause of

if   rand <= 0.01: point = f1(point)
elif rand >  0.01 and rand <= 0.86: point = f2(point)

you already know that rand > 0.01 (otherwise the if clause would've been executed), so

elif rand <= 0.86: point = f2(point)

does just as good. The entire cascade should be written as

if   rand <= 0.01: point = f1(point)
elif rand <= 0.86: point = f2(point)
elif rand <= 0.93: point = f3(point)
else: point = f4(point)

• DRY. All f[1-4] are essentially the same function with different parameters:

x = a*point + b*point
y = c*point + d*point + e

I recommend to make a list of corresponding factors and shifts:

parameters = [(0, 0, 0, 0.16, 0),
(0.85, 0.04, -0.04, 0.85, 1.6),
etc
]

compute an index into a parameter list based on your random number, and pass the corresponding parameter tuple, along with the point, to (the only) function computing the next point.