001package squidpony.squidgrid.mapping;
002
003import squidpony.ArrayTools;
004import squidpony.FakeLanguageGen;
005import squidpony.Maker;
006import squidpony.Thesaurus;
007import squidpony.squidgrid.Measurement;
008import squidpony.squidgrid.MultiSpill;
009import squidpony.squidmath.*;
010
011import java.util.ArrayList;
012import java.util.List;
013import java.util.Map;
014
015/**
016 * When you have a world map as produced by {@link WorldMapGenerator} or some other source, you may want to fill it with
017 * claims by various nations/factions, possibly procedural or possibly hand-made. This can assign contiguous areas of
018 * land to various factions, while ignoring some amount of "wild" land if desired, and keeping oceans unclaimed.
019 * The factions can be given procedural names in an atlas that is linked to the chars used by the world map.
020 * Uses {@link MultiSpill} internally to produce the semi-random nation shapes. Stores an {@link #atlas} that can be
021 * used to figure out what a char in a produced 2D char array means for its claiming nation, a {@link #briefAtlas} that
022 * will have short, identifiable parts of generated names corresponding to the same chars as in atlas, and an OrderedMap
023 * of {@link #spokenLanguages} that contains any randomly generated languages produced for nations.
024 * <a href="https://gist.github.com/tommyettinger/4a16a09bebed8e2fe8473c8ea444a2dd">Example output of a related class</a>.
025 * @see FantasyPoliticalMapper There's also an alternative for cases where non-human species compete for specific areas.
026 */
027public class PoliticalMapper {
028    public int width;
029    public int height;
030    public StatefulRNG rng;
031    public String name;
032    public char[][] politicalMap;
033    public static final char[] letters = ArrayTools.letterSpan(256);
034    /**
035     * Maps chars, as found in the returned array from generate(), to Strings that store the full name of nations.
036     */
037    public final OrderedMap<Character, String> atlas = new OrderedMap<>(32);
038    /**
039     * Maps chars, as found in the returned array from generate(), to Strings that store the short name of nations.
040     */
041    public final OrderedMap<Character, String> briefAtlas = new OrderedMap<>(32);
042    /**
043     * Maps chars, as found in the returned array from generate(), to Strings that store the languages spoken in those
044     * nations, which could be user-assigned, unassigned, or randomly-generated.
045     */
046    public final OrderedMap<Character, List<FakeLanguageGen>> spokenLanguages = new OrderedMap<>(32);
047
048    public PoliticalMapper()
049    {
050        name = "Permadeath Planet";
051        rng = new StatefulRNG(CrossHash.hash64(name));
052    }
053    /**
054     * Constructs a SpillWorldMap using the given world name, and uses the world name as the
055     * basis for all future random generation in this object.
056     *
057     * @param worldName a String name for the world that will be used as a seed for all random generation here
058     */
059    public PoliticalMapper(String worldName) {
060        name = worldName;
061        rng = new StatefulRNG(CrossHash.hash64(name));
062    }
063    /**
064     * Constructs a SpillWorldMap using the given world name, and uses the world name as the
065     * basis for all future random generation in this object.
066     *
067     * @param random an RNG to generate the name for the world in a random language, which will also serve as a seed
068     */
069    public PoliticalMapper(IRNG random) {
070        this(FakeLanguageGen.SIMPLISH.word(random, true));
071    }
072    /**
073     * Produces a political map for the land stored in the given WorldMapGenerator, with the given number
074     * of factions trying to take land in the world (essentially, nations). The output is a 2D char array where each
075     * letter char is tied to a different faction, while '~' is always water, and '%' is always wilderness or unclaimed
076     * land. The amount of unclaimed land is determined by the controlledFraction parameter, which will be clamped
077     * between 0.0 and 1.0, with higher numbers resulting in more land owned by factions and lower numbers meaning more
078     * wilderness. This version generates an atlas with the procedural names of all the factions and a
079     * mapping to the chars used in the output; the atlas will be in the {@link #atlas} member of this object. For every
080     * Character key in atlas, there will be a String value in atlas that is the name of the nation, and for the same
081     * key in {@link #spokenLanguages}, there will be a non-empty List of {@link FakeLanguageGen} languages (usually
082     * one, sometimes two) that should match any names generated for the nation. Ocean and Wilderness get the default
083     * FakeLanguageGen instances "ELF" and "DEMONIC", in case you need languages for those areas for some reason.
084     * @param wmg a WorldMapGenerator that has produced a map; this gets the land parts of the map to assign claims to,
085     *            including rivers and lakes as part of nations but not oceans
086     * @param factionCount the number of factions to have claiming land, cannot be negative or more than 255
087     * @param controlledFraction between 0.0 and 1.0 inclusive; higher means more land has a letter, lower has more '%'
088     * @return a 2D char array where letters represent the claiming faction, '~' is water, and '%' is unclaimed
089     */
090    public char[][] generate(WorldMapGenerator wmg, int factionCount, double controlledFraction) {
091        return generate(new GreasedRegion(wmg.heightCodeData, 4, 999), factionCount, controlledFraction);
092    }
093    /**
094     * Produces a political map for the land stored in the "on" cells of the given GreasedRegion, with the given number
095     * of factions trying to take land in the world (essentially, nations). The output is a 2D char array where each
096     * letter char is tied to a different faction, while '~' is always water, and '%' is always wilderness or unclaimed
097     * land. The amount of unclaimed land is determined by the controlledFraction parameter, which will be clamped
098     * between 0.0 and 1.0, with higher numbers resulting in more land owned by factions and lower numbers meaning more
099     * wilderness. This version generates an atlas with the procedural names of all the factions and a
100     * mapping to the chars used in the output; the atlas will be in the {@link #atlas} member of this object. For every
101     * Character key in atlas, there will be a String value in atlas that is the name of the nation, and for the same
102     * key in {@link #spokenLanguages}, there will be a non-empty List of {@link FakeLanguageGen} languages (usually
103     * one, sometimes two) that should match any names generated for the nation. Ocean and Wilderness get the default
104     * FakeLanguageGen instances "ELF" and "DEMONIC", in case you need languages for those areas for some reason.
105     * @param land a GreasedRegion that stores "on" cells for land and "off" cells for anything un-claimable, like ocean
106     * @param factionCount the number of factions to have claiming land, cannot be negative or more than 255
107     * @param controlledFraction between 0.0 and 1.0 inclusive; higher means more land has a letter, lower has more '%'
108     * @return a 2D char array where letters represent the claiming faction, '~' is water, and '%' is unclaimed
109     */
110    public char[][] generate(GreasedRegion land, int factionCount, double controlledFraction) {
111        factionCount &= 255;
112        width = land.width;
113        height = land.height;
114        MultiSpill spreader = new MultiSpill(new short[width][height], Measurement.MANHATTAN, rng);
115        Coord.expandPoolTo(width, height);
116        GreasedRegion map = land.copy();
117        //Coord[] centers = map.randomSeparated(0.1, rng, factionCount);
118        int controlled = (int) (map.size() * Math.max(0.0, Math.min(1.0, controlledFraction)));
119        map.randomScatter(rng, (width + height) / 25, factionCount);
120
121        spreader.initialize(land.toChars());
122        OrderedMap<Coord, Double> entries = new OrderedMap<>();
123        entries.put(Coord.get(-1, -1), 0.0);
124        for (int i = 0; i < factionCount; i++) {
125            entries.put(map.nth(i), rng.between(0.5, 1.0));
126        }
127        spreader.start(entries, controlled, null);
128        short[][] sm = spreader.spillMap;
129        politicalMap = new char[width][height];
130        for (int x = 0; x < width; x++) {
131            for (int y = 0; y < height; y++) {
132                politicalMap[x][y] = (sm[x][y] == -1) ? '~' : (sm[x][y] == 0) ? '%' : letters[(sm[x][y] - 1) & 255];
133            }
134        }
135
136        atlas.clear();
137        briefAtlas.clear();
138        spokenLanguages.clear();
139        atlas.put('~', "Ocean");
140        briefAtlas.put('~', "Ocean");
141        spokenLanguages.put('~', Maker.makeList(FakeLanguageGen.ELF));
142        atlas.put('%', "Wilderness");
143        briefAtlas.put('%', "Wilderness");
144        spokenLanguages.put('%', Maker.makeList(FakeLanguageGen.DEMONIC));
145
146        if (factionCount > 0) {
147            Thesaurus th = new Thesaurus(rng.nextLong());
148            for (int i = 0; i < factionCount; i++) {
149                atlas.put(letters[i], th.makeNationName());
150                briefAtlas.put(letters[i], th.latestGenerated);
151                if(th.randomLanguages == null || th.randomLanguages.isEmpty())
152                    spokenLanguages.put(letters[i], Maker.makeList(FakeLanguageGen.randomLanguage(rng)));
153                else
154                    spokenLanguages.put(letters[i], new ArrayList<>(th.randomLanguages));
155            }
156        }
157        return politicalMap;
158    }
159    /**
160     * Produces a political map for the land stored in the given WorldMapGenerator, with the given number
161     * of factions trying to take land in the world (essentially, nations). The output is a 2D char array where each
162     * letter char is tied to a different faction, while '~' is always water, and '%' is always wilderness or unclaimed
163     * land. The amount of unclaimed land is determined by the controlledFraction parameter, which will be clamped
164     * between 0.0 and 1.0, with higher numbers resulting in more land owned by factions and lower numbers meaning more
165     * wilderness. This version uses an existing atlas and does not assign to {@link #spokenLanguages}; it does not
166     * alter the existingAtlas parameter but uses it to determine what should be in this class' {@link #atlas} field.
167     * The atlas field will always contain '~' as the first key in its ordering (with value "Ocean" if no value was
168     * already assigned in existingAtlas to that key), and '%' as the second key (with value "Wilderness" if not already
169     * assigned); later entries will be taken from existingAtlas (not duplicating '~' or '%', but using the rest).
170     * @param wmg a WorldMapGenerator that has produced a map; this gets the land parts of the map to assign claims to,
171     *            including rivers and lakes as part of nations but not oceans
172     * @param existingAtlas a Map (ideally an OrderedMap) of Character keys to be used in the 2D array, to String values
173     *                      that are the names of nations; should not have size greater than 255
174     * @param controlledFraction between 0.0 and 1.0 inclusive; higher means more land has a letter, lower has more '%'
175     * @return a 2D char array where letters represent the claiming faction, '~' is water, and '%' is unclaimed
176     */
177    public char[][] generate(WorldMapGenerator wmg, Map<Character, String> existingAtlas, double controlledFraction) {
178        return generate(new GreasedRegion(wmg.heightCodeData, 4, 999), existingAtlas, controlledFraction);
179    }
180    /**
181     * Produces a political map for the land stored in the "on" cells of the given GreasedRegion, with the given number
182     * of factions trying to take land in the world (essentially, nations). The output is a 2D char array where each
183     * letter char is tied to a different faction, while '~' is always water, and '%' is always wilderness or unclaimed
184     * land. The amount of unclaimed land is determined by the controlledFraction parameter, which will be clamped
185     * between 0.0 and 1.0, with higher numbers resulting in more land owned by factions and lower numbers meaning more
186     * wilderness. This version uses an existing atlas and does not assign to {@link #spokenLanguages}; it does not
187     * alter the existingAtlas parameter but uses it to determine what should be in this class' {@link #atlas} field.
188     * The atlas field will always contain '~' as the first key in its ordering (with value "Ocean" if no value was
189     * already assigned in existingAtlas to that key), and '%' as the second key (with value "Wilderness" if not already
190     * assigned); later entries will be taken from existingAtlas (not duplicating '~' or '%', but using the rest).
191     * @param land a GreasedRegion that stores "on" cells for land and "off" cells for anything un-claimable, like ocean
192     * @param existingAtlas a Map (ideally an OrderedMap) of Character keys to be used in the 2D array, to String values
193     *                      that are the names of nations; should not have size greater than 255
194     * @param controlledFraction between 0.0 and 1.0 inclusive; higher means more land has a letter, lower has more '%'
195     * @return a 2D char array where letters represent the claiming faction, '~' is water, and '%' is unclaimed
196     */
197    public char[][] generate(GreasedRegion land, Map<Character, String> existingAtlas, double controlledFraction) {
198        atlas.clear();
199        briefAtlas.clear();
200        atlas.putAll(existingAtlas);
201        if(atlas.getAndMoveToFirst('%') == null)
202            atlas.putAndMoveToFirst('%', "Wilderness");
203        if(atlas.getAndMoveToFirst('~') == null)
204            atlas.putAndMoveToFirst('~', "Ocean");
205        int factionCount = atlas.size() - 2;
206        briefAtlas.putAll(atlas);
207        width = land.width;
208        height = land.height;
209        MultiSpill spreader = new MultiSpill(new short[width][height], Measurement.MANHATTAN, rng);
210        Coord.expandPoolTo(width, height);
211        GreasedRegion map = land.copy();
212        //Coord[] centers = map.randomSeparated(0.1, rng, factionCount);
213        int controlled = (int) (map.size() * Math.max(0.0, Math.min(1.0, controlledFraction)));
214        map.randomScatter(rng, (width + height) / 25, factionCount);
215
216        spreader.initialize(land.toChars());
217        OrderedMap<Coord, Double> entries = new OrderedMap<>();
218        entries.put(Coord.get(-1, -1), 0.0);
219        for (int i = 0; i < factionCount; i++) {
220            entries.put(map.nth(i), rng.between(0.5, 1.0));
221        }
222        spreader.start(entries, controlled, null);
223        short[][] sm = spreader.spillMap;
224        politicalMap = new char[width][height];
225        for (int x = 0; x < width; x++) {
226            for (int y = 0; y < height; y++) {
227                politicalMap[x][y] = (sm[x][y] == -1) ? '~' : (sm[x][y] == 0) ? '%' : atlas.keyAt((sm[x][y] + 1));
228            }
229        }
230        return politicalMap;
231    }
232    /**
233     * Produces a political map for the land stored in the given WorldMapGenerator, with the given number
234     * of factions trying to take land in the world (essentially, nations). The output is a 2D char array where each
235     * letter char is tied to a different faction, while '~' is always water, and '%' is always wilderness or unclaimed
236     * land. The amount of unclaimed land is determined by the controlledFraction parameter, which will be clamped
237     * between 0.0 and 1.0, with higher numbers resulting in more land owned by factions and lower numbers meaning more
238     * wilderness. This version uses a "recipe for an atlas" instead of a complete atlas; this is an OrderedMap of
239     * Character keys to FakeLanguageGen values, where each key represents a faction and each value is the language to
240     * use to generate names for that faction. This does assign to {@link #spokenLanguages}, but it doesn't change the
241     * actual FakeLanguageGen objects, since they are immutable. It may add some "factions" if not present to represent
242     * oceans and unclaimed land. The atlas field will always contain '~' as the first key in its ordering (with value
243     * "Ocean" if no value was already assigned in existingAtlas to that key, or a random nation name in the language
244     * that was mapped if there is one), and '%' as the second key (with value "Wilderness" if not already assigned, or
245     * a similar random nation name if there is one); later entries will be taken from existingAtlas (not duplicating
246     * '~' or '%', but using the rest).
247     * @param wmg a WorldMapGenerator that has produced a map; this gets the land parts of the map to assign claims to,
248     *            including rivers and lakes as part of nations but not oceans
249     * @param atlasLanguages an OrderedMap of Character keys to be used in the 2D array, to FakeLanguageGen objects that
250     *                       will be used to generate names; should not have size greater than 255
251     * @param controlledFraction between 0.0 and 1.0 inclusive; higher means more land has a letter, lower has more '%'
252     * @return a 2D char array where letters represent the claiming faction, '~' is water, and '%' is unclaimed
253     */
254    public char[][] generate(WorldMapGenerator wmg, OrderedMap<Character, FakeLanguageGen> atlasLanguages, double controlledFraction) {
255        return generate(new GreasedRegion(wmg.heightCodeData, 4, 999), atlasLanguages, controlledFraction);
256    }
257    /**
258     * Produces a political map for the land stored in the "on" cells of the given GreasedRegion, with the given number
259     * of factions trying to take land in the world (essentially, nations). The output is a 2D char array where each
260     * letter char is tied to a different faction, while '~' is always water, and '%' is always wilderness or unclaimed
261     * land. The amount of unclaimed land is determined by the controlledFraction parameter, which will be clamped
262     * between 0.0 and 1.0, with higher numbers resulting in more land owned by factions and lower numbers meaning more
263     * wilderness. This version uses a "recipe for an atlas" instead of a complete atlas; this is an OrderedMap of
264     * Character keys to FakeLanguageGen values, where each key represents a faction and each value is the language to
265     * use to generate names for that faction. This does assign to {@link #spokenLanguages}, but it doesn't change the
266     * actual FakeLanguageGen objects, since they are immutable. It may add some "factions" if not present to represent
267     * oceans and unclaimed land. The atlas field will always contain '~' as the first key in its ordering (with value
268     * "Ocean" if no value was already assigned in existingAtlas to that key, or a random nation name in the language
269     * that was mapped if there is one), and '%' as the second key (with value "Wilderness" if not already assigned, or
270     * a similar random nation name if there is one); later entries will be taken from existingAtlas (not duplicating
271     * '~' or '%', but using the rest).
272     * @param land a GreasedRegion that stores "on" cells for land and "off" cells for anything un-claimable, like ocean
273     * @param atlasLanguages an OrderedMap of Character keys to be used in the 2D array, to FakeLanguageGen objects that
274     *                       will be used to generate names; should not have size greater than 255
275     * @param controlledFraction between 0.0 and 1.0 inclusive; higher means more land has a letter, lower has more '%'
276     * @return a 2D char array where letters represent the claiming faction, '~' is water, and '%' is unclaimed
277     */
278    public char[][] generate(GreasedRegion land, OrderedMap<Character, FakeLanguageGen> atlasLanguages, double controlledFraction) {
279        atlas.clear();
280        briefAtlas.clear();
281        spokenLanguages.clear();
282
283        Thesaurus th = new Thesaurus(rng.nextLong());
284        FakeLanguageGen flg;
285        if((flg = atlasLanguages.get('~')) == null) {
286            atlas.put('~', "Ocean");
287            briefAtlas.put('~', "Ocean");
288            spokenLanguages.put('~', Maker.makeList(FakeLanguageGen.ELF));
289        }
290        else {
291            atlas.put('~', th.makeNationName(flg));
292            briefAtlas.put('~', th.latestGenerated);
293            spokenLanguages.put('~', Maker.makeList(flg));
294        }
295        if((flg = atlasLanguages.get('%')) == null) {
296            atlas.put('%', "Wilderness");
297            briefAtlas.put('%', "Wilderness");
298            spokenLanguages.put('%', Maker.makeList(FakeLanguageGen.DEMONIC));
299        }
300        else {
301            atlas.put('%', th.makeNationName(flg));
302            briefAtlas.put('%', th.latestGenerated);
303            spokenLanguages.put('%', Maker.makeList(flg));
304        }
305
306        for (int i = 0; i < atlasLanguages.size() && i < 256; i++) {
307            Character c = atlasLanguages.keyAt(i);
308            flg = atlasLanguages.getAt(i);
309            atlas.put(c, th.makeNationName(flg));
310            briefAtlas.put(c, th.latestGenerated);
311            spokenLanguages.put(c, Maker.makeList(flg));
312        }
313        int factionCount = atlas.size() - 2;
314        width = land.width;
315        height = land.height;
316        MultiSpill spreader = new MultiSpill(new short[width][height], Measurement.MANHATTAN, rng);
317        Coord.expandPoolTo(width, height);
318        GreasedRegion map = land.copy();
319        //Coord[] centers = map.randomSeparated(0.1, rng, factionCount);
320        int controlled = (int) (map.size() * Math.max(0.0, Math.min(1.0, controlledFraction)));
321        map.randomScatter(rng, (width + height) / 25, factionCount);
322
323        spreader.initialize(land.toChars());
324        OrderedMap<Coord, Double> entries = new OrderedMap<>();
325        entries.put(Coord.get(-1, -1), 0.0);
326        for (int i = 0; i < factionCount; i++) {
327            entries.put(map.nth(i), rng.between(0.5, 1.0));
328        }
329        spreader.start(entries, controlled, null);
330        short[][] sm = spreader.spillMap;
331        politicalMap = new char[width][height];
332        for (int x = 0; x < width; x++) {
333            for (int y = 0; y < height; y++) {
334                politicalMap[x][y] = atlas.keyAt(sm[x][y] + 1);
335            }
336        }
337        return politicalMap;
338    }
339}