home · contact · privacy
Server/AI: Explore map for (long-time) unexplored cells.
[plomrogue] / src / server / ai.c
1 /* src/server/ai.c
2  *
3  * This file is part of PlomRogue. PlomRogue is licensed under the GPL version 3
4  * or any later version. For details on its copyright, license, and warranties,
5  * see the file NOTICE in the root directory of the PlomRogue source package.
6  */
7
8 #include "ai.h"
9 #include <stdint.h> /* uint8_t, uint16_t, uint32_t, int16_t, UINT16_MAX */
10 #include <stdlib.h> /* free() */
11 #include "../common/try_malloc.h" /* try_malloc() */
12 #include "hardcoded_strings.h" /* s */
13 #include "thing_actions.h" /* get_thing_action_id_by_name() */
14 #include "things.h" /* Thing, ThingType, ThingInMemory */
15 #include "world.h" /* world */
16
17
18
19 #define N_DIRS 6
20
21
22
23 /* Write into "neighbors" scores of the N_DIRS immediate neighbors of the
24  * "score_map" cell at "pos_i" (array index), as found in the directions
25  * north-east, east, south-east etc. (clockwise order). Use "max_score" for
26  * illegal neighborhoods (i.e. if direction would lead beyond the map's border).
27  */
28 static void get_neighbor_scores(uint16_t * score_map, uint16_t pos_i,
29                                 uint16_t max_score, uint16_t * neighbors);
30
31 /* Iterate over scored cells in "score_map" of world.map's geometry. Compare
32  * each cell's score against the score of its immediate neighbors in N_DIRS
33  * directions. If any neighbor's score is at least two points lower than the
34  * current cell's score, re-set it to 1 point higher than its lowest-scored
35  * neighbor. Repeat this whole process until all cells have settled on their
36  * final score. Ignore cells whose score is greater than "max_score". Expect
37  * "max_score" to be the maximum score for cells, marking them as unreachable.
38  */
39 static void dijkstra_map(uint16_t * score_map, uint16_t max_score);
40
41 /* get_dir_to_nearest_target() helper: Prepare "score_map" for dijkstra_map(). */
42 static void init_score_map(char filter, uint16_t * score_map, uint32_t map_size,
43                            struct Thing * t_eye);
44
45 /* Set (if possible) as "t_eye"'s command a move to the path to the path-wise
46  * nearest target that is not "t_eye" and fits criteria set by "filter". On
47  * success, return !0, else 0. Values for "filter":
48  * "e": thing in FOV is animate, but not of "t_eye"'s thing type; build path as
49  *      avoiding things of "t_eye"'s type
50  * "c": thing in memorized map is consumable
51  * "s": memory map cell with greatest-reachable degree of unexploredness
52  */
53 static uint8_t get_dir_to_nearest_target(struct Thing * t_eye, char filter);
54
55 /* Return 1 if any thing not "t_eye" is known and fulfills some criteria defined
56  * by "filter", else 0. Values for "filter":
57  * "e": thing in FOV is animate, but not of "t_eye"'s thing type
58  * "c": thing in memorized map is consumable
59  */
60 static uint8_t seeing_thing(struct Thing * t_eye, char filter);
61
62 /* Return slot ID of strongest consumable in "t_owner"'s inventory, else -1. */
63 static int16_t get_inventory_slot_to_consume(struct Thing * t_owner);
64
65 /* Return 1 if "t_standing" is standing on a consumable, else 0. */
66 static uint8_t standing_on_consumable(struct Thing * t_standing);
67
68
69
70 static void get_neighbor_scores(uint16_t * score_map, uint16_t pos_i,
71                                 uint16_t max_score, uint16_t * neighbors)
72 {
73     uint32_t map_size = world.map.length * world.map.length;
74     uint8_t i_dir;
75     for (i_dir = 0; i_dir < N_DIRS; neighbors[i_dir] = max_score, i_dir++);
76     uint8_t open_north     = pos_i >= world.map.length;
77     uint8_t open_east      = pos_i + 1 % world.map.length;
78     uint8_t open_south     = pos_i + world.map.length < map_size;
79     uint8_t open_west      = pos_i % world.map.length;
80     uint8_t is_indented    = (pos_i / world.map.length) % 2;
81     uint8_t open_diag_west = is_indented || open_west;
82     uint8_t open_diag_east = !is_indented || open_east;
83     if (open_north && open_diag_east)
84     {
85         neighbors[0] = score_map[pos_i - world.map.length + is_indented];
86     }
87     if (open_east)
88     {
89         neighbors[1] = score_map[pos_i + 1];
90     }
91     if (open_south && open_diag_east)
92     {
93         neighbors[2] = score_map[pos_i + world.map.length + is_indented];
94     }
95     if (open_south && open_diag_west)
96     {
97         neighbors[3] = score_map[pos_i + world.map.length - !is_indented];
98     }
99     if (open_west)
100     {
101         neighbors[4] = score_map[pos_i - 1];
102     }
103     if (open_north && open_diag_west)
104     {
105         neighbors[5] = score_map[pos_i - world.map.length - !is_indented];
106     }
107 }
108
109
110
111 static void dijkstra_map(uint16_t * score_map, uint16_t max_score)
112 {
113     uint32_t map_size = world.map.length * world.map.length;
114     uint32_t pos;
115     uint16_t i_scans, neighbors[N_DIRS], min_neighbor;
116     uint8_t scores_still_changing = 1;
117     uint8_t i_dirs;
118     for (i_scans = 0; scores_still_changing; i_scans++)
119     {
120         scores_still_changing = 0;
121         for (pos = 0; pos < map_size; pos++)
122         {
123             if (score_map[pos] <= max_score)
124             {
125                 get_neighbor_scores(score_map, pos, max_score, neighbors);
126                 min_neighbor = max_score;
127                 for (i_dirs = 0; i_dirs < N_DIRS; i_dirs++)
128                 {
129                     if (min_neighbor > neighbors[i_dirs])
130                     {
131                         min_neighbor = neighbors[i_dirs];
132                     }
133                 }
134                 if (score_map[pos] > min_neighbor + 1)
135                 {
136                     score_map[pos] = min_neighbor + 1;
137                     scores_still_changing = 1;
138                 }
139             }
140         }
141     }
142 }
143
144
145
146 static void init_score_map(char filter, uint16_t * score_map, uint32_t map_size,
147                            struct Thing * t_eye)
148 {
149     uint32_t i;
150     for (i = 0; i < map_size; i++)
151     {
152         score_map[i] = UINT16_MAX;
153         if ('.' == t_eye->mem_map[i])
154         {
155             score_map[i] = UINT16_MAX-1;
156         }
157     }
158     if      ('e' == filter)
159     {
160         struct Thing * t = world.things;
161         for (; t; t = t->next)
162         {
163             if (   t==t_eye || !t->lifepoints
164                 || 'H' == t_eye->fov_map[t->pos.y*world.map.length + t->pos.x])
165             {
166                 continue;
167             }
168             else if (t->lifepoints && t->type == t_eye->type)
169             {
170                 score_map[t->pos.y * world.map.length + t->pos.x] = UINT16_MAX;
171                 continue;
172             }
173             score_map[t->pos.y * world.map.length + t->pos.x] = 0;
174         }
175     }
176     else if ('c' == filter)
177     {
178         struct ThingInMemory * tm = t_eye->t_mem;
179         for (; tm; tm = tm->next)
180         {
181             if (' ' == t_eye->mem_map[tm->pos.y * world.map.length + tm->pos.x])
182             {
183                 continue;
184             }
185             struct ThingType * tt = get_thing_type(tm->type);
186             if (!tt->consumable)
187             {
188                 continue;
189             }
190             score_map[tm->pos.y * world.map.length + tm->pos.x] = 0;
191         }
192     }
193     else if (('0' < filter && '9' >= filter) || ' ' == filter)
194     {
195         uint32_t i;
196         for (i = 0; i < (uint32_t) (world.map.length * world.map.length); i++)
197         {
198             score_map[i] = filter == t_eye->mem_depth_map[i] ? 0 : score_map[i];
199         }
200     }
201 }
202
203
204
205 static uint8_t get_dir_to_nearest_target(struct Thing * t_eye, char filter)
206 {
207     char dir_to_nearest_target = 0;
208     uint8_t run_i = 9 /* maximum mem depth age below never-explored */ + 1;
209     uint8_t mem_depth_char = ' ';
210     while (run_i && ('s' == filter || seeing_thing(t_eye, filter)))
211     {
212         run_i = 's' != filter ? 0 : run_i - 1;
213         uint32_t map_size = world.map.length * world.map.length;
214         uint16_t * score_map = try_malloc(map_size * sizeof(uint16_t),__func__);
215         init_score_map('s' == filter ? mem_depth_char : filter,
216                        score_map, map_size, t_eye);
217         mem_depth_char = ' ' == mem_depth_char ? '9' : mem_depth_char - 1;
218         dijkstra_map(score_map, UINT16_MAX-1);
219         uint16_t neighbors[N_DIRS];
220         uint16_t pos_i = (t_eye->pos.y * world.map.length) + t_eye->pos.x;
221         get_neighbor_scores(score_map, pos_i, UINT16_MAX-1, neighbors);
222         free(score_map);
223         uint16_t min_neighbor = UINT16_MAX-1;
224         char * dirs = "edcxsw";/* get_neighbor_scores()'s clockwise dir order.*/
225         uint8_t i;
226         for (i = 0; i < N_DIRS; i++)
227         {
228             if (min_neighbor > neighbors[i])
229             {
230                 min_neighbor = neighbors[i];
231                 dir_to_nearest_target = dirs[i];
232             }
233         }
234         if (dir_to_nearest_target)
235         {
236             t_eye->command = get_thing_action_id_by_name(s[S_CMD_MOVE]);
237             t_eye->arg = dir_to_nearest_target;
238             run_i = 0;
239         }
240     }
241     return dir_to_nearest_target;
242 }
243
244
245
246 static uint8_t seeing_thing(struct Thing * t_eye, char filter)
247 {
248     if (t_eye->fov_map && 'e' == filter)
249     {
250         struct Thing * t = world.things;
251         for (; t; t = t->next)
252         {
253             if (   t != t_eye
254                 && 'v' == t_eye->fov_map[t->pos.y*world.map.length + t->pos.x])
255             {
256                 if (t->lifepoints && t->type != t_eye->type)
257                 {
258                     return 1;
259                 }
260             }
261         }
262     }
263     else if (t_eye->mem_map && 'c' == filter)
264     {
265         struct ThingInMemory * tm = t_eye->t_mem;
266         for (; tm; tm = tm->next)
267         {
268             if (' ' != t_eye->mem_map[tm->pos.y * world.map.length + tm->pos.x])
269             {
270                 struct ThingType * tt = get_thing_type(tm->type);
271                 if (tt->consumable)
272                 {
273                     return 1;
274                 }
275             }
276         }
277     }
278     return 0;
279 }
280
281
282
283 static int16_t get_inventory_slot_to_consume(struct Thing * t_owner)
284 {
285     uint8_t compare_consumability = 0;
286     int16_t selection = -1;
287     struct Thing * t = t_owner->owns;;
288     uint8_t i;
289     for (i = 0; t; t = t->next, i++)
290     {
291         struct ThingType * tt = get_thing_type(t->type);
292         if (tt->consumable > compare_consumability)
293         {
294             compare_consumability = tt->consumable;
295             selection = i;
296         }
297     }
298     return selection;
299 }
300
301
302
303 static uint8_t standing_on_consumable(struct Thing * t_standing)
304 {
305     struct Thing * t = world.things;
306     for (; t; t = t->next)
307     {
308         if (   t != t_standing
309             && t->pos.y == t_standing->pos.y && t->pos.x == t_standing->pos.x)
310         {
311             struct ThingType * tt = get_thing_type(t->type);
312             if (tt->consumable)
313             {
314                 return 1;
315             }
316         }
317     }
318     return 0;
319 }
320
321
322
323 extern void ai(struct Thing * t)
324 {
325     t->command = get_thing_action_id_by_name(s[S_CMD_WAIT]);
326     if (!get_dir_to_nearest_target(t, 'e'))
327     {
328         int16_t sel = get_inventory_slot_to_consume(t);
329         if (-1 != sel)
330         {
331             t->command = get_thing_action_id_by_name(s[S_CMD_USE]);
332             t->arg = (uint8_t) sel;
333         }
334         else if (standing_on_consumable(t))
335         {
336             t->command = get_thing_action_id_by_name(s[S_CMD_PICKUP]);
337         }
338         else if (!get_dir_to_nearest_target(t, 'c'))
339         {
340             get_dir_to_nearest_target(t, 's');
341         }
342     }
343 }