Python 043 - Accidentally Learned Something
My browser was open to the
random
page, and for a moment, my mind was open too.
I found this:
random.choice(seq)
Return a random element from the non-empty sequence seq. If seq is empty, raises IndexError.
So that should save us some code and a test. We have this:
def __init__(self, position=None, size=2):
self.position = position if position is not None else u.CENTER
self.size = size
self.velocity = u.SAUCER_VELOCITY
self.directions = [self.velocity.rotate(45), self.velocity, self.velocity, self.velocity.rotate(-45)]
...
def check_zigzag(self, delta_time):
self.zig_timer -= delta_time
if self.zig_timer <= 0:
self.zig_timer = u.SAUCER_ZIG_TIME
rand = random.randint(0, 3)
self.velocity = self.new_direction(rand)*self.direction
def new_direction(self, index):
return self.directions[index % 4]
Seems like we could do this:
...
self.directions = (self.velocity.rotate(45), self.velocity, self.velocity, self.velocity.rotate(-45))
def check_zigzag(self, delta_time):
self.zig_timer -= delta_time
if self.zig_timer <= 0:
self.zig_timer = u.SAUCER_ZIG_TIME
self.velocity = self.new_direction()*self.direction
def new_direction(self):
return random.choice(self.directions)
That works fine but breaks two tests. I expected one of them, this:
def test_zig_zag_directions(self):
saucer = Saucer()
straight = u.SAUCER_VELOCITY
up = straight.rotate(45)
down = straight.rotate(-45)
assert saucer.new_direction(0) == up
assert saucer.new_direction(1) == straight
assert saucer.new_direction(2) == straight
assert saucer.new_direction(3) == down
assert saucer.new_direction(4) == up
assert saucer.new_direction(5) == straight
assert saucer.new_direction(-1) == down
I did not expect this one to fail:
def test_vanish_at_edge(self):
saucer = Saucer()
saucers = [saucer]
saucer.ready()
saucer.move(1, saucers)
assert saucers
saucer.move(u.SCREEN_SIZE/u.SAUCER_VELOCITY.x, saucers)
assert saucer.position.x > u.SCREEN_SIZE
assert not saucers
The error is:
> assert saucer.position.x > u.SCREEN_SIZE
E assert 649.1240251292506 > 768
E + where 649.1240251292506 = <Vector2(649.124, 786.124)>.x
E + where <Vector2(649.124, 786.124)> = <saucer.Saucer object at 0x1023e1e90>.position
E + and 768 = u.SCREEN_SIZE
What happened, of course, is that the first move changed the velocity, so the saucer was moving at an angle, and didn’t reach the edge because, after all, it’s going slower in the x direction now.
How can we fix this test? Maybe we should loop, in smaller steps, until we find its x position greater than screen size and then expect saucers
to be empty.
Let’s check the move code to see if that works:
def move(self, delta_time, saucers):
self.check_zigzag(delta_time)
self.position += delta_time*self.velocity
x = self.position.x
if x < 0 or x > u.SCREEN_SIZE:
if self in saucers:
saucers.remove(self)
Why did I check the code, rather than just write the test? Because I wanted to be sure that we didn’t bring the coordinate back into range. Probably I should have just written the test, like this:
I settle for this test:
def test_vanish_at_edge(self):
saucer = Saucer()
saucers = [saucer]
saucer.ready()
saucer.move(1, saucers)
assert saucers
saucer.move(u.SCREEN_SIZE/u.SAUCER_VELOCITY.x, saucers)
saucer.move(u.SCREEN_SIZE/u.SAUCER_VELOCITY.x, saucers)
assert saucer.position.x > u.SCREEN_SIZE
assert not saucers
Duplicating the line certainly ensures that we’ll be outside the boundary. However, the reason for doing it isn’t clear at all. Instead of that, I go hog-wild:
def test_vanish_at_edge(self):
saucer = Saucer()
saucers = [saucer]
saucer.ready()
saucer.move(1, saucers)
assert saucers
distance_to_edge = u.SCREEN_SIZE - saucer.position.x
slowest_possible_speed = u.SAUCER_VELOCITY.x * sin(pi/4)
a_little_bit_more = 50
time = (distance_to_edge + a_little_bit_more)/slowest_possible_speed
saucer.move(time, saucers)
assert saucer.position.x > u.SCREEN_SIZE
assert not saucers
If that doesn’t explain what I’m doing, it should certainly scare away anyone who wants to simplify the test.
The test is a solid green now. Commit: modify saucer to use random.choice().
Still, maybe that test is too hard to figure out “why is he doing all that???”
Let’s try the loop, and if necessary, make the time large enough that it won’t slow the tests much.
def test_vanish_at_edge(self):
saucer = Saucer()
saucers = [saucer]
saucer.ready()
saucer.move(1, saucers)
assert saucers
while saucer.position.x < u.SCREEN_SIZE:
assert saucers
saucer.move(0.1, saucers)
assert not saucers
How’s that? I think I like it. Commit: improve test.
Summary
Better idea, random.choice
. Easily done, improves code and tests.
So that’s nice. We’ll file that under old dog / new tricks.
See you next time!