# Written by Mark Shuttleworth, 2014, for Python 3.4. The program generates clusters of concentric spirographs
# (flowers) each of which have random major and minor gear wheel radii and teeth counts. The plot colour
# rotates through yuv space with the plotting of the graph so that when the ends join up, their colours match.
import turtle # Only one import, that means we'll have to do...
pi = 3.141592653589793238 #...a definition of pi...
def sin(x): #...a Taylor/Maclaurin expansion of sin(x)...
x = x % (2 * pi) # Series is only good over a limited range
xs = -(x * x) # Work out minus x squared in advance rather than in loop
xterm = x * xs # Set up numerator and denominator to start with cubed term
fterm = 6.0
for i in range (4,18,2): # Less than x^^19 gives discontinuities (up to x^^17 done in loop)
x = x + (xterm / fterm)
xterm = xterm * xs
fterm = fterm * i * (i + 1)
return (x + (xterm / fterm)) # Use the x^^19 terms we calculated before we dropped out of the loop
def rand(min, max): #...and a random number generator (based on c code for xorshift+ from Wikipedia and giving out an int between min and max) the hard way.
s1 = rand.random & 0xFFFFFFFFFFFFFFFF # We use rand.random function attribute as a static/global to maintain our random number between calls (not very nice, but compact)
s0 = (rand.random >> 64) & 0xFFFFFFFFFFFFFFFF # the 'and'ing with 16Fs is to emulate 64-bit fixed length registers/variables - python just keeps expanding its long to accomodate whatever you've got otherwise
s1 = (s1 ^ (s1 << 23)) & 0xFFFFFFFFFFFFFFFF
s1 = ((s1 ^ s0 ^ (s1 >> 17) ^ (s0 >> 26)) + s0) & 0xFFFFFFFFFFFFFFFF
rand.random = s0 + (s1 << 64)
return min + int((max + 1 - min) * (rand.random / int(1 << 128)))
def numturns(maj, min): # We can find the minimum number of complete turns required by removing common prime factors
searchlim = min + 1
while ((maj % 2) == 0): # between the major and minor teeth numbers. The answer is what's left of the minor
maj = maj // 2 # teeth number. First of all we eliminate two as a factor, coz then we only have half
if ((min % 2) == 0): # as many checks to do, as we can use a step size of two in the for loop
min = min // 2
for factor in range (3, (searchlim), 2): # Then we do it for any remaining factors, note factors that are not prime
while ((maj % factor) == 0): # eg 6, have already been eliminated as their constituent, smaller prime
maj = maj // factor # factors (eg 2, 3) have already been removed, so the 'while (maj % factor)' test will fail.
if ((min % factor) == 0):
min = min // factor
return min
def colourcalc(y, u, v): # Function takes a uv vector and random y value to generate rgb values
r = (y + (v / 0.877)) # These constants are wrong, but I'm out of time and it works roughly so...c'est la vie
g = (y - (0.395 * u) - (0.581 * v))
b = (y + (u / 0.492))
r = 0 if (r < 0) else (1 if (r > 1) else r) # Limit the rgb values between 0 and 1
g = 0 if (g < 0) else (1 if (g > 1) else g)
b = 0 if (b < 0) else (1 if (b > 1) else b)
return(r,g,b)
seedstring = input("Enter a word or phrase to act as a random number seed: ") # Initialise random number generator with a seed derived from a user supplied word
seedlen = len(seedstring)
if seedlen == 0:
seed = 1 # Make sure we don't end up with '0'
else:
if seedlen >=16: # Use the first 16 letters to create a seed value
seedlen = 16
seed = 0
for iter in range (0, seedlen):
seed = seed * 256 + ord(seedstring[iter])
rand.random = seed # Set the random number to the seed value
for loop in range (0,10): # Run a few iterations of the generator to propogate some bits through (otherwise first few values are zeroish)
rand(0, 0)
myrtle = turtle.Turtle() # Setup our turtle, called myrtle
myrtle.penup() # Put its pen up
flowercount = rand (1,20) # How many spriograph flowers shall we do on our paper?
stepsperloop = 30 # For each smaller wheel loop, how many points should we have to give a smooth curve (30 seems good)
for i in range(0,flowercount): # For each flower, generate a centrepoint and how many individual spirographs will be centred on it
centrex = rand(-400,400)
centrey = rand(-300,300)
numconc = rand(1, 4)
for j in range (0,numconc): # For each individual spirograph, generate a major and minor radius and number of teeth
majorrad = rand(25,100)
minorrad = rand(0.1 * majorrad, 2 * majorrad)
majorteeth = rand(25,50)
minorteeth = rand(5,majorteeth-1)
stepsperturn = int(stepsperloop * majorteeth / minorteeth) # Pre-calculate some stepping variables outside the main loop
turns = numturns(majorteeth, minorteeth)
majorstep = 2 * pi /stepsperturn
minorstep = (majorteeth / minorteeth) * majorstep
uvstep = (2 * pi / (stepsperturn * turns))
yval = rand(0,255)/255
myrtle.goto(centrex, centrey + majorrad + minorrad) # Get the pen in place before we put it down
myrtle.pendown()
for k in range (0, (stepsperturn * turns) + 1): # Main point drawing loop
uval = sin(k * uvstep) # Calculate colour vector and generate rgb values
vval = sin((k * uvstep) + pi/2)
myrtle.pencolor(colourcalc(yval,uval,vval))
x = centrex + majorrad * sin(k * majorstep) + minorrad * sin(k * minorstep) # Calc x,y coords and draw to there
y = centrey + majorrad * sin(k * majorstep + pi / 2) + minorrad * sin(k * minorstep + pi / 2)
myrtle.goto(x, y)
myrtle.penup() # Finished that graph, pen up before starting the next
input("Press Enter Key To Finish") # Completely finished, hang around 'til user confirms