Python 019 - Fiddling
Just some random cleanup, unless I get an easy idea.
I’m chatting on line with some friends, and whiling away the time by cleaning up some code. I’ve added size to the Asteroid init and plan to use it more directly in other methods.
class Asteroid:
def __init__(self, size=2, position=None):
self.size = size
if self.size not in [0,1,2]:
self.size = 2
self.radius = [16, 32, 64][self.size]
self.position = position if position else Vector2(0, random.randrange(0, u.SCREEN_SIZE))
angle_of_travel = random.randint(0, 360)
self.velocity = u.ASTEROID_SPEED.rotate(angle_of_travel)
self.offset = Vector2(self.radius, self.radius)
self.surface = SurfaceMaker.asteroid_surface(self.radius * 2)
It’s used in the splitting code, which now occurs twice:
class Asteroid ...
def collide_with_missile(self, missile, missiles, asteroids):
if self.withinRange(missile.position, missile.radius):
missiles.remove(missile)
asteroids.remove(self)
size = [16, 32, 64].index(self.radius)
if size > 0:
a1 = Asteroid(size - 1, self.position)
asteroids.append(a1)
a2 = Asteroid(size - 1, self.position)
asteroids.append(a2)
main ...
def check_ship_vs_asteroid():
if ship.active:
for asteroid in asteroids.copy():
ship.collide_with_asteroid(asteroid)
if not ship.active:
asteroids.remove(asteroid)
radius = asteroid.radius
size = [16, 32, 64].index(radius)
if size > 0:
a1 = Asteroid(size - 1, asteroid.position)
asteroids.append(a1)
a2 = Asteroid(size - 1, asteroid.position)
asteroids.append(a2)
ship.active = True
Let’s extract a method on Asteroid:
def collide_with_missile(self, missile, missiles, asteroids):
if self.withinRange(missile.position, missile.radius):
self.remove_attacker_and_split(asteroids, missile, missiles)
def remove_attacker_and_split(self, asteroids, missile, missiles):
missiles.remove(missile)
asteroids.remove(self)
size = [16, 32, 64].index(self.radius)
if size > 0:
a1 = Asteroid(size - 1, self.position)
asteroids.append(a1)
a2 = Asteroid(size - 1, self.position)
asteroids.append(a2)
Let’s rename the missile and missiles collection to attacker and attackers.
def remove_attacker_and_split(self, asteroids, attacker, attackers):
attackers.remove(attacker)
asteroids.remove(self)
size = [16, 32, 64].index(self.radius)
if size > 0:
a1 = Asteroid(size - 1, self.position)
asteroids.append(a1)
a2 = Asteroid(size - 1, self.position)
asteroids.append(a2)
Now we should be able to call that method from the main:
No, that’s not good. We don’t have anything to remove. Do the extract differently:
def collide_with_missile(self, missile, missiles, asteroids):
if self.withinRange(missile.position, missile.radius):
missiles.remove(missile)
self.split_or_die(asteroids)
def split_or_die(self, asteroids):
asteroids.remove(self)
size = [16, 32, 64].index(self.radius)
if size > 0:
a1 = Asteroid(size - 1, self.position)
asteroids.append(a1)
a2 = Asteroid(size - 1, self.position)
asteroids.append(a2)
And now:
def check_ship_vs_asteroid():
if ship.active:
for asteroid in asteroids.copy():
ship.collide_with_asteroid(asteroid)
if not ship.active:
asteroid.split_or_die(asteroids)
ship.active = True
That should work as advertised. And it does. We’ll commit: refactor to provide asteroid split_or_die.
Oh! I forgot the important part, changing this:
def split_or_die(self, asteroids):
asteroids.remove(self)
size = [16, 32, 64].index(self.radius)
if size > 0:
a1 = Asteroid(size - 1, self.position)
asteroids.append(a1)
a2 = Asteroid(size - 1, self.position)
asteroids.append(a2)
To this:
def split_or_die(self, asteroids):
asteroids.remove(self)
if self.size > 0:
a1 = Asteroid(self.size - 1, self.position)
asteroids.append(a1)
a2 = Asteroid(self.size - 1, self.position)
asteroids.append(a2)
So that’s nice. Commit: use self.size rather than fancy computing of size.
Now it seems to me that there’s some asymmetry here, in that we let the asteroids deal with being hit by missiles, but not with being hit by the ship. One issue with this is that the ship is not in a collection, so we cannot remove it from its collection when it’s dead. Unless, of course, we made a collection for it. That would be more mechanism than needed, perhaps, but it would mean that the ship could be treated more nearly the same as the other objects.
Shall we try it? Let’s do.
main ...
ship = Ship(pygame.Vector2(u.SCREEN_SIZE / 2, u.SCREEN_SIZE / 2))
ships = [ship]
...
for ship in ships:
ship.draw(screen)
for asteroid in asteroids:
asteroid.draw(screen)
for missile in missiles:
missile.draw(screen)
...
There are a lot of lines that address ship. For now, I’ll leave them, because I don’t intend to destroy the ship, just to remove it from its collection. So we’ll sort of half-way use the collection and half-way not.
Let’s change this code …
def check_collisions():
check_ship_vs_asteroid()
check_asteroids_vs_missiles()
def check_ship_vs_asteroid():
if ship.active:
for asteroid in asteroids.copy():
ship.collide_with_asteroid(asteroid)
if not ship.active:
asteroid.split_or_die(asteroids)
ship.active = True
… to call over to asteroid:
def check_ship_vs_asteroid():
if ship.active:
for asteroid in asteroids.copy():
asteroid.collide_with_missile(ship, ships, asteroids)
if not ships:
ship.active = True
ships.append(ship)
I think this may just work. Worth a try. It nearly does unless I don’t put the ship right back as the code does above. We really should have done this:
def check_ship_vs_asteroid():
for ship in ships:
for asteroid in asteroids.copy():
asteroid.collide_with_missile(ship, ships, asteroids)
if not ships:
ship.active = True
ships.append(ship)
Also, does Python have a way of removing something and not exploding if it isn’t there?
I do this:
def collide_with_attacker(self, attacker, attackers, asteroids):
if self.withinRange(attacker.position, attacker.radius):
if attacker in attackers: attackers.remove(attacker)
self.split_or_die(asteroids)
def split_or_die(self, asteroids):
asteroids.remove(self)
if self.size > 0:
a1 = Asteroid(self.size - 1, self.position)
asteroids.append(a1)
a2 = Asteroid(self.size - 1, self.position)
asteroids.append(a2)
Rename the method to refer to attacker / attackers rather than just missiles, and do a safe remove of the attacker just in case. I don’t fully trust the ship not to be processed more than once, though once I changed to loop over the possibly-empty ships collection, I think everything is OK.
Let’s commit: refactor to process asteroid-missile and asteroid-ship collisions in asteroid.
Summary
Slightly better. It’ll be interesting to see how we might do scoring. And the active-inactive thing needs fixing, with a standard time delay for recreating the ship. And other matters. We have lots of fun left.
See you next time!