home · contact · privacy
Added diagonal movement, with a 1.4 penalty.
[plomrogue] / src / server / ai.c
1 /* src/server/ai.c */
2
3 #include "ai.h"
4 #include <stddef.h> /* NULL */
5 #include <stdint.h> /* uint8_t, uint16_t, uint32_t, UINT16_MAX */
6 #include "map_object_actions.h" /* get_moa_id_by_name() */
7 #include "map_objects.h" /* struct MapObj */
8 #include "world.h" /* global world */
9
10
11
12 #define N_DIRS 8
13
14
15
16 /* Write into "neighbors" scores of the eight immediate 2D neighbors of the
17  * "score_map" cell at "pos_i" (array index), as found in the directions north,
18  * north-east, east etc. (clockwise order). "max_score" is used for illegal
19  * neighborhoods (i.e. if the direction would lead beyond the map's border).
20  */
21 static void get_neighbor_scores(uint32_t * score_map, uint16_t pos_i,
22                                 uint32_t max_score, uint32_t * neighbors);
23
24 /* Iterate over scored cells in "score_map" of world.map's 2D geometry. Compare
25  * each cell's score against the score of its immediate neighbors in N_DIRS
26  * directions. If it's neighbors are low enough that the result would be lower
27  * than the current value, re-set it to world.map.dist_orthogonal points higher
28  * than its lowest-scored orthogonal neighbor or world.map.dist_diagonal points
29  * higher than its lowest-scored diagonal neighbor (whatever would result in a
30  * lower value). Repeat this whole process until all cells have settled on their
31  * final score. Ignore cells whose position in "score_map" fits cells of
32  * unreachable terrain in world.map.cells. Expect "max_score" to be the maximum
33  * score for cells, marking them as unreachable.
34  */
35 static void dijkstra_map(uint32_t * score_map, uint32_t max_score);
36
37 /* Return numpad char of direction ("8", "6", "2", "4" etc.) of enemy with the
38  * shortest path to "mo_origin". If no enemy is around, return 0.
39  */
40 static char get_dir_to_nearest_enemy(struct MapObj * mo_origin);
41
42
43
44 static void get_neighbor_scores(uint32_t * score_map, uint16_t pos_i,
45                                 uint32_t max_score, uint32_t * neighbors)
46 {
47     uint16_t map_size = world.map.size.y * world.map.size.x;
48     uint8_t i_dir;
49     for (i_dir = 0; i_dir < N_DIRS; neighbors[i_dir] = max_score, i_dir++);
50     uint8_t open_north = pos_i >= world.map.size.x;
51     uint8_t open_east  = pos_i + 1 % world.map.size.x;
52     uint8_t open_south = pos_i + world.map.size.x < map_size;
53     uint8_t open_west  = pos_i % world.map.size.x;
54     if (open_north)
55     {
56         neighbors[0] = score_map[pos_i - world.map.size.x];
57     }
58     if (open_north && open_east)
59     {
60         neighbors[1] = score_map[pos_i - world.map.size.x + 1];
61     }
62     if (open_east)
63     {
64         neighbors[2] = score_map[pos_i + 1];
65     }
66     if (open_east && open_south)
67     {
68         neighbors[3] = score_map[pos_i + 1 + world.map.size.x];
69     }
70     if (open_south)
71     {
72         neighbors[4] = score_map[pos_i + world.map.size.x];
73     }
74     if (open_south && open_west)
75     {
76         neighbors[5] = score_map[pos_i + world.map.size.x - 1];
77     }
78     if (open_west)
79     {
80         neighbors[6] = score_map[pos_i - 1];
81     }
82     if (open_west && open_north)
83     {
84         neighbors[7] = score_map[pos_i - 1 - world.map.size.x];
85     }
86 }
87
88
89
90 static void dijkstra_map(uint32_t * score_map, uint32_t max_score)
91 {
92     uint32_t i_scans, neighbors[N_DIRS], min_neighbor_o, min_neighbor_d;
93     uint16_t map_size = world.map.size.y * world.map.size.x;
94     uint16_t pos;
95     uint8_t scores_still_changing = 1;
96     uint8_t i_dirs;
97     for (i_scans = 0; scores_still_changing; i_scans++)
98     {
99         scores_still_changing = 0;
100         for (pos = 0; pos < map_size; pos++)
101         {
102             if ('.' == world.map.cells[pos])
103             {
104                 get_neighbor_scores(score_map, pos, max_score, neighbors);
105                 min_neighbor_d = max_score;
106                 min_neighbor_o = max_score;
107                 for (i_dirs = 0; i_dirs < N_DIRS; i_dirs++)
108                 {
109                     if   (!(i_dirs % 2) && min_neighbor_o > neighbors[i_dirs])
110                     {
111                         min_neighbor_o = neighbors[i_dirs];
112                     }
113                     else if (i_dirs % 2 && min_neighbor_d > neighbors[i_dirs])
114                     {
115                         min_neighbor_d = neighbors[i_dirs];
116                     }
117                 }
118                 if (score_map[pos] > min_neighbor_o + world.map.dist_orthogonal)
119                 {
120                     score_map[pos] = min_neighbor_o + world.map.dist_orthogonal;
121                     scores_still_changing = 1;
122                 }
123                 if (score_map[pos] > min_neighbor_d + world.map.dist_diagonal)
124                 {
125                     score_map[pos] = min_neighbor_d + world.map.dist_diagonal;
126                     scores_still_changing = 1;
127                 }
128             }
129         }
130     }
131 }
132
133
134
135 static char get_dir_to_nearest_enemy(struct MapObj * mo_origin)
136 {
137     /* Calculate for each cell the distance to the nearest map actor that is
138      * not "mo_origin", with movement only possible in the directions of "dir".
139      * (Actors' own cells start with a distance of 0 towards themselves.)
140      */
141     uint16_t map_size = world.map.size.y * world.map.size.x;
142     uint32_t max_score = UINT32_MAX - (world.map.dist_diagonal + 1);
143     uint32_t score_map[map_size];
144     uint32_t i;
145     for (i = 0; i < map_size; i++)
146     {
147         score_map[i] = max_score;
148     }
149     struct MapObj * mo = world.map_objs;
150     for (; mo != NULL; mo = mo->next)
151     {
152         if (!mo->lifepoints || mo == mo_origin)
153         {
154             continue;
155         }
156         score_map[(mo->pos.y * world.map.size.x) + mo->pos.x] = 0;
157     }
158     dijkstra_map(score_map, max_score);
159
160     /* Return direction of "mo_origin"'s lowest-scored neighbor cell. */
161     uint32_t neighbors[N_DIRS];
162     uint16_t pos_i = (mo_origin->pos.y * world.map.size.x) + mo_origin->pos.x;
163     get_neighbor_scores(score_map, pos_i, max_score, neighbors);
164     char dir_to_nearest_enemy = 0;
165     uint32_t min_neighbor = max_score;
166     char * dirs = "89632147";  /* get_neighbor_scores()'s clockwise dir order.*/
167     for (i = 0; i < N_DIRS; i++)
168     {
169         if (min_neighbor > neighbors[i])
170         {
171             min_neighbor = neighbors[i];
172             dir_to_nearest_enemy = dirs[i];
173         }
174     }
175     return dir_to_nearest_enemy;
176 }
177
178
179
180 extern void ai(struct MapObj * mo)
181 {
182     mo->command = get_moa_id_by_name("wait");
183     char sel = get_dir_to_nearest_enemy(mo);
184     if (0 != sel)
185     {
186         mo->command = get_moa_id_by_name("move");
187         mo->arg = sel;
188     }
189 }