Add monsters and AIs.
authorNeil Moore <neil@s-z.org>
Sat, 31 May 2008 12:53:16 +0000 (08:53 -0400)
committerNeil Moore <neil@s-z.org>
Sat, 31 May 2008 12:53:16 +0000 (08:53 -0400)
New method Creature.tick():
  Call self.breathe().

Call tick, not breathe, from AppUI.event_loop.

All monsters can swim (with 50% effectiveness).

New module ai, with class AI.
  Method act: pick something to do.
  Method die: give up the ghost.

Make a Monster, not a Creature, on the starting level.

New class creature.Monster: a creature with an AI.
  Monster.tick(): call AI.act().

ai.py [new file with mode: 0644]
creature.py
level.py
roguelike.py

diff --git a/ai.py b/ai.py
new file mode 100644 (file)
index 0000000..d5063ed
--- /dev/null
+++ b/ai.py
@@ -0,0 +1,65 @@
+#! /usr/bin/env python
+# -*- coding: utf-8 -*-
+
+# Copyright © 2008 Neil Moore <neil@s-z.org>.
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#  
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+
+import math
+
+import __main__
+
+class AI (object):
+    def __init__(self, creature):
+        self.body = creature
+
+    def vis_creatures(self):
+        loc = self.body.location
+        (my, mx) = (loc.y, loc.x)
+
+        return sorted(
+                (critter
+                    for (y, x) in self.body.visibles()
+                    if (y, x) != (my, mx)
+                    for critter in self.body.level().loc(y, x)._creatures),
+                key = lambda c: math.sqrt(
+                    (c.location.y - my)**2 + (c.location.x - mx)**2))
+
+    def act(self):
+        loc = self.body.location
+        (my, mx) = (loc.y, loc.x)
+
+        for c in self.vis_creatures():
+            (cy, cx) = (c.location.y, c.location.x)
+            (dy, dx) = (cy - my, cx - mx)
+
+            if abs(dy) + abs(dx) <= 1:
+                pass
+            elif abs(dy) > abs(dx):
+                if dy < 0:
+                    self.body.move_north()
+                else:
+                    self.body.move_south()
+            else:
+                if dx < 0:
+                    self.body.move_west()
+                else:
+                    self.body.move_east()
+
+            break
+
+    def die(self, cause):
+        __main__.AppUI.instance.message("AI %s died: %s" % (self, cause))
+
index 4370b7a..e9e6c4d 100644 (file)
@@ -20,6 +20,7 @@
 import random
 
 import cacher
+import ai
 import loc
 import thing
 import ui
@@ -89,6 +90,9 @@ class Creature(thing.Thing):
             else:
                 self.losehp(1, "asphyxia")
 
+    def tick(self):
+        self.breathe()
+
     def losehp(self, n, cause):
         if n >= self.hp:
             self.die(cause)
@@ -104,7 +108,7 @@ class Creature(thing.Thing):
         thing.ItemClass("corpse")().place(loc)
 
     def can_swim(self):
-        return False
+        return random.random() < .5
     def render(self, pic=None):
         return ( "?", ui.Color('BrightRed').cp )
     def move_north(self):
@@ -164,11 +168,24 @@ class Creature(thing.Thing):
             if equip and equip.intrinsics:
                 self.extrinsics |= equip.intrinsics
 
+class Monster (Creature):
+    def __init__(self, location=None):
+        super(Monster,self).__init__(location)
+        self.ai = ai.AI(self)
+    def tick(self):
+        super(Monster, self).tick()
+        if self.dead:
+            self.ai = None
+        else:
+            self.ai.act()
+    def die(self, cause):
+        self.ai.die(cause)
+        super(Monster,self).die(cause)
 
 
-class Player(Creature):
+class Player (Creature):
     def __init__(self, location=None):
-        Creature.__init__(self, location)
+        super(Player,self).__init__(location)
         self.active = False
     def render(self, pic=None):
         return ("@", ui.Color(('Bright' if self.active else '') + 'Magenta').cp)
index 35977b2..adb4c6b 100644 (file)
--- a/level.py
+++ b/level.py
@@ -525,6 +525,12 @@ class Level (object):
     def level(self):
         return self
 
+    def creatures(self):
+        return [ creat 
+                for y in range(self.h)
+                for x in range(self.w)
+                for creat in self.loc(y,x)._creatures ]
+
     def affect_all(self):
         for (y,x) in self.active_tiles:
             self.grid[y][x].affect_all()
index 16446e3..53ecad1 100755 (executable)
@@ -341,19 +341,19 @@ class AppUI:
                 break
 
             self.level().affect_all()
-            dead = []
-            for pl in self.players:
-                pl.breathe()
-                if pl.dead:
-                    self.message("Player %s is dead: %s" % (pl, pl.dead))
-                    dead.append(pl)
-            for pl in dead:
+
+            for c in self.level().creatures():
+                c.tick()
+            
+            for pl in [ p for p in self.players if p.dead ]:
+                self.message("Player %s is dead: %s" % (pl, pl.dead))
                 self.players.remove(pl)
+
             if len(self.players) == 0:
                 self.message("You have lost")
                 self.more()
                 return
-            elif self.player in dead:
+            elif self.player not in self.players:
                 self.switch()
                     
 
@@ -361,7 +361,7 @@ if __name__ == '__main__':
     def main(stdscr):
         me = creature.Player()
         him = creature.Player()
-        it = creature.Creature()
+        it = creature.Monster()
         ui = AppUI(stdscr, [me, him])
 
         thing.Material.loadall(open("materials/materials.mtl"))