# Cycloids

A cycloid is the curve generated by tracing a point on a smaller circle rolling around an bigger circle.
The word cycloid comes from the greek word κύκλος meaning circle. There are epicycloids and hypocycloids (smaller circle located above and beneath the bigger circle).
The cycloid is determined by the ratio between the two circle’s radii $k = \frac{R}{r}$.

# Python 2.7.7 Code
# Pygame 1.9.1 (for Python 2.7.7)
# Jonathan Frech 3rd of June, 2016
#         edited 4th of June, 2016

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

# define the cycloid's type and k
CYCLOID = ["hypo", "epi"][1]
K = 10.1

""" CLASSES """

# vector class (2-dimensional)
class Vector():
# initialization
def __init__(self, p1, p2 = None):
# get the vector as a 2-dimensional list
if not p2:
self.x = p1[0]
self.y = p1[0]
else:

# get the vector between two points
if type(p1) == type([]):
self.x = p2[0] - p1[0]
self.y = p2[1] - p1[1]

# get the vector as two numbers
else:
self.x = p1
self.y = p2

# calculate the vector's length
def length(self):
return math.sqrt(self.x**2 + self.y**2)

# multiply the vector by n
def multiply(self, n):
self.x *= n
self.y *= n

# unify the vector (set it's length to 1)
def unify(self):
l = self.length()
if l != 0:
self.multiply(1. / l)

# set the vector's length
def setlength(self, n):
self.unify()
self.multiply(n)

# stick the vector to a position
def stickto(self, p):
return p[0] + self.x, p[1] + self.y

# angles used in degrees!
# conversion between radians and degrees
return (phi * (math.pi/180.)) % (2*math.pi)
def degrees(self, phi):
return (phi * (180./math.pi)) % 360

# get the vector's angle
def getangle(self):
return self.degrees( math.atan2(self.y, self.x) )

# set the vector's angle
def setangle(self, phi):
l = self.length()

self.x = l * math.cos(self.radians( phi ))
self.y = l * math.sin(self.radians( phi ))

# add an angle phi to current rotation
def rotate(self, phi):
self.setangle(self.getangle() + phi)

""" FUNCTIONS """

# validates a color integer
def colorValid(_color, _min = 0, _max = 255):
newColor = math.fabs(_color)
n = _max - _min
if newColor > n:
if int(newColor / n) % 2 == 0:
newColor = newColor % n
else:
newColor = n - (newColor % n)

return int(newColor) + _min

# gets the position on a circle
def getcirclepos(p, r, phi):
return [p[0] + r * math.cos(phi * (math.pi/180.)), p[1] + r * math.sin(phi * (math.pi/180.))]

# returns an integer version of given position
def intpos(_pos):
return [int(_pos[0]), int(_pos[1])]

""" GAME """

# game class
class GAME():
# initialize program
def __init__(self):

# surface
self.width, self.height = 720, 720
self.size = [self.width, self.height]
self.surface = pygame.Surface(self.size)

# img
self.img = pygame.Surface(self.size)

# screen
self.Width, self.Height = self.width, self.height
self.Size = [self.Width, self.Height]
self.Screen = pygame.display.set_mode(self.Size)

# running variables
self.ticks = 0
self.running = True

# show gray lines
self.gray = False

# cycloid variables
self.k = K
if CYCLOID == "epi":
self.r = self.width / 2. / (self.k + 2)
elif CYCLOID == "hypo":
self.r = self.width / 2. / self.k
self.R = self.r * self.k

self.phi = 0
self.Phi = 0

self.dphi = self.k-1
self.dPhi = -1

self.res = 10.
self.dphi /= self.res
self.dPhi /= self.res

self.speed = self.res * 10

# functions
pygame.display.set_caption("*CAPTION*")

# tick function
def tick(self):
# handle events
for event in pygame.event.get():

# quit when window is closed
if event.type == pygame.QUIT:
self.quit()

# handle key presses
if event.type == pygame.KEYDOWN:
# F12 quits
if event.key == pygame.K_F12:
self.quit()

# F1 screenshots
if event.key == pygame.K_F1:
self.screenshot()

for _ in range(0, int(self.speed)):
m = [self.width / 2., self.height / 2.]
P = getcirclepos(m, self.R + self.r * {"hypo" : -1, "epi" : 1}[CYCLOID], self.Phi)

p = getcirclepos(P, self.r, self.phi)
self.img.set_at(intpos(p), [255, 0, 0])

self.phi += self.dphi
self.Phi += self.dPhi

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

m = [self.width / 2., self.height / 2.]
if self.gray:
pygame.draw.circle(self.surface, [50, 50, 50], intpos(m), int(self.R), 1)
P = getcirclepos(m, self.R + self.r * {"hypo" : -1, "epi" : 1}[CYCLOID], self.Phi)
if self.gray:
pygame.draw.circle(self.surface, [70, 70, 70], intpos(P), 3, 0)
pygame.draw.circle(self.surface, [50, 50, 50], intpos(P), int(self.r), 1)

p = getcirclepos(P, self.r, self.phi)
if self.gray:
pygame.draw.line(self.surface, [70, 70, 70], P, p)
pygame.draw.circle(self.surface, [255, 255, 255], intpos(p), 3, 0)

# 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))) + " (k = " + str(self.k) + ", " + CYCLOID + ").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

frames = 0
ticks = 0

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