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