001package squidpony.squidai;
002
003import squidpony.squidgrid.FOV;
004import squidpony.squidgrid.Measurement;
005import squidpony.squidgrid.Radius;
006import squidpony.squidgrid.mapping.DungeonUtility;
007import squidpony.squidmath.Coord;
008import squidpony.squidmath.OrderedMap;
009import squidpony.squidmath.OrderedSet;
010
011import java.io.Serializable;
012import java.util.ArrayList;
013import java.util.Arrays;
014import java.util.Collection;
015
016/**
017 * An AOE type that has a center and a radius, and uses shadowcasting to create a burst of rays from the center, out to
018 * the distance specified by radius. You can specify the RadiusType to Radius.DIAMOND for Manhattan distance,
019 * RADIUS.SQUARE for Chebyshev, or RADIUS.CIRCLE for Euclidean.
020 *
021 * This will produce doubles for its {@link #findArea()} method which are equal to 1.0.
022 *
023 * This class uses {@link FOV} to create its area of effect.
024 * Created by Tommy Ettinger on 7/13/2015.
025 */
026public class BurstAOE implements AOE, Serializable {
027    private static final long serialVersionUID = 2L;
028    private FOV fov;
029    private Coord center, origin;
030    private int radius;
031    private double[][] map;
032    private char[][] dungeon;
033    private Radius radiusType;
034    private Reach reach = new Reach(1, 1, Radius.SQUARE, AimLimit.FREE);
035
036    public BurstAOE(Coord center, int radius, Radius radiusType)
037    {
038        fov = new FOV(FOV.SHADOW);
039        this.center = center;
040        this.radius = radius;
041        this.radiusType = radiusType;
042    }
043    public BurstAOE(Coord center, int radius, Radius radiusType, int minRange, int maxRange)
044    {
045        fov = new FOV(FOV.SHADOW);
046        this.center = center;
047        this.radius = radius;
048        this.radiusType = radiusType;
049        reach.minDistance = minRange;
050        reach.maxDistance = maxRange;
051    }
052
053    public Coord getCenter() {
054        return center;
055    }
056
057    public void setCenter(Coord center) {
058
059        if (map != null && center.isWithin(map.length, map[0].length) &&
060                AreaUtils.verifyReach(reach, origin, center))
061        {
062            this.center = center;
063        }
064    }
065
066    public int getRadius() {
067        return radius;
068    }
069
070    public void setRadius(int radius) {
071        this.radius = radius;
072    }
073
074    public Radius getRadiusType() {
075        return radiusType;
076    }
077
078    public void setRadiusType(Radius radiusType) {
079        this.radiusType = radiusType;
080    }
081
082    @Override
083    public void shift(Coord aim) {
084        setCenter(aim);
085    }
086
087    @Override
088    public boolean mayContainTarget(Collection<Coord> targets) {
089        for (Coord p : targets)
090        {
091            if(radiusType.radius(center.x, center.y, p.x, p.y) <= radius)
092                return true;
093        }
094        return false;
095    }
096
097    @Override
098    public OrderedMap<Coord, ArrayList<Coord>> idealLocations(Collection<Coord> targets, Collection<Coord> requiredExclusions) {
099        if(targets == null)
100            return new OrderedMap<>();
101        if(requiredExclusions == null) requiredExclusions = new OrderedSet<>();
102
103        //requiredExclusions.remove(origin);
104        int totalTargets = targets.size();
105        OrderedMap<Coord, ArrayList<Coord>> bestPoints = new OrderedMap<>(totalTargets * 8);
106
107        if(totalTargets == 0)
108            return bestPoints;
109
110        if(radius == 0)
111        {
112            for(Coord p : targets)
113            {
114                ArrayList<Coord> ap = new ArrayList<>();
115                ap.add(p);
116                bestPoints.put(p, ap);
117            }
118            return bestPoints;
119        }
120        Coord[] ts = targets.toArray(new Coord[0]);
121        Coord[] exs = requiredExclusions.toArray(new Coord[0]);
122        Coord t;
123
124        double[][][] compositeMap = new double[ts.length][dungeon.length][dungeon[0].length];
125
126        char[][] dungeonCopy = new char[dungeon.length][dungeon[0].length];
127        for (int i = 0; i < dungeon.length; i++) {
128            System.arraycopy(dungeon[i], 0, dungeonCopy[i], 0, dungeon[i].length);
129        }
130        double[][] tmpfov;
131        Coord tempPt;
132        for (int i = 0; i < exs.length; ++i) {
133            t = exs[i];
134
135            tmpfov = fov.calculateFOV(map, t.x, t.y, radius, radiusType);
136            for (int x = 0; x < dungeon.length; x++) {
137                for (int y = 0; y < dungeon[x].length; y++) {
138                    tempPt = Coord.get(x, y);
139                    dungeonCopy[x][y] = (tmpfov[x][y] > 0.0 || !AreaUtils.verifyReach(reach, origin, tempPt)) ? '!' : dungeonCopy[x][y];
140                }
141            }
142        }
143
144        Measurement dmm = Measurement.MANHATTAN;
145        if(radiusType == Radius.SQUARE || radiusType == Radius.CUBE) dmm = Measurement.CHEBYSHEV;
146        else if(radiusType == Radius.CIRCLE || radiusType == Radius.SPHERE) dmm = Measurement.EUCLIDEAN;
147
148        for (int i = 0; i < ts.length; ++i) {
149            DijkstraMap dm = new DijkstraMap(dungeon, dmm);
150
151            t = ts[i];
152            tmpfov = fov.calculateFOV(map, t.x, t.y, radius, radiusType);
153
154            double dist;
155            for (int x = 0; x < dungeon.length; x++) {
156                for (int y = 0; y < dungeon[x].length; y++) {
157                    if (tmpfov[x][y] > 0.0) {
158                        dist = reach.metric.radius(origin.x, origin.y, x, y);
159                        if(dist <= reach.maxDistance + radius && dist >= reach.minDistance - radius)
160                            compositeMap[i][x][y] = dm.physicalMap[x][y];
161                        else
162                            compositeMap[i][x][y] = DijkstraMap.WALL;
163                    }
164                    else compositeMap[i][x][y] = DijkstraMap.WALL;
165                }
166            }
167            if(compositeMap[i][ts[i].x][ts[i].y] > DijkstraMap.FLOOR)
168            {
169                for (int x = 0; x < dungeon.length; x++) {
170                    Arrays.fill(compositeMap[i][x], 99999.0);
171                }
172                continue;
173            }
174
175
176            dm.initialize(compositeMap[i]);
177            dm.setGoal(t);
178            dm.scan(null, null);
179            for (int x = 0; x < dungeon.length; x++) {
180                for (int y = 0; y < dungeon[x].length; y++) {
181                    compositeMap[i][x][y] = (dm.gradientMap[x][y] < DijkstraMap.FLOOR  && dungeonCopy[x][y] != '!') ? dm.gradientMap[x][y] : 99999.0;
182                }
183            }
184        }
185        double bestQuality = 99999 * ts.length;
186        double[][] qualityMap = new double[dungeon.length][dungeon[0].length];
187        for (int x = 0; x < qualityMap.length; x++) {
188            for (int y = 0; y < qualityMap[x].length; y++) {
189                qualityMap[x][y] = 0.0;
190                long bits = 0;
191                for (int i = 0; i < ts.length; ++i) {
192                    qualityMap[x][y] += compositeMap[i][x][y];
193                    if(compositeMap[i][x][y] < 99999.0 && i < 63)
194                        bits |= 1 << i;
195                }
196                if(qualityMap[x][y] < bestQuality)
197                {
198                    ArrayList<Coord> ap = new ArrayList<>();
199
200                    for (int i = 0; i < ts.length && i < 63; ++i) {
201                        if((bits & (1 << i)) != 0)
202                            ap.add(ts[i]);
203                    }
204
205                    if(ap.size() > 0) {
206                        bestQuality = qualityMap[x][y];
207                        bestPoints.clear();
208                        bestPoints.put(Coord.get(x, y), ap);
209                    }                }
210                else if(qualityMap[x][y] == bestQuality)
211                {
212                    ArrayList<Coord> ap = new ArrayList<>();
213
214                    for (int i = 0; i < ts.length && i < 63; ++i) {
215                        if((bits & (1 << i)) != 0)
216                            ap.add(ts[i]);
217                    }
218
219                    if (ap.size() > 0) {
220                        bestPoints.put(Coord.get(x, y), ap);
221                    }
222                }
223            }
224        }
225
226        return bestPoints;
227    }
228
229
230    @Override
231    public OrderedMap<Coord, ArrayList<Coord>> idealLocations(Collection<Coord> priorityTargets, Collection<Coord> lesserTargets, Collection<Coord> requiredExclusions) {
232        if(priorityTargets == null)
233            return idealLocations(lesserTargets, requiredExclusions);
234        if(requiredExclusions == null) requiredExclusions = new OrderedSet<>();
235
236        //requiredExclusions.remove(origin);
237        int totalTargets = priorityTargets.size() + lesserTargets.size();
238        OrderedMap<Coord, ArrayList<Coord>> bestPoints = new OrderedMap<>(totalTargets * 8);
239
240        if(totalTargets == 0)
241            return bestPoints;
242
243        if(radius == 0)
244        {
245            for(Coord p : priorityTargets)
246            {
247                ArrayList<Coord> ap = new ArrayList<>();
248                ap.add(p);
249                bestPoints.put(p, ap);
250            }
251            return bestPoints;
252        }
253        Coord[] pts = priorityTargets.toArray(new Coord[0]);
254        Coord[] lts = lesserTargets.toArray(new Coord[0]);
255        Coord[] exs = requiredExclusions.toArray(new Coord[0]);
256        Coord t;
257
258        double[][][] compositeMap = new double[totalTargets][dungeon.length][dungeon[0].length];
259
260        char[][] dungeonCopy = new char[dungeon.length][dungeon[0].length],
261                dungeonPriorities = new char[dungeon.length][dungeon[0].length];
262        for (int i = 0; i < dungeon.length; i++) {
263            System.arraycopy(dungeon[i], 0, dungeonCopy[i], 0, dungeon[i].length);
264            Arrays.fill(dungeonPriorities[i], '#');
265        }
266        double[][] tmpfov;
267        Coord tempPt;
268        for (int i = 0; i < exs.length; ++i) {
269            t = exs[i];
270            tmpfov = fov.calculateFOV(map, t.x, t.y, radius, radiusType);
271            for (int x = 0; x < dungeon.length; x++) {
272                for (int y = 0; y < dungeon[x].length; y++) {
273                    tempPt = Coord.get(x, y);
274                    dungeonCopy[x][y] = (tmpfov[x][y] > 0.0 || !AreaUtils.verifyReach(reach, origin, tempPt)) ? '!' : dungeonCopy[x][y];
275                }
276            }
277        }
278
279        Measurement dmm = Measurement.MANHATTAN;
280        if(radiusType == Radius.SQUARE || radiusType == Radius.CUBE) dmm = Measurement.CHEBYSHEV;
281        else if(radiusType == Radius.CIRCLE || radiusType == Radius.SPHERE) dmm = Measurement.EUCLIDEAN;
282
283        for (int i = 0; i < pts.length; ++i) {
284            DijkstraMap dm = new DijkstraMap(dungeon, dmm);
285            t = pts[i];
286
287            tmpfov = fov.calculateFOV(map, t.x, t.y, radius, radiusType);
288
289            double dist;
290            for (int x = 0; x < dungeon.length; x++) {
291                for (int y = 0; y < dungeon[x].length; y++) {
292                    if (tmpfov[x][y] > 0.0){
293                        dist = reach.metric.radius(origin.x, origin.y, x, y);
294                        if(dist <= reach.maxDistance + radius && dist >= reach.minDistance - radius) {
295                            compositeMap[i][x][y] = dm.physicalMap[x][y];
296                            dungeonPriorities[x][y] = dungeon[x][y];
297                        }
298                        else
299                            compositeMap[i][x][y] = DijkstraMap.WALL;
300                    }
301                    else compositeMap[i][x][y] = DijkstraMap.WALL;
302                }
303            }
304            if(compositeMap[i][t.x][t.y] > DijkstraMap.FLOOR)
305            {
306                for (int x = 0; x < dungeon.length; x++) {
307                    Arrays.fill(compositeMap[i][x], 399999.0);
308                }
309                continue;
310            }
311
312
313            dm.initialize(compositeMap[i]);
314            dm.setGoal(t);
315            dm.scan(null, null);
316            for (int x = 0; x < dungeon.length; x++) {
317                for (int y = 0; y < dungeon[x].length; y++) {
318                    compositeMap[i][x][y] = (dm.gradientMap[x][y] < DijkstraMap.FLOOR  && dungeonCopy[x][y] != '!') ? dm.gradientMap[x][y] : 399999.0;
319                }
320            }
321            dm.resetMap();
322            dm.clearGoals();
323        }
324
325        for (int i = pts.length; i < totalTargets; ++i) {
326            DijkstraMap dm = new DijkstraMap(dungeon, dmm);
327            t = lts[i - pts.length];
328
329            tmpfov = fov.calculateFOV(map, t.x, t.y, radius, radiusType);
330
331            double dist;
332            for (int x = 0; x < dungeon.length; x++) {
333                for (int y = 0; y < dungeon[x].length; y++) {
334                    if (tmpfov[x][y] > 0.0){
335                        dist = reach.metric.radius(origin.x, origin.y, x, y);
336                        if(dist <= reach.maxDistance + radius && dist >= reach.minDistance - radius)
337                            compositeMap[i][x][y] = dm.physicalMap[x][y];
338                        else
339                            compositeMap[i][x][y] = DijkstraMap.WALL;
340                    }
341                    else compositeMap[i][x][y] = DijkstraMap.WALL;
342                }
343            }
344            if(compositeMap[i][t.x][t.y] > DijkstraMap.FLOOR)
345            {
346                for (int x = 0; x < dungeon.length; x++)
347                {
348                    Arrays.fill(compositeMap[i][x], 99999.0);
349                }
350                continue;
351            }
352
353
354            dm.initialize(compositeMap[i]);
355            dm.setGoal(t);
356            dm.scan(null, null);
357            for (int x = 0; x < dungeon.length; x++) {
358                for (int y = 0; y < dungeon[x].length; y++) {
359                    compositeMap[i][x][y] = (dm.gradientMap[x][y] < DijkstraMap.FLOOR  && dungeonCopy[x][y] != '!' && dungeonPriorities[x][y] != '#') ? dm.gradientMap[x][y] : 99999.0;
360                }
361            }
362            dm.resetMap();
363            dm.clearGoals();
364        }
365        double bestQuality = 99999 * lts.length + 399999 * pts.length;
366        double[][] qualityMap = new double[dungeon.length][dungeon[0].length];
367        for (int x = 0; x < qualityMap.length; x++) {
368            for (int y = 0; y < qualityMap[x].length; y++) {
369                qualityMap[x][y] = 0.0;
370                long pbits = 0, lbits = 0;
371                for (int i = 0; i < pts.length; ++i) {
372                    qualityMap[x][y] += compositeMap[i][x][y];
373                    if(compositeMap[i][x][y] < 399999.0 && i < 63)
374                        pbits |= 1 << i;
375                }
376                for (int i = pts.length; i < totalTargets; ++i) {
377                    qualityMap[x][y] += compositeMap[i][x][y];
378                    if(compositeMap[i][x][y] < 99999.0 && i < 63)
379                        lbits |= 1 << i;
380                }
381                if(qualityMap[x][y] < bestQuality)
382                {
383                    ArrayList<Coord> ap = new ArrayList<>();
384
385                    for (int i = 0; i < pts.length && i < 63; ++i) {
386                        if((pbits & (1 << i)) != 0)
387                            ap.add(pts[i]);
388                    }
389                    for (int i = pts.length; i < totalTargets && i < 63; ++i) {
390                        if((lbits & (1 << i)) != 0)
391                            ap.add(lts[i - pts.length]);
392                    }
393
394                    if(ap.size() > 0) {
395                        bestQuality = qualityMap[x][y];
396                        bestPoints.clear();
397                        bestPoints.put(Coord.get(x, y), ap);
398                    }
399                }
400                else if(qualityMap[x][y] == bestQuality)
401                {
402                    ArrayList<Coord> ap = new ArrayList<>();
403
404                    for (int i = 0; i < pts.length && i < 63; ++i) {
405                        if ((pbits & (1 << i)) != 0) {
406                            ap.add(pts[i]);
407                            ap.add(pts[i]);
408                            ap.add(pts[i]);
409                            ap.add(pts[i]);
410                        }
411                    }
412                    for (int i = pts.length; i < totalTargets && i < 63; ++i) {
413                        if((lbits & (1 << i)) != 0)
414                            ap.add(lts[i - pts.length]);
415                    }
416
417                    if (ap.size() > 0) {
418                        bestPoints.put(Coord.get(x, y), ap);
419                    }
420                }
421            }
422        }
423
424        return bestPoints;
425    }
426
427    /*
428    @Override
429    public ArrayList<ArrayList<Coord>> idealLocations(Set<Coord> targets, Set<Coord> requiredExclusions) {
430        int totalTargets = targets.size() + 1;
431        int maxEffect = (int)radiusType.volume2D(radius);
432        ArrayList<ArrayList<Coord>> locs = new ArrayList<ArrayList<Coord>>(totalTargets);
433
434        for(int i = 0; i < totalTargets; i++)
435        {
436            locs.add(new ArrayList<Coord>(maxEffect));
437        }
438        if(totalTargets == 1)
439            return locs;
440
441        int ctr = 0;
442        if(radius < 1)
443        {
444            locs.get(totalTargets - 2).addAll(targets);
445            return locs;
446        }
447
448        boolean[][] tested = new boolean[dungeon.length][dungeon[0].length];
449        for (int x = 1; x < dungeon.length - 1; x += radius) {
450            BY_POINT:
451            for (int y = 1; y < dungeon[x].length - 1; y += radius) {
452                for(Coord ex : requiredExclusions)
453                {
454                    if(radiusType.radius(x, y, ex.x, ex.y) <= radius)
455                        continue BY_POINT;
456                }
457                ctr = 0;
458                for(Coord tgt : targets)
459                {
460                    if(radiusType.radius(x, y, tgt.x, tgt.y) <= radius)
461                        ctr++;
462                }
463                if(ctr > 0)
464                    locs.get(totalTargets - ctr).add(Coord.get(x, y));
465            }
466        }
467        Coord it;
468        for(int t = 0; t < totalTargets - 1; t++)
469        {
470            if(locs.get(t).size() > 0) {
471                int numPoints = locs.get(t).size();
472                for (int i = 0; i < numPoints; i++) {
473                    it = locs.get(t).get(i);
474                    for (int x = Math.max(1, it.x - radius / 2); x < it.x + (radius + 1) / 2 && x < dungeon.length - 1; x++) {
475                        BY_POINT:
476                        for (int y = Math.max(1, it.y - radius / 2); y <= it.y + (radius - 1) / 2 && y < dungeon[0].length - 1; y++)
477                        {
478                            if(tested[x][y])
479                                continue;
480                            tested[x][y] = true;
481
482                            for(Coord ex : requiredExclusions)
483                            {
484                                if(radiusType.radius(x, y, ex.x, ex.y) <= radius)
485                                    continue BY_POINT;
486                            }
487
488                            ctr = 0;
489                            for(Coord tgt : targets)
490                            {
491                                if(radiusType.radius(x, y, tgt.x, tgt.y) <= radius)
492                                    ctr++;
493                            }
494                            if(ctr > 0)
495                                locs.get(totalTargets - ctr).add(Coord.get(x, y));
496                        }
497                    }
498                }
499            }
500        }
501        return locs;
502    }
503*/
504    @Override
505    public void setMap(char[][] map) {
506        this.map = DungeonUtility.generateResistances(map);
507        dungeon = map;
508    }
509
510    @Override
511    public OrderedMap<Coord, Double> findArea() {
512        return AreaUtils.arrayToHashMap(fov.calculateFOV(map, center.x, center.y, radius, radiusType));
513    }
514
515    @Override
516    public Coord getOrigin() {
517        return origin;
518    }
519
520    @Override
521    public void setOrigin(Coord origin) {
522        this.origin = origin;
523    }
524
525    @Override
526    public AimLimit getLimitType() {
527        return reach.limit;
528    }
529
530    @Override
531    public int getMinRange() {
532        return reach.minDistance;
533    }
534
535    @Override
536    public int getMaxRange() {
537        return reach.maxDistance;
538    }
539
540    @Override
541    public Radius getMetric() {
542        return reach.metric;
543    }
544
545    /**
546     * Gets the same values returned by getLimitType(), getMinRange(), getMaxRange(), and getMetric() bundled into one
547     * Reach object.
548     *
549     * @return a non-null Reach object.
550     */
551    @Override
552    public Reach getReach() {
553        return reach;
554    }
555
556    @Override
557    public void setLimitType(AimLimit limitType) {
558        reach.limit = limitType;
559
560    }
561
562    @Override
563    public void setMinRange(int minRange) {
564        reach.minDistance = minRange;
565    }
566
567    @Override
568    public void setMaxRange(int maxRange) {
569        reach.maxDistance = maxRange;
570
571    }
572
573    @Override
574    public void setMetric(Radius metric) {
575        reach.metric = metric;
576    }
577
578    /**
579     * Sets the same values as setLimitType(), setMinRange(), setMaxRange(), and setMetric() using one Reach object.
580     *
581     * @param reach a non-null Reach object.
582     */
583    @Override
584    public void setReach(Reach reach) {
585        if(reach != null)
586            this.reach = reach;
587    }
588
589}