In this lab, you will explore principles from event-based programming and create a simple video game that roughly approximates the 70s arcade game Asteroids. Specifically, we will use timer events, keyboard button events, and collision detection.
As the gateway into the final project, this lab will review concepts we've learned all semester: lists, objects and object-oriented design, classes, inheritance, top-down design, and more.
turtle_interpreter.py, lsystem.py, and shapes.py from Project 10.home.py or mosaic.py). This will be the background artwork for your video game!TurtleInterpreterturtle.setup() in the TurtleInterpreter constructor with self.screen = turtle.Screen(). This creates an object of type Screen, which represents the turtle canvas. Call the setup() method on the Screen object, passing in your desired canvas size (e.g. dx, dy).TurtleInterpreter has been initialized!getScreen for the screen instance variable.ShapeTurtleInterpreter object from draw to the constructor. Make it an instance variable of Shape. Update your function calls in draw to refer to your TurtleInterpreter instance variable.getScreen that calls the getScreen method from your TurtleInterpreter instance variable. This method will pass the Screen object from the TurtleInterpreter to your game.Game: Background artworkhome.py).TurtleInterpreter/terp in your file (this includes removing calls to hold()!). You should only work with Shape objects in your function.Shape object, call its getScreen method and assign the screen object that it returns to a variable. For example:sky = shapes.Square(distance=1000, fill=True) screen = sky.getScreen()
Note if using mosaic: You most likely have a mosaic function that calls a tile function in a nested loop.
mosaic (before the loops): e.g. sqr = shapes.Square() (filling in any required parameters). We're calling it 'dummy' because we aren't actually going to draw it, we just want to use it to get the Screen object.getScreen on your 'dummy' shape object (e.g. sqr), assign the screen object that it returns to a variable.mosaic function.Note about the purpose of screen: We want to pass the Screen object from where it was created in the first shape in your scene to the game. Here is a summary of its path:self.screen (TurtleInterpreter constructor) -> getScreen (TurtleInterpreter) -> getScreen (Shape) -> scene (home.py/mosaic.py/etc) -> setupBackground (Game) -> self.screen (Game constructor)
screen object.game.py.Game class. Import turtle.
setupBackground method. It should call your scene function to draw your background artwork (e.g. in home.py). Have setupBackground return the screen object returned by artwork scene function.setupBackground method and assign the return value to an instance variable in Game (e.g. self.screen). This should be the first thing that you do in the constructor.main function outside of the class. Create a new Game object, then call its run method (see next step) to start the game.if __name__ == '__main__' top-level code at the bottom of the file that calls the main function.run method to your Game class:def run(self): ''' Turns the tracer animations on (but speeds up animations) and starts the main game loop. ''' # Make sure animations are on but fast turtle.tracer(True) turtle.speed(0) # Let's go! self.screen.mainloop()
game.py. If everything is working so far, you should simply see your background artwork scene drawn to the screen (no game elements yet).Game: Player TurtlemakePlayer method in Game. It shouldturt = turtle.Turtle())turtle.register_shape('rocketship.gif') # Make the rocketship image available to turtle turt.shape('rocketship.gif') # Set the turtle icon to the rocketship image
makePlayer method from your constructor (after you setup the background artwork), assigning the output to an instance variable (e.g. self.player).Game: Interactive player controlWe're going to make the player turtle (rocketship) move under keyboard control!
Game constructor. You might wish to create optional parameters for these, giving them default values of 20 and 10, respectively.Game:up: Move the player forward by its speeddown: Move the player backward by its speedleft: Turn the player left by its rotation rateright: Turn the player right by its rotation ratesetupEvents method that creates your game events. Add keyboard callback events to the four movement methods you just created. Here, callback means that we will instruct our game to move our player by calling one of movement methods when we press the appropriate keyboard button. To add a callback, we call the onkey method of the Screen object. It has the following syntax: self.screen.onkey(<Method name>, <String of keyboard button>). For example self.screen.onkey(self.up, "Up") for moving the player up. To assign to the four arrow keys, use the strings: "Up", "Down", "Left", "Right".quit function when the user presses the "q" key.listen method on your screen object so that it will start calling the movement methods when you press the arrow keys.setupEvents at the end of the constructor.Game: Enemies!In this task, we will create a number of enemy turtle objects that move randomly.
def placeEnemyRandomly(self, turt):, which will move the turt object to random coordinates within the enemy bounds you defined in (1).def makeEnemies(self, n): It shouldn turtle objects.turt.shape('square')) and set the color to a color of your choosing via the usual means.self.placeEnemyRandomly on each enemy.makeEnemies from your Game constructor, assign the return value to an instance variable (e.g. self.enemies). Either hard code n (number of enemies) or make it an (optional) parameter in your constructor.Game: Enemies on the moveIn this task, we will create a timer event to randomly move the enemies over time.
def moveEnemiesRandomly(self):. It should set each enemy's position to the current position plus a small random (x, y) offset.setupEvents, create a timer that will automatically call moveEnemiesRandomly 50 msec after the game starts. The syntax is: self.screen.ontimer(<method name to call>, <int: wait time in msec>).ontimer only will call the moveEnemiesRandomly method ONCE 50 msec in the future, but we want to continue moving them every 50 msec indefinitely. To do this, add an identical timer at the end of moveEnemiesRandomly. Make sure that you understand why we're doing this before continuing.Game: Collision detectionWe will create another timer event to have the enemy take an action when the player collides with it.
def checkForCollisions(self):. It shouldplaceEnemyRandomly on the enemy that the player collided with.setupEvents to check for collisions.checkForCollisions.