Join us and get access to hundreds of tutorials and a community of expert Pythonistas.

Unlock This Lesson

This lesson is for members only. Join us and get access to hundreds of tutorials and a community of expert Pythonistas.

Unlock This Lesson

Hint: You can adjust the default video playback speed in your account settings.
Hint: You can set the default subtitles language in your account settings.
Sorry! Looks like there’s an issue with video playback 🙁 This might be due to a temporary outage or because of a configuration issue with your browser. Please see our video player troubleshooting guide to resolve the issue.

Splitting Your Space Rocks Into Smaller Rocks

00:00 In the previous lesson, you made things crash into each other instead of floating on by. In this lesson, you’ll make your big rocks turn into little rocks and your little rocks into space pebbles. Bamm-Bamm would be proud.

00:13 In the current version of the game, when you hit a rock with a bullet, the rock goes away. As any fan of bad Bruce Willis movies will tell you, hitting a rock with a bullet isn’t going to make it just disappear.

00:24 Let’s break it into smaller pieces instead. To do this, your Rock constructor will need to change. Right now, your rock positions are randomly generated, but if a rock is supposed to split into pieces, you don’t want that.

00:38 So the constructor needs to handle both the random appearance case for start conditions and the position specific case for rock splits. It will also need the ability to construct different sizes of rocks.

00:51 Let’s see some code here. Here I am inside of models.py. Let me scroll down to the Rock object class.

01:01 What I’ve decided to do here is create a factory method. A factory method is a class method that constructs and returns an object. This pattern is useful if there are a bunch of different ways of constructing something.

01:13 Factory methods can sometimes make your code clearer. Instead of having to read all the various parameters to interpret how a constructor is going to be called, you can have a factory method called .create_random() whose name might be considered clearer than having to use a use_random_position=True parameter.

01:32 The end result is the same, and a matter of style and preference. If you’ve come to Python from other languages, especially Java, you’ll find there are far fewer factories in the snake-based coding world, but the occasional one used judiciously can still make your code easier to read.

01:48 The contents of this factory are pretty much a copy of the old .__init__() method. A random position is created. It is checked if it is in a valid starting position. If so, the rock is constructed. If not, a new position is tried until a rock outside the minimum start gap is generated. Line 83 here is the end of the factory method, which actually creates the new rock with the random position and returns it.

02:12 Let me scroll down just a little bit.

02:18 The .__init__() method has also changed. In addition to position, you now need to be concerned about the size of the rock being created. I’m going to use three sizes and an integer value of 1, 2, or 3 to indicate the size.

02:32 Normally, I’m adverse to using constants directly in the code. These are sometimes referred to as magic numbers because the source of their value might be considered magical.

02:41 Most of the time, you want to use a constant, instead, with a good name. In this case, though, I’ll make an exception, and you’ll see why in a second. Magic number or not, it needs to be stored in the class. And then based on this number, you need a scaling factor. This scaling factor is used to calculate the size of the rock. Large rocks will be the size currently in use, medium rocks will be half as big, and small rocks will be half as big again.

03:10 Line 94 scales the sprite based on the size just determined. Lines 97 through 99 determine the speed of the rock. This is the same random velocity code used before the change to different sized rocks. Let me scroll down just a little more.

03:27 I’ve added a method that takes a given rock and splits it. This method creates two new rocks in the same spot as the current object.

03:37 First, it checks if the rock is bigger than the smallest size. You don’t want to split rocks of size 1. Next, as you’re impacting the number of rocks in the game, you have to access the thing containing the rocks. Before now, the container for rocks lived inside of the SpaceRocks game object, same as the bullets.

03:57 This is a good place to have a conversation about the trade-offs of global variables. The standard advice to new programmers is not to use global variables.

04:06 Encapsulation is a good thing. It tends to organize your code in a more readable fashion. That being said, if you need to access something that is encapsulated, you always have to pass that something around.

04:18 You saw the consequence of this earlier when the reference to the bullet container had to be added to the Spaceship class. To show you a different way of thinking about the same problem, I’ve changed the way the code stores rocks and bullets.

04:31 I’ve made them global variables inside of the game.py module. You can decide for yourself which you think is more readable: breaking the rules about encapsulation or passing the container around every time you need it.

04:44 Making this a global variable means you can access it by importing the module. The problem is the game.py module imports the models.py module, the file you’re in right now.

04:55 Having this file import something from game.py is what’s called a circular import. Python won’t let you do this. This is why this import is inside of this method on line 105 instead of at the top of the file. If the import had happened when the module was loaded the first time, there would be a circular import and the compiler would complain.

05:16 Putting it inside of this method is fine though. The module is already loaded into memory, so this code simply accesses the already-loaded module. Circular import problem resolved. Now, what was I talking about?

05:30 Oh yeah, splitting rocks. Lines 107 and 108 are where two new rocks are generated. They are both generated in the exact same space as the current rock. Note that the value for size is one less than the current size.

05:45 This is why I was okay with using the magic numbers 1, 2, and 3 for the rock sizes. It makes generating the next smaller rock a simple subtraction operation.

05:55 If I had used a constant or an enum instead of a magic number, this would end up having to be an if/else condition block. Like a lot of coding, this is a trade-off. Magic numbers are harder to read, but in this case, it gives you easier-to-read splitting code. A better compromise would be to add some comments around the use of the magic numbers. You should do that. Comments are good.

06:17 I’ll wait right here.

06:23 All right. This is game.py. The first change in here is turning the bullet and rock containers into global variables as promised. Anywhere where you had self.rocks or self.bullets in the SpaceRocks object will now need to change to a global reference.

06:39 And here’s the first case of that. global rocks sounds like a bad name for a band, but what it is actually doing is telling Python you want to use the global variable instead of creating a new overriding local one. With the global established, the rock generation code is similar.

06:55 It is still creating six rocks inside of a list comprehension, but now the .create_random() rock factory is being used instead of calling for a new object. Scrolling down,

07:07 here’s the .game_objects() property. And once again, because of the change to global variables, this method has to be updated. I’m creating a temporary list here with all the bullets and rocks.

07:19 And because our ship now might have gone away, it should only be included with the game objects if it is still there. With the .game_objects() property up-to-date, now the collision logic needs to change. Line 66 indicates that bullets and rocks will be global, and then line 79 is what removes the rock when a collision between a rock and a bullet is found.

07:42 Now that the rock is removed, it can be replaced with two rocks through the call to .split(). Remember that .split() has a check in it for tiny rocks. If the rock is too small, split does nothing, so it is safe to call it either way.

07:57 The remaining code is the same as before: removing the bullet and carrying on. Let’s break some rocks, hot sun optional. Kudos to the tiny little subset of Python coders with the appropriate taste in well-aged music that got that reference. I suspect it was one of you.

08:14 Might’ve only been my editor in fact, but it made me smile. So, moving on.

08:20 Here, I am recovered from my inside joke tangent flying my spaceship again.

08:27 Pew, pew! Look at that, smaller rocks! And there you go, three rock sizes. And when the third one is hit, it disappears. I won’t tell you how many takes this took.

08:38 It’s time to make some noise. Next up, you’ll add sound to your game.

Become a Member to join the conversation.