home · contact · privacy
Make AI logic clearer in code.
[plomrogue2-experiments] / new / plomrogue / things.py
1 from plomrogue.errors import GameError
2
3
4
5 class ThingBase:
6     type_ = '?'
7
8     def __init__(self, world, id_=None, position=(0,0)):
9         self.world = world
10         self.position = position
11         if id_ is None:
12             self.id_ = self.world.new_thing_id()
13         else:
14             self.id_ = id_
15
16
17
18 class Thing(ThingBase):
19     blocking = False
20     in_inventory = False
21
22     def __init__(self, *args, **kwargs):
23         super().__init__(*args, **kwargs)
24         self.inventory = []
25
26     def proceed(self):
27         pass
28
29
30
31 class ThingItem(Thing):
32     pass
33
34
35
36 class ThingFood(ThingItem):
37     type_ = 'food'
38
39
40
41 class ThingAnimate(Thing):
42     blocking = True
43
44     def __init__(self, *args, **kwargs):
45         super().__init__(*args, **kwargs)
46         self.set_task('WAIT')
47         self._last_task_result = None
48         self._stencil = None
49
50     def move_towards_position(self, target):
51         dijkstra_map = type(self.world.map_)(self.world.map_.size)
52         n_max = 256
53         dijkstra_map.terrain = [n_max for i in range(dijkstra_map.size_i)]
54         dijkstra_map[target] = 0
55         shrunk = True
56         visible_map = self.get_visible_map()
57         while shrunk:
58             shrunk = False
59             for pos in dijkstra_map:
60                 if visible_map[pos] != '.':
61                     continue
62                 neighbors = dijkstra_map.get_neighbors(tuple(pos))
63                 for direction in neighbors:
64                     yx = neighbors[direction]
65                     if yx is not None and dijkstra_map[yx] < dijkstra_map[pos] - 1:
66                         dijkstra_map[pos] = dijkstra_map[yx] + 1
67                         shrunk = True
68         neighbors = dijkstra_map.get_neighbors(tuple(self.position))
69         n = n_max
70         target_direction = None
71         for direction in sorted(neighbors.keys()):
72             yx = neighbors[direction]
73             if yx is not None:
74                 n_new = dijkstra_map[yx]
75                 if n_new < n:
76                     n = n_new
77                     target_direction = direction
78         if target_direction:
79             self.set_task('MOVE', (target_direction,))
80
81     def hunt_player(self):
82         visible_things = self.get_visible_things()
83         target = None
84         for t in visible_things:
85             if t.type_ == 'human':
86                 target = t.position
87                 break
88         if target is not None:
89             try:
90                 self.move_towards_position(target)
91                 return True
92             except GameError:
93                 pass
94         return False
95
96     def decide_task(self):
97         if not self.hunt_player():
98             self.set_task('WAIT')
99
100     def set_task(self, task_name, args=()):
101         task_class = self.world.game.tasks[task_name]
102         self.task = task_class(self, args)
103         self.task.check()  # will throw GameError if necessary
104
105     def proceed(self, is_AI=True):
106         """Further the thing in its tasks, decrease its health.
107
108         First, ensures an empty map, decrements .health and kills
109         thing if crossing zero (removes from self.world.things for AI
110         thing, or unsets self.world.player_is_alive for player thing);
111         then checks that self.task is still possible and aborts if
112         otherwise (for AI things, decides a new task).
113
114         Then decrements .task.todo; if it thus falls to <= 0, enacts
115         method whose name is 'task_' + self.task.name and sets .task =
116         None. If is_AI, calls .decide_task to decide a self.task.
117
118         """
119         self._stencil = None
120         self.health -= 1
121         if self.health <= 0:
122             if self is self.world.player:
123                 self.world.player_is_alive = False
124             else:
125                 del self.world.things[self.world.things.index(self)]
126             return
127         try:
128             self.task.check()
129         except GameError as e:
130             self.task = None
131             self._last_task_result = e
132             if is_AI:
133                 try:
134                     self.decide_task()
135                 except GameError:
136                     self.set_task('WAIT')
137             return
138         self.task.todo -= 1
139         if self.task.todo <= 0:
140             self._last_task_result = self.task.do()
141             self.task = None
142         if is_AI and self.task is None:
143             try:
144                 self.decide_task()
145             except GameError:
146                 self.set_task('WAIT')
147
148     def get_stencil(self):
149         if self._stencil is not None:
150             return self._stencil
151         self._stencil = self.world.map_.get_fov_map(self.position)
152         return self._stencil
153
154     def get_visible_map(self):
155         stencil = self.get_stencil()
156         m = self.world.map_.new_from_shape(' ')
157         for pos in m:
158             if stencil[pos] == '.':
159                 m[pos] = self.world.map_[pos]
160         return m
161
162     def get_visible_things(self):
163         stencil = self.get_stencil()
164         visible_things = []
165         for thing in self.world.things:
166             if (not thing.in_inventory) and stencil[thing.position] == '.':
167                 visible_things += [thing]
168         return visible_things
169
170     def get_pickable_items(self):
171         pickable_ids = []
172         for t in [t for t in self.get_visible_things() if
173                   isinstance(t, ThingItem) and
174                   (t.position == self.position or
175                    t.position in
176                    self.world.map_.get_neighbors(self.position).values())]:
177             pickable_ids += [t.id_]
178         return pickable_ids
179
180
181
182 class ThingHuman(ThingAnimate):
183     type_ = 'human'
184     health = 100
185
186
187
188 class ThingMonster(ThingAnimate):
189     type_ = 'monster'
190     health = 50