001package squidpony.squidgrid.mapping;
002
003import squidpony.squidmath.*;
004
005import java.util.*;
006
007/**
008 * A variant on {@link MixedGenerator} that creates bi-radially symmetric maps (basically a yin-yang shape). Useful for
009 * strategy games and possibly competitive multi-player games. The Coords passed to constructors as room positions do
010 * not necessarily need to be
011 *
012 * Created by Tommy Ettinger on 11/20/2015.
013 */
014public class SymmetryDungeonGenerator extends MixedGenerator {
015
016    public static OrderedMap<Coord, List<Coord>> removeSomeOverlap(int width, int height, Collection<Coord> sequence)
017    {
018        List<Coord> s2 = new ArrayList<>(sequence.size());
019        for(Coord c : sequence)
020        {
021            if(c.x * 1.0 / width + c.y * 1.0 / height <= 1.0)
022                s2.add(c);
023        }
024        return listToMap(s2);
025    }
026    public static OrderedMap<Coord, List<Coord>> removeSomeOverlap(int width, int height, Map<Coord, List<Coord>> connections) {
027        OrderedMap<Coord, List<Coord>> om2 = new OrderedMap<>(connections.size());
028        Set<Coord> keyset = connections.keySet(), newkeys = new OrderedSet<>(connections.size());
029        for (Coord c : keyset) {
030            if (c.x * 1.0 / width + c.y * 1.0 / height <= 1.0) {
031                newkeys.add(c);
032            }
033        }
034        Coord[] keys = newkeys.toArray(new Coord[0]);
035        for (int i = 0; i < keys.length; i++) {
036            Coord c = keys[i];
037            if (c.x * 1.0 / width + c.y * 1.0 / height <= 1.0) {
038                List<Coord> cs = new ArrayList<>(4);
039                for (Coord c2 : connections.get(c)) {
040                    if (c2.x * 1.0 / width + c2.y * 1.0 / height <= 1.0) {
041                        cs.add(c2);
042                    } else if (keys[(i + 1) % keys.length].x * 1.0 / width +
043                            keys[(i + 1) % keys.length].y * 1.0 / height <= 1.0) {
044                        cs.add(keys[(i + 1) % keys.length]);
045                    }
046
047                }
048                om2.put(c, cs);
049            }
050        }
051        return om2;
052    }
053    /**
054     * This prepares a map generator that will generate a map with the given width and height, using the given RNG.
055     * This version of the constructor uses Poisson Disk sampling to generate the points it will draw caves and
056     * corridors between, ensuring a minimum distance between points, but it does not ensure that paths between points
057     * will avoid overlapping with rooms or other paths. You call the different carver-adding methods to affect what the
058     * dungeon will look like, putCaveCarvers(), putBoxRoomCarvers(), and putRoundRoomCarvers(), defaulting to only
059     * caves if none are called. You call generate() after adding carvers, which returns a char[][] for a map.
060     *
061     * @param width  the width of the final map in cells
062     * @param height the height of the final map in cells
063     * @param rng    an RNG object to use for random choices; this make a lot of random choices.
064     * @see PoissonDisk used to ensure spacing for the points.
065     */
066    public SymmetryDungeonGenerator(int width, int height, IRNG rng) {
067        this(width, height, rng, basicPoints(width, height, rng));
068    }
069
070    /**
071     * This prepares a map generator that will generate a map with the given width and height, using the given RNG.
072     * This version of the constructor uses a List of Coord points from some other source to determine the path to add
073     * rooms or caves to and then connect. You call the different carver-adding methods to affect what the
074     * dungeon will look like, putCaveCarvers(), putBoxRoomCarvers(), and putRoundRoomCarvers(), defaulting to only
075     * caves if none are called. You call generate() after adding carvers, which returns a char[][] for a map.
076     *
077     * @param width    the width of the final map in cells
078     * @param height   the height of the final map in cells
079     * @param rng      an IRNG, such as an RNG, to use for random choices; this make a lot of random choices.
080     * @param sequence a List of Coord to connect in order; index 0 is the start, index size() - 1 is the end.
081     * @see SerpentMapGenerator a class that uses this technique
082     */
083    public SymmetryDungeonGenerator(int width, int height, IRNG rng, List<Coord> sequence) {
084        this(width, height, rng, listToMap(sequence), 1f);
085    }
086
087    /**
088     * This prepares a map generator that will generate a map with the given width and height, using the given RNG.
089     * This version of the constructor uses a List of Coord points from some other source to determine the path to add
090     * rooms or caves to and then connect. You call the different carver-adding methods to affect what the
091     * dungeon will look like, putCaveCarvers(), putBoxRoomCarvers(), and putRoundRoomCarvers(), defaulting to only
092     * caves if none are called. You call generate() after adding carvers, which returns a char[][] for a map.
093     *
094     * @param width    the width of the final map in cells
095     * @param height   the height of the final map in cells
096     * @param rng      an IRNG, such as an RNG, to use for random choices; this make a lot of random choices.
097     * @param sequence a List of Coord to connect in order; index 0 is the start, index size() - 1 is the end.
098     * @see SerpentMapGenerator a class that uses this technique
099     */
100    public SymmetryDungeonGenerator(int width, int height, IRNG rng, OrderedSet<Coord> sequence) {
101        this(width, height, rng, setToMap(sequence), 1f);
102    }
103
104    /**
105     * This prepares a map generator that will generate a map with the given width and height, using the given RNG.
106     * This version of the constructor uses a LinkedHashMap with Coord keys and Coord array values to determine a
107     * branching path for the dungeon to take; each key will connect once to each of the Coords in its value, and you
108     * usually don't want to connect in both directions. You call the different carver-adding methods to affect what the
109     * dungeon will look like, putCaveCarvers(), putBoxRoomCarvers(), and putRoundRoomCarvers(), defaulting to only
110     * caves if none are called. You call generate() after adding carvers, which returns a char[][] for a map.
111     *
112     * @param width       the width of the final map in cells
113     * @param height      the height of the final map in cells
114     * @param rng         an RNG object to use for random choices; this make a lot of random choices.
115     * @param connections a Map of Coord keys to arrays of Coord to connect to next; shouldn't connect both ways
116     * @see SerpentMapGenerator a class that uses this technique
117     */
118    public SymmetryDungeonGenerator(int width, int height, IRNG rng, OrderedMap<Coord, List<Coord>> connections) {
119        this(width, height, rng, connections, 0.8f);
120    }
121
122    /**
123     * This prepares a map generator that will generate a map with the given width and height, using the given RNG.
124     * This version of the constructor uses a LinkedHashMap with Coord keys and Coord array values to determine a
125     * branching path for the dungeon to take; each key will connect once to each of the Coords in its value, and you
126     * usually don't want to connect in both directions. You call the different carver-adding methods to affect what the
127     * dungeon will look like, putCaveCarvers(), putBoxRoomCarvers(), and putRoundRoomCarvers(), defaulting to only
128     * caves if none are called. You call generate() after adding carvers, which returns a char[][] for a map.
129     *
130     * @param width              the width of the final map in cells
131     * @param height             the height of the final map in cells
132     * @param rng                an RNG object to use for random choices; this make a lot of random choices.
133     * @param connections        a Map of Coord keys to arrays of Coord to connect to next; shouldn't connect both ways
134     * @param roomSizeMultiplier a float multiplier that will be applied to each room's width and height
135     * @see SerpentMapGenerator a class that uses this technique
136     */
137    public SymmetryDungeonGenerator(int width, int height, IRNG rng, OrderedMap<Coord, List<Coord>> connections, float roomSizeMultiplier) {
138        super(width, height, rng, crossConnect(width, height, connections), roomSizeMultiplier);
139    }
140
141    protected static OrderedMap<Coord, List<Coord>> listToMap(List<Coord> sequence)
142    {
143        OrderedMap<Coord, List<Coord>> conns = new OrderedMap<>(sequence.size() - 1);
144        for (int i = 0; i < sequence.size() - 1; i++) {
145            Coord c1 = sequence.get(i), c2 = sequence.get(i+1);
146            List<Coord> cs = new ArrayList<>(1);
147            cs.add(c2);
148            conns.put(c1, cs);
149        }
150        return conns;
151    }
152    protected static OrderedMap<Coord, List<Coord>> setToMap(OrderedSet<Coord> sequence)
153    {
154        OrderedMap<Coord, List<Coord>> conns = new OrderedMap<>(sequence.size() - 1);
155        for (int i = 0; i < sequence.size() - 1; i++) {
156            Coord c1 = sequence.getAt(i), c2 = sequence.getAt(i+1);
157            List<Coord> cs = new ArrayList<>(1);
158            cs.add(c2);
159            conns.put(c1, cs);
160        }
161        return conns;
162    }
163
164    protected static OrderedMap<Coord, List<Coord>> crossConnect(int width, int height, Map<Coord, List<Coord>> connections)
165    {
166        OrderedMap<Coord, List<Coord>> conns = new OrderedMap<>(connections.size());
167        for(Map.Entry<Coord, List<Coord>> entry : connections.entrySet())
168        {
169            conns.put(entry.getKey(), new ArrayList<>(entry.getValue()));
170        }
171        double lowest1 = 9999, lowest2 = 9999, lowest3 = 9999, lowest4 = 9999;
172        Coord l1 = null, l2 = null, l3 = null, l4 = null, r1 = null, r2 = null, r3 = null, r4 = null;
173        for(List<Coord> left : connections.values())
174        {
175            for(List<Coord> right : connections.values())
176            {
177                for(Coord lc : left)
178                {
179                    for(Coord rc : right)
180                    {
181                        Coord rc2 = Coord.get(width - 1 - rc.x, height - 1 - rc.y);
182                        double dist = lc.distance(rc2);
183                        if(dist < 0.001)
184                            continue;
185                        if(dist < lowest1)
186                        {
187                            lowest1 = dist;
188                            l1 = lc;
189                            r1 = rc2;
190                        }
191                        else if(dist < lowest2 && !lc.equals(l1) && !rc2.equals(r1))
192                        {
193                            lowest2 = dist;
194                            l2 = lc;
195                            r2 = rc2;
196                        }
197                        else if(dist < lowest3
198                                && !lc.equals(l1) && !rc2.equals(r1) && !lc.equals(l2) && !rc2.equals(r2))
199                        {
200                            lowest3 = dist;
201                            l3 = lc;
202                            r3 = rc2;
203                        }
204                        else if(dist < lowest4
205                                && !lc.equals(l1) && !rc2.equals(r1)
206                                && !lc.equals(l2) && !rc2.equals(r2)
207                                && !lc.equals(l3) && !rc2.equals(r3))
208                        {
209                            lowest4 = dist;
210                            l4 = lc;
211                            r4 = rc2;
212                        }
213                    }
214                }
215            }
216        }
217        if(l1 != null && r1 != null)
218        {
219            if(conns.containsKey(l1))
220            {
221                conns.get(l1).add(r1);
222            }
223            else if(conns.containsKey(r1))
224            {
225                conns.get(r1).add(l1);
226            }
227        }
228        if(l2 != null && r2 != null)
229        {
230            if(conns.containsKey(l2))
231            {
232                conns.get(l2).add(r2);
233            }
234            else if(conns.containsKey(r2))
235            {
236                conns.get(r2).add(l2);
237            }
238        }
239        if(l3 != null && r3 != null)
240        {
241            if(conns.containsKey(l3))
242            {
243                conns.get(l3).add(r3);
244            }
245            else if(conns.containsKey(r3))
246            {
247                conns.get(r3).add(l3);
248            }
249        }
250        if(l4 != null && r4 != null)
251        {
252            if(conns.containsKey(l4))
253            {
254                conns.get(l4).add(r4);
255            }
256            else if(conns.containsKey(r4))
257            {
258                conns.get(r4).add(l4);
259            }
260        }
261        return conns;
262    }
263    /**
264     * Internal use. Marks a point to be made into floor.
265     *
266     * @param x x position to mark
267     * @param y y position to mark
268     * @return false if everything is normal, true if and only if this failed to mark because the position is walled
269     */
270    @Override
271    protected boolean mark(int x, int y) {
272        return super.mark(x, y) || super.mark(width - 1 - x, height - 1 - y);
273    }
274
275    /**
276     * Internal use. Marks a point to be made into floor.
277     *
278     * @param x x position to mark
279     * @param y y position to mark
280     */
281    @Override
282    protected void markPiercing(int x, int y) {
283        super.markPiercing(x, y);
284        super.markPiercing(width - 1 - x, height - 1 - y);
285    }
286
287    /**
288     * Internal use. Marks a point to be made into floor.
289     *
290     * @param x x position to mark
291     * @param y y position to mark
292     */
293    @Override
294    protected void wallOff(int x, int y) {
295        super.wallOff(x, y);
296        super.wallOff(width - 1 - x, height - 1 - y);
297    }
298    /**
299     * Internal use. Marks a point's environment type as the appropriate kind of environment.
300     * @param x x position to mark
301     * @param y y position to mark
302     * @param kind an int that should be one of the constants in MixedGenerator for environment types.
303     */
304    @Override
305    protected void markEnvironment(int x, int y, int kind) {
306        super.markEnvironment(x, y, kind);
307        super.markEnvironment(width - 1 - x, height - 1 - y, kind);
308    }
309}