001package squidpony.squidai;
002
003import squidpony.squidmath.Coord;
004import squidpony.squidmath.OrderedMap;
005
006
007/**
008 * Static utilities for use in AOE and anything else that might need HashMaps of Coord keys to Double values.
009 * Created by Tommy Ettinger on 7/13/2015.
010 */
011public class AreaUtils {
012    /**
013     * This takes a 2D boolean array and returns a HashMap of Coord keys to Double values, but will only use the value
014     * 1.0, and only for positions in map that have as their boolean element true.
015     * @param map width by height, commonly generated by FOV methods
016     * @return a HashMap of Coord keys to Double values, but the only value used is 1.0
017     */
018    public static OrderedMap<Coord, Double> arrayToHashMap(boolean[][] map)
019    {
020        OrderedMap<Coord, Double> ret = new OrderedMap<>();
021        for(int i = 0; i < map.length; i++)
022        {
023            for(int j = 0; j < map[i].length; j++)
024            {
025                if(map[i][j])
026                    ret.put(Coord.get(i, j), 1.0);
027            }
028        }
029        return ret;
030    }
031
032    /**
033     * This takes a 2D double array called map and returns a HashMap of Coord keys to Double values, and will have a key
034     * for every position in map that is greater than 0.0, with values equal to those in map.
035     * @param map width by height, commonly generated by FOV methods
036     * @return a HashMap of Coord keys to Double values, with values all greater than 0.0
037     */
038    public static OrderedMap<Coord, Double> arrayToHashMap(double[][] map)
039    {
040        OrderedMap<Coord, Double> ret = new OrderedMap<>();
041        for(int i = 0; i < map.length; i++)
042        {
043            for(int j = 0; j < map[i].length; j++)
044            {
045                if(map[i][j] > 0.0)
046                    ret.put(Coord.get(i, j), map[i][j]);
047            }
048        }
049        return ret;
050    }
051
052    /**
053     * This takes a 2D double array and returns a HashMap of Coord keys to Double values, but will only use the value
054     * 1.0, and only does this if the passed double[][] has a value at that position that is greater than cutoff.
055     * For example, a cutoff of 0.3 will make all elements in the 2D array that are 0.3 or less be ignored and not put
056     * into the HashMap, but all elements that are greater than 0.3 will be placed in as 1.0.
057     * @param map width by height, commonly generated by FOV methods
058     * @param cutoff any elements greater than this will be 1.0 in the return, anything else will be ignored
059     * @return a HashMap of Coord keys to Double values, but the only value used is 1.0
060     */
061    public static OrderedMap<Coord, Double> arrayToHashMap(double[][] map, double cutoff)
062    {
063        OrderedMap<Coord, Double> ret = new OrderedMap<>();
064        for(int i = 0; i < map.length; i++)
065        {
066            for(int j = 0; j < map[i].length; j++)
067            {
068                if(map[i][j] > cutoff)
069                    ret.put(Coord.get(i, j), 1.0);
070            }
071        }
072        return ret;
073    }
074
075    /**
076     * This takes a DijkstraMap that has already completed a scan() and returns a HashMap of Coord keys to Double
077     * values, and will have a key for every position that was reached in the DijkstraMap, with 1.0 as the only value.
078     * @param map a double[][] returned by a DijkstraMap running its scan()
079     * @return a HashMap of Coord keys to Double values, with values of 1.0 only
080     */
081    public static OrderedMap<Coord, Double> dijkstraToHashMap(double[][] map)
082    {
083        OrderedMap<Coord, Double> ret = new OrderedMap<>();
084        for(int i = 0; i < map.length; i++)
085        {
086            for(int j = 0; j < map[i].length; j++)
087            {
088                if(map[i][j] < DijkstraMap.WALL)
089                    ret.put(Coord.get(i, j), 1.0);
090            }
091        }
092        return ret;
093    }
094
095    /**
096     * Checks that the given end Coord can be targeted from the given origin Coord given the directional targeting
097     * rules specified by limit. If any of the arguments are null, returns true (it assumes that any limits are not
098     * valid and don't restrict anything). The following AimLimit enum values for limit have the following meanings:
099     *
100     * <ul>
101     *     <li>AimLimit.FREE makes no restrictions; it is equivalent here to passing null for limit.</li>
102     *     <li>AimLimit.EIGHT_WAY will only consider Points to be valid targets
103     *     if they are along a straight line with an angle that is a multiple of 45 degrees, relative to the positive x
104     *     axis. Essentially, this limits the points to those a queen could move to in chess.</li>
105     *     <li>AimLimit.ORTHOGONAL will cause the AOE to only consider Points to be valid targets if
106     *     they are along a straight line with an angle that is a multiple of 90 degrees, relative to the positive x
107     *     axis. Essentially, this limits the points to those a rook could move to in chess.</li>
108     *     <li>AimLimit.DIAGONAL will cause the AOE to only consider Points to be valid targets if they are along a
109     *     straight line with an angle that is 45 degrees greater than a multiple of 90 degrees, relative to the
110     *     positive x axis. Essentially, this limits the points to those a bishop could move to in chess.</li>
111     * </ul>
112     *
113     * @param limit an AimLimit enum that restricts valid points unless it is AimLimit.FREE or null
114     * @param origin where the user is
115     * @param end where the point we want to verify is
116     * @return true if the point is a valid target or if the limits are invalid (non-restricting), false otherwise
117     */
118    public static boolean verifyLimit(AimLimit limit, Coord origin, Coord end)
119    {
120        if (limit != null && origin != null && end != null) {
121            switch (limit) {
122                case EIGHT_WAY:
123                    if(Math.abs(end.x - origin.x) == Math.abs(end.y - origin.y) ||
124                            end.x == origin.x || end.y == origin.y)
125                    {
126                        return true;
127                    }
128                    break;
129                case DIAGONAL:
130                    if(Math.abs(end.x - origin.x) == Math.abs(end.y - origin.y))
131                    {
132                        return true;
133                    }
134                    break;
135                case ORTHOGONAL:
136                    if(end.x == origin.x || end.y == origin.y)
137                    {
138                        return true;
139                    }
140                    break;
141                case FREE: return true;
142            }
143            return false;
144        }
145        return true;
146    }
147
148    /**
149     * Checks that the given end Coord can be targeted from the given origin Coord given the complete targeting rules
150     * specified by reach. If any of the arguments are null, returns true (it assumes that any limits are not
151     * valid and don't restrict anything). If reach.limit is null, it treats it as equivalent to {@link AimLimit#FREE}.
152     * Otherwise, it uses the metric, minDistance, and maxDistance from reach to calculate if end is target-able from
153     * origin assuming an unobstructed playing field.
154     *
155     * @param reach a Reach object that, if non-null, gives limits for how targeting can proceed.
156     * @param origin where the user is
157     * @param end where the point we want to verify is
158     * @return true if the point is a valid target or if the limits are invalid (non-restricting), false otherwise
159     */
160    public static boolean verifyReach(Reach reach, Coord origin, Coord end)
161    {
162        if(reach == null)
163            return true;
164        AimLimit limit = reach.limit;
165        if(limit == null) limit = AimLimit.FREE;
166        if (origin != null && end != null) {
167            switch (limit) {
168                case EIGHT_WAY:
169                    if(Math.abs(end.x - origin.x) == Math.abs(end.y - origin.y) ||
170                            end.x == origin.x || end.y == origin.y)
171                    {
172                        return reach.metric.inRange(origin.x, origin.y, end.x, end.y,
173                                reach.minDistance, reach.maxDistance);
174                    }
175                    break;
176                case DIAGONAL:
177                    if(Math.abs(end.x - origin.x) == Math.abs(end.y - origin.y))
178                    {
179                        return reach.metric.inRange(origin.x, origin.y, end.x, end.y,
180                                reach.minDistance, reach.maxDistance);
181                    }
182                    break;
183                case ORTHOGONAL:
184                    if(end.x == origin.x || end.y == origin.y)
185                    {
186                        return reach.metric.inRange(origin.x, origin.y, end.x, end.y,
187                                reach.minDistance, reach.maxDistance);
188                    }
189                    break;
190                case FREE: return reach.metric.inRange(origin.x, origin.y, end.x, end.y,
191                            reach.minDistance, reach.maxDistance);
192            }
193            return false;
194        }
195        return true;
196    }
197}