Problem A. (12 points) Getting Started
To get you started, we’ll give some pretty detailed instructions for the first few steps of the project: hopefully you can extrapolate from those steps to figure out how to proceed in parts B, C, and D as well.
Our first task is to set up classes to represent the entities that need to be manipulated for the game. One reasonable division of functionality is to have one class for the snake(s), and another overarching class for the game as a whole.
We’ll begin with the Game class. For now, all the Game class is going to do is draw the board and call a bunch of turtle functions that ensure that the turtle graphics are running as fast as possible. Copy-paste the following code into your hw13.py file:
import turtle, random
class Game:
def __init__(self):
turtle.setup(700, 700)
turtle.setworldcoordinates(-40, -40, 640, 640)
cv = turtle.getcanvas()
cv.adjustScrolls()
turtle.hideturtle()
turtle.delay(0)
turtle.speed(0)
for i in range(4):
turtle.forward(600)
turtle.left(90)
Game()
When you run the program, it should produce a turtle graphics window that resembles the one shown below:
Next, we’re going to create a Snake class that represents one of the snakes involved in the game.
Snake.__init__(self, x, y, color) should take in three arguments aside from self: x and y are integer arguments representing the current position of the square forming the head of the Snake, and color represents the color of the Snake.
The constructor should create instance variables self.x, self.y, and self.color and initialize them each to the value from the appropriate parameter. It should also create self.segments, which should be set to an empty list. This list will eventually store the square segments that make up the snake, each of which will be a turtle object.
To produce each segment, create another method called grow(self). This takes in no arguments (other than self), and creates a new head segment for the snake at the current position - we’ll call this each time the snake grows.
grow should do the following:
(The hyperlinks below link to the Python documentation for the turtle module - if you’re unsure what the documentation is saying, ask your TAs.)
Create a turtle object using the constructor turtle.Turtle(). Assign this to some variable (say, head, since this will represent the head of the Snake).
Set the turtle object’s speed to 0. You must use the turtle object you created in the last step - if you typed out turtle.speed(0), you’re not using the turtle object.
Set the turtle object’s fill color to self.color.
Set the turtle object’s shape to 'square'
Set the turtle object’s size to 1.5 times normal in both width and length
Set the turtle object’s pen state to up.
Set the turtle object’s position to self.x, self.y
Append the turtle object to the end of the self.segments list.
You should put a call to self.grow() at the end of __init__ so that the snake starts out with one segment.
To test the Snake class, create a Snake object called self.player (representing the snake that the player controls) at the end of Game.__init__, with x = 315, y = 315, color = 'green'. You should see something like this:
Next, we’ll get the snake to start moving. To start, we’ll create a game loop - a function that calls itself at a set interval to keep the game moving. Create a method gameloop(self) within the Game class that does nothing but print out "In gameloop", and then calls turtle.ontimer(self.gameloop, 200).
If you add a call to self.gameloop() at the end of the __init__ method, you should see "In gameloop" print out roughly once every 200 ms.
Now, create a method move(self) in the Snake class that increases self.x by 30, and then moves the head of the snake (that is, the turtle object at index -1 of self.segments) to the updated position self.x, self.y.
Change Game.gameloop to call the move method on the player’s Snake object instead of printing out “In gameloop” every 200 ms. You should see the green square start to move 30 units to the right every 200 ms or so, until it eventually leaves the screen.
The last thing we’ll go over in detail is how to get the snake to change directions. First, we’ll need to add two instance variables to Snake.__init__ to represent the snake’s current velocity. Call these self.vx and self.vy, and initialize them to 30 and 0, respectively. Now, alter the move method so that rather than always adding 30 to self.x, we increase self.x by self.vx, and self.y by self.vy at each step.
This shouldn’t actually change anything just yet, since self.vx is set to 30 and self.vy is set to 0, so it should still just start moving right by 30 pixels every 200 ms or so.
Add a method called go_down(self) to the Snake class. All this method should do is set self.vx to 0 and self.vy to -30. Try calling this method at the end of Snake.__init__ - your snake should start moving down initially rather than to the right.
Once you’re certain that the go_down method works as intended, remove the call from the end of Snake.__init__ - we do want the snake to initially start by moving right, but we want it to start moving down when the user presses the down arrow key.
To do this, add the following three lines of code to the end of Game.__init__:
turtle.onkeypress(self.player.go_down, 'Down')
turtle.listen()
turtle.mainloop()
This binds the go_down method to run on the self.player object every time the down arrow key is pressed, and then sets up the turtle graphics window to listen for keyboard input. Note that on some systems you may need to click on the turtle graphics window before it starts paying attention to your key presses.
Your snake should now initially move right but start moving down if the down arrow key is pressed.
Follow the same process to bind the left, up, and right arrow keys so that you can move the snake in all four directions (you don’t need to use .listen / .mainloop again, just .onkeypress).
For the remaining parts of the assignment, we’ll provide minimal guidance - you need to implement the required features but you can choose how to approach the task.
Problem B. (12 points) Food
Implement a red, circular food pellet that’s placed in a random position aligned with the grid. If the snake’s head moves into the same space as the food pellet, the snake should grow by one segment, and a new food pellet should appear in a random grid-aligned position. You will need to adjust the move method so that when the snake grows, each segment follows the head.
Hints:
15 + 30*random.randint(0,19) will select a random grid-aligned x or y-coordinate for the food pellet (we need to add 15 because we want the object to be in the middle of the grid space).
It may be useful to create a separate class for the food pellet and make a method that causes it to move to a random grid-aligned position - this way you can call that method when it’s first created, and each time the snake eats it.
You may want to change Snake.move to take in the food pellet as an additional parameter so that you have the ability to check whether the snake is going to hit it.
There are two cases to handle for moving the snake:
If the snake would move into a spot that contains food and grow, just add a new head segment and don’t move any of the existing segments.
Otherwise, loop through every segment in the list except the last one (the head), and move it into the position of the segment after it. Then, move the head to the new self.x and self.y as before.
Problem C. (6 points) Game Over
The game should end if either of the following two things occur:
The player snake goes outside of the bounding box. (Remember, this is a square that spans from (0, 0) in the lower left, to (600, 600) in the upper right).
The player snake runs into itself (the head of the snake is in the same space as one of the other segments).
If this occurs, the snake should stop moving and the words “Game Over” should be written somewhere on screen. You can use turtle.write to accomplish this.
Hints:
To stop the game, you need to prevent Game.gameloop from calling itself again.
You may want some method in the Snake class that checks for either type of collision and returns True if a collision occurred, or False otherwise. You can then change the gameloop method to only call itself using ontimer if no collision occurred.
What I have tried:
This is the beginning of the code
import turtle, random
class Game:
def __init__(self):
turtle.setup(700, 700)
turtle.setworldcoordinates(-40, -40, 640, 640)
cv = turtle.getcanvas()
cv.adjustScrolls()
turtle.hideturtle()
turtle.delay(0)
turtle.speed(0)
for i in range(4):
turtle.forward(600)
turtle.left(90)
Game()