# This example draws a shape called the Koch snowflake. import pippy, pygame, sys from pygame.locals import * from random import * import math import gtk # always need to init first thing pygame.init() # XO screen is 1200x900 size = width, height = gtk.gdk.screen_width(), gtk.gdk.screen_height() # create the window and keep track of the surface # for drawing into screen = pygame.display.set_mode(size) # turn off the cursor pygame.mouse.set_visible(False) # Set some variables. # Snowflakes are white! color = (255,255,255) # the center point of the screen # We need it because we'll place out triangle around it. center = width / 2, height / 2 # Side of an equilateral triangle (the one we start with). # Here it is defined relative to the resolution of the screen. # Practice: We could play with other values, absolute (like a = 100) or # relative (a = height / 2, a = width / 3, etc). What looks best for you? a = height/1.4 # Height of that triangle. # Just a simple geometry formula, nothing Python-special about it. h = int(a * math.sqrt(3) / 2) # These will be the vertices for the starting triangle. # The triangle vertices are named like this: # # C # / \ # A _ B # A = center[0]- a/2, center[1] + h/3 B = A[0] + a, A[1] # To find the third point, we need slightly more advanced math. # If you with to understand it, you could try finding some material about trigonometry. # We use the coordinates of vertice A to calculate where point C will be. C = A[0] + math.cos(math.pi/3) * a, A[1] - math.sin(math.pi/3) * a # This class will allow us to store data about a line. class Line(object): def __init__(self, a = A, b = B): # This is how a line object will remember its points and length. self.A = a self.B = b self.points = [self.A, self.B] # We use the Pythagorean theorem to calculate the length of a line # from the coordinates of its points. self.length = math.sqrt( (a[0]-b[0])**2 + (a[1]-b[1])**2 ) # Projection of the line on the x axis. # We use it to figure out the angle which the line closes with the x axis. projection_length = b[0] - a[0] # If the line is descending, the angle is negative. Trigonometry, again. if b[1] > a[1]: self.angle = -math.acos(projection_length / self.length) else: self.angle = math.acos(projection_length / self.length) # To draw new shapes, an old line must be split into its thirds. def split(self): third = self.length / 3.0 self.D = self.A[0] + math.cos(self.angle) * third, \ self.A[1] - math.sin(self.angle) * third self.E = self.A[0] + math.cos(self.angle) * 2*third, \ self.A[1] - math.sin(self.angle) * 2*third self.points.append(self.D) self.points.append(self.E) # Give a line (from which we'll need the coordinates of its starting point) and # an angle, calculate the position of a new point - the vertex of out snowflake. # The length of its sides should be 1/3 of the length of the line from which # it is made. def calculate_new_point(line, angle): p = line.D[0] + math.cos(angle + line.angle) * line.length / 3, \ line.D[1] - math.sin(angle + line.angle) * line.length / 3 return p # The following function from a single line, like this: # # A___B # # creates four lines, like this: # # F # A_D/ \E_B # def transform_line(line): line.split() C = calculate_new_point(line, math.pi/3) line1 = Line(line.A, line.D) line2 = Line(line.D, C) line3 = Line(C, line.E) line4 = Line(line.E, line. B) lines = [line1, line2, line3, line4] return lines # For each line in starting_lines, call transform_line(). # Repeat "depth" times for each line created this way. def produce_lines(starting_lines, depth): all_lines = starting_lines for i in range(depth): new_lines = [] for line in all_lines: new_lines += transform_line(line) # clearn the old lines first all_lines = [] all_lines = new_lines return all_lines # Write the lines on screen. def draw_lines(lines): for line in lines: pygame.draw.line(screen, color, line.A, line.B) # The color we'll use for the screen background (black). bgcolor = (0,0,0) # Lines for the initial triangle. # Remember, the vertices are named like this: # # C # / \ # A _ B # # , could be changed to: # # B # / \ # A _ C # # if that felt more natural. AC = Line(A, C) CB = Line(C, B) BA = Line(B, A) starting_lines = [] starting_lines.append(AC) starting_lines.append(CB) starting_lines.append(BA) # The starting depth is 0; we just need the triangle. depth = 0 # For displaying the instructions. use = "Use left and right arrows; q to quit." font_size = 36 # Remember, colors are in RGB (Red, Green, Blue) system. font_colour = (10, 250, 20) font = pygame.font.Font(None, font_size) text = font.render(use, True, font_colour) text_box = text.get_rect() # Where the box will be placed. text_box.top = height / 15 + 30 text_box.left = width / 30 lines = starting_lines while pippy.pygame.next_frame(): for event in pygame.event.get(): if event.type == QUIT: sys.exit() # When R arrow is pressed, go one step further. # This means creating new lines on each existing line. elif event.type == KEYDOWN and event.key == K_RIGHT and depth < 6: lines = produce_lines(lines, 1) depth = depth +1 # When L arrow is pressed, go one step back, reducing the complexity of # the snowflake. # Basically, goes depth-1 steps forward from the starting lines. elif event.type == KEYDOWN and event.key == K_LEFT and depth > 0: depth = depth-1 lines = produce_lines(starting_lines, depth) elif event.type == KEYDOWN and event.key == K_q: sys.exit() screen.fill(bgcolor) # Display the current step. msg = "Step: " + str(depth) + "/6" text2 = font.render(msg , True, font_colour) text_box2 = text2.get_rect() text_box2.top = height / 15 text_box2.left = width / 30 # Write the instructions and the current step on the screen. screen.blit(text, text_box) screen.blit(text2, text_box2) # Draw the lines on the screen. draw_lines(lines) # Refresh the screen. pygame.display.flip()