Wolf, Sheep, and Grass

Let's use some of our computing skills to build a simulation, but this time let's have some interation among the objects to see if we can get some interesting behavior to emerge.

First, we'll need some ability to create objects in random places, with random properties. And we'll want to visualize our results.

In [22]:
import random
import Graphics

For these simulations, we want to have sheep who eat grass and wolves who eat sheep.

Let's start with a Sheep object, using the "class" notation.

In [23]:
class Sheep:
    def __init__(self, world):
        self.world = world
        self.x = random.randint(0, world.width - 1)
        self.y = random.randint(0, world.height - 1)
        self.energy = self.initial_energy()
        self.state = "alive"
        self.t = 0
        self.gestation = random.randint(int(self.energy * 1.5), 
                                        int(self.energy * 2.0))
        
    def initial_energy(self):
        return 10
        
    def move(self):
        self.t += 1
        if self.energy <= 0:
            self.state = "dead"
        elif self.t % self.gestation == 0:
            newSheep = Sheep(self.world)
            if self.clear(newSheep.x, newSheep.y):
                self.world.sheep.append(newSheep)
        else:
            if self.world.grass[self.x][self.y] > 0:
                self.world.grass[self.x][self.y] -= 1
                self.energy += 1
            else:
                self.energy -= 1
                self.x += random.choice([-1, 0, 1])
                self.y += random.choice([-1, 0, 1])
                if self.x < 0:
                    self.x = self.world.width - 1
                elif self.x >= self.world.width:
                    self.x = 0
                if self.y < 0:
                    self.y = self.world.height - 1
                elif self.y >= self.world.height:
                    self.y = 0
        
    def clear(self, x, y):
        for w in self.world.wolf:
            if w.x == x and w.y == y:
                return False
        for s in self.world.sheep:
            if s.x == x and s.y == y:
                return False
        return True
        

Next, we'll create a Wolf that extends, and overrides some of the Sheep's properties.

In [24]:
class Wolf(Sheep):
    def initial_energy(self):
        return 20

    def move(self):
        self.t += 1
        if self.energy <= 0:
            self.state = "dead"
        elif self.t % self.gestation == 0:
            newWolf = Wolf(self.world)
            if self.clear(newWolf.x, newWolf.y):
                self.world.wolf.append(newWolf)
        else:
            for s in self.world.sheep:
                if s.state == "alive" and s.x == self.x and s.y == self.y:
                    # yum!
                    s.state = "dead"
                    self.energy += s.initial_energy()
                    return
            self.energy -= 1
            self.x += random.choice([-1, 0, 1])
            self.y += random.choice([-1, 0, 1])
            if self.x < 0:
                self.x = self.world.width - 1
            elif self.x >= self.world.width:
                self.x = 0
            if self.y < 0:
                self.y = self.world.height - 1
            elif self.y >= self.world.height:
                self.y = 0

    def clear(self, x, y):
        for w in self.world.wolf:
            if w.x == x and w.y == y:
                return False
        return True
        

A now a World where they can both live.

In [25]:
class World:
    def __init__(self, width, height, wolves, sheep):
        self.width = width
        self.height = height
        
        self.wolf = [Wolf(self) for w in range(wolves)]
        self.sheep = [Sheep(self) for s in range(sheep)]
        
        self.grass = [[0 for y in range(height)] for x in range(width)]

        for x in range(width):
            for y in range(height):
                if random.random() < .3:
                    self.grass[x][y] = 10

        self.size = 20
        self.window = Graphics.Window(width * self.size, height * self.size)
        self.window.mode = "bitmap"
        
    def show(self):
        for x in range(self.width):
            for y in range(self.height):
                grass = self.grass[x][y]
                rec = Graphics.Rectangle((x * self.size, y * self.size), 
                                        ((x + 1) * self.size, (y + 1) * self.size))
                rec.fill = Graphics.Color(0, grass/20 * 255, 0)
                rec.draw(self.window)
        for w in self.wolf:
            if w.state == "alive":
                wolf = Graphics.Text((w.x * self.size + self.size/2, 
                                      w.y * self.size + self.size/2), "W")
                wolf.draw(self.window)
        for s in self.sheep:
            if s.state == "alive":
                sheep = Graphics.Text((s.x * self.size + self.size/2, 
                                       s.y * self.size + self.size/2), "s")
                sheep.fill = Graphics.Color(255,255,255)
                sheep.draw(self.window)
        self.window.step()
        return self.window
    
    def move(self):
        for s in self.sheep[:]:
            if s.state == "alive":
                s.move()
            else:
                self.sheep.remove(s)
        for w in self.wolf[:]:
            if w.state == "alive":
                w.move()
            else:
                self.wolf.remove(w)
        for x in range(self.width):
            for y in range(self.height):
                if random.random() < .005:
                    self.grass[x][y] = max(10, self.grass[x][y])
In [26]:
world = World(25, 25, 0, 50)
In [27]:
world.show()
Out[27]:
In [28]:
count = 0
while True:
    world.move()
    calico.animate(world.show())
    print(count, 
          len([w for w in world.wolf if w.state == "alive"]), 
          len([s for s in world.sheep if s.state == "alive"]))
    count += 1
40 0 160
Running script aborted!
In [29]:
world.show()
Out[29]:
In [32]:
world = World(25, 25, 10, 50)
In [33]:
world.show()
Out[33]:

In [34]:
count = 0
while True:
    world.move()
    calico.animate(world.show())
    print(count, 
          len([w for w in world.wolf if w.state == "alive"]), 
          len([s for s in world.sheep if s.state == "alive"]))
    count += 1
65 9 142
Running script aborted!
In [35]:
world.show()
Out[35]:
In [ ]: