# Mandelbrot Set

Working with complex numbers and iterating the function $f_c(z) = z^2 + c$, I created this program which lets you explore the depth of this self-repeating fractal. The different colors are caused by different color schemes.

#### Controls

• Left mouse click and mouse movements control the zoom frame
• Middle mouse click zooms in
• Space takes a screenshot
• F1 moves a zoom back
• F2 zooms to default zoom

``````# Python 2.7.7 Code
# Pygame 1.9.1 (for Python 2.7.7)
# Jonathan Frech  4th of December, 2015
#         edited  5th of December, 2015
#         edited 11th of December, 2015``````

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

""" COMPLEX NUMBER FUNCTIONS """
# get the absolute value (real number!) of a complex number
def complexAbsolute(Complex):
real, imaginary = Complex[0], Complex[1]
return math.sqrt( real**2 + imaginary**2 )

# multiply two complex numbers with each other
def complexMultiply(Complex1, Complex2):
real1, imaginary1 = Complex1[0], Complex1[1]
real2, imaginary2 = Complex2[0], Complex2[1]
return [real1 * real2 - imaginary1 * imaginary2, real1 * imaginary2 + real1 * imaginary2]

# add two complex numbers together
real1, imaginary1 = Complex1[0], Complex1[1]
real2, imaginary2 = Complex2[0], Complex2[1]
return [real1 + real2, imaginary1 + imaginary2]

""" GAME """
# game class
class GAME():
# initialize program
def __init__(self):
self.WIDTH, self.HEIGHT = 1080, 720
self.SIZE = [self.WIDTH, self.HEIGHT]
self.SURF = pygame.Surface(self.SIZE)

# scaling is set to 1
self.SCALE = 1
self.SCALEDSIZE = [self.WIDTH * self.SCALE, self.HEIGHT * self.SCALE]
self.SCREEN = pygame.display.set_mode(self.SCALEDSIZE)

self.TICKS = 0
self.RUNNING = True

self.history = []
self.tl = [-6.75, -4.5]
self.br = [6.75, 4.5]
self.mouse1 = None
self.mouse2 = None

# create color scheme
self.colors = []
# white scheme
#"""
for _ in range(0, 256):
self.colors.append([_, _, _])#"""

# red-blue scheme
"""
for r in range(0, 16):
for b in range(0, 16):
self.colors.append( [255/16*r, 0, 255/16*b] )
self.colors[-1] = [0, 0, 0]#"""

# totally random scheme
"""
for _ in range(0, 256):
self.colors.append([random.randint(0, 255), random.randint(0, 255), random.randint(0, 255)])#"""

# red-green scheme
"""
color1 = []
for _ in range(0, 16):
color1.append(255/16.*_)

color2 = []
for _ in range(0, 16):
color2.append(255/16.*_)

for c1 in color1:
for c2 in color2:
self.colors.append([c1, c2, 0])#"""

"""for r in range(0, 16):
for b in range(0, 16):
self.colors.append( [70/16.*r, 0, 70/16.*b] )"""

# randomly related colors
"""
self.colors.append([50, 50, 50])
for _ in range(0, 255):
c = self.colors[_]
n = random.randint(0, 2)
if random.randint(0, 1) == 0:
c[n] += 10
if c[n] > 255:
c[n] = 255
else:
c[n] -= 10
if c[n]  0:
self.tl = self.history[-1][0]
self.br = self.history[-1][1]
self.history.pop(-1)
self.redraw()
self.caption()

# reset game
def reset(self):
self.setcorners([-6.75, -4.5], [6.75, 4.5])
self.lastpos = None
self.redraw()
self.caption()

# redraw mandelbrot set
def redraw(self):
self.n = 0
self.draw = True

# update caption
def caption(self):
pygame.display.set_caption("Mandelbrot set from " + str(self.tl) + " to " + str(self.br))

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

if event.type == pygame.KEYDOWN:
# screenshot
if event.key == pygame.K_SPACE:
self.screenshot()

# total reset
if event.key == pygame.K_F2:
self.reset()

# move one step back
if event.key == pygame.K_F1:
self.moveback()

# mouse up
if event.type == pygame.MOUSEBUTTONUP:
if event.button == 1:
self.mouse2 = list( pygame.mouse.get_pos() )
self.mouse1, self.mouse2 = self.fixmouse(self.mouse1, self.mouse2)

# left clicking starts a new zoom frame
# middle clicking zooms into said zoom frame
if event.type == pygame.MOUSEBUTTONDOWN:
if event.button == 1:
self.mouse1 = list( pygame.mouse.get_pos() )
self.mouse2 = None

if event.button == 2 and self.mouse1 and self.mouse2 and self.mouse1[0] != self.mouse2[0] and self.mouse1[1] != self.mouse2[1]:
# get positions
p1 = self.mouse1[:]
p2 = self.mouse2[:]

# calculate
xdif, ydif = float(self.br[0] - self.tl[0]), float(self.br[1] - self.tl[1])

x = p1[0] / (self.WIDTH  / xdif) + self.tl[0]
y = p1[1] / (self.HEIGHT / ydif) + self.tl[1]
p1 = [x, y]

x = p2[0] / (self.WIDTH  / xdif) + self.tl[0]
y = p2[1] / (self.HEIGHT / ydif) + self.tl[1]
p2 = [x, y]

# update
self.setcorners(p1, p2)
self.caption()

# reset
self.redraw()
self.lastpos = None

# right mouse click moves zoom frame
if pygame.mouse.get_pressed()[2]:
if self.mouse1 and self.mouse2:
xdif = self.mouse2[0] - self.mouse1[0]
ydif = self.mouse2[1] - self.mouse1[1]

self.mouse1 = list( pygame.mouse.get_pos() )
self.mouse2[0] = self.mouse1[0] + xdif
self.mouse2[1] = self.mouse1[1] + ydif

self.mouse1[0] -= xdif / 2
self.mouse1[1] -= ydif / 2
self.mouse2[0] -= xdif / 2
self.mouse2[1] -= ydif / 2

# fixes the mouse positions
def fixmouse(self, tl, br):
# make sure, p1 is at the top left, p2 at the bottom right
if tl[0] > br[0]:
p = tl[0]
tl[0] = br[0]
br[0] = p
if tl[1] > br[1]:
p = tl[1]
tl[1] = br[1]
br[1] = p

# fix ratio
ratio = float(self.WIDTH) / float(self.HEIGHT)
xdif = float(br[0] - tl[0])
tl[1] = br[1] - (xdif/ratio)

return tl, br

# render function
def render(self):
# draw
if self.draw:
for _ in range(0, 3000):
X, Y = self.n % self.WIDTH, self.n / self.WIDTH

xdif, ydif = float(self.br[0] - self.tl[0]), float(self.br[1] - self.tl[1])
x = X / (self.WIDTH  / xdif) + self.tl[0]
y = Y / (self.HEIGHT / ydif) + self.tl[1]

# calculate
Complex = [x, y]
Z = [0, 0]
iterations = 0

while complexAbsolute(Z) < 2 and iterations  self.WIDTH * self.HEIGHT:
self.draw = False

# blit
self.SCREEN.blit(pygame.transform.scale(self.SURF, self.SCALEDSIZE), [0, 0])

# zoom frame
if self.mouse1:
# get positions
p1 = self.mouse1[:]
if self.mouse2:
p2 = self.mouse2[:]
else:
p2 = list( pygame.mouse.get_pos() )

p1, p2 = self.fixmouse(p1, p2)

# draw zoom frame
pygame.draw.line(self.SCREEN, [255, 0, 0], [p1[0], p1[1]], [p1[0], p2[1]])
pygame.draw.line(self.SCREEN, [255, 0, 0], [p1[0], p1[1]], [p2[0], p1[1]])
pygame.draw.line(self.SCREEN, [255, 0, 0], [p2[0], p2[1]], [p2[0], p1[1]])
pygame.draw.line(self.SCREEN, [255, 0, 0], [p2[0], p2[1]], [p1[0], p2[1]])

# flip
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))) + " " + str(self.tl) + " to " + str(self.br) + ".png"
pygame.image.save(self.SURF, 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
GAME().run()``````