Skip to content

Dumb Enemies and Smart Enemies

A game needs opposition. In this lab you'll spawn a squad of red enemy tanks — first ones that patrol mindlessly back and forth, then ones that turn and come after you. The difference between "dumb" and "smart" turns out to be about eight lines of code, which should worry you a little.


Overview

In this lab you'll create several enemies with a loop instead of typing each one, then break your update() code into helper functions — move_player(), move_enemies(), and move_enemies_smart() — so a growing game stays organized. The smart-enemy logic is yours to write.

Builds on: Rotation, Tiles, and Solid Walls — the angle attribute and keyboard driving.

Before you start

Open Thonny and start a new program named enemies.py. Save it in your pygame-zero folder, next to your images folder.

You've got it when…

  • Three red enemy tanks appear at random positions, facing random directions.
  • Your update() function is just a short list of helper-function calls.
  • Enemies patrol and turn around at the screen edges.
  • With move_enemies_smart() in charge, the enemies chase the player.

Collaboration & AI

Work: On your own. Ask a neighbor for a hand if you're stuck, but the program you submit should be yours.

AI — AIAS Level 1, No AI: The smart-enemy section is the assessment — it has to come from you. What the levels mean.


New Images

If you completed Getting Set Up for Pygame Zero, both tanks are already in your images folder.

The blue player tank. tank_blue.png

The red enemy tank. tank_red.png


Setting the Scene

  1. Let's make a simple game.

    1. Start a new Python file with the bookends.
    2. Make the screen 800 pixels wide and 600 pixels tall.
    3. Create an Actor for a tank. We'll call this one player, and it will use the image tank_blue.png. We'll start with the player in the center of the screen.
    4. Fill the screen with a color and draw the tank on every frame.
    enemies.py
    import pgzrun
    
    WIDTH = 800
    HEIGHT = 600
    
    player = Actor("tank_blue")
    player.x = WIDTH / 2
    player.y = HEIGHT / 2
    
    def draw():
        screen.fill("lawngreen")
        player.draw()
    
    pgzrun.go()
    

    Try your program. Your screen should look like this.

    The blue tank alone on the field.


Adding Enemies

  1. Now make an empty list to hold enemies. Add the highlighted line.

    WIDTH = 800
    HEIGHT = 600
    
    player = Actor("tank_blue")
    player.x = WIDTH / 2
    player.y = HEIGHT / 2
    
    enemies = []
    
  2. Let's create some enemies and add them to the list. We'll start each enemy at a random location on the screen and facing in a random direction, so we'll need to import the random library. Add the highlighted lines.

    import pgzrun
    import random
    
    WIDTH = 800
    HEIGHT = 600
    
    player = Actor("tank_blue")
    player.x = WIDTH / 2
    player.y = HEIGHT / 2
    
    enemies = []
    
    e1 = Actor("tank_red")
    e1.x = random.randint(0, WIDTH)
    e1.y = random.randint(0, HEIGHT)
    e1.angle = random.randint(0, 3) * 90
    enemies.append(e1)
    
    e2 = Actor("tank_red")
    e2.x = random.randint(0, WIDTH)
    e2.y = random.randint(0, HEIGHT)
    e2.angle = random.randint(0, 3) * 90
    enemies.append(e2)
    
    e3 = Actor("tank_red")
    e3.x = random.randint(0, WIDTH)
    e3.y = random.randint(0, HEIGHT)
    e3.angle = random.randint(0, 3) * 90
    enemies.append(e3)
    

    That was a lot of work for just three enemies! Notice how we're doing the same things for each enemy:

    • Create the enemy Actor.
    • Set it at a random x and y location.
    • Add it to the list of enemies.

    What if we wanted dozens of enemies? Typing each one in would become hard to manage.

    We can do better and let the computer do the work for us. Let's use a loop with the range() function to control how many enemies we create.

  3. Delete the fifteen lines that create e1, e2, and e3 — all three five-line blocks.

  4. Add the highlighted loop in their place. It does the same job for any number of enemies.

    import pgzrun
    import random
    
    WIDTH = 800
    HEIGHT = 600
    
    player = Actor("tank_blue")
    player.x = WIDTH / 2
    player.y = HEIGHT / 2
    
    enemies = []
    for i in range(3):
        e = Actor("tank_red")
        e.x = random.randint(0, WIDTH)
        e.y = random.randint(0, HEIGHT)
        e.angle = random.randint(0, 3) * 90
        enemies.append(e)
    
  5. Finally, we'll add another loop in the draw() function to draw each of the enemies in the list to the screen every frame. Add the highlighted lines.

    def draw():
        screen.fill("lawngreen")
        player.draw()
        for e in enemies:
            e.draw()
    

    Try your program. Your screen should look like this, though the enemies will be in different, random locations every time you run the game.

    Three red enemy tanks scattered around the blue player.

  6. Experiment with the number in the range() function to add more or fewer enemies.


Keyboard Control with Cleaner Code

  1. Add the update() function to add keyboard control to the tank.

    def update():
        if keyboard.right:
            player.angle = 0
            player.x = player.x + 1
        elif keyboard.up:
            player.angle = 90
            player.y = player.y - 1
        elif keyboard.left:
            player.angle = 180
            player.x = player.x - 1
        elif keyboard.down:
            player.angle = 270
            player.y = player.y + 1
    

We do a lot of work in the update() function, and it can get cluttered. Let's make a helper function to handle getting the keyboard keys and moving the player.

This is not a special Pygame Zero function like update() or draw(), and you can name it whatever you like. For this lab, we'll call it move_player().

  1. Create a new helper function, move_player(), and move all the lines we just added to the update() function into it.

    def move_player():
        if keyboard.right:
            player.angle = 0
            player.x = player.x + 1
        elif keyboard.up:
            player.angle = 90
            player.y = player.y - 1
        elif keyboard.left:
            player.angle = 180
            player.x = player.x - 1
        elif keyboard.down:
            player.angle = 270
            player.y = player.y + 1
    
  2. Replace all the lines in the update() function with a simple call to our new move_player() function.

    def update():
        move_player()
    

    Now our code is nice and tidy! Try your program. You should be able to move the blue tank with the keyboard.


Dumb Enemies

Let's give our enemies some simple movement. They won't respond to where we are. They will just patrol constantly, bouncing off the edges of the screen.

We'll create a new helper function named move_enemies(). Again, this name could be anything you choose — the purpose is to organize the code.

  1. The move_enemies() function has been started for you below. Remove the highlighted comment and continue the if/elif statements to move each enemy 1 step in the direction it is currently facing.

    def move_enemies():
        for e in enemies:
            if e.angle == 0:  # facing right
                e.x = e.x + 1
            # elif ... your code here!
    

    Tip

    Look at the code in your move_player() function for help with the angles, and whether to add to or subtract from x or y for each.

  2. Call your move_enemies() from the update() function so it will run during the update for each frame. Add the highlighted line.

    def update():
        move_player()
        move_enemies()
    

    This works pretty well, but the enemy tanks drive off the screen.

  3. Add the code below inside the loop in the move_enemies() function, at the bottom of the loop, to turn them around if they hit the edge of the screen.

            if e.x < 0 or e.x > WIDTH or e.y < 0 or e.y > HEIGHT:
                e.angle = (e.angle + 180) % 360
    

    How does this work?

    The modulo (%) operator works like division (/), but gives us the remainder. This way, we can always turn around by adding 180 degrees. If the result goes above 360 degrees, the modulo "wraps it around" to zero again.


Smart Enemies

Let's write some code to make the enemies smarter.

  1. Set up your code to use a new helper function that will make the enemies move in a smarter way.

    1. Define a new helper function named move_enemies_smart().
    2. Comment out the call to move_enemies() in the update() function so it will no longer be called during updates.
    3. Add a call to your move_enemies_smart() function in the update() function.

    The update() function should now look like this:

    def update():
        move_player()
        # move_enemies()
        move_enemies_smart()
    
  2. Add code to the new move_enemies_smart() function to make the enemies follow the player. This is a challenge for you to solve. Here are the steps:

    • Loop over all the enemies in the enemies list.
    • If an enemy's x position is to the right of the player's x position, turn the enemy to the left.
    • Else, if an enemy's x position is to the left of the player's x position, turn the enemy to the right.
    • Else, if an enemy's y position is above the player's y position, turn the enemy upward.
    • Else, if an enemy's y position is below the player's y position, turn the enemy downward.

    After you have looped over all the enemies and turned them, call your move_enemies() function from outside the loop — as the last line in your move_enemies_smart() function — to let it handle moving each enemy in its new direction and bouncing off the edges.

    def move_enemies_smart():
        for e in enemies:
            # Your code here!
    
        move_enemies()
    

    Warning

    The program won't run until you replace the comment with real code — Python doesn't allow a loop with nothing in it.


Turn It In

  • Submit your enemies.py file in Google Classroom.

How It's Graded

This lab is worth up to 4 points. One score covers everything you turn in.

Score What it looks like
4 — Excellent Three enemies spawn from one loop at random spots and facings, update() reads as a short list of helper calls, patrols turn around at the edges, and your move_enemies_smart() — the part written from the steps, by you — turns every enemy toward the player and hands the walking back to move_enemies().
3 — Above Average All the behavior works with a minor slip — one angle inverted so a chase drifts sideways, or an edge case where an enemy hesitates.
2 — Average Dumb enemies patrol and bounce, but the smart chase is missing or doesn't pursue — or the enemies were typed out one by one instead of built by the loop.
1 — Below Average Enemies appear on screen but never move.
0 — Failing Nothing submitted, or the program doesn't run.