Python 036 - Game Object Continues
I’ll just peck away at moving top-level fuctions into my game class. Tedious unless I get a clever idea. Very boring, do not read.
I think the fact that I have a game instance embdded in the game file will help transition things, but it’ll have to go if we’re to have a true game object that can be created and destroyed.
An alternative, I suppose, would be a game object all on the class side, but that trick has never worked well for me in the past. We’ll continue with the instance.
The thing, I guess, will be to look for things called by the methods we already have, and drag them in. I do think we’ll extract some separate classes, maybe one for collisions, and maybe separate objects of some kind for score and the GAME OVER screen. For now, I think it’s best to move to one object rather than zero, and work from there.
Ah, here’s a thing:
class Game ...
def define_score(self):
global score_font
u.score = 0
score_font = pygame.font.SysFont("arial", 48)
We should almost certainly have score
as a member in Game. But maybe not yet, that’s going to break some tests. We’ll let it ride. I wonder if I can put TODO things in and then find them later. Yes, PyCharm has a TODO window that will give a list of things like this:
def define_score(self):
global score_font
u.score = 0
# TODO: move to Game class
score_font = pygame.font.SysFont("arial", 48)
That will help. I think I’ll mark much of what needs moving with that TODO comment, so that I donm’t miss opportunities. It might make it easier to spot things as I clean up. Then again, it may tick me off and if so I’ll remove them.
Oh bummer. PyCharm won’t let me commit with a TODO in place. That’s useless. I’ll change the comment: can’t find where to turn off the check. Grr.
OK let’s see what we can do with this:
def game_init(self):
global screen, running
pygame.init()
screen = pygame.display.set_mode((u.SCREEN_SIZE, u.SCREEN_SIZE))
pygame.display.set_caption("Asteroids")
self.define_game_over()
self.define_score()
running = True
self.insert_quarter(0)
Can I move the screen
and running
to members?
Python will let me do this:
def __init__(self):
self.running = False
self.screen = None
self.clock = pygame.time.Clock()
self.delta_time = 0
def game_init(self):
pygame.init()
self.screen = pygame.display.set_mode((u.SCREEN_SIZE, u.SCREEN_SIZE))
pygame.display.set_caption("Asteroids")
self.define_game_over()
self.define_score()
self.running = True
self.insert_quarter(0)
Now I’ll have to find all the references to screen and running.
This is tedious and not educational. I’ll just do it and report on anything interesting or inthe unlikely event that I think of anything intelligent to do.
OK, here’s one. We have this function:
def set_ship_timer(seconds):
global ship_timer
if ship_timer <= 0:
ship_timer = seconds
Clearly this function should be im Game, and ship_timer should be a member of Game (or perhaps pushed down toward ship, but anyway not global). But there is a test that looks at it:
class TestCollisions:
def test_respawn_ship(self):
ship = Ship(Vector2(0, 0))
ship.velocity = Vector2(31, 32)
ship.angle = 90
ships = []
set_ship_timer(3)
check_ship_spawn(ship, ships, 0.1)
assert not ships
check_ship_spawn(ship, ships, u.SHIP_EMERGENCE_TIME)
assert ships
assert ship.position == u.CENTER
assert ship.velocity == Vector2(0, 0)
assert ship.angle == 0
Let’s work toward making a game instance here to run this test. First I’ll move the set and check mehods in.
Moving the set
in requires me to comment out lines of that test and makes another test fail. I’ll allow that for now. Now the check? That goes in OK, with the two tests down.
I’ve committed those with two broken tests. Let’s make them work.
I’ve patched them to run, but they are starting and stopping the game window. The tests look like this:
def test_respawn_ship(self):
game = Game()
ship = Ship(Vector2(0, 0))
ship.velocity = Vector2(31, 32)
ship.angle = 90
ships = []
game.set_ship_timer(3)
game.check_ship_spawn(ship, ships, 0.1)
assert not ships
game.check_ship_spawn(ship, ships, u.SHIP_EMERGENCE_TIME)
assert ships
assert ship.position == u.CENTER
assert ship.velocity == Vector2(0, 0)
assert ship.angle == 0
def test_respawn_count(self):
test_game = Game()
ship = Ship(Vector2(0, 0))
ships = []
game.ships_remaining = 2
test_game.check_ship_spawn(ship, ships, 3.1)
assert game.ships_remaining == 1
assert len(ships) == 1
ships = []
test_game.check_ship_spawn(ship, ships, 3.1)
assert game.ships_remaining == 0
assert len(ships) == 1
ships = []
test_game.check_ship_spawn(ship, ships, 3.1)
assert game.game_over
assert not ships
They’re pretty ragged, due to only some things being inside Game class as yet. But they do run1.
The game getting started is more of an issue. I add a testing flag to Game.init:
def __init__(self, testing=False):
if testing: return
self.score_font = None
self.running = False
self.screen = pygame.display.set_mode((u.SCREEN_SIZE, u.SCREEN_SIZE))
self.clock = pygame.time.Clock()
self.delta_time = 0
And I set testing to True in those two tests. That works, no more window. Will the game work?
Yes it will. Commit: fix tests using set_ship_timer and check_ship_spawn.
Summary
Just tedious work, that would be unnecessary if I had moved more swiftly to a real Game object. No real insights yet. Maybe there’s a better way to do this. I’ll ask my betters. And if you have an idea, do pass it on to me.
I’ll call it a day and publish this but not advertise it. No one deserves to feel they should read this.
See you next time.
-
But you have heard of me. —Captain Jack Sparrow ↩