1 /**
2  * @file
3  * @brief Coordinate iterators.
4 **/
5 
6 #include "AppHdr.h"
7 
8 #include "coordit.h"
9 
10 #include "coord.h"
11 #include "libutil.h"
12 #include "losglobal.h"
13 #include "los.h"
14 #include "random.h"
15 
rectangle_iterator(const coord_def & corner1,const coord_def & corner2)16 rectangle_iterator::rectangle_iterator(const coord_def& corner1,
17                                         const coord_def& corner2)
18 {
19     topleft.x = min(corner1.x, corner2.x);
20     topleft.y = min(corner1.y, corner2.y); // not really necessary
21     bottomright.x = max(corner1.x, corner2.x);
22     bottomright.y = max(corner1.y, corner2.y);
23     current = topleft;
24 }
25 
rectangle_iterator(const coord_def & center,int halfside,bool clip_to_map)26 rectangle_iterator::rectangle_iterator(const coord_def& center, int halfside,
27                                        bool clip_to_map)
28 {
29     topleft.x = center.x - halfside;
30     topleft.y = center.y - halfside;
31     if (clip_to_map)
32     {
33         topleft.x = max(topleft.x, X_BOUND_1);
34         topleft.y = max(topleft.y, Y_BOUND_1);
35     }
36     bottomright.x = center.x + halfside;
37     bottomright.y = center.y + halfside;
38     if (clip_to_map)
39     {
40         bottomright.x = min(bottomright.x, X_BOUND_2);
41         bottomright.y = min(bottomright.y, Y_BOUND_2);
42     }
43     current = topleft;
44 }
45 
rectangle_iterator(int x_border_dist,int y_border_dist)46 rectangle_iterator::rectangle_iterator(int x_border_dist, int y_border_dist)
47 {
48     if (y_border_dist < 0)
49         y_border_dist = x_border_dist;
50 
51     topleft.set(x_border_dist, y_border_dist);
52     bottomright.set(GXM - x_border_dist - 1, GYM - y_border_dist - 1);
53     current = topleft;
54 }
55 
operator bool() const56 rectangle_iterator::operator bool() const
57 {
58     return current.y <= bottomright.y;
59 }
60 
operator *() const61 coord_def rectangle_iterator::operator *() const
62 {
63     return current;
64 }
65 
operator ->() const66 const coord_def* rectangle_iterator::operator->() const
67 {
68     return &current;
69 }
70 
operator ++()71 void rectangle_iterator::operator ++()
72 {
73     if (current.x == bottomright.x)
74     {
75         current.x = topleft.x;
76         current.y++;
77     }
78     else
79         current.x++;
80 }
81 
operator ++(int)82 void rectangle_iterator::operator++(int)
83 {
84     ++(*this);
85 }
86 
random_rectangle_iterator(const coord_def & corner1,const coord_def & corner2)87 random_rectangle_iterator::random_rectangle_iterator(const coord_def& corner1,
88                                                      const coord_def& corner2)
89 {
90     int left   = min(corner1.x, corner2.x);
91     int right  = max(corner1.x, corner2.x);
92     int top    = min(corner1.y, corner2.y);
93     int bottom = max(corner1.y, corner2.y);
94 
95     top_left.x = left;
96     top_left.y = top;
97 
98     for (int y = top; y <= bottom; y++)
99         for (int x = left; x <= right; x++)
100             remaining.emplace_back(x, y);
101 
102     if (remaining.empty())
103         current = 0;
104     else
105         current = random2(remaining.size());
106 }
107 
random_rectangle_iterator(int x_border_dist,int y_border_dist)108 random_rectangle_iterator::random_rectangle_iterator(int x_border_dist,
109                                                      int y_border_dist)
110 {
111     if (y_border_dist < 0)
112         y_border_dist = x_border_dist;
113 
114     int right  = GXM - x_border_dist - 1;
115     int bottom = GYM - y_border_dist - 1;
116 
117     top_left.x = x_border_dist;
118     top_left.y = y_border_dist;
119 
120     for (int y = y_border_dist; y <= bottom; y++)
121         for (int x = x_border_dist; x <= right; x++)
122             remaining.emplace_back(x, y);
123 
124     if (remaining.empty())
125         current = 0;
126     else
127         current = random2(remaining.size());
128 }
129 
operator bool() const130 random_rectangle_iterator::operator bool() const
131 {
132     return !remaining.empty();
133 }
134 
operator *() const135 coord_def random_rectangle_iterator::operator *() const
136 {
137     if (remaining.empty())
138         return top_left;
139     else
140         return remaining[current];
141 }
142 
operator ->() const143 const coord_def* random_rectangle_iterator::operator->() const
144 {
145     if (remaining.empty())
146         return &top_left;
147     else
148         return &(remaining[current]);
149 }
150 
operator ++()151 void random_rectangle_iterator::operator ++()
152 {
153     if (!remaining.empty())
154     {
155         remaining[current] = remaining.back();
156         remaining.pop_back();
157         if (!remaining.empty())
158             current = random2(remaining.size());
159     }
160 }
161 
operator ++(int)162 void random_rectangle_iterator::operator++(int)
163 {
164     ++(*this);
165 }
166 
167 /*
168  *  radius iterator
169  */
radius_iterator(const coord_def _center,int r,circle_type ctype,bool _exclude_center)170 radius_iterator::radius_iterator(const coord_def _center, int r,
171                                  circle_type ctype,
172                                  bool _exclude_center)
173     : state(RI_START),
174       center(_center),
175       los(LOS_NONE)
176 {
177     ASSERT(map_bounds(_center));
178     switch (ctype)
179     {
180     case C_CIRCLE: credit = r; break;
181     case C_POINTY: credit = r * r; break;
182     case C_ROUND:  credit = r * r + 1; break;
183     case C_SQUARE: credit = r; break;
184     }
185     is_square = (ctype == C_SQUARE);
186     ++(*this);
187     if (_exclude_center)
188         ++(*this);
189 }
190 
radius_iterator(const coord_def _center,los_type _los,bool _exclude_center)191 radius_iterator::radius_iterator(const coord_def _center,
192                                  los_type _los,
193                                  bool _exclude_center)
194     : state(RI_START),
195       center(_center),
196       los(_los)
197 {
198     ASSERT(map_bounds(_center));
199     credit = get_los_radius();
200     is_square = true;
201     ++(*this);
202     if (_exclude_center)
203         ++(*this);
204 }
205 
radius_iterator(const coord_def _center,int r,circle_type ctype,los_type _los,bool _exclude_center)206 radius_iterator::radius_iterator(const coord_def _center,
207                                  int r,
208                                  circle_type ctype,
209                                  los_type _los,
210                                  bool _exclude_center)
211     : state(RI_START),
212       center(_center),
213       los(_los)
214 {
215     ASSERT(map_bounds(_center));
216     switch (ctype)
217     {
218     case C_CIRCLE: credit = r; break;
219     case C_POINTY: credit = r * r; break;
220     case C_ROUND:  credit = r * r + 1; break;
221     case C_SQUARE: credit = r; break;
222     }
223     is_square = (ctype == C_SQUARE);
224     ++(*this);
225     if (_exclude_center)
226         ++(*this);
227 }
228 
operator bool() const229 radius_iterator::operator bool() const
230 {
231     return state;
232 }
233 
operator *() const234 coord_def radius_iterator::operator *() const
235 {
236     return current;
237 }
238 
operator ->() const239 const coord_def* radius_iterator::operator->() const
240 {
241     return &current;
242 }
243 
244 #define coreturn(id) { state = id; return; case id:; }
245 #define cobegin(id)  switch (state) { case id:
246 #define coend(id)    coreturn(id); }
247 #define ret_coord(dx, dy, id) do                                \
248     {                                                           \
249         current.x = center.x + (dx);                            \
250         current.y = center.y + (dy);                            \
251         if (!los || cell_see_cell(center, current, los))        \
252             coreturn(id);                                       \
253     } while (0)
254 
operator ++()255 void radius_iterator::operator++()
256 {
257     cobegin(RI_START);
258 
259     base_cost = is_square ? 1 : -1;
260     inc_cost = is_square ? 0 : 2;
261 
262     y = 0;
263     cost_y = base_cost;
264     credit_y = credit;
265 
266     do
267     {
268         x = 0;
269         cost_x = base_cost;
270         credit_x = (is_square ? credit : credit_y);
271 
272         do
273         {
274             if (x + center.x < GXM)
275             {
276                 if (y + center.y < GYM)
277                     ret_coord( x,  y, RI_SE);
278                 if (y && y <= center.y)
279                     ret_coord( x, -y, RI_NE);
280             }
281             if (x && x <= center.x)
282             {
283                 if (y + center.y < GYM)
284                     ret_coord(-x,  y, RI_SW);
285                 if (y && y <= center.y)
286                     ret_coord(-x, -y, RI_NW);
287             }
288             x++;
289             credit_x -= (cost_x += inc_cost);
290         } while (credit_x >= 0);
291 
292         y++;
293         credit_y -= (cost_y += inc_cost);
294     } while (credit_y >= 0);
295 
296     coend(RI_DONE);
297 }
298 
operator ++(int)299 void radius_iterator::operator++(int)
300 {
301     ++(*this);
302 }
303 
operator ++()304 void vision_iterator::operator++()
305 {
306     do
307     {
308         radius_iterator::operator++();
309     }
310     while (*this && !who.see_cell(**this));
311 }
312 
operator ++(int)313 void vision_iterator::operator++(int)
314 {
315     ++(*this);
316 }
317 
318 /*
319  *  adjacent iterator
320  */
321 extern const struct coord_def Compass[9];
322 
operator bool() const323 adjacent_iterator::operator bool() const
324 {
325     return i >= 0;
326 }
327 
operator *() const328 coord_def adjacent_iterator::operator *() const
329 {
330     return val;
331 }
332 
operator ->() const333 const coord_def* adjacent_iterator::operator->() const
334 {
335     return &val;
336 }
337 
operator ++()338 void adjacent_iterator::operator ++()
339 {
340     while (--i >= 0)
341     {
342         val = center + Compass[i];
343         if (map_bounds(val))
344             return;
345     }
346 }
347 
operator ++(int)348 void adjacent_iterator::operator++(int)
349 {
350     ++(*this);
351 }
352 
353 /*
354  *  spiral iterator
355  */
distance_iterator(const coord_def & _center,bool _fair,bool exclude_center,int _max_radius)356 distance_iterator::distance_iterator(const coord_def& _center, bool _fair,
357                                  bool exclude_center, int _max_radius) :
358     center(_center), current(_center), r(0), max_radius(_max_radius),
359     threshold(0), icur(0), iend(0), fair(_fair)
360 {
361     vcur  = lists + 0;
362     vnear = lists + 1;
363     vfar  = lists + 2;
364 
365     for (int dx = -1; dx <= 1; dx++)
366         for (int dy = -1; dy <= 1; dy++)
367             if (dx || dy)
368                 vnear->emplace_back(dx, dy);
369 
370     if (exclude_center)
371         advance();
372 }
373 
advance()374 bool distance_iterator::advance()
375 {
376     while (true)
377     {
378         if (++icur >= vcur->size())
379             icur = 0;
380         if (icur == iend)
381         {
382             // Advance to the next radius.
383             vector<coord_def> *tmp = vcur;
384             vcur = vnear;
385             vnear = vfar;
386             vfar = tmp;
387             tmp->clear();
388 
389             if (!vcur->size())
390                 return false;
391             // Randomize the order various directions are returned.
392             // Just the initial angle is enough.
393             if (fair)
394                 icur = iend = random2(vcur->size());
395             else
396                 icur = iend = 0; // randomness is costly
397 
398             if (r++ >= max_radius)
399             {
400                 vcur->clear();
401                 return false;
402             }
403             threshold = r+1;
404         }
405 
406         coord_def d = (*vcur)[icur];
407         if (in_bounds(current = center + d))
408         {
409             ASSERT(d.x || d.y);
410             if (!d.y)
411                 push_neigh(d, sgn(d.x), 0);
412             if (!d.x)
413                 push_neigh(d, 0, sgn(d.y));
414             if (d.x <= 0)
415             {
416                 if (d.y <= 0)
417                     push_neigh(d, -1, -1);
418                 if (d.y >= 0)
419                     push_neigh(d, -1, +1);
420             }
421             if (d.x >= 0)
422             {
423                 if (d.y <= 0)
424                     push_neigh(d, +1, -1);
425                 if (d.y >= 0)
426                     push_neigh(d, +1, +1);
427             }
428 
429             return true;
430         }
431     }
432 }
433 
push_neigh(coord_def d,int dx,int dy)434 void distance_iterator::push_neigh(coord_def d, int dx, int dy)
435 {
436     d.x += dx;
437     d.y += dy;
438     ((d.rdist() <= threshold) ? vnear : vfar)->push_back(d);
439 }
440 
operator bool() const441 distance_iterator::operator bool() const
442 {
443     return in_bounds(current) && r <= max_radius;
444 }
445 
operator *() const446 coord_def distance_iterator::operator *() const
447 {
448     return current;
449 }
450 
operator ->() const451 const coord_def* distance_iterator::operator->() const
452 {
453     return &current;
454 }
455 
operator ++()456 const distance_iterator& distance_iterator::operator++()
457 {
458     advance();
459     return *this;
460 }
461 
operator ++(int)462 void distance_iterator::operator++(int)
463 {
464     ++(*this);
465 }
466 
radius() const467 int distance_iterator::radius() const
468 {
469     return r;
470 }
471 
472 /********************/
473 /* regression tests */
474 /********************/
475 #ifdef DEBUG_TESTS
_test_ai(const coord_def c,bool exc,size_t expected)476 static void _test_ai(const coord_def c, bool exc, size_t expected)
477 {
478     set<coord_def> seen;
479 
480     for (adjacent_iterator ai(c, exc); ai; ++ai)
481     {
482         if (seen.count(*ai))
483             die("adjacent_iterator: %d,%d seen twice", ai->x, ai->y);
484         seen.insert(*ai);
485 
486         if (c == *ai && !exc)
487             continue;
488         if ((c - *ai).rdist() != 1)
489         {
490             die("adjacent_iterator: %d,%d not adjacent to %d,%d",
491                 ai->x, ai->y, c.x, c.y);
492         }
493     }
494 
495     if (seen.size() != expected)
496     {
497         die("adjacent_iterator(%d,%d): seen %d, expected %d",
498             c.x, c.y, (int)seen.size(), (int)expected);
499     }
500 }
501 
coordit_tests()502 void coordit_tests()
503 {
504     // bounding box of our playground
505     #define BC   16
506     #define BBOX 32
507     int los_radius = get_los_radius();
508     ASSERT(los_radius < BC - 2);
509     coord_def center(BC, BC);
510 
511     FixedBitArray<BBOX, BBOX> seen;
512 
513     for (int r = 0; r <= los_radius * los_radius + 1; ++r)
514     {
515         seen.reset();
516 
517         for (radius_iterator ri(center, r, C_CIRCLE); ri; ++ri)
518         {
519             if (seen(*ri))
520                 die("radius_iterator(C%d): %d,%d seen twice", r, ri->x, ri->y);
521             seen.set(*ri);
522         }
523 
524         for (int x = 0; x < BBOX; x++)
525             for (int y = 0; y < BBOX; y++)
526             {
527                 bool in = sqr(x - BC) + sqr(y - BC) <= r;
528                 if (seen(coord_def(x, y)) != in)
529                 {
530                     die("radius_iterator(C%d) mismatch at %d,%d: %d != %d",
531                         r, x, y, seen(coord_def(x, y)), in);
532                 }
533             }
534     }
535 
536     for (radius_iterator ri(coord_def(2, 2), 5, C_ROUND); ri; ++ri)
537         if (!map_bounds(*ri))
538             die("radius_iterator(R5) out of bounds at %d, %d", ri->x, ri->y);
539     for (radius_iterator ri(coord_def(GXM - 1, GYM - 1), 7, C_ROUND); ri; ++ri)
540         if (!map_bounds(*ri))
541             die("radius_iterator(R7) out of bounds at %d, %d", ri->x, ri->y);
542 
543     seen.reset();
544     int rd = 0;
545     for (distance_iterator di(center, true, false, BC - 1); di; ++di)
546     {
547         if (seen(*di))
548             die("distance_iterator: %d,%d seen twice", di->x, di->y);
549         seen.set(*di);
550 
551         int rc = (center - *di).rdist();
552         if (rc < rd)
553             die("distance_iterator went backwards");
554         rd = rc;
555     }
556 
557     for (int x = 0; x < BBOX; x++)
558         for (int y = 0; y < BBOX; y++)
559         {
560             bool in = max(abs(x - BC), abs(y - BC)) <= BC - 1;
561             if (seen(coord_def(x, y)) != in)
562             {
563                 die("distance_iterator mismatch at %d,%d: %d != %d",
564                     x, y, seen(coord_def(x, y)), in);
565             }
566         }
567 
568     _test_ai(center, false, 9);
569     _test_ai(center, true, 8);
570     _test_ai(coord_def(3, 0), false, 6);
571     _test_ai(coord_def(3, 0), true, 5);
572     _test_ai(coord_def(0, 0), false, 4);
573     _test_ai(coord_def(0, 0), true, 3);
574     _test_ai(coord_def(GXM, GYM), false, 1);
575     _test_ai(coord_def(GXM, GYM), true, 1);
576 }
577 #endif
578