# Koch Snowflake

In my collection of programs generating fractals this famous one cannot miss.
The Koch snowflake is generated by starting with an equilateral triangle. Every side of the triangle then gets cut into three equal pieces and the center one gets replaced with yet another equilateral triangle.
To get the perfect fractal, you would need to repeat this process infinitely many times.
More information on the snowflake can be found in this Wikipedia entry.

#### Controls

• F1 iterates the fractal
• F2 zooms in
• F3 zooms out
• F4 resets zoom
• F5 takes a screenshot
• Arrow keys move the camera around

# Python 2.7.7 Code
# Pygame 1.9.1 (for Python 2.7.7)
# Jonathan Frech 25th of April, 2016
#         edited 26th of April, 2016
#         edited 29th of April, 2016

# importing needed modules
import pygame, sys, time, math, os, random

# define to fill areas
FILL = True
FILLCOLOR = [255, 255, 255]

# define the line's thickness (in pixels)
THICKNESS = 0

# ImageMagick command to create the animated gif
# convert -delay 100 -loop 0 img*.png koch.gif

""" CLASSES """
# line class
class line():
# init
def __init__(self, a, b):
self.a = a
self.b = b

self.debug1 = [2, 2]
self.debug2 = [3, 3]

return

# get initial vector
vec = vecConvert(self.a, self.b)

# get orthogonal vector
_vec = [-vec[1], vec[0]]

# calculate orthogonal vector's length and unit vector
_vecl = vecLen(_vec)
if _vecl != 0:

# do the same thing for the initial vector
vecl = vecLen(vec)
if vecl != 0:
vec0 = vecMultiply(vec, 1. / vecl)
vecn = vecMultiply(vec0, vecl / 2.)

# point for orthogonal vector to stick out
point = vecGetPoint(self.a, vecn)

# the factor (sqrt(3) / 6) was calculated by using the Pythagorean theorem
_vec0 = vecMultiply(_vec, 1. / _vecl)
_vecn = vecMultiply(_vec0, vecl * (math.sqrt(3)/6.))

# that is the tip of the new triangle
point2 = vecGetPoint(point, _vecn)

# draw a line from the tip to the two initial vector's thirds
third1 = vecGetPoint(self.a, vecMultiply(vec0, vecl / 3.))
third2 = vecGetPoint(self.a, vecMultiply(vec0, vecl / 3. * 2))
line1 = line(third1, point2)
line2 = line(point2, third2)

# set debug variables
self.debug1 = point[:]
self.debug2 = point2[:]

# calculate filled area
polygon = [point2, third1, third2]

# cut the initial vector in three
l1 = line( vecGetPoint(self.a, vecMultiply(vec0, vecl / 3. * 0)), vecGetPoint(self.a, vecMultiply(vec0, vecl / 3. * 1)) )
l2 = line( vecGetPoint(self.a, vecMultiply(vec0, vecl / 3. * 1)), vecGetPoint(self.a, vecMultiply(vec0, vecl / 3. * 2)) )
l3 = line( vecGetPoint(self.a, vecMultiply(vec0, vecl / 3. * 2)), vecGetPoint(self.a, vecMultiply(vec0, vecl / 3. * 3)) )

# return the new lines and polygon
return [line1, line2, l1, l3], polygon

# render line
def render(self, surface):
# actual line
#pygame.draw.line(surface, [255, 255, 255], [(self.a[0] + G.offset[0]) * G.zoom, (self.a[1] + G.offset[1]) * G.zoom], [(self.b[0] + G.offset[0]) * G.zoom, (self.b[1] + G.offset[1]) * G.zoom])
pygame.draw.line(surface, [255, 255, 255], [self.a[0] * G.zoom + G.offset[0], self.a[1] * G.zoom + G.offset[1]], [self.b[0] * G.zoom + G.offset[0], self.b[1] * G.zoom + G.offset[1]], THICKNESS)

# debug line
#pygame.draw.line(surface, [255, 0, 0], self.debug1, self.debug2)

""" FUNCTIONS """
# gets the position on a circle with a position, radius and an angle
return [
]

# basic vector functions
def vecConvert(p1, p2):
return [p2[0] - p1[0], p2[1] - p1[1]]
def vecLen(vec):
return math.sqrt( (vec[0]**2) + (vec[1]**2) )
def vecMultiply(vec, n):
return [vec[0] * n, vec[1] * n]
def vecGetPoint(vec, point):
return [point[0] + vec[0], point[1] + vec[1]]

""" GAME """
# game class
class GAME():
# initialize program
def __init__(self):
self.width, self.height = 800, 800
self.size = [self.width, self.height]
self.surface = pygame.Surface(self.size)
self.screen = pygame.display.set_mode(self.size)

self.ticks = 0
self.running = True

# calculate initial triangle
center = [self.width / 2., self.height / 2.]
r = 400
A = getCirclePos(center, r, 360. / 3 * 0)
B = getCirclePos(center, r, 360. / 3 * 1)
C = getCirclePos(center, r, 360. / 3 * 2)
self.lines = [line(B, A), line(C, B), line(A, C)]

# fill area?
if FILL:
self.polygons = [[A, B, C]]

# zoom and offset
self.zoom = 1
self.zoomdest = 1
self.offset = [0, 0]
self.offsetdest = [0, 0]

# caption
pygame.display.set_caption("Koch Snowflake")

# reset zoom and offset
def reset(self):
self.zoomdest = 1
self.offsetdest = [0, 0]

# tick function
def tick(self):
# handle events
for event in pygame.event.get():
# quit game
if event.type == pygame.QUIT:
self.quit()

# handle keys
if event.type == pygame.KEYDOWN:
if event.key == pygame.K_F1:
# do not overfill the list!
if len(self.lines) < 1000:
# generate new triangle's lines
newlines = []
for line in self.lines:

# new line!
if newline:
newlines.append(newline)

# area?
if FILL:
self.polygons.append(polygon)

# destroy old lines
self.lines = []

for newline in newlines:
for n in newline:
self.lines.append(n)

# zoom in
elif event.key == pygame.K_F2:
self.zoomdest *= 2
self.offsetdest[0] *= 2
self.offsetdest[1] *= 2

# zoom out
elif event.key == pygame.K_F3:
self.zoomdest /= 2.
self.offsetdest[0] /= 2
self.offsetdest[1] /= 2

# reset zoom and offset
elif event.key == pygame.K_F4:
self.reset()

# save a screenshot
if event.key == pygame.K_F5:
self.screenshot()

# handle held down keys
keys = pygame.key.get_pressed()
if keys[pygame.K_UP]:
self.offsetdest[1] += 10
elif keys[pygame.K_DOWN]:
self.offsetdest[1] -= 10
elif keys[pygame.K_LEFT]:
self.offsetdest[0] += 10
elif keys[pygame.K_RIGHT]:
self.offsetdest[0] -= 10

# approach zoomdest
self.zoom += (self.zoomdest - self.zoom) / 50.

# approach offsetdest
for _ in range(0, 2):
self.offset[_] += (self.offsetdest[_] - self.offset[_]) / 50.

# render function
def render(self):
# fill
self.surface.fill([0, 0, 0])

# render filled area?
if FILL:
for polygon in self.polygons:
p = []
for _ in polygon:
p.append([_[0] * G.zoom + G.offset[0], _[1] * G.zoom + G.offset[1]])
pygame.draw.polygon(self.surface, FILLCOLOR, p, 0)

# render lines
for line in self.lines:
line.render(self.surface)

# blit and flip
self.screen.blit(self.surface, [0, 0])
pygame.display.flip()

# quits
def quit(self):
self.running = False

# takes a screenshot
def screenshot(self):
path = os.getcwd() + "/out/"

try:
if not os.path.isdir(path):
os.mkdir(path)

name = "img" + str(len(os.listdir(path))) + ".png"
pygame.image.save(self.surface, path + name)
except:
pass

# run function (uses tick() and render())
def run(self):
ticksPerSecond = 60
lastTime = time.time() * 1000000000
nsPerTick =  1000000000.0 / float(ticksPerSecond)

ticks = 0
frames = 0

lastTimer = time.time() * 1000
delta = 0.0

while self.running:
now = time.time() * 1000000000
delta += float(now - lastTime) / float(nsPerTick)
lastTime = now
shouldRender = False

while delta >= 1:
ticks += 1
self.ticks += 1
self.tick()
delta -= 1
shouldRender = True

if shouldRender:
frames += 1
self.render()

if time.time() * 1000 - lastTimer >= 1000:
lastTimer += 1000

# debug
#print("Frames: " + str(frames) + ", ticks: " + str(ticks))

frames = 0
ticks = 0

# start game
G = GAME()
G.run()