INST326 Homework 02 Framework

Game Framework Using Turtle

The following code sets up a game in which a number of pieces of cake are spread randomly throughout a playing field. Your turtle starts at the middle of this playing field and needs to eat cake.

Scoring

Every time you get within a few pixels of a piece of cake, your turtle eats the cake, gets a point, and grows in size.

Assignment

Your objective is to write the body of the function seekCake(), such that your turtle tries to eat as much cake as possible. This function will be called once every animation frame, so your function should call at least call the forward() and right()/left() functions of the passed turtle object.

The arguments to the seekCake() function consist of the turtle object, the current frame index (i.e., how many moves have been taken), a maximum distance the turtle can move in one turn, and a grid height and width. The height and width defines the playing field in which the pieces of cake will be placed. Ideally, your turtle should not go outside this playing field (there is no cake there, and you won't be able to see it animated).

Grading

You will be assessed based on how well your turtle stays within bounds, how easily understandable your code is, and how many points it scores over the course of a game round.

Assitance

The following code is provided, so you can test your own function. It will set up a playing field, randomly place pieces of cake, and determine whether you consume one.

Game Setup and Configuration

In [1]:
# Import the turtle package, so we can actually play the game
import turtle

# Need random for cake placement
import random

# Need math for sqrt(), so we can calculate collisions
import math
In [2]:
# Create a screen on which we can play
screen = turtle.Screen()

# Configure the playing field
dimX = 400
dimY = 400
screen.setup(dimX, dimY)

Helper Functions

In [3]:
# Calculate euclidean distance between (x1, y1) and (x2, y2)
def distance(x1, y1, x2, y2):
    """Given two points, (x1, y1) and (x2, y2), calculate the 
    euclidean distance between them"""
    return math.sqrt((x1 - x2) ** 2 + (y1 - y2) ** 2)

# Collision detection and scoring for turtles and cake pieces
def nearPie(ttl, pieceMap, scoreMap, edibleDistance=30):
    """Given a turtle object, map of piece locations, map of scores,
    and optionally a maximum distance at which a piece can be eaten,
    determine whether this turtle actuall eats anything this frame."""
    x, y = ttl.position()
    for (pieceLoc, piece) in pieceMap.items():
        otherX, otherY = pieceLoc
        
        if ( piece.isvisible() and 
            distance(x, y, otherX, otherY) < edibleDistance ):
            piece.hideturtle()
            scoreMap[ttl] += 1
            
            (w, h, stretch) = ttl.shapesize()
            ttl.shapesize(w, h, stretch+5)

!!!IMPLEMENT YOUR FUNCTION HERE!!!

Fill out the body of your movement function here. I have also provided a few sample functions here for your reference.

In [4]:
def seekCake_Random(ttl, frameIndex, gridW, gridH, dist=10):
    """Randomly run some random direction and move"""
    if ( random.random() < 0.5 ):
        ttl.right(90 * random.random())
        
    ttl.fd(dist)
        
    return None

def seekCake_RandomV2(ttl, frameIndex, gridW, gridH, dist=10):
    """Every third frame, turn a random direction"""
    if ( frameIndex % 3 == 0 ):
        ttl.right(90 * random.random())
        
    ttl.fd(dist)
        
    return None

def seekCake_Checker(ttl, frameIndex, gridW, gridH, dist=10):
    """Spiral outward"""
    
    # These functions ensure we continuously expand our
    #  spiral
    factor = int(math.sqrt(frameIndex))
    checkVal = (frameIndex) - factor ** 2
    
    # If we have a perfect square or one before that, turn
    if ( checkVal == 0 or checkVal == factor ):
        ttl.right(90)

    ttl.fd(dist)
    
    return None

def seekCake_Spiro(ttl, frameIndex, gridW, gridH, dist=10):
    """Spiral outward"""
    
    # Calculate the current radius of our arc
    r = 20 + 2*frameIndex
    
    # What angle do we need to achieve this distance?
    angle = (360 * dist) / (2 * math.pi * r)
    
    # How many whole steps whould we take?
    n = int(dist / 3) + 1
    step_length = dist / n
    step_angle = angle / n
    
    # Move the turtle
    for i in range(n):
        ttl.fd(step_length)
        ttl.right(step_angle)
                            
    return None

def seekCake(ttl, frameIndex, gridW, gridH, dist=10):
    # Implement your own code here or call one of the examples
    seekCake_Spiro(ttl, frameIndex, gridW, gridH, dist)
    return None
In [5]:
def randomWalk(ttl, frameIndex, gridW, gridH, maxD):
    
    # Determine the boundaries
    xBoundary = gridW // 2 - 1
    yBoundary = gridH // 2 - 1

    d = random.random()
    angle = random.randint(0,90)
    
    if ( d < 0.5 ):
        ttl.left(angle)
    else:
        ttl.right(angle)

    ttl.fd(maxD * random.random())
    x, y = ttl.position()
    
    # Set boundary condition
    if ( abs(x) > xBoundary ):
        ttl.right(180)
        if ( x < 0 ):
            ttl.setx(-xBoundary)
        else:
            ttl.setx(xBoundary)

    if ( abs(y) > yBoundary ):
        ttl.right(180)
        if ( y < 0 ):
            ttl.sety(-yBoundary)
        else:
            ttl.sety(yBoundary)

Actual Playing Code

In [6]:
# Reset the screen
turtle.clearscreen()
screen.reset()
screen.screensize(dimX, dimY)

# Create a turtle
ttl = turtle.Turtle()

# Determine the boundaries
gridW = screen.canvwidth
gridH = screen.canvheight
xBoundary = screen.canvwidth // 2 - 1
yBoundary = screen.canvheight // 2 - 1

# Collision space. If we're within this many pixels,
#  you've eaten a piece. Also, pieces can't overlap
#  or be closer to each other than this.
minPieRadius = 30

# Store scores and where pieces are placed
scoreMap = {ttl : 0}
piePlacement = {}

# Configure turtle size
ttl.resizemode("user")
ttl.shapesize(1.0, 1.0, 1)

#  Place pieces of cake
pieceCount = 10 # How many pieces?
turtle.addshape("cake.gif") # What does the piece look like?

# For each piece, place it
for i in range(pieceCount):
    piePiece = turtle.Turtle()
    piePiece.speed(0) # Don't animate the piece's movement
    piePiece.hideturtle() # Hide it while we're placing
    piePiece.penup() # Don't track its path

    # Sentinel for whether this piece is in a valid location
    safeDistance = False

    # Loop until we are in a valid place
    while ( not safeDistance ):
        # Find a random x, y point
        newX = random.randint(-xBoundary, xBoundary)
        newY = random.randint(-yBoundary, yBoundary)

        # We assume we are in a good place
        safeDistance = True
        
        # For all other pieces that have been placed, check
        #  if we collide with them
        for otherPiece in piePlacement.keys():
            otherX, otherY = otherPiece # Other location
            
            # Are we closer than min distance?
            if ( distance(newX, newY, otherX, otherY) < minPieRadius ):
                safeDistance = False # If so, fail and break out
                break
        
        # If we **ARE** in a safe space, set the new position,
        #  shape, show the piece, and add this piece to the 
        #  dictionary of other pieces
        if ( safeDistance ):
            piePiece.setposition((newX, newY))
            piePiece.shape("cake.gif")
            piePiece.showturtle()

            piePlacement[(newX, newY)] = piePiece


# Set animation speed (1-10, 1 = slow, 10 = fast, 0 = no animation)
ttl.speed(10)

# Maximum distance we can move in one frame
maxMovement = 30

for i in range(1000):

    # Here's where your code gets called.
    randomWalk(ttl, i, gridW, gridH, maxMovement)
            
    # Perform collision testing, sizing, and scoring
    nearPie(ttl, piePlacement, scoreMap, minPieRadius)
    
---------------------------------------------------------------------------
KeyboardInterrupt                         Traceback (most recent call last)
<ipython-input-6-a4191adfaa1c> in <module>()
     79 
     80     # Here's where your code gets called.
---> 81     randomWalk(ttl, i, gridW, gridH, maxMovement)
     82 
     83     # Perform collision testing, sizing, and scoring

<ipython-input-5-8fdc10e6dbc4> in randomWalk(ttl, frameIndex, gridW, gridH, maxD)
     11         ttl.left(angle)
     12     else:
---> 13         ttl.right(angle)
     14 
     15     ttl.fd(maxD * random.random())

/Users/cbuntain/Development/thirdparty/anaconda3/lib/python3.6/turtle.py in right(self, angle)
   1676         337.0
   1677         """
-> 1678         self._rotate(-angle)
   1679 
   1680     def left(self, angle):

/Users/cbuntain/Development/thirdparty/anaconda3/lib/python3.6/turtle.py in _rotate(self, angle)
   3276                 self._update()
   3277         self._orient = neworient
-> 3278         self._update()
   3279 
   3280     def _newLine(self, usePos=True):

/Users/cbuntain/Development/thirdparty/anaconda3/lib/python3.6/turtle.py in _update(self)
   2661             self._drawturtle()
   2662             screen._update()                  # TurtleScreenBase
-> 2663             screen._delay(screen._delayvalue) # TurtleScreenBase
   2664         else:
   2665             self._update_data()

/Users/cbuntain/Development/thirdparty/anaconda3/lib/python3.6/turtle.py in _delay(self, delay)
    564     def _delay(self, delay):
    565         """Delay subsequent canvas actions for delay ms."""
--> 566         self.cv.after(delay)
    567 
    568     def _iscolorstring(self, color):

/Users/cbuntain/Development/thirdparty/anaconda3/lib/python3.6/tkinter/__init__.py in after(self, ms, func, *args)
    739         if not func:
    740             # I'd rather use time.sleep(ms*0.001)
--> 741             self.tk.call('after', ms)
    742         else:
    743             def callit():

KeyboardInterrupt: 
In [ ]:
for item in scoreMap.items():
    print("Score:", item[1])
In [ ]:
random.randint(-200, 200)
In [ ]: