Initial AI implementation. master
authorNeil Moore <neil@s-z.org>
Sat, 10 Apr 2010 03:10:53 +0000 (23:10 -0400)
committerNeil Moore <neil@s-z.org>
Sat, 10 Apr 2010 03:10:53 +0000 (23:10 -0400)
creature.Creature:
  new member speed:
    Average number of moves per tick.
  new method tick_turns():
    Number of moves this tick.

ai.Attitude:
  New class: monster attitudes (enumeration).

ai.Monster:
  new member default_attitude:
    Attitude for newly-encountered creatures.
  new member attitudes:
    Dictionary from creatures to attitude.
  new method attitude():
    Get attitude toward another creature.
  act():
    Refactor into:
      new method see_creatures():
Act based on attitude to seen creatures.
      new method approach():
Approach a location.
      new method flee():
Flee a location.
  tick():
    Act tick_turns() times.

roguelike.main():
  Create three monsters, speed 0.9.
    One tame, one friendly, one wary.

ai.py
creature.py
roguelike.py

diff --git a/ai.py b/ai.py
index b925c32..9580b28 100644 (file)
--- a/ai.py
+++ b/ai.py
 
 import math
 
+import cacher
 import creature
 
+class Attitude ():
+    __metaclass__ = cacher.FirstArg
+
+    def __init__(self, name):
+        self.name = name
+
+Attitude.values = set((
+    Attitude("terrified"),
+    Attitude("afraid"),
+    Attitude("wary"),
+    Attitude("unaware"),
+    Attitude("tame"),
+    Attitude("helpful"),
+    Attitude("friendly"),
+    Attitude("neutral"),
+    Attitude("suspicious"),
+    Attitude("unfriendly"),
+    Attitude("hostile"),
+    Attitude("vengeant"),
+))
+Attitude.__init__ = None
+
+class Alignment ():
+    __metaclass__ = cacher.FirstArg
+
+    def __init__(self, name):
+        self.name = name
+
 class Monster (creature.Creature):
     def __init__(self, location=None):
         super(Monster,self).__init__(location)
 
+        self.default_attitude = Attitude("neutral")
+        # self.attitudes maps from Creatures to Attitudes.
+        self.attitudes = {};
+
+    def attitude(self, other):
+        try:
+            return self.attitudes[other]
+        except:
+            self.attitudes[other] = self.default_attitude
+            return self.default_attitude
+
     def tick(self):
         super(Monster, self).tick()
         if not self.dead:
-            self.act()
+            for n in range(self.tick_turns()):
+                self.act()
     
     def vis_creatures(self):
         (my, mx) = (self.location.y, self.location.x)
@@ -41,23 +82,64 @@ class Monster (creature.Creature):
                 key = lambda c: math.sqrt(
                     (c.location.y - my)**2 + (c.location.x - mx)**2))
 
-    def act(self):
+    def see_creatures(self):
+        for c in self.vis_creatures():
+            dy = c.location.y - self.location.y
+            dx = c.location.x - self.location.x
+            dist = math.sqrt(dy**2 + dx**2)
+            if self.attitude(c) == Attitude("wary") and dist < 1:
+                self.attitudes[c] = Attitude("afraid")
+            elif self.attitude(c) == Attitude("afraid") and dist < 1:
+                self.attitudes[c] = Attitude("terrified")
+            elif self.attitude(c) == Attitude("suspicious") and dist < 1:
+                self.attitudes[c] = Attitude("wary")
+            elif self.attitude(c) == Attitude("unfriendly") and dist < 1:
+                self.attitudes[c] = Attitude("hostile")
+
+            if(self.attitude(c) == Attitude("wary") and dist < 2
+              or self.attitude(c) == Attitude("afraid") and dist < 10
+              or self.attitude(c) == Attitude("terrified")):
+                self.flee(c.location)
+                break
+            elif (self.attitude(c) == Attitude("tame") and dist > 1
+              or self.attitude(c) == Attitude("helpful") and dist > 2 and dist < 10
+              or self.attitude(c) == Attitude("friendly") and dist > 3 
+              or self.attitude(c) == Attitude("hostile") and dist > 1
+              or self.attitude(c) == Attitude("vengeant") and dist > 1):
+                self.approach(c.location)
+                break
+    
+    def flee(self, loc):
+        (dy, dx) = (loc.y - self.location.y, loc.x - self.location.x)
+
+        if abs(dy) > abs(dx):
+            if dy < 0:
+                self.move_south()
+            else:
+                self.move_north()
+        else:
+            if dx < 0:
+                self.move_east()
+            else:
+                self.move_west()
+
+    def approach(self, loc):
         (my, mx) = (self.location.y, self.location.x)
 
-        for c in self.vis_creatures():
-            (dy, dx) = (c.location.y - my, c.location.x - mx)
-
-            if abs(dy) + abs(dx) <= 1:
-                pass
-            elif abs(dy) > abs(dx):
-                if dy < 0:
-                    self.move_north()
-                else:
-                    self.move_south()
+        (dy, dx) = (loc.y - my, loc.x - mx)
+
+        if abs(dy) + abs(dx) <= 1:
+            pass
+        elif abs(dy) > abs(dx):
+            if dy < 0:
+                self.move_north()
             else:
-                if dx < 0:
-                    self.move_west()
-                else:
-                    self.move_east()
+                self.move_south()
+        else:
+            if dx < 0:
+                self.move_west()
+            else:
+                self.move_east()
 
-            break
+    def act(self):
+        self.see_creatures()
index 9d9b500..7abd3b8 100644 (file)
@@ -34,6 +34,7 @@ class Creature(thing.Thing):
         self.hpmax = 10
         self.hp = self.hpmax
         self.oxygen = 10
+        self.speed = 1.0
         
         self.intrinsics = set()
         self.extrinsics = set()
@@ -41,6 +42,16 @@ class Creature(thing.Thing):
         self.inv = loc.Inventory(self)
         self.slots = { 'hand': None, 'arm': None, 'body': None, 'head': None }
 
+    def tick_turns(self):
+        speed = self.speed
+        turns = 0
+        if (speed >= 1.0):
+            turns = int(speed)
+            speed %= 1.0
+        if random.random() < self.speed:
+            turns += 1
+        return turns
+
     def has(self, intrinsic):
         if isinstance(intrinsic, basestring):
             intrinsic = thing.Intrinsic(intrinsic)
index c68190d..670fdf4 100755 (executable)
@@ -382,6 +382,14 @@ if __name__ == '__main__':
         me = creature.Player()
         him = creature.Player()
         it = ai.Monster()
+        it.speed = 0.9
+        it.default_attitude = ai.Attitude("tame")
+        it2 = ai.Monster()
+        it2.speed = 0.9
+        it2.default_attitude = ai.Attitude("friendly")
+        it3 = ai.Monster()
+        it3.speed = 0.9
+        it3.default_attitude = ai.Attitude("wary")
         ui = AppUI(stdscr, [me, him])
 
         thing.Material.loadall(open("materials/materials.mtl"))
@@ -390,7 +398,9 @@ if __name__ == '__main__':
         lvl = level.Level("level0")
         me.place(lvl.loc(min(13, lvl.h - 2), min(2, lvl.w - 2)))
         him.place(lvl.loc(max(2, lvl.h - 5), max(5, lvl.w - 10)))
-        it.place(lvl.loc(min(43, lvl.h - 2), min(5, lvl.w - 2)))
+        it.place(lvl.loc(min(33, lvl.h - 5), min(8, lvl.w - 2)))
+        it2.place(lvl.loc(min(32, lvl.h - 6), min(4, lvl.w - 6)))
+        it3.place(lvl.loc(min(30, lvl.h - 8), min(6, lvl.w - 4)))
         ui.set_level(lvl)
 
         ui.event_loop()