Programming Agents

An artful spaceship.

For my programming elective, I thought I’d try to get students programming autonomous agents that we could all compile together into some type of game. Since it was, for most of them a first class in programming, this has proven a little too ambitious, and I’ve had to adjust a bit to build up to it.

Step 1: Build a spaceship/vehicle/agent.

A simple ship made of a sphere, ring, and pyramid, with a clear front end (pointed to the left: vector=(1,0,0).
  • We’re using VPython so we can build 3d ships. Students get to exercise their creativity a bit and learn how to place things in 3d co-ordinate space.
  • The ship must have a front end and all the parts must be put into a single frame so the whole thing can be moved as a unit.
  • Although it’s not required, I’ve added in the beginnings of a trail so we can track the motion of the spaceship
from visual import *

f = frame()
r = ring(frame=f, thickness=.25, axis=(0,1,0))
cabin = sphere(frame=f, radius=0.6, color=color.red)
front = pyramid(frame=f, height=1,width=1,length=2, color=color.blue)
trail = curve(pos=[f.pos,])

Step 2: Make the spaceship a class.

  • Putting the spaceship in a separate class, away from the rest of the code, makes it easier to move from one program to another. That means we can import everyone’s ships into a single program at the end.
from visual import *

class spaceship1:

    def __init__(self):

        self.f = frame()
        self.r = ring(frame=self.f, thickness=.25, axis=(0,1,0))
        self.cabin = sphere(frame=self.f, radius=0.6, color=color.red)
        self.front = pyramid(frame=self.f, height=1,width=1,length=2, color=color.blue)
        self.trail = curve(pos=[self.f.pos,])

ss = spaceship1()

Make the spaceship move

  • We’re going to control the movement of the spaceship using the arrow keys. Left and right to turn; up and down to accelerate and decelerate.
  • To allow movement, we’ll put the keyboard controls into an infinite loop, but to allow maximum flexibility (and to show how to use create methods in a class) we’re putting the actual movement in as methods in the class: the turning method is named turn_xz and movement is move_xz since all the motion is going to be restricted in the xz plane for now.
from visual import *

class spaceship1:

    def __init__(self):

        self.f = frame()
        self.r = ring(frame=self.f, thickness=.25, axis=(0,1,0))
        self.cabin = sphere(frame=self.f, radius=0.6, color=color.red)
        self.front = pyramid(frame=self.f, height=1,width=1,length=2, color=color.blue)
        self.trail = curve(pos=[self.f.pos,])

    def turn_xz(self, turn_rate):
        self.f.axis = rotate(self.f.axis, turn_rate, (0,1,0))

    def move_xz(self, speed):
        self.f.pos = self.f.pos + self.f.axis*speed
        self.trail.append(pos=self.f.pos)
                          
ss = spaceship1()
turn = 0.0
speed=0.0

while 1:
    rate(20)  #slows down execution of the loop

    ss.turn_xz(turn)
    ss.move_xz(speed)

    if scene.kb.keys: # event waiting to be processed?
        s = scene.kb.getkey() # get keyboard info

        if s == 'left':
            turn = turn + 0.01
        if s == 'right':
            turn = turn - 0.01

        if s == 'up':
            speed = speed + 0.01
        if s == 'down':
            speed = speed - 0.01

        if s == ' ':
            if turn <> 0:
                turn = 0
            elif speed <> 0:
                speed = 0

Fire missiles

  • We add in missiles by creating a class very similar to the spaceship. For now our missile is just a ball but I’m putting it into a frame anyway in case later on I want it to be a composite object.
  • Since we can fire multiple missiles, in the code I create a list to hold all the missiles.
  • Missiles are fired using the “a” key.
from visual import *

class spaceship1:

    def __init__(self):

        self.f = frame()
        self.r = ring(frame=self.f, thickness=.25, axis=(0,1,0))
        self.cabin = sphere(frame=self.f, radius=0.6, color=color.red)
        self.front = pyramid(frame=self.f, height=1,width=1,length=2, color=color.blue)
        self.trail = curve(pos=[self.f.pos,])

    def turn_xz(self, turn_rate):
        self.f.axis = rotate(self.f.axis, turn_rate, (0,1,0))

    def move_xz(self, speed):
        self.f.pos = self.f.pos + self.f.axis*speed
        self.trail.append(pos=self.f.pos)
          
class missile:
    def __init__(self, axis, pos):

        self.f = frame(axis=axis, pos=pos)
        self.r = sphere(frame=self.f, radius=0.2, color=color.green)
        self.speed = 0.5
        

    def move_xz(self):
        self.f.pos = self.f.pos + self.f.axis*self.speed

                
ss = spaceship1()
turn = 0.0
speed=0.0

missiles = []  #list for missiles

while 1:
    rate(20)  #slows down execution of the loop

    ss.turn_xz(turn)
    ss.move_xz(speed)

    #move missiles
    for i, m in enumerate(missiles):
        m.move_xz()

    if scene.kb.keys: # event waiting to be processed?
        s = scene.kb.getkey() # get keyboard info

        if s == 'left':
            turn = turn + 0.01
        if s == 'right':
            turn = turn - 0.01

        if s == 'up':
            speed = speed + 0.01
        if s == 'down':
            speed = speed - 0.01

        if s == ' ':
            if turn <> 0:
                turn = 0
            elif speed <> 0:
                speed = 0

        #fire missile
        if s == 'a':
            missiles.append(missile(ss.f.axis, ss.f.pos))

Final adjustments

  • Finally, I’m going to put in a boundary, and set it up to delete the missiles if they go out of bounds.
  • I’m also going to set an option so that the scene follows the ship, which makes it easier to keep track of things.
from visual import *

class spaceship1:

    def __init__(self):

        self.f = frame()
        self.r = ring(frame=self.f, thickness=.25, axis=(0,1,0))
        self.cabin = sphere(frame=self.f, radius=0.6, color=color.red)
        self.front = pyramid(frame=self.f, height=1,width=1,length=2, color=color.blue)
        self.trail = curve(pos=[self.f.pos,])

    def turn_xz(self, turn_rate):
        self.f.axis = rotate(self.f.axis, turn_rate, (0,1,0))

    def move_xz(self, speed):
        self.f.pos = self.f.pos + self.f.axis*speed
        self.trail.append(pos=self.f.pos)
                          

class missile:
    def __init__(self, axis, pos):

        self.f = frame(axis=axis, pos=pos)
        self.r = sphere(frame=self.f, radius=0.2, color=color.green)
        self.speed = 0.5
        

    def move_xz(self):
        self.f.pos = self.f.pos + self.f.axis*self.speed

class bounds:
    def __init__(self, boundary_distance):
        self.boundary_distance = boundary_distance
        self.border = curve(pos=[(-boundary_distance,0,-boundary_distance),
                                 (boundary_distance,0,-boundary_distance),
                                 (boundary_distance,0,boundary_distance),
                                 (-boundary_distance,0,boundary_distance),
                                 (-boundary_distance,0,-boundary_distance)])
    def in_bounds(self, pos):
        if ( pos.x < -self.boundary_distance or
             pos.x > self.boundary_distance or
             pos.z < -self.boundary_distance or
             pos.z > self.boundary_distance):
            return false
        else:
            return true
        

ss = spaceship1()
turn = 0.0
speed=0.0
l_track_ship = 1
scene.range=10
scene.forward = (0,-1,0)
missiles = []

boundary_distance = 100
boundary = bounds(boundary_distance)

while 1:
    rate(20)

    if l_track_ship > 0:
        scene.center = ss.f.pos
    
    ss.turn_xz(turn)
    ss.move_xz(speed)

    #move missiles
    del_list = []
    for i, m in enumerate(missiles):
        m.move_xz()

        #delete missile if out of bounds
        if boundary.in_bounds(m.f.pos) == False:
            del_list.append(i)
    for i in del_list:
        missiles[i].f.visible = False
        del missiles[i]
            
    
    #if m <> None:
    #    m.move_xz()

    if scene.kb.keys: # event waiting to be processed?
        s = scene.kb.getkey() # get keyboard info

        if s == 'left':
            turn = turn + 0.01
        if s == 'right':
            turn = turn - 0.01

        if s == 'up':
            speed = speed + 0.01
        if s == 'down':
            speed = speed - 0.01

        if s == ' ':
            if turn <> 0:
                turn = 0
            elif speed <> 0:
                speed = 0

        #fire missile
        if s == 'a':
            missiles.append(missile(ss.f.axis, ss.f.pos))
    

Next steps

Now that we’re cooking with charcoal — we have a functioning program — the next steps will be setting up a target to shoot at, and, if the students are ready, letting them program their ships to move autonomously. If they’re not ready then, as an example, I’ll program some opposition.

Leave a Reply