Initial AI implementation.
[roguelike.git] / ai.py
1 #! /usr/bin/env python
2 # -*- coding: utf-8 -*-
3
4 # Copyright © 2008 Neil Moore <neil@s-z.org>.
5 #
6 # This program is free software; you can redistribute it and/or modify
7 # it under the terms of the GNU General Public License as published by
8 # the Free Software Foundation; either version 2 of the License, or
9 # (at your option) any later version.
10 #
11 # This program is distributed in the hope that it will be useful,
12 # but WITHOUT ANY WARRANTY; without even the implied warranty of
13 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 # GNU General Public License for more details.
15 #  
16 # You should have received a copy of the GNU General Public License
17 # along with this program; if not, write to the Free Software
18 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19
20 import math
21
22 import cacher
23 import creature
24
25 class Attitude ():
26     __metaclass__ = cacher.FirstArg
27
28     def __init__(self, name):
29         self.name = name
30
31 Attitude.values = set((
32     Attitude("terrified"),
33     Attitude("afraid"),
34     Attitude("wary"),
35     Attitude("unaware"),
36     Attitude("tame"),
37     Attitude("helpful"),
38     Attitude("friendly"),
39     Attitude("neutral"),
40     Attitude("suspicious"),
41     Attitude("unfriendly"),
42     Attitude("hostile"),
43     Attitude("vengeant"),
44 ))
45 Attitude.__init__ = None
46
47 class Alignment ():
48     __metaclass__ = cacher.FirstArg
49
50     def __init__(self, name):
51         self.name = name
52
53 class Monster (creature.Creature):
54     def __init__(self, location=None):
55         super(Monster,self).__init__(location)
56
57         self.default_attitude = Attitude("neutral")
58         # self.attitudes maps from Creatures to Attitudes.
59         self.attitudes = {};
60
61     def attitude(self, other):
62         try:
63             return self.attitudes[other]
64         except:
65             self.attitudes[other] = self.default_attitude
66             return self.default_attitude
67
68     def tick(self):
69         super(Monster, self).tick()
70         if not self.dead:
71             for n in range(self.tick_turns()):
72                 self.act()
73     
74     def vis_creatures(self):
75         (my, mx) = (self.location.y, self.location.x)
76
77         return sorted(
78                 (critter
79                     for (cy, cx) in self.visibles()
80                     if (cy, cx) != (my, mx)
81                     for critter in self.level().loc(cy, cx)._creatures),
82                 key = lambda c: math.sqrt(
83                     (c.location.y - my)**2 + (c.location.x - mx)**2))
84
85     def see_creatures(self):
86         for c in self.vis_creatures():
87             dy = c.location.y - self.location.y
88             dx = c.location.x - self.location.x
89             dist = math.sqrt(dy**2 + dx**2)
90             if self.attitude(c) == Attitude("wary") and dist < 1:
91                 self.attitudes[c] = Attitude("afraid")
92             elif self.attitude(c) == Attitude("afraid") and dist < 1:
93                 self.attitudes[c] = Attitude("terrified")
94             elif self.attitude(c) == Attitude("suspicious") and dist < 1:
95                 self.attitudes[c] = Attitude("wary")
96             elif self.attitude(c) == Attitude("unfriendly") and dist < 1:
97                 self.attitudes[c] = Attitude("hostile")
98
99             if(self.attitude(c) == Attitude("wary") and dist < 2
100               or self.attitude(c) == Attitude("afraid") and dist < 10
101               or self.attitude(c) == Attitude("terrified")):
102                 self.flee(c.location)
103                 break
104             elif (self.attitude(c) == Attitude("tame") and dist > 1
105               or self.attitude(c) == Attitude("helpful") and dist > 2 and dist < 10
106               or self.attitude(c) == Attitude("friendly") and dist > 3 
107               or self.attitude(c) == Attitude("hostile") and dist > 1
108               or self.attitude(c) == Attitude("vengeant") and dist > 1):
109                 self.approach(c.location)
110                 break
111     
112     def flee(self, loc):
113         (dy, dx) = (loc.y - self.location.y, loc.x - self.location.x)
114
115         if abs(dy) > abs(dx):
116             if dy < 0:
117                 self.move_south()
118             else:
119                 self.move_north()
120         else:
121             if dx < 0:
122                 self.move_east()
123             else:
124                 self.move_west()
125
126     def approach(self, loc):
127         (my, mx) = (self.location.y, self.location.x)
128
129         (dy, dx) = (loc.y - my, loc.x - mx)
130
131         if abs(dy) + abs(dx) <= 1:
132             pass
133         elif abs(dy) > abs(dx):
134             if dy < 0:
135                 self.move_north()
136             else:
137                 self.move_south()
138         else:
139             if dx < 0:
140                 self.move_west()
141             else:
142                 self.move_east()
143
144     def act(self):
145         self.see_creatures()