[Tutor] A more Pythonic way to do this

D. Hartley denise.hartley at gmail.com
Fri Jul 1 00:11:56 CEST 2005


Hey guys!

I have a 'quest,' and at first glance this email looks long, but the
problem is probably not as complex as the length implies.  Please bear
with me, if I could get some advice on this particular problem, it
would go along way toward helping me be a better Python programmer.

Some of you might remember, a few months ago, I was working on a space
invaders game for a birthday present.  One of the functions I really
wanted to add to the game was to change the enemy "ships" at each new
level. Because of my deadline and my newness to Python, I did this in
an extremely ugly way.  To give you an idea:

the original main loop called:
    enemyship_sprites = createEnemies(screen, enemy_speed)

createEnemies looks like this:

def createEnemies(screen, speed):
   enemyship_sprites = EnemySprites(speed)
   for rows in xrange(5):
       for cols in xrange(8):
           enemyship_sprites.add(Enemy((cols*60)+20, (rows*40)+30), level)
   enemyship_sprites.draw(screen)
   return enemyship_sprites

It creates an instance of EnemySprites (my holder class) which
contains code for when the ships hit bottom, when to update them, etc.
 It also calls "Enemy()", which looks like this:

class Enemy(pygame.sprite.Sprite):
   def __init__(self, startx, starty):
       pygame.sprite.Sprite.__init__(self)
       if level == 1:
           self.image, self.rect = load_image('salad_ship.bmp', -1)
       self.rect.centerx = startx
       self.rect.centery = starty
   def update(self, direction, go_down):
       jump = 40 # how much the enemies move to the right/left on
each jump
       if go_down:
           # if a ship is moving down on this round,
           # it doesn't move on the x-axys
           self.rect.move_ip((0, 5))
       else:
           # move a ship in the x-axys.
           # if direction=1, it moves to the right; -1 to the left
           self.rect.move_ip((jump * direction, 0))
       # maybe it's time for a shot? :)
       # the chances are 1/30
       dice = random.randint(0,30)
       global enemy_shot_sprites
       if dice == 1:
           shot = EnemyShot(self.rect.midtop)
           enemy_shot_sprites.add(shot)


Now, get ready for one of the uglier things you've probably seen lately (!!):

Since I can only access the variable "level" from within the main
loop, for some reason I thought I couldnt use it in my class
definitions (which of course come before and are outside of the
mainloop).  So within the main loop, I did:

                   if level == 1:
                       enemyship_sprites = createEnemies1(screen, enemy_speed)
                   elif level == 2:
                       enemyship_sprites = createEnemies2(screen, enemy_speed)
                   elif level == 3:
                       enemyship_sprites = createEnemies3(screen, enemy_speed)
                   elif level == 4:
                       enemyship_sprites = createEnemies4(screen, enemy_speed)
                   elif level == 5:
                       enemyship_sprites = createEnemies5(screen, enemy_speed)
                   elif level == 6:
                       enemyship_sprites = createEnemies6(screen, enemy_speed)
                   elif level == 7:
                       enemyship_sprites = createEnemies7(screen, enemy_speed)
                   elif level == 8:
                       enemyship_sprites = createEnemies8(screen, enemy_speed)
                   else:
                       enemyship_sprites = createEnemies9(screen, enemy_speed)

And yes, I created 9 different createEnemies, all pretty much carbon
copies of each other except that they called Enemy1, Enemy2, Enemy3,
etc.  And then (you guessed it), I created 9 different Enemy
functions, too, so that each one could have its own (different)
bitmap.

Now... this is just ridiculous.

To my credit, I did actually know that at the time. But the day before
the birthday, I figured if I could make it work somehow, I would, and
I'd make it prettier later.  Now just happens to be that later ;)

So now that I've got a little more Python experience under my belt
(not much, but a little), it occurred to me that when I call
createEnemies in the mainloop, I could pass in an argument 'level'.

mainloop now starting off with:
    enemyship_sprites = createEnemies(screen, enemy_speed, level)

level is one of my main loop variables, so it'll know what i'm talking
about and can take that variable and pass it in when it calls
createEnemies. (Btw, please forgive me if I mix up terms like argument
and variable or use them in the wrong places, I'm still getting used
to them!)  In any case, then I changed my createEnemies to look like
this:

def createEnemies(screen, speed, level):
   enemyship_sprites = EnemySprites(speed)
   for rows in xrange(5):
       for cols in xrange(8):
           enemyship_sprites.add(Enemy((cols*60)+20, (rows*40)+30), level)
   enemyship_sprites.draw(screen)
   return enemyship_sprites

Since the part that changes each level actually matters when the Enemy
function gets called (because that's where the bmps are loaded), I
made Enemy need 'level':

class Enemy(pygame.sprite.Sprite):
   def __init__(self, startx, starty, level):
       pygame.sprite.Sprite.__init__(self)
       if level == 1:
           self.image, self.rect = load_image('salad_ship.bmp', -1)
       elif level == 2:
           (.................etc)
       self.rect.centerx = startx
       self.rect.centery = starty
(...............etc)


Now I know this email is getting hard to read ;)

At this point I was thinking I'd have one createEnemies, called in the
mainloop, which would take the level and, when it called Enemy to
create the ships, use that level argument to determine which bmp to
use for the ship itself.  One createEnemies(), one Enemy(), there you
go.

But now it's giving me an error when createEnemies is called, for the
enemyship_sprites.add(Enemy((cols*60)+20, (rows*40)+30), level) line,
saying __init__() (and this would be Enemy's init, correct?) takes
exactly 4 arguments (3 given):  and Enemy's init, as you can see,
takes (self, startx, starty, level).  But the first argument is just
'self'!  I don't have to pass that in.... I never *did*, anyway, in
all my Enemy1, Enemy2, Enemy3 code...?

I can send the full code for anybody who thinks that would be
clearer to look at (ha ha) - I was going to attach it here but it
would make the message too big for the list.  I know that going
through such a messy
problem like this is a pain, and questions like this don't always get
a lot of responses on the list.  My code worked, it was just messy,
and so I suppose I could just leave it alone. But I want to clean it
up: I want to make it more Pythonic, more organized, more efficient,
and would really love any advice anyone could offer.

Heck, for all I know, I could just be needing to add a "self.___"
somewhere, heaven knows that has tripped me up before!

Would love to hear from you guys, and thanks again,
Denise


More information about the Tutor mailing list