#---------------------------------------------------------------------- # Sierpinski Triangle once again. # # At the end of the previous class, I issued a challenge to the class # to take the following code and simplify it. #---------------------------------------------------------------------- import random def Main1(): W = 800 H = 600 Canvas = makeEmptyPicture(W,H) show(Canvas) P0X = W / 2 P0Y = 0 P1X = 0 P1Y = H - 1 P2X = W - 1 P2Y = H - 1 PPX = P0X PPY = P0Y Counter = 1000 while (True): N = random.randrange(3) if (N == 0): PPX = (PPX + P0X) / 2 PPY = (PPY + P0Y) / 2 MyColor = red elif (N == 1): PPX = (PPX + P1X) / 2 PPY = (PPY + P1Y) / 2 MyColor = green else: PPX = (PPX + P2X) / 2 PPY = (PPY + P2Y) / 2 MyColor = blue PX = getPixel(Canvas,PPX,PPY) setColor(PX, MyColor) Counter = Counter - 1 if (Counter == 0): repaint(Canvas) Counter = 1000 return #---------------------------------------------------------------------- # The problem with the original code is that it uses two variables for # every point, an X and a Y. For example, P0 requires P0X and P0Y. # # What we would prefer is to have one variable for each concept; that # is, one variable for one point. That point still has to encapsulate # both the X and the Y coordinate values, however. # # The solution is to define a point as a list of two values. # If L is one of these points, then X will be at L[0] and Y will be # at L[1]. L = [X,Y] #---------------------------------------------------------------------- def Main2(): W = 800 H = 600 Canvas = makeEmptyPicture(W,H) show(Canvas) P0 = [W / 2, 0] P1 = [0, H - 1] P2 = [W - 1, H - 1] PP = [P0[0], P0[1]] # Why not do PP = P0 ? I'll leave that for later. Counter = 1000 while (True): N = random.randrange(3) if (N == 0): PP[0] = (PP[0] + P0[0]) / 2 PP[1] = (PP[1] + P0[1]) / 2 MyColor = red elif (N == 1): PP[0] = (PP[0] + P1[0]) / 2 PP[1] = (PP[1] + P1[1]) / 2 MyColor = green else: PP[0] = (PP[0] + P2[0]) / 2 PP[1] = (PP[1] + P2[1]) / 2 MyColor = blue PX = getPixel(Canvas,PP[0],PP[1]) setColor(PX, MyColor) Counter = Counter - 1 if (Counter == 0): repaint(Canvas) Counter = 1000 return #---------------------------------------------------------------------- # That solution is fine for the *definitions* of the points, but it # doesn't help much for the computations, which are, if anything, # even more complicated than they were before. However, since we are # doing the same thing in each of the three branches of the if, we # can write a function to handle the point averaging. That makes a # lot of the code simpler. #---------------------------------------------------------------------- def Average3 (Q0,Q1): # Q0 and Q1 are both [X,Y] lists... X = (Q0[0] + Q1[0]) / 2 Y = (Q0[1] + Q1[1]) / 2 return [X,Y] # ...and the average of those points is returned def Main3(): W = 800 H = 600 Canvas = makeEmptyPicture(W,H) show(Canvas) P0 = [W / 2, 0] P1 = [0, H - 1] P2 = [W - 1, H - 1] PP = [P0[0], P0[1]] # Why not do PP = P0 ? I'll leave that for later. Counter = 1000 while (True): N = random.randrange(3) if (N == 0): PP = Average3(PP,P0) MyColor = red elif (N == 1): PP = Average3(PP,P1) MyColor = green else: PP = Average3(PP,P2) MyColor = blue PX = getPixel(Canvas,PP[0],PP[1]) # We still have to extract X and Y for getPixel setColor(PX, MyColor) Counter = Counter - 1 if (Counter == 0): repaint(Canvas) Counter = 1000 return #---------------------------------------------------------------------- # Now, since the N=0 branch deals with P0, the N=1 branch deals with # P1, and the N=2 branch deals with P2, we can store those points into # their own list and use N to index into that list. The colors can be # in their own list as well. The whole if-elif-else block vanishes. #---------------------------------------------------------------------- def Average4 (Q0,Q1): # Q0 and Q1 are both [X,Y] lists... X = (Q0[0] + Q1[0]) / 2 Y = (Q0[1] + Q1[1]) / 2 return [X,Y] # ...and the average of those points is returned def Main4(): W = 800 H = 600 Canvas = makeEmptyPicture(W,H) show(Canvas) P0 = [W / 2, 0] P1 = [0, H - 1] P2 = [W - 1, H - 1] PP = [P0[0], P0[1]] # Why not do PP = P0 ? I'll leave that for later. Points = [P0,P1,P2] Colors = [red,green,blue] Counter = 1000 while (True): N = random.randrange(3) PP = Average4(PP,Points[N]) MyColor = Colors[N] PX = getPixel(Canvas,PP[0],PP[1]) # We still have to extract X and Y for getPixel setColor(PX, MyColor) Counter = Counter - 1 if (Counter == 0): repaint(Canvas) Counter = 1000 return #---------------------------------------------------------------------- # The rest is now just tune-ups. MyColor and PX are both defined once # and used once, so we can combine those lines of code and eliminate # the unneeded variables. # # We also need to spend some time with the Average function. # We could implement it as an explicit for-loop: # # Result = [] # for I in range(2): # Result = Result + [(Q0[I] + Q1[I]) / 2] # return Result # # or as a list comprehension: # # return [(Q0[I] + Q1[I]) / 2 for I in range(2)] # # but by using the lengths of Q0 and Q1 to control the loop, we now # have a version that will work for any number of dimensions. Our # points Q0 and Q1 could be 2D [X,Y] or 3D [X,Y,Z] or even higher # dimensions. This tool can be used for a LOT of geometrical code! #---------------------------------------------------------------------- def Average5(Q0,Q1): # Q0 and Q1 are both points return [(Q0[I] + Q1[I])/2 for I in range(min(len(Q0),len(Q1)))] def Main5(): W = 800 H = 600 Canvas = makeEmptyPicture(W,H) show(Canvas) P0 = [W / 2, 0] P1 = [0, H - 1] P2 = [W - 1,H - 1] PP = [P0[0],P0[1]] # Why not do PP = P0 ? Points = [P0,P1,P2] Colors = [red,green,blue] Counter = 1000 while (True): N = random.randrange(3) PP = Average5(PP, Points[N]) setColor(getPixel(Canvas,PP[0],PP[1]), Colors[N]) Counter = Counter - 1 if (Counter == 0): repaint(Canvas) Counter = 1000 return