001package squidpony;
002
003import regexodus.*;
004import squidpony.squidmath.*;
005
006import java.io.Serializable;
007import java.util.ArrayList;
008import java.util.Collection;
009import java.util.Iterator;
010import java.util.Map;
011
012import static squidpony.Maker.makeList;
013import static squidpony.Maker.makeOM;
014
015/**
016 * A text processing class that can swap out occurrences of words and replace them with their synonyms.
017 * Created by Tommy Ettinger on 5/23/2016.
018 */
019public class Thesaurus implements Serializable{
020    private static final long serialVersionUID = 3387639905758074640L;
021    protected static final Pattern wordMatch = Pattern.compile("([\\pL`]+|@)"),
022            similarFinder = Pattern.compile(".*?\\b(\\w\\w\\w\\w).*?{\\@1}.*$", "ui");
023    public OrderedMap<CharSequence, GapShuffler<String>> mappings;
024    public ArrayList<FakeLanguageGen.Alteration> alterations = new ArrayList<>(4);
025    public SilkRNG rng;
026    protected GapShuffler<String> plantTermShuffler, fruitTermShuffler, nutTermShuffler, flowerTermShuffler,
027            potionTermShuffler;
028    public FakeLanguageGen defaultLanguage = FakeLanguageGen.SIMPLISH;
029    public transient ArrayList<FakeLanguageGen> randomLanguages = new ArrayList<>(2);
030    public transient String latestGenerated = "Nationia";
031    /**
032     * Constructs a new Thesaurus with an unseeded RNG used to shuffle word order.
033     */
034    public Thesaurus()
035    {
036        mappings = new OrderedMap<>(256, Hashers.caseInsensitiveStringHasher);
037        rng = new SilkRNG();
038        addKnownCategories();
039    }
040
041    /**
042     * Constructs a new Thesaurus, seeding its RNG (used to shuffle word order) with the next long from the given RNG.
043     * @param rng an RNG that will only be used to get one long (for seeding this class' RNG)
044     */
045    public Thesaurus(IRNG rng)
046    {
047        this(rng.nextLong());
048    }
049
050    /**
051     * Constructs a new Thesaurus, seeding its RNG (used to shuffle word order) with shuffleSeed.
052     * @param shuffleSeed a long for seeding this class' RNG
053     */
054    public Thesaurus(long shuffleSeed)
055    {
056        mappings = new OrderedMap<>(256, Hashers.caseInsensitiveStringHasher);
057        this.rng = new SilkRNG(shuffleSeed);
058        addKnownCategories();
059    }
060
061
062    /**
063     * Constructs a new Thesaurus, seeding its RNG (used to shuffle word order) with shuffleSeed.
064     * @param shuffleSeed a String for seeding this class' RNG
065     */
066    public Thesaurus(String shuffleSeed)
067    {
068        this(CrossHash.hash64(shuffleSeed));
069    }
070
071    /**
072     * Changes the sequences for all groups of synonyms this can produce, effectively turning this Thesaurus into a
073     * different version that knows all the same synonyms and categories but will produce different results.
074     * @param state any long; will be given to {@link SilkRNG#setState(long)}
075     */
076    public void refresh(long state)
077    {
078        refresh((int)(state & 0xFFFFFFFFL), (int)(state >>> 32));
079    }
080
081    /**
082     * Changes the sequences for all groups of synonyms this can produce, effectively turning this Thesaurus into a
083     * different version that knows all the same synonyms and categories but will produce different results.
084     * @param stateA any int; the first part of the state of a {@link SilkRNG}
085     * @param stateB any int; the second part of the state of a {@link SilkRNG}
086     */
087    public void refresh(int stateA, int stateB)
088    {
089        this.rng.setState(stateA, stateB);
090        final int sz = mappings.size();
091        for (int i = 0; i < sz; i++) {
092            mappings.getAt(i).setRNG(rng, true);
093        }
094    }
095
096    public Thesaurus addReplacement(CharSequence before, String after)
097    {
098        mappings.put(before, new GapShuffler<>(after));
099        return this;
100    }
101
102    /**
103     * Allows this Thesaurus to replace a specific keyword, typically containing multiple backtick characters
104     * ({@code `}) so it can't be confused with a "real word," with one of the words in synonyms (chosen in shuffled
105     * order). The backtick is the only punctuation character that this class' word matcher considers part of a word,
106     * both for this reason and because it is rarely used in English text.
107     * @param keyword a word (typically containing backticks, {@code `}) that will be replaced by a word from synonyms
108     * @param synonyms a Collection of lower-case Strings with similar meaning and the same part of speech
109     * @return this for chaining
110     */
111    public Thesaurus addCategory(String keyword, Collection<String> synonyms)
112    {
113        if(synonyms.isEmpty())
114            return this;
115        GapShuffler<String> shuffler = new GapShuffler<>(synonyms, rng, true);
116        mappings.put(keyword, shuffler);
117        return this;
118    }
119
120    /**
121     * Adds several pre-made categories to this Thesaurus' known categories, but won't cause it to try to replace normal
122     * words with synonyms (only categories, which contain backticks in the name). The keywords this currently knows,
123     * and the words it will replace those keywords with, are:
124     * <br>
125     * <ul>
126     *     <li>calm`adj` : calm, harmonious, peaceful, placid, pleasant, serene, tranquil</li>
127     *     <li>calm`noun` : calm, harmony, kindness, peace, serenity, tranquility</li>
128     *     <li>org`noun` : association, brotherhood, fellowship, foundation, fraternity, group, guild, order, partnership</li>
129     *     <li>org`nouns` : associations, brotherhoods, fellowships, foundations, fraternities, groups, guilds, orders, partnerships</li>
130     *     <li>empire`adj` : ascendant, dynastic, emir's, hegemonic, imperial, king's, lordly, monarchic, prince's, regal, royal, sultan's</li>
131     *     <li>empire`noun` : ascendancy, commonwealth, dominion, dynasty, emirate, empire, hegemony, imperium, kingdom, monarchy, sultanate, triumvirate</li>
132     *     <li>empire`nouns` : ascendancies, commonwealths, dominions, dynasties, emirates, empires, hegemonies, imperia, kingdoms, monarchies, sultanates, triumvirates</li>
133     *     <li>emperor`noun` : emir, emperor, king, lord, pharaoh, ruler, sultan</li>
134     *     <li>emperor`nouns` : emirs, emperors, kings, lords, pharaohs, rulers, sultans</li>
135     *     <li>empress`noun` : emira, empress, lady, pharaoh, queen, ruler, sultana</li>
136     *     <li>empress`nouns` : emiras, empresses, ladies, pharaohs, queens, rulers, sultanas</li>
137     *     <li>union`adj` : allied, associated, confederated, congressional, democratic, federated, independent, people's, unified, united</li>
138     *     <li>union`noun` : alliance, coalition, confederacy, confederation, congress, faction, federation, league, republic, union</li>
139     *     <li>union`nouns` : alliances, coalitions, confederacies, confederations, congresses, factions, federations, leagues, republics, unions</li>
140     *     <li>militia`noun` : fighters, front, irregulars, liberators, militants, militia, rebellion, resistance, warriors</li>
141     *     <li>militia`nouns` : fighters, fronts, irregulars, liberators, militants, militias, rebellions, resistances, warriors</li>
142     *     <li>gang`noun` : cartel, crew, gang, mafia, mob, posse, syndicate</li>
143     *     <li>gang`nouns` : cartels, crews, gangs, mafias, mobs, posses, syndicates</li>
144     *     <li>duke`noun` : baron, duke, earl, fief, lord, shogun</li>
145     *     <li>duke`nouns` : barons, dukes, earls, fiefs, lords, shoguns</li>
146     *     <li>duchy`noun` : barony, duchy, earldom, fiefdom, lordship, shogunate</li>
147     *     <li>duchy`nouns` : baronies, duchies, earldoms, fiefdoms, lordships, shogunates</li>
148     *     <li>magical`adj` : arcane, enchanted, ensorcelled, magical, mystical, sorcerous</li>
149     *     <li>holy`adj` : auspicious, blessed, divine, godly, holy, prophetic, sacred, virtuous</li>
150     *     <li>priest`noun` : bishop, cardinal, chaplain, cleric, preacher, priest</li>
151     *     <li>priest`nouns` : bishops, cardinals, chaplains, clergy, preachers, priests</li>
152     *     <li>unholy`adj` : accursed, bewitched, macabre, occult, profane, unholy, vile</li>
153     *     <li>witch`noun` : cultist, defiler, necromancer, occultist, warlock, witch</li>
154     *     <li>witch`nouns` : cultists, defilers, necromancers, occultists, warlocks, witches</li>
155     *     <li>forest`adj` : bountiful, fertile, lush, natural, primal, verdant</li>
156     *     <li>forest`noun` : copse, forest, glen, greenery, grove, jungle, nature, woodland</li>
157     *     <li>shaman`noun` : animist, druid, shaman, warden</li>
158     *     <li>shaman`nouns` : animists, druids, shamans, wardens</li>
159     *     <li>fancy`adj` : glorious, grand, great, magnanimous, magnificent, majestic, powerful</li>
160     *     <li>evil`adj` : abhorrent, cruel, debased, evil, heinous, horrible, malevolent, nefarious, scurrilous, terrible, vile, wicked</li>
161     *     <li>villain`noun` : blasphemer, evildoer, killer, knave, monster, murderer, villain</li>
162     *     <li>villain`nouns` : blasphemers, evildoers, killers, knaves, monsters, murderers, villains</li>
163     *     <li>monster`noun` : abomination, beast, creature, demon, devil, fiend, ghoul, monster</li>
164     *     <li>monsters`nouns` : abominations, beasts, creatures, demons, devils, fiends, ghouls, monsters</li>
165     *     <li>good`adj` : compassionate, flawless, good, kind, moral, perfect, pure, righteous</li>
166     *     <li>lethal`adj` : bloodstained, cutthroat, deadly, fatal, lethal, murderous, poisonous, silent, stalking, venomous</li>
167     *     <li>lethal`noun` : assassin, blood, killer, murder, ninja, poison, razor, silence, slayer, snake, tiger, venom</li>
168     *     <li>blade`noun` : axe, blade, cutlass, flail, glaive, halberd, hammer, hatchet, katana, knife, lance, mace, maul, nunchaku, saber, scimitar, scythe, sickle, spear, stiletto, sword, trident, whip</li>
169     *     <li>bow`noun` : atlatl, bolas, bow, crossbow, dagger, javelin, longbow, net, shortbow, shuriken, sling</li>
170     *     <li>weapon`noun` : atlatl, axe, blade, bolas, bow, crossbow, cutlass, dagger, flail, glaive, halberd, hammer, hatchet, javelin, katana, knife, lance, longbow, mace, maul, net, nunchaku, saber, scimitar, scythe, shortbow, shuriken, sickle, sling, spear, stiletto, sword, trident, whip</li>
171     *     <li>musket`noun` : arquebus, blunderbuss, cannon, flintlock, matchlock, musket, wheellock</li>
172     *     <li>grenade`noun` : bomb, explosive, flamethrower, grenade, missile, rocket, warhead</li>
173     *     <li>rifle`noun` : firearm, handgun, longarm, pistol, rifle, shotgun</li>
174     *     <li>blade`nouns` : axes, blades, cutlasses, flails, glaives, halberds, hammers, hatchets, katana, knives, lances, maces, mauls, nunchaku, sabers, scimitars, scythes, sickles, spears, stilettos, swords, tridents, whips</li>
175     *     <li>bow`nouns` : atlatls, bolases, bows, crossbows, daggers, javelins, longbows, nets, shortbows, shuriken, slings</li>
176     *     <li>weapon`nouns` : atlatls, axes, blades, bolases, bows, crossbows, cutlasses, daggers, flails, glaives, halberds, hammers, hatchets, javelins, katana, knives, lances, longbows, maces, mauls, nets, nunchaku, sabers, scimitars, scythes, shortbows, shuriken, sickles, slings, spears, stilettos, swords, tridents, whips</li>
177     *     <li>musket`nouns` : arquebusses, blunderbusses, cannons, flintlocks, matchlocks, muskets, wheellocks</li>
178     *     <li>grenade`nouns` : bombs, explosives, flamethrowers, grenades, missiles, rockets, warheads</li>
179     *     <li>rifle`nouns` : firearms, handguns, longarms, pistols, rifles, shotguns</li>
180     *     <li>scifi`adj` : genetic, gravitational, laser, nanoscale, phase, photonic, plasma, quantum, tachyonic, warp</li>
181     *     <li>tech`adj` : crypto, cyber, digital, electronic, hacker, mechanical, servo, techno, turbo</li>
182     *     <li>sole`adj` : final, last, singular, sole, total, true, ultimate</li>
183     *     <li>light`adj` : bright, gleaming, glowing, luminous, lunar, radiant, shimmering, solar, stellar</li>
184     *     <li>light`noun` : dawn, gleam, glow, light, moon, radiance, shimmer, star, sun, torch</li>
185     *     <li>light`nouns` : glimmers, lights, moons, stars, suns, torches</li>
186     *     <li>shadow`noun` : blackness, darkness, gloom, murk, shadow, twilight</li>
187     *     <li>shadow`nouns` : blackness, darkness, gloom, murk, shadows, twilight</li>
188     *     <li>fire`noun` : blaze, conflagration, fire, flame, inferno, pyre</li>
189     *     <li>fire`nouns` : blazes, conflagrations, fires, flames, infernos, pyres</li>
190     *     <li>ice`noun` : blizzard, chill, cold, frost, ice, snow</li>
191     *     <li>ice`nouns` : blizzards, chills, cold, frosts, ice, snow</li>
192     *     <li>lightning`noun` : lightning, shock, spark, storm, thunder, thunderbolt</li>
193     *     <li>lightning`nouns` : lightning, shocks, sparks, storms, thunder, thunderbolts</li>
194     *     <li>ground`noun` : clay, dirt, earth, loam, mud, peat, sand, soil</li>
195     *     <li>lake`noun` : bog, fen, glade, lake, pond, puddle, sea, swamp</li>
196     *     <li>leaf`noun` : bark, blossom, branch, bud, cress, flower, leaf, root, sap, seed, shoot, stalk, stem, thorn, twig, vine, wood, wort</li>
197     *     <li>fruit`noun` : apple, banana, berry, cherry, date, fig, fruit, grape, juniper, lime, mango, melon, papaya, peach, pear, quince</li>
198     *     <li>nut`noun` : almond, bean, cashew, chestnut, hazelnut, nut, pea, peanut, pecan, walnut</li>
199     *     <li>flower`noun` : amaryllis, camellia, chrysanthemum, daisy, dandelion, flower, gardenia, hibiscus, jasmine, lantana, lilac, lily, lotus, mallow, oleander, orchid, peony, petunia, phlox, rose, tulip</li>
200     *     <li>tree`noun` : alder, beech, birch, cactus, cedar, elm, hazel, juniper, larch, magnolia, mangrove, maple, oak, palm, pine, tree, willow</li>
201     *     <li>flavor`noun` : acid, grease, herb, salt, smoke, spice, sugar</li>
202     *     <li>flavor`adj` : bitter, salty, savory, smoky, sour, spicy, sweet</li>
203     *     <li>color`adj` : black, blue, brown, gray, green, orange, red, violet, white, yellow</li>
204     *     <li>shape`adj` : delicate, drooping, fibrous, fragile, giant, hardy, hollow, long, miniature, spiny, stiff, stubby, sturdy, thorny, tufted, yielding</li>
205     *     <li>sensory`adj` : aromatic, fragrant, fuzzy, glossy, pungent, rough, rustling, smooth, soft, weeping</li>
206     *     <li>liquid`noun` : brew, broth, elixir, fluid, liquid, potion, serum, tonic</li>
207     *     <li>liquid`adj` : bubbling, congealing, effervescent, milky, murky, slimy, sloshing, swirling, thick</li>
208     *     <li>bottle`noun` : bottle, canister, flagon, flask, jug, phial, vial</li>
209     *     <li>bottle`adj` : brown glass, clear glass, curvaceous glass, dull pewter, fluted crystal, green glass, rough-cut glass, sharp-edged tin, shining silver, smoky glass, tarnished silver</li>
210     *     <li>calabash`adj` : calabash, hollow gourd, milk carton, waterskin, wineskin</li>
211     *     <li>smart`adj` : aware, brilliant, clever, cunning, genius, mindful, smart, wise</li>
212     *     <li>smart`noun` : acumen, awareness, cunning, genius, knowledge, mindfulness, smarts, wisdom</li>
213     *     <li>stupid`adj` : careless, complacent, dull, dumb, foolish, idiotic, moronic, reckless, sloppy, stupid</li>
214     *     <li>stupid`noun` : carelessness, complacency, foolishness, idiocy, recklessness, sloppiness, stupidity</li>
215     *     <li>bandit`noun` : bandit, brigand, highwayman, pirate, raider, rogue, thief</li>
216     *     <li>bandit`nouns` : bandits, brigands, highwaymen, pirates, raiders, rogues, thieves</li>
217     *     <li>soldier`noun` : combatant, fighter, mercenary, soldier, trooper, warrior</li>
218     *     <li>soldier`nouns` : combatants, fighters, mercenaries, soldiers, troops, warriors</li>
219     *     <li>guard`noun` : defender, guard, guardian, knight, paladin, protector, sentinel, shield, templar, warden, watchman</li>
220     *     <li>guard`nouns` : defenders, guardians, guards, knights, paladins, protectors, sentinels, shields, templars, wardens, watchmen</li>
221     *     <li>hunter`noun` : hunter, poacher, stalker, tracker, trapper, warden</li>
222     *     <li>explorer`noun` : explorer, nomad, pathfinder, questant, seeker, wanderer</li>
223     *     <li>hunter`nouns` : hunters, poachers, stalkers, trackers, trappers, wardens</li>
224     *     <li>explorer`nouns` : explorers, nomads, pathfinders, questants, seekers, wanderers</li>
225     *     <li>rage`noun` : anger, frenzy, fury, rage, vengeance, wrath</li>
226     *     <li>ominous`adj` : baleful, fateful, foreboding, ominous, portentous</li>
227     *     <li>many`adj` : countless, infinite, manifold, many, myriad, thousandfold, unlimited</li>
228     *     <li>impossible`adj` : abominable, forbidden, impossible, incomprehensible, indescribable, ineffable, unearthly, unspeakable</li>
229     *     <li>gaze`noun` : eye, gaze, observation, purveyance, stare, watch</li>
230     *     <li>pain`noun` : agony, excruciation, misery, pain, torture</li>
231     *     <li>god`noun` : deity, father, god, king, lord, lordship, ruler</li>
232     *     <li>goddess`noun` : deity, goddess, lady, ladyship, mother, queen, ruler</li>
233     *     <li>hero`noun` : champion, crusader, hero, knight, savior</li>
234     *     <li>heroes`nouns` : champions, crusaders, heroes, knights, saviors</li>
235     *     <li>heroine`noun` : champion, crusader, heroine, knight, maiden, savior</li>
236     *     <li>heroines`nouns` : champions, crusaders, heroines, knights, maidens, saviors</li>
237     *     <li>popular`adj` : adored, beloved, revered, worshipped</li>
238     *     <li>unpopular`adj` : despised, hated, loathed, reviled</li>
239     *     <li>glyph`noun` : glyph, mark, seal, sigil, sign, symbol</li>
240     *     <li>glyph`nouns` : glyphs, marks, seals, sigils, signs, symbols</li>
241     *     <li>power`noun` : authority, dominance, force, potency, power, strength</li>
242     *     <li>power`adj` : authoritative, dominant, forceful, potent, powerful, strong</li>
243     * </ul>
244     * There are also terms, which typically produce multiple words procedurally and may use {@link #defaultLanguage}.
245     * See {@link #makePlantName()}, {@link #makeFruitName()}, {@link #makeNutName()}, {@link #makeFlowerName()}, and
246     * {@link #makePotionDescription()} for more info and examples.
247     * <li>
248     *     <li>plant`term` : @'s color`adj`     leaf`noun`, @'s color`adj` flower`noun`, @'s color`adj` tree`noun`, @'s flower`noun`, @'s ground`noun`  leaf`noun`, @'s sensory`adj`-leaf`noun`, @'s shape`adj` flower`noun`, @'s tree`noun`, color`adj` flower`noun`, color`adj` flower`noun` of @, color`adj` fruit`noun` tree`noun`, color`adj` nut`noun` tree`noun`, color`adj`-leaf`noun` flower`noun`, flavor`adj` fruit`noun` tree`noun`, flavor`adj` nut`noun` tree`noun`, flavor`noun` leaf`noun` tree`noun`, flower`noun` of @, ground`noun`  flower`noun`, ground`noun`      leaf`noun`, ground`noun`        leaf`noun` of @, leaf`noun` of @, sensory`adj` flower`noun` of @, sensory`adj` flower`noun`-flower`noun`, sensory`adj` tree`noun` of @, sensory`adj` tree`noun`-tree`noun`, sensory`adj`-leaf`noun` flower`noun`, sensory`adj`-leaf`noun` tree`noun`, shape`adj` flower`noun`, shape`adj`-fruit`noun` tree`noun`, shape`adj`-leaf`noun` flower`noun`, shape`adj`-leaf`noun` tree`noun`</li>
249     *     <li>fruit`term` : @'s color`adj` fruit`noun`, @'s flavor`adj` fruit`noun`, @'s fruit`noun`, color`adj` fruit`noun`-fruit`noun`, flavor`adj` fruit`noun`-fruit`noun`, fruit`noun` of @</li>
250     *     <li>nut`term` : @'s color`adj` nut`noun`, @'s flavor`adj` nut`noun`, @'s nut`noun`, color`adj` nut`noun`, color`adj` nut`noun` of @, flavor`adj` nut`noun`, nut`noun` of @, sensory`adj` nut`noun`</li>
251     *     <li>flower`term` : @'s color`adj` flower`noun`, @'s flower`noun`, @'s shape`adj` flower`noun`, color`adj` flower`noun`, color`adj` flower`noun` of @, color`adj`-leaf`noun` flower`noun`, flower`noun` of @, ground`noun`    flower`noun`, sensory`adj` flower`noun` of @, sensory`adj` flower`noun`-flower`noun`, sensory`adj`-leaf`noun` flower`noun`, shape`adj` flower`noun`, shape`adj`-leaf`noun` flower`noun`</li>
252     *     <li>potion`term` : a bottle`adj` bottle`noun` containing a few drops of a color`adj` liquid`noun`, a bottle`adj` bottle`noun` filled with a color`adj` liquid`noun`, a bottle`adj` bottle`noun` filled with a liquid`adj` color`adj` liquid`noun`, a bottle`adj` bottle`noun` half-filled with a liquid`adj` color`adj` liquid`noun`, a calabash`adj` filled with a color`adj` liquid`noun`</li>
253     * </ul>
254     * Capitalizing the first letter in the keyword where it appears in text you call process() on will capitalize the
255     * first letter of the produced fake word. Capitalizing the second letter will capitalize the whole produced fake
256     * word. This applies only per-instance of each keyword; it won't change the internally-stored list of words.
257     * @return this for chaining
258     */
259    public Thesaurus addKnownCategories()
260    {
261        for(Map.Entry<String, ArrayList<String>> kv : categories.entrySet())
262        {
263            addCategory(kv.getKey(), kv.getValue());
264        }
265        plantTermShuffler =  mappings.get("plant`term`");
266        fruitTermShuffler =  mappings.get("fruit`term`");
267        nutTermShuffler =    mappings.get("nut`term`");  
268        flowerTermShuffler = mappings.get("flower`term`");
269        potionTermShuffler = mappings.get("potion`term`");
270        return this;
271    }
272
273    /**
274     * Given an archive String saved by {@link #archiveCategories()} (probably from another version of SquidLib), this
275     * makes the Thesaurus class act like it did in that archive, assuming the {@link #rng} is seeded the same. This
276     * modifies the {@link #categories}, {@link #adjective}, {@link #noun}, and {@link #nouns} static fields, so it can
277     * affect other Thesaurus objects produced later (it won't change previously-made ones, probably).
278     * @param archive an archived String of categories produced by {@link #archiveCategories()}
279     * @return this Thesaurus, but static state of the class will also be modified so this may affect other Thesaurus objects
280     */
281    public Thesaurus addArchivedCategories(String archive){
282        final OrderedMap<String, ArrayList<String>> cat = Converters.convertOrderedMap(
283                Converters.convertString,
284                Converters.convertArrayList(Converters.convertString)
285        ).restore(archive);
286        
287        categories.clear();
288        categories.putAll(cat);
289        
290        adjective.clear();
291        adjective.putAll(cat);
292        
293        noun.clear();
294        noun.putAll(cat);
295        
296        nouns.clear();
297        nouns.putAll(cat);
298        
299        Iterator<String> it = adjective.keySet().iterator();
300        while (it.hasNext()){
301            if(!it.next().contains("`adj`"))
302                it.remove();
303        }
304        it = noun.keySet().iterator();
305        while (it.hasNext()){
306            if(!it.next().contains("`noun`"))
307                it.remove();
308        }
309        it = nouns.keySet().iterator();
310        while (it.hasNext()){
311            if(!it.next().contains("`nouns`"))
312                it.remove();
313        }
314
315        for(Map.Entry<String, ArrayList<String>> kv : categories.entrySet())
316        {
317            addCategory(kv.getKey(), kv.getValue());
318        }
319        plantTermShuffler =  mappings.get("plant`term`");
320        fruitTermShuffler =  mappings.get("fruit`term`");
321        nutTermShuffler =    mappings.get("nut`term`");
322        flowerTermShuffler = mappings.get("flower`term`");
323        potionTermShuffler = mappings.get("potion`term`");
324
325        return this;
326
327    }
328
329    /**
330     * Adds a large list of words pre-generated by FakeLanguageGen and hand-picked for fitness, and makes them
331     * accessible with a keyword based on the language. The keywords this currently knows:
332     * <br>
333     * "lovecraft`pre`", "english`pre`", "greek_romanized`pre`",
334     * "greek_authentic`pre`", "french`pre`", "russian_romanized`pre`",
335     * "russian_authentic`pre`", "japanese_romanized`pre`", "swahili`pre`",
336     * "somali`pre`", "hindi_romanized`pre`", "arabic_romanized`pre`",
337     * "inuktitut`pre`", "norse`pre`", "nahuatl`pre`", "mongolian`pre`",
338     * "fantasy`pre`", "fancy_fantasy`pre`", "goblin`pre`", "elf`pre`",
339     * "demonic`pre`", "infernal`pre`", "simplish`pre`", "alien_a`pre`",
340     * "korean_romanized`pre`", "alien_e`pre`", "alien_i`pre`", "alien_o`pre`",
341     * "alien_u`pre`", "dragon`pre`", "kobold`pre`", "insect`pre`", "maori`pre`",
342     * "spanish`pre`", "deep_speech`pre`", "norse_simplified`pre`",
343     * "hletkip`pre`", "ancient_egyptian`pre`", "crow`pre`", "imp`pre`",
344     * "malay`pre`", "celestial`pre`", "chinese_romanized`pre`",
345     * "cherokee_romanized`pre`", "vietnamese`pre`"
346     * <br>
347     * These correspond to similarly-named fields in {@link FakeLanguageGen}, just without {@code `pre`}; for instance
348     * {@code "cherokee_romanized`pre`"} corresponds to {@link FakeLanguageGen#CHEROKEE_ROMANIZED}. You can use these
349     * same keywords with {@code `gen`} instead of {@code `pre`} to generate at runtime based on the current RNG state,
350     * instead of using one of several pre-generated words, and doing that does not require addFakeWords() to be used.
351     * <br>
352     * Capitalizing the first letter in the keyword where it appears in text you call process() on will capitalize the
353     * first letter of the produced fake word, which is often desirable for things like place names. Capitalizing the
354     * second letter will capitalize the whole produced fake word. This applies only per-instance of each keyword; it
355     * won't change the internally-stored list of words.
356     * @return this for chaining
357     */
358    public Thesaurus addFakeWords()
359    {
360        //long state = rng.getState();
361        for(Map.Entry<CharSequence, FakeLanguageGen> kv : languages.entrySet())
362        {
363            ArrayList<String> words = new ArrayList<>(16);
364            for (int i = 0; i < 16; i++) {
365                words.add(kv.getValue().word(rng, false, rng.between(2, 4)));
366            }
367            addCategory(StringKit.replace(kv.getKey(), "`gen", "`pre"), words);
368        }
369        //rng.setState(state);
370        return this;
371    }
372
373    private StringBuilder modify(CharSequence text)
374    {
375        Matcher m;
376        StringBuilder sb = new StringBuilder(text);
377        Replacer.StringBuilderBuffer tb, working = Replacer.wrap(sb);
378        StringBuilder tmp;
379        boolean found;
380        FakeLanguageGen.Alteration alt;
381        for (int a = 0; a < alterations.size(); a++) {
382            alt = alterations.get(a);
383            tmp = working.sb;
384            tb = Replacer.wrap(new StringBuilder(tmp.length()));
385            m = alt.replacer.getPattern().matcher(tmp);
386
387            found = false;
388            while (true) {
389                if (rng.nextDouble() < alt.chance) {
390                    if (!Replacer.replaceStep(m, alt.replacer.getSubstitution(), tb))
391                        break;
392                    found = true;
393                } else {
394                    if (!m.find())
395                        break;
396                    found = true;
397                    m.getGroup(MatchResult.PREFIX, tb);
398                    m.getGroup(MatchResult.MATCH, tb);
399                    m.setTarget(m, MatchResult.SUFFIX);
400                }
401            }
402            if (found) {
403                m.getGroup(MatchResult.TARGET, tb);
404                working = tb;
405            }
406        }
407        return working.sb;
408
409    }
410    /**
411     * Given a String, StringBuilder, or other CharSequence that should contain words this knows synonyms for, this
412     * replaces each occurrence of such a known word with one of its synonyms, leaving unknown words untouched. Words
413     * that were learned together as synonyms with addSynonyms() will be replaced in such a way that an individual
414     * replacement word should not occur too close to a previous occurrence of the same word; that is, replacing the
415     * text "You fiend! You demon! You despoiler of creation; devil made flesh!", where "fiend", "demon", and "devil"
416     * are all synonyms, would never produce a string that contained "fiend" as the replacement for all three of those.
417     * @param text a CharSequence, such as a String, that contains words in the source language
418     * @return a String of the translated text.
419     */
420    public String process(CharSequence text)
421    {
422//        Replacer rep = wordMatch.replacer(new SynonymSubstitution());
423        //String t = rep.replace(text);
424        Matcher m = wordMatch.matcher(text);
425        Substitution substitution = new SynonymSubstitution();
426        Replacer.StringBuilderBuffer dest = Replacer.wrap(new StringBuilder(text.length()));
427        while (m.find()) {
428            if (m.start() > 0) m.getGroup(MatchResult.PREFIX, dest);
429            substitution.appendSubstitution(m, dest);
430            m.setTarget(m, MatchResult.SUFFIX);
431        }
432        m.getGroup(MatchResult.TARGET, dest);
433
434        m.setTarget(dest.sb);
435        dest.sb.setLength(0);
436        while (m.find()) {
437            if (m.start() > 0) m.getGroup(MatchResult.PREFIX, dest);
438            substitution.appendSubstitution(m, dest);
439            m.setTarget(m, MatchResult.SUFFIX);
440        }
441        m.getGroup(MatchResult.TARGET, dest);
442
443        if(alterations.isEmpty())
444            return StringKit.replace(StringKit.correctABeforeVowel(dest.sb), "\t", "");
445        else
446            return StringKit.replace(modify(StringKit.correctABeforeVowel(dest.sb)), "\t", "");
447    }
448
449    public String lookup(String word)
450    {
451        if(word.isEmpty())
452            return word;
453        if("@".equals(word))
454        {
455            return defaultLanguage.word(rng, true, rng.between(2, 4));
456        }
457        String word2 = word.toLowerCase();
458        if(mappings.containsKey(word2))
459        {
460            String nx = mappings.get(word2).next();
461            if(nx.isEmpty())
462                return nx;
463            if(word.length() > 1 && Category.Lu.contains(word.charAt(1)))
464                return nx.toUpperCase();
465            if(Category.Lu.contains(word.charAt(0)))
466            {
467                return Character.toUpperCase(nx.charAt(0)) + nx.substring(1);
468            }
469            return nx;
470        }
471        else if(languages.containsKey(word2))
472        {
473            if(word.length() > 1 && Category.Lu.contains(word.charAt(1)))
474                return languages.get(word2).word(rng, false, rng.between(2, 4)).toUpperCase();
475            if(Category.Lu.contains(word.charAt(0)))
476            {
477                return languages.get(word2).word(rng, true, rng.between(2, 4));
478            }
479            return languages.get(word2).word(rng, false, rng.between(2, 4));
480        }
481        return word;
482    }
483
484    private class SynonymSubstitution implements Substitution
485    {
486        private final StringBuilder temp = new StringBuilder(64);
487        @Override
488        public void appendSubstitution(MatchResult match, TextBuffer dest) {
489            //dest.append(lookup(match.group(0)));
490            temp.setLength(0);
491            match.getGroup(0, temp);
492            writeLookup(dest, temp);
493        }
494    }
495
496    private void writeLookup(TextBuffer dest, StringBuilder word) {
497        if(word == null || word.length() <= 0)
498            return;
499        if(word.charAt(0) == '@' && word.length() == 1)
500        {
501            dest.append(defaultLanguage.word(rng, true, rng.between(2, 4)));
502            return;
503        }
504        else if(mappings.containsKey(word))
505        {
506            String nx = mappings.get(word).next();
507            if(nx.isEmpty())
508                return;
509            if(word.length() > 1 && Category.Lu.contains(word.charAt(1)))
510            {
511                dest.append(nx.toUpperCase());
512                return;
513            }
514            if(Category.Lu.contains(word.charAt(0)))
515            {
516                dest.append(Character.toUpperCase(nx.charAt(0)));
517                dest.append(nx.substring(1));
518                return;
519            }
520            dest.append(nx);
521            return;
522        }
523        else if(languages.containsKey(word))
524        {
525            if(word.length() > 1 && Category.Lu.contains(word.charAt(1)))
526            {
527                dest.append(languages.get(word).word(rng, false, rng.between(2, 4)).toUpperCase());
528            }
529            else if(Category.Lu.contains(word.charAt(0)))
530            {
531                dest.append(languages.get(word).word(rng, true, rng.between(2, 4)));
532            }
533            else
534            {
535                dest.append(languages.get(word).word(rng, false, rng.between(2, 4)));
536            }
537            return;
538        }
539        else if(numbers.containsKey(word))
540        {
541            if(word.length() > 1 && Category.Lu.contains(word.charAt(1)))
542            {
543                dest.append(numberWordInRange(2, numbers.get(word)).toUpperCase());
544            }
545            else if(Category.Lu.contains(word.charAt(0)))
546            {
547                String w = numberWordInRange(2, numbers.get(word));
548                dest.append(Character.toUpperCase(w.charAt(0)));
549                dest.append(w.substring(1));
550            }
551            else
552            {
553                dest.append(numberWordInRange(2, numbers.get(word)));
554            }
555            return;
556        }
557        else if(numberAdjectives.containsKey(word))
558        {
559            if(word.length() > 1 && Category.Lu.contains(word.charAt(1)))
560            {
561                dest.append(numberAdjectiveInRange(2, numberAdjectives.get(word)).toUpperCase());
562            }
563            else if(Category.Lu.contains(word.charAt(0)))
564            {
565                String w = numberAdjectiveInRange(2, numberAdjectives.get(word));
566                dest.append(Character.toUpperCase(w.charAt(0)));
567                dest.append(w.substring(1));
568            }
569            else
570            {
571                dest.append(numberAdjectiveInRange(2, numberAdjectives.get(word)));
572            }
573            return;
574        }
575        if(dest instanceof Replacer.StringBuilderBuffer)
576        {
577            ((Replacer.StringBuilderBuffer)dest).sb.append(word);
578        }
579        else
580            dest.append(word.toString());
581
582    }
583
584    private class RandomLanguageSubstitution implements Substitution
585    {
586        @Override
587        public void appendSubstitution(MatchResult match, TextBuffer dest) {
588            FakeLanguageGen lang = FakeLanguageGen.randomLanguage(rng.nextLong());
589            randomLanguages.add(lang);
590            if(match.isCaptured(1))
591            {
592                lang = FakeLanguageGen.randomLanguage(rng.nextLong());
593                randomLanguages.add(lang);
594                do {
595                    latestGenerated = randomLanguages.get(0).word(rng, true, Math.min(rng.between(2, 5), rng.between(1, 5)))
596                            + "-" + randomLanguages.get(1).word(rng, true, Math.min(rng.between(2, 5), rng.between(1, 5)));
597                }while (latestGenerated.length() <= 5 || latestGenerated.length() >= 17);
598                dest.append(latestGenerated);
599            }
600            else
601            {
602                do{
603                    latestGenerated = lang.word(rng, true, Math.min(rng.between(2, 5), rng.between(1, 5)));
604                }while (latestGenerated.length() <= 2 || latestGenerated.length() >= 11);
605                dest.append(latestGenerated);
606            }
607        }
608    }
609
610    private class KnownLanguageSubstitution implements Substitution
611    {
612        public FakeLanguageGen language;
613        public KnownLanguageSubstitution(FakeLanguageGen lang)
614        {
615            language = lang;
616        }
617        @Override
618        public void appendSubstitution(MatchResult match, TextBuffer dest) {
619            if (match.isCaptured(1)) {
620                do
621                {
622                    latestGenerated = language.word(rng, true, Math.min(rng.between(2, 5), rng.between(1, 5))) +
623                            "-" + language.word(rng, true, Math.min(rng.between(2, 5), rng.between(1, 5)));
624                }while (latestGenerated.length() <= 5 || latestGenerated.length() >= 17);
625                dest.append(latestGenerated);
626            } else
627            {
628                do{
629                    latestGenerated = language.word(rng, true, Math.min(rng.between(2, 5), rng.between(1, 5)));
630                }while (latestGenerated.length() <= 2 || latestGenerated.length() >= 11);
631                dest.append(latestGenerated);
632            }
633        }
634    }
635
636    /**
637     * Generates a random possible name for a nation, such as "Iond-Gouccief Alliance" or "The Last Drayo Commonwealth".
638     * May use accented characters, as in
639     * "Thùdshù-Hyóttiálb Hegemony" or "The Glorious Chô Empire"; if you want to strip these out and replace accented
640     * chars with their un-accented counterparts, you can use {@link FakeLanguageGen#removeAccents(CharSequence)}, which
641     * returns a CharSequence that can be converted to String if needed. Shortly after calling this method, but before
642     * calling it again, you can retrieve the generated random languages, if any were used while making nation names, by
643     * getting the FakeLanguageGen elements of this class' {@link #randomLanguages} field. Using one of these
644     * FakeLanguageGen objects, you can produce many more words with a similar style to the nation name, like "Drayo" in
645     * "The Last Drayo Commonwealth". If more than one language was used in the nation name, as in "Thùdshù-Hyóttiálb
646     * Hegemony", you will have two languages in randomLanguages, so here "Thùdshù" would be generated by the first
647     * language, and "Hyóttiálb" by the second language. Calling this method replaces the current contents of
648     * randomLanguages, so if you want to use those languages, get them while you can. This also assigns the
649     * {@link #latestGenerated} field to contain the part of the nation name without any larger titles; in the case of
650     * "The Glorious Chô Empire", the latestGenerated field would be assigned "Chô" at the same time the longer name
651     * would be returned. This field will be reassigned if this method is called again.
652     *
653     * @return a random name for a nation or a loose equivalent to a nation, as a String
654     */
655    public String makeNationName()
656    {
657        if(!this.mappings.containsKey("empire`noun`"))
658        {
659            addKnownCategories();
660        }
661        String working = process(rng.getRandomElement(nationTerms));
662        int frustration = 0;
663        while (frustration++ < 8 && similarFinder.matches(working))
664            working = process(rng.getRandomElement(nationTerms));
665        randomLanguages.clear();
666        RandomLanguageSubstitution sub = new RandomLanguageSubstitution();
667        Replacer replacer = Pattern.compile("@(-@)?").replacer(sub);
668        return replacer.replace(working);
669    }
670    /**
671     * Generates a random possible name for a nation, such as "Iond-Gouccief Alliance" or "The Last Drayo Commonwealth",
672     * with the FakeLanguageGen already available instead of randomly created. May use accented characters, as in
673     * "Thùdshù Hegemony" or "The Glorious Chô Empire",
674     * if the given language can produce them; if you want to strip these out and replace accented chars
675     * with their un-accented counterparts, you can use {@link FakeLanguageGen#removeAccents(CharSequence)}, which
676     * returns a CharSequence that can be converted to String if needed, or simply get an accent-less language by
677     * calling {@link FakeLanguageGen#removeAccents()} on the FakeLanguageGen you would give this. This assigns the
678     * {@link #latestGenerated} field to contain the part of the nation name without any larger titles; in the case of
679     * "The Glorious Chô Empire", the latestGenerated field would be assigned "Chô" at the same time the longer name
680     * would be returned. This field will be reassigned if this method is called again.
681     * <br>
682     * Some nation names use a hyphenated pairing of what would normally be names in two different languages; if one of
683     * those names is produced by this it will produce two names in the same linguistic style. The randomLanguages field
684     * is not populated by this method; it is assumed that since you are passing this a FakeLanguageGen, you already
685     * have the one you want to use anyway.
686     *
687     * @param language a FakeLanguageGen that will be used to construct any non-English names
688     * @return a random name for a nation or a loose equivalent to a nation, as a String
689     */
690    public String makeNationName(FakeLanguageGen language)
691    {
692        if(!this.mappings.containsKey("empire`noun`"))
693        {
694            addKnownCategories();
695        }
696        String working = process(rng.getRandomElement(nationTerms));
697        int frustration = 0;
698        while (frustration++ < 8 && similarFinder.matches(working))
699            working = process(rng.getRandomElement(nationTerms));
700        randomLanguages.clear();
701        KnownLanguageSubstitution sub = new KnownLanguageSubstitution(language);
702        Replacer replacer = Pattern.compile("@(-@)?").replacer(sub);
703        return replacer.replace(working);
704    }
705
706    /**
707     * Generates a random possible name for a plant or tree, such as "Ikesheha's maple" or "sugarleaf birch".
708     * May use accented characters, as in "Emôa's greenwood", if the given language can
709     * produce them; if you want to strip these out and replace accented chars with their un-accented counterparts, you
710     * can use {@link FakeLanguageGen#removeAccents(CharSequence)}, which returns a CharSequence that can be converted
711     * to String if needed. Shortly after calling this method, but before
712     * calling it again, you can retrieve the generated random languages, if any were used while making names, by
713     * getting the FakeLanguageGen elements of this class' {@link #randomLanguages} field. Using one of these
714     * FakeLanguageGen objects, you can produce many more words with a similar style to the person or place name, like
715     * "Drayo" in "The Last Drayo Commonwealth". Calling this method replaces the current contents of
716     * randomLanguages, so if you want to use those languages, get them while you can.
717     *
718     * @return a random name for a plant, shrub, or tree, as a String
719     */
720    public String makePlantName()
721    {
722        if(!this.mappings.containsKey("tree`noun`"))
723        {
724            addKnownCategories();
725        }
726        String working = process(plantTermShuffler.next());
727        int frustration = 0;
728        while (frustration++ < 8 && similarFinder.matches(working))
729            working = process(plantTermShuffler.next());
730        randomLanguages.clear();
731        RandomLanguageSubstitution sub = new RandomLanguageSubstitution();
732        Replacer replacer = Pattern.compile("@(-@)?").replacer(sub);
733        return replacer.replace(working).replace("\t", "");
734    }
735    /**
736     * Generates a random possible name for a plant or tree, such as "Ikesheha's maple" or "sugarleaf birch",
737     * with the FakeLanguageGen already available instead of randomly created. May use accented characters, as in
738     * "Emôa's greenwood", if the given language can
739     * produce them; if you want to strip these out and replace accented chars with their un-accented counterparts, you
740     * can use {@link FakeLanguageGen#removeAccents(CharSequence)}, which
741     * returns a CharSequence that can be converted to String if needed, or simply get an accent-less language by
742     * calling {@link FakeLanguageGen#removeAccents()} on the FakeLanguageGen you would give this.
743     *
744     * @param language a FakeLanguageGen that will be used to construct any non-English names
745     * @return a random name for a plant, shrub, or tree, as a String
746     */
747    public String makePlantName(FakeLanguageGen language)
748    {
749        if(!this.mappings.containsKey("tree`noun`"))
750        {
751            addKnownCategories();
752        }
753        String working = process(plantTermShuffler.next());
754        int frustration = 0;
755        while (frustration++ < 8 && similarFinder.matches(working))
756            working = process(plantTermShuffler.next());
757        randomLanguages.clear();
758        KnownLanguageSubstitution sub = new KnownLanguageSubstitution(language);
759        Replacer replacer = Pattern.compile("@(-@)?").replacer(sub);
760        return replacer.replace(working).replace("\t", "");
761    }
762
763    /**
764     * Generates a random possible name for a plant or tree, such as "green lime-melon" or "Ung's date".
765     * May use accented characters, as in "Emôa's greenwood", if the given language can
766     * produce them; if you want to strip these out and replace accented chars with their un-accented counterparts, you
767     * can use {@link FakeLanguageGen#removeAccents(CharSequence)}, which returns a CharSequence that can be converted
768     * to String if needed. Shortly after calling this method, but before
769     * calling it again, you can retrieve the generated random languages, if any were used while making names, by
770     * getting the FakeLanguageGen elements of this class' {@link #randomLanguages} field. Using one of these
771     * FakeLanguageGen objects, you can produce many more words with a similar style to the person or place name, like
772     * "Drayo" in "The Last Drayo Commonwealth". Calling this method replaces the current contents of
773     * randomLanguages, so if you want to use those languages, get them while you can.
774     *
775     * @return a random name for a plant, shrub, or tree, as a String
776     */
777    public String makeFruitName()
778    {
779        if(!this.mappings.containsKey("fruit`noun`"))
780        {
781            addKnownCategories();
782        }
783        String working = process(fruitTermShuffler.next());
784        int frustration = 0;
785        while (frustration++ < 8 && similarFinder.matches(working))
786            working = process(fruitTermShuffler.next());
787        randomLanguages.clear();
788        RandomLanguageSubstitution sub = new RandomLanguageSubstitution();
789        Replacer replacer = Pattern.compile("@(-@)?").replacer(sub);
790        return replacer.replace(working).replace("\t", "");
791    }
792    /**
793     * Generates a random possible name for a plant or tree, such as "green lime-melon" or "Ung's date",
794     * with the FakeLanguageGen already available instead of randomly created. May use accented characters, as in
795     * "Emôa's greenwood", if the given language can
796     * produce them; if you want to strip these out and replace accented chars with their un-accented counterparts, you
797     * can use {@link FakeLanguageGen#removeAccents(CharSequence)}, which
798     * returns a CharSequence that can be converted to String if needed, or simply get an accent-less language by
799     * calling {@link FakeLanguageGen#removeAccents()} on the FakeLanguageGen you would give this.
800     *
801     * @param language a FakeLanguageGen that will be used to construct any non-English names
802     * @return a random name for a plant, shrub, or tree, as a String
803     */
804    public String makeFruitName(FakeLanguageGen language)
805    {
806        if(!this.mappings.containsKey("fruit`noun`"))
807        {
808            addKnownCategories();
809        }
810        String working = process(fruitTermShuffler.next());
811        int frustration = 0;
812        while (frustration++ < 8 && similarFinder.matches(working))
813            working = process(fruitTermShuffler.next());
814        randomLanguages.clear();
815        KnownLanguageSubstitution sub = new KnownLanguageSubstitution(language);
816        Replacer replacer = Pattern.compile("@(-@)?").replacer(sub);
817        return replacer.replace(working).replace("\t", "");
818    }
819
820    /**
821     * Generates a random possible name for a plant or tree, such as "nut of Gikoim" or "Pelyt's cashew".
822     * May use accented characters, as in "Emôa's greenwood", if the given language can
823     * produce them; if you want to strip these out and replace accented chars with their un-accented counterparts, you
824     * can use {@link FakeLanguageGen#removeAccents(CharSequence)}, which returns a CharSequence that can be converted
825     * to String if needed. Shortly after calling this method, but before
826     * calling it again, you can retrieve the generated random languages, if any were used while making names, by
827     * getting the FakeLanguageGen elements of this class' {@link #randomLanguages} field. Using one of these
828     * FakeLanguageGen objects, you can produce many more words with a similar style to the person or place name, like
829     * "Drayo" in "The Last Drayo Commonwealth". Calling this method replaces the current contents of
830     * randomLanguages, so if you want to use those languages, get them while you can.
831     *
832     * @return a random name for a plant, shrub, or tree, as a String
833     */
834    public String makeNutName()
835    {
836        if(!this.mappings.containsKey("nut`noun`"))
837        {
838            addKnownCategories();
839        }
840        String working = process(nutTermShuffler.next());
841        int frustration = 0;
842        while (frustration++ < 8 && similarFinder.matches(working))
843            working = process(nutTermShuffler.next());
844        randomLanguages.clear();
845        RandomLanguageSubstitution sub = new RandomLanguageSubstitution();
846        Replacer replacer = Pattern.compile("@(-@)?").replacer(sub);
847        return replacer.replace(working).replace("\t", "");
848    }
849    /**
850     * Generates a random possible name for a plant or tree, such as "nut of Gikoim" or "Pelyt's cashew",
851     * with the FakeLanguageGen already available instead of randomly created.
852     * May use accented characters, as in "Emôa's greenwood", if the given language can
853     * produce them; if you want to strip these out and replace accented chars with their un-accented counterparts, you
854     * can use {@link FakeLanguageGen#removeAccents(CharSequence)}, which
855     * returns a CharSequence that can be converted to String if needed, or simply get an accent-less language by
856     * calling {@link FakeLanguageGen#removeAccents()} on the FakeLanguageGen you would give this.
857     *
858     * @param language a FakeLanguageGen that will be used to construct any non-English names
859     * @return a random name for a plant, shrub, or tree, as a String
860     */
861    public String makeNutName(FakeLanguageGen language)
862    {
863        if(!this.mappings.containsKey("nut`noun`"))
864        {
865            addKnownCategories();
866        }
867        String working = process(nutTermShuffler.next());
868        int frustration = 0;
869        while (frustration++ < 8 && similarFinder.matches(working))
870            working = process(nutTermShuffler.next());
871        randomLanguages.clear();
872        KnownLanguageSubstitution sub = new KnownLanguageSubstitution(language);
873        Replacer replacer = Pattern.compile("@(-@)?").replacer(sub);
874        return replacer.replace(working).replace("\t", "");
875    }
876
877    /**
878     * Generates a random possible name for a plant or tree, such as "tulip of Jirui" or "Komert's thorny lilac".
879     * May use accented characters, as in "Emôa's greenwood", if the given language can
880     * produce them; if you want to strip these out and replace accented chars with their un-accented counterparts, you
881     * can use {@link FakeLanguageGen#removeAccents(CharSequence)}, which returns a CharSequence that can be converted
882     * to String if needed. Shortly after calling this method, but before
883     * calling it again, you can retrieve the generated random languages, if any were used while making names, by
884     * getting the FakeLanguageGen elements of this class' {@link #randomLanguages} field. Using one of these
885     * FakeLanguageGen objects, you can produce many more words with a similar style to the person or place name, like
886     * "Drayo" in "The Last Drayo Commonwealth". Calling this method replaces the current contents of
887     * randomLanguages, so if you want to use those languages, get them while you can.
888     *
889     * @return a random name for a plant, shrub, or tree, as a String
890     */
891    public String makeFlowerName()
892    {
893        if(!this.mappings.containsKey("flower`noun`"))
894        {
895            addKnownCategories();
896        }
897        String working = process(flowerTermShuffler.next());
898        int frustration = 0;
899        while (frustration++ < 8 && similarFinder.matches(working))
900            working = process(flowerTermShuffler.next());
901        randomLanguages.clear();
902        RandomLanguageSubstitution sub = new RandomLanguageSubstitution();
903        Replacer replacer = Pattern.compile("@(-@)?").replacer(sub);
904        return replacer.replace(working).replace("\t", "");
905    }
906    /**
907     * Generates a random possible name for a plant or tree, such as "tulip of Jirui" or "Komert's thorny lilac",
908     * with the FakeLanguageGen already available instead of randomly created. May use accented characters, as in
909     * "Emôa's greenwood", if the given language can
910     * produce them; if you want to strip these out and replace accented chars with their un-accented counterparts, you
911     * can use {@link FakeLanguageGen#removeAccents(CharSequence)}, which
912     * returns a CharSequence that can be converted to String if needed, or simply get an accent-less language by
913     * calling {@link FakeLanguageGen#removeAccents()} on the FakeLanguageGen you would give this.
914     *
915     * @param language a FakeLanguageGen that will be used to construct any non-English names
916     * @return a random name for a plant, shrub, or tree, as a String
917     */
918    public String makeFlowerName(FakeLanguageGen language)
919    {
920        if(!this.mappings.containsKey("flower`noun`"))
921        {
922            addKnownCategories();
923        }
924        String working = process(flowerTermShuffler.next());
925        int frustration = 0;
926        while (frustration++ < 8 && similarFinder.matches(working))
927            working = process(flowerTermShuffler.next());
928        randomLanguages.clear();
929        KnownLanguageSubstitution sub = new KnownLanguageSubstitution(language);
930        Replacer replacer = Pattern.compile("@(-@)?").replacer(sub);
931        return replacer.replace(working).replace("\t", "");
932    }
933
934    /**
935     * Generates a random possible description for a potion in a container, such as "a smoky glass flask containing a
936     * few drops of an orange tonic", "a milk carton filled with a red fluid", "a shining silver bottle filled with an
937     * effervescent violet potion", or "a wineskin filled with a black serum".
938     *
939     * @return a random description for a potion or other liquid in a container
940     */
941    public String makePotionDescription()
942    {
943        if(!this.mappings.containsKey("liquid`noun`"))
944        {
945            addKnownCategories();
946        }
947        String working = process(potionTermShuffler.next());
948        int frustration = 0;
949        while (frustration++ < 8 && similarFinder.matches(working))
950            working = process(potionTermShuffler.next());
951        return StringKit.correctABeforeVowel(working);
952    }
953
954    /**
955     * Gets an English word for a given number, if this knows it. These words are known from 0 ("zero") to 20
956     * ("twenty"), as well as some higher numbers. If a word isn't known for a number, this returns the number as a
957     * String, such as "537" or "-1".
958     * @param number the number to get a word for
959     * @return the word associated with a number as a String, or if none is known, {@code String.valueOf(number)}.
960     */
961    public static String numberWord(final int number)
962    {
963        switch (number){
964            case 0: return "zero";
965            case 1: return "one";
966            case 2: return "two";
967            case 3: return "three";
968            case 4: return "four";
969            case 5: return "five";
970            case 6: return "six";
971            case 7: return "seven";
972            case 8: return "eight";
973            case 9: return "nine";
974            case 10: return "ten";
975            case 11: return "eleven";
976            case 12: return "twelve";
977            case 13: return "thirteen";
978            case 14: return "fourteen";
979            case 15: return "fifteen";
980            case 16: return "sixteen";
981            case 17: return "seventeen";
982            case 18: return "eighteen";
983            case 19: return "nineteen";
984            case 20: return "twenty";
985            case 30: return "thirty";
986            case 40: return "fourty";
987            case 50: return "fifty";
988            case 60: return "sixty";
989            case 70: return "seventy";
990            case 80: return "eighty";
991            case 90: return "ninety";
992            case 100: return "hundred";
993            case 1000: return "thousand";
994            case 1000000: return "million";
995            case 1000000000: return "billion";
996            default: return String.valueOf(number);
997        }
998    }
999
1000    /**
1001     * Gets an English word that describes a numbered position in some sequence, if this knows it (such as "second" or
1002     * "eleventh"). These words are known from 1 ("first") to 20 ("twentieth"), as well as some higher numbers. If a
1003     * word isn't known for a number, this appends a suffix based on the last base-10 digit to the number, as with "0th"
1004     * or "42nd".
1005     * @param number the number to get a position adjective for
1006     * @return the word associated with a number as a String, or if none is known, {@code String.valueOf(number)} followed by a two-char suffix from the last digit.
1007     */
1008    public static String numberAdjective(final int number)
1009    {
1010        switch (number){
1011            case 1: return "first";
1012            case 2: return "second";
1013            case 3: return "third";
1014            case 4: return "fourth";
1015            case 5: return "fifth";
1016            case 6: return "sixth";
1017            case 7: return "seventh";
1018            case 8: return "eighth";
1019            case 9: return "ninth";
1020            case 10: return "tenth";
1021            case 11: return "eleventh";
1022            case 12: return "twelfth";
1023            case 13: return "thirteenth";
1024            case 14: return "fourteenth";
1025            case 15: return "fifteenth";
1026            case 16: return "sixteenth";
1027            case 17: return "seventeenth";
1028            case 18: return "eighteenth";
1029            case 19: return "nineteenth";
1030            case 20: return "twentieth";
1031            case 30: return "thirtieth";
1032            case 40: return "fourtieth";
1033            case 50: return "fiftieth";
1034            case 60: return "sixtieth";
1035            case 70: return "seventieth";
1036            case 80: return "eightieth";
1037            case 90: return "ninetieth";
1038            case 100: return "hundredth";
1039            case 1000: return "thousandth";
1040            case 1000000: return "millionth";
1041            case 1000000000: return "billionth";
1042            default: 
1043            {
1044                switch (number % 10){
1045                    case 1: return number + "st";
1046                    case 2: return number + "nd";
1047                    case 3: return number + "rd";
1048                    default:return number + "th";
1049                }
1050            }
1051        }
1052    }
1053
1054    /**
1055     * Gets an English word for a number, if this knows it, where the number is chosen randomly between lowest and
1056     * highest, both inclusive. These words are known from 0 ("zero") to 20 ("twenty"), as well as some higher numbers.
1057     * If a word isn't known for a number, this returns the number as a String, such as "537" or "-1".
1058     * @param lowest the lower bound for numbers this can choose, inclusive
1059     * @param highest the upper bound for numbers this can choose, inclusive
1060     * @return a String word for a number in the given range, such as "six" or "eleven", if it is known
1061     */
1062    public String numberWordInRange(int lowest, int highest)
1063    {
1064        return numberWord(rng.nextSignedInt(highest + 1 - lowest) + lowest);
1065    }
1066    /**
1067     * Gets an English word that describes a numbered position in some sequence, if this knows it (such as "second" or
1068     * "eleventh"), where the number is chosen randomly between lowest and highest, both inclusive. These words are
1069     * known from 1 ("first") to 20 ("twentieth"), as well as some output for other numbers (such as "42nd" or "23rd").
1070     * @param lowest the lower bound for numbers this can choose, inclusive
1071     * @param highest the upper bound for numbers this can choose, inclusive
1072     * @return a String word for a number in the given range, such as "third" or "twelfth", if it is known
1073     */
1074    public String numberAdjectiveInRange(int lowest, int highest)
1075    {
1076        return numberAdjective(rng.nextSignedInt(highest + 1 - lowest) + lowest);
1077    }
1078    
1079    private static final ArrayList<String> nationTerms = Maker.makeList(
1080            "Union`adj` Union`noun` of @", "Union`adj` @ Union`noun`", "@ Union`noun`", "@ Union`noun`", "@-@ Union`noun`", "Union`adj` Union`noun` of @",
1081            "Union`adj` Duchy`nouns` of @",  "The @ Duchy`noun`", "The Fancy`adj` @ Duchy`noun`", "The Sole`adj` @ Empire`noun`",
1082            "@ Empire`noun`", "@ Empire`noun`", "@ Empire`noun`", "@-@ Empire`noun`", "The Fancy`adj` @ Empire`noun`", "The Fancy`adj` @ Empire`noun`", "The Holy`adj` @ Empire`noun`");
1083
1084    private static final ArrayList<String> plantTerms = Maker.makeList(
1085            "@'s color`adj`\tleaf`noun`",
1086            "@'s tree`noun`",
1087            "@'s color`adj` tree`noun`",
1088            "@'s flower`noun`",
1089            "@'s shape`adj` flower`noun`",
1090            "@'s color`adj` flower`noun`",
1091            "flower`noun` of @",
1092            "leaf`noun` of @",
1093            "@'s ground`noun`\tleaf`noun`",
1094            "ground`noun`\tleaf`noun` of @",
1095            "sensory`adj` tree`noun` of @",
1096            "sensory`adj` flower`noun` of @",
1097            "color`adj` flower`noun` of @",
1098            "@'s sensory`adj`-leaf`noun`",
1099            "ground`noun`\tleaf`noun`",
1100
1101            "shape`adj` flower`noun`",
1102            "color`adj` flower`noun`",
1103            "flavor`noun`\tleaf`noun` tree`noun`",
1104            "flavor`adj` fruit`noun` tree`noun`",
1105            "flavor`adj` nut`noun` tree`noun`",
1106            "color`adj` fruit`noun` tree`noun`",
1107            "color`adj` nut`noun` tree`noun`",
1108            "shape`adj`-fruit`noun` tree`noun`",
1109            "shape`adj`-leaf`noun` tree`noun`",
1110            "sensory`adj` tree`noun`-tree`noun`",
1111            "sensory`adj`-leaf`noun` tree`noun`",
1112            "color`adj`-leaf`noun` flower`noun`",
1113            "shape`adj`-leaf`noun` flower`noun`",
1114            "sensory`adj` flower`noun`-flower`noun`",
1115            "sensory`adj`-leaf`noun` flower`noun`",
1116            "ground`noun`\tflower`noun`"
1117    );
1118    private static final ArrayList<String> fruitTerms = Maker.makeList(
1119            "fruit`noun` of @",
1120            "@'s fruit`noun`",
1121            "@'s flavor`adj` fruit`noun`",
1122            "@'s color`adj` fruit`noun`",
1123            "flavor`adj` fruit`noun`-fruit`noun`",
1124            "color`adj` fruit`noun`-fruit`noun`"
1125    );
1126    private static final ArrayList<String> nutTerms = Maker.makeList(
1127            "nut`noun` of @",
1128            "color`adj` nut`noun` of @",
1129            "@'s nut`noun`",
1130            "@'s flavor`adj` nut`noun`",
1131            "@'s color`adj` nut`noun`",
1132            "flavor`adj` nut`noun`",
1133            "color`adj` nut`noun`",
1134            "sensory`adj` nut`noun`"
1135            );
1136    private static final ArrayList<String> flowerTerms = Maker.makeList(
1137            "flower`noun` of @",
1138            "sensory`adj` flower`noun` of @",
1139            "color`adj` flower`noun` of @",
1140            "@'s flower`noun`",
1141            "@'s shape`adj` flower`noun`",
1142            "@'s color`adj` flower`noun`",
1143            "shape`adj` flower`noun`",
1144            "color`adj` flower`noun`",
1145            "color`adj`-leaf`noun` flower`noun`",
1146            "shape`adj`-leaf`noun` flower`noun`",
1147            "sensory`adj` flower`noun`-flower`noun`",
1148            "sensory`adj`-leaf`noun` flower`noun`",
1149            "ground`noun`\tflower`noun`"
1150        );
1151    private static final ArrayList<String> potionTerms = Maker.makeList(
1152            "a bottle`adj` bottle`noun` filled with a liquid`adj` color`adj` liquid`noun`",
1153            "a bottle`adj` bottle`noun` filled with a color`adj` liquid`noun`",
1154            "a calabash`adj` filled with a color`adj` liquid`noun`",
1155            "a bottle`adj` bottle`noun` half-filled with a liquid`adj` color`adj` liquid`noun`",
1156            "a bottle`adj` bottle`noun` containing a few drops of a color`adj` liquid`noun`"
1157        );
1158    public static final OrderedMap<String, ArrayList<String>> categories = makeOM(
1159            "calm`adj`",
1160            makeList("harmonious", "peaceful", "pleasant", "serene", "placid", "tranquil", "calm"),
1161            "calm`noun`",
1162            makeList("harmony", "peace", "kindness", "serenity", "tranquility", "calm"),
1163            "org`noun`",
1164            makeList("fraternity", "brotherhood", "order", "group", "foundation", "association", "guild", "fellowship", "partnership"),
1165            "org`nouns`",
1166            makeList("fraternities", "brotherhoods", "orders", "groups", "foundations", "associations", "guilds", "fellowships", "partnerships"),
1167            "empire`adj`",
1168            makeList("imperial", "prince's", "king's", "sultan's", "regal", "dynastic", "royal", "hegemonic", "monarchic", "ascendant", "emir's", "lordly"),
1169            "empire`noun`",
1170            makeList("empire", "emirate", "kingdom", "sultanate", "dominion", "dynasty", "imperium", "hegemony", "triumvirate", "ascendancy", "monarchy", "commonwealth"),
1171            "empire`nouns`",
1172            makeList("empires", "emirates", "kingdoms", "sultanates", "dominions", "dynasties", "imperia", "hegemonies", "triumvirates", "ascendancies", "monarchies", "commonwealths"),
1173            "emperor`noun`",
1174            makeList("emperor", "emir", "king", "sultan", "lord", "ruler", "pharaoh"),
1175            "emperor`nouns`",
1176            makeList("emperors", "emirs", "kings", "sultans", "lords", "rulers", "pharaohs"),
1177            "empress`noun`",
1178            makeList("empress", "emira", "queen", "sultana", "lady", "ruler", "pharaoh"),
1179            "empress`nouns`",
1180            makeList("empresses", "emiras", "queens", "sultanas", "ladies", "rulers", "pharaohs"),
1181            "union`adj`",
1182            makeList("united", "allied", "people's", "confederated", "federated", "congressional", "independent", "associated", "unified", "democratic"),
1183            "union`noun`",
1184            makeList("union", "alliance", "coalition", "confederation", "federation", "congress", "confederacy", "league", "faction", "republic"),
1185            "union`nouns`",
1186            makeList("unions", "alliances", "coalitions", "confederations", "federations", "congresses", "confederacies", "leagues", "factions", "republics"),
1187            "militia`noun`",
1188            makeList("rebellion", "resistance", "militia", "liberators", "warriors", "fighters", "militants", "front", "irregulars"),
1189            "militia`nouns`",
1190            makeList("rebellions", "resistances", "militias", "liberators", "warriors", "fighters", "militants", "fronts", "irregulars"),
1191            "gang`noun`",
1192            makeList("gang", "syndicate", "mob", "crew", "posse", "mafia", "cartel"),
1193            "gang`nouns`",
1194            makeList("gangs", "syndicates", "mobs", "crews", "posses", "mafias", "cartels"),
1195            "duke`noun`",
1196            makeList("duke", "earl", "baron", "fief", "lord", "shogun"),
1197            "duke`nouns`",
1198            makeList("dukes", "earls", "barons", "fiefs", "lords", "shoguns"),
1199            "duchy`noun`",
1200            makeList("duchy", "earldom", "barony", "fiefdom", "lordship", "shogunate"),
1201            "duchy`nouns`",
1202            makeList("duchies", "earldoms", "baronies", "fiefdoms", "lordships", "shogunates"),
1203            "magical`adj`",
1204            makeList("arcane", "enchanted", "sorcerous", "ensorcelled", "magical", "mystical"),
1205            "holy`adj`",
1206            makeList("auspicious", "divine", "holy", "sacred", "prophetic", "blessed", "godly", "virtuous"),
1207            "priest`noun`",
1208            makeList("priest", "bishop", "chaplain", "cleric", "cardinal", "preacher"),
1209            "priest`nouns`",
1210            makeList("priests", "bishops", "chaplains", "clergy", "cardinals", "preachers"),
1211            "unholy`adj`",
1212            makeList("bewitched", "occult", "unholy", "macabre", "accursed", "profane", "vile"),
1213            "witch`noun`",
1214            makeList("witch", "warlock", "necromancer", "cultist", "occultist", "defiler"),
1215            "witch`nouns`",
1216            makeList("witches", "warlocks", "necromancers", "cultists", "occultists", "defilers"),
1217            "forest`adj`",
1218            makeList("natural", "primal", "verdant", "lush", "fertile", "bountiful"),
1219            "forest`noun`",
1220            makeList("nature", "forest", "greenery", "jungle", "woodland", "grove", "copse", "glen"),
1221            "shaman`noun`",
1222            makeList("shaman", "druid", "warden", "animist"),
1223            "shaman`nouns`",
1224            makeList("shamans", "druids", "wardens", "animists"),
1225            "fancy`adj`",
1226            makeList("grand", "glorious", "magnificent", "magnanimous", "majestic", "great", "powerful"),
1227            "evil`adj`",
1228            makeList("heinous", "scurrilous", "terrible", "horrible", "debased", "wicked", "evil", "malevolent", "nefarious", "vile", "cruel", "abhorrent"),
1229            "villain`noun`",
1230            makeList("villain", "knave", "evildoer", "killer", "blasphemer", "monster", "murderer"),
1231            "villain`nouns`",
1232            makeList("villains", "knaves", "evildoers", "killers", "blasphemers", "monsters", "murderers"),
1233            "monster`noun`",
1234            makeList("fiend", "abomination", "demon", "devil", "ghoul", "monster", "beast", "creature"),
1235            "monsters`nouns`",
1236            makeList("fiends", "abominations", "demons", "devils", "ghouls", "monsters", "beasts", "creatures"),
1237            "good`adj`",
1238            makeList("righteous", "moral", "good", "pure", "compassionate", "flawless", "perfect", "kind"),
1239            "lethal`adj`",
1240            makeList("silent", "lethal", "deadly", "fatal", "venomous", "cutthroat", "murderous", "bloodstained", "stalking", "poisonous"),
1241            "lethal`noun`",
1242            makeList("silence", "killer", "assassin", "ninja", "venom", "poison", "snake", "murder", "blood", "razor", "tiger", "slayer"),
1243            "blade`noun`", // really any melee weapon
1244            makeList("blade", "knife", "sword", "axe", "stiletto", "katana", "scimitar", "hatchet", "spear", "glaive", "halberd",
1245                    "hammer", "maul", "flail", "mace", "sickle", "scythe", "whip", "lance", "nunchaku", "saber", "cutlass", "trident"),
1246            "bow`noun`", // really any medieval or earlier ranged weapon
1247            makeList("bow", "longbow", "shortbow", "crossbow", "sling", "atlatl", "bolas", "javelin", "net", "shuriken", "dagger"),
1248            "weapon`noun`", // any medieval or earlier weapon (not including firearms or newer)
1249            makeList("blade", "knife", "sword", "axe", "stiletto", "katana", "scimitar", "hatchet", "spear", "glaive", "halberd",
1250                    "hammer", "maul", "flail", "mace", "sickle", "scythe", "whip", "lance", "nunchaku", "saber", "cutlass", "trident",
1251                    "bow", "longbow", "shortbow", "crossbow", "sling", "atlatl", "bolas", "javelin", "net", "shuriken", "dagger"),
1252            "musket`noun`",
1253            makeList("arquebus", "blunderbuss", "musket", "matchlock", "flintlock", "wheellock", "cannon"),
1254            "grenade`noun`",
1255            makeList("rocket", "grenade", "missile", "bomb", "warhead", "explosive", "flamethrower"),
1256            "rifle`noun`",
1257            makeList("pistol", "rifle", "handgun", "firearm", "longarm", "shotgun"),
1258            "blade`nouns`",
1259            makeList("blades", "knives", "swords", "axes", "stilettos", "katana", "scimitars", "hatchets", "spears", "glaives", "halberds",
1260                    "hammers", "mauls", "flails", "maces", "sickles", "scythes", "whips", "lances", "nunchaku", "sabers", "cutlasses", "tridents"),
1261            "bow`nouns`",
1262            makeList("bows", "longbows", "shortbows", "crossbows", "slings", "atlatls", "bolases", "javelins", "nets", "shuriken", "daggers"),
1263            "weapon`nouns`",
1264            makeList("blades", "knives", "swords", "axes", "stilettos", "katana", "scimitars", "hatchets", "spears", "glaives", "halberds",
1265                    "hammers", "mauls", "flails", "maces", "sickles", "scythes", "whips", "lances", "nunchaku", "sabers", "cutlasses", "tridents",
1266                    "bows", "longbows", "shortbows", "crossbows", "slings", "atlatls", "bolases", "javelins", "nets", "shuriken", "daggers"),
1267            "musket`nouns`",
1268            makeList("arquebusses", "blunderbusses", "muskets", "matchlocks", "flintlocks", "wheellocks", "cannons"),
1269            "grenade`nouns`",
1270            makeList("rockets", "grenades", "missiles", "bombs", "warheads", "explosives", "flamethrowers"),
1271            "rifle`nouns`",
1272            makeList("pistols", "rifles", "handguns", "firearms", "longarms", "shotguns"),
1273            "scifi`adj`",
1274            makeList("plasma", "warp", "tachyonic", "phase", "gravitational", "photonic", "nanoscale", "laser", "quantum", "genetic"),
1275            "tech`adj`",
1276            makeList("cyber", "digital", "electronic", "techno", "hacker", "crypto", "turbo", "mechanical", "servo"),
1277            "sole`adj`",
1278            makeList("sole", "true", "singular", "total", "ultimate", "final", "last"),
1279            "light`adj`",
1280            makeList("bright", "glowing", "solar", "stellar", "lunar", "radiant", "luminous", "shimmering", "gleaming"),
1281            "light`noun`",
1282            makeList("light", "glow", "sun", "star", "moon", "radiance", "dawn", "torch", "shimmer", "gleam"),
1283            "light`nouns`",
1284            makeList("lights", "glimmers", "suns", "stars", "moons", "torches"),
1285            "shadow`noun`",
1286            makeList("shadow", "darkness", "gloom", "blackness", "murk", "twilight"),
1287            "shadow`nouns`",
1288            makeList("shadows", "darkness", "gloom", "blackness", "murk", "twilight"),
1289            "fire`noun`",
1290            makeList("fire", "flame", "inferno", "conflagration", "pyre", "blaze"),
1291            "fire`nouns`",
1292            makeList("fires", "flames", "infernos", "conflagrations", "pyres", "blazes"),
1293            "ice`noun`",
1294            makeList("ice", "frost", "snow", "chill", "blizzard", "cold"),
1295            "ice`nouns`",
1296            makeList("ice", "frosts", "snow", "chills", "blizzards", "cold"),
1297            "lightning`noun`",
1298            makeList("lightning", "thunder", "thunderbolt", "storm", "spark", "shock"),
1299            "lightning`nouns`",
1300            makeList("lightning", "thunder", "thunderbolts", "storms", "sparks", "shocks"),
1301            "ground`noun`",
1302            makeList("earth", "sand", "soil", "loam", "dirt", "clay", "mud", "peat"),
1303            "lake`noun`",
1304            makeList("puddle", "pond", "lake", "sea", "swamp", "bog", "fen", "glade"),
1305            "leaf`noun`",
1306            makeList("leaf", "bark", "root", "thorn", "seed", "branch", "twig", "wort", "cress", "flower", "wood", "vine", "sap", "bud", "blossom", "shoot", "stalk", "stem"),
1307            "fruit`noun`",
1308            makeList("fruit", "berry", "apple", "peach", "cherry", "melon", "lime", "fig", "date", "mango", "banana", "juniper", "grape", "papaya", "pear", "quince"),
1309            "nut`noun`",
1310            makeList("nut", "bean", "almond", "peanut", "pecan", "walnut", "cashew", "pea", "chestnut", "hazelnut"),
1311            "flower`noun`",
1312            makeList("flower", "rose", "lilac", "orchid", "peony", "oleander", "chrysanthemum", "amaryllis", "camellia", "mallow", "lily", "gardenia", "daisy", "hibiscus", "dandelion", "jasmine", "lotus", "lantana", "phlox", "petunia", "tulip"),
1313            "tree`noun`",
1314            makeList("tree", "oak", "pine", "juniper", "maple", "beech", "birch", "larch", "willow", "alder", "cedar", "palm", "magnolia", "hazel", "cactus", "mangrove", "elm"),
1315            "flavor`noun`",
1316            makeList("sugar", "spice", "acid", "herb", "salt", "grease", "smoke"),
1317            "flavor`adj`",
1318            makeList("sweet", "spicy", "sour", "bitter", "salty", "savory", "smoky"),
1319            "color`adj`",
1320            makeList("black", "white", "red", "orange", "yellow", "green", "blue", "violet", "gray", "brown"),
1321            "shape`adj`",
1322            makeList("hollow", "tufted", "drooping", "fibrous", "giant", "miniature", "delicate", "hardy", "spiny", "thorny", "fragile", "sturdy", "long", "stubby", "stiff", "yielding"),
1323            "sensory`adj`",
1324            makeList("fragrant", "pungent", "rustling", "fuzzy", "glossy", "weeping", "rough", "smooth", "soft", "aromatic"),
1325            "liquid`noun`",
1326            makeList("liquid", "elixir", "tonic", "fluid", "brew", "broth", "potion", "serum"),
1327            "liquid`adj`",
1328            makeList("bubbling", "effervescent", "swirling", "murky", "thick", "congealing", "sloshing", "slimy", "milky"),
1329            "bottle`noun`",
1330            makeList("bottle", "flask", "vial", "jug", "phial", "flagon", "canister"),
1331            "bottle`adj`",
1332            makeList("clear glass", "smoky glass", "green glass", "brown glass", "fluted crystal", "tarnished silver", "dull pewter", "shining silver", "curvaceous glass", "rough-cut glass", "sharp-edged tin"),
1333            "calabash`adj`",
1334            makeList("hollow gourd", "calabash", "milk carton", "waterskin", "wineskin"),
1335            "smart`adj`",
1336            makeList("brilliant", "smart", "genius", "wise", "clever", "cunning", "mindful", "aware"),
1337            "smart`noun`",
1338            makeList("genius", "wisdom", "cunning", "awareness", "mindfulness", "acumen", "smarts", "knowledge"),
1339            "stupid`adj`",
1340            makeList("stupid", "dumb", "idiotic", "foolish", "reckless", "careless", "sloppy", "dull", "moronic", "complacent"),
1341            "stupid`noun`",
1342            makeList("stupidity", "idiocy", "foolishness", "recklessness", "carelessness", "sloppiness", "complacency"),
1343            "bandit`noun`",
1344            makeList("thief", "raider", "bandit", "rogue", "brigand", "highwayman", "pirate"),
1345            "bandit`nouns`",
1346            makeList("thieves", "raiders", "bandits", "rogues", "brigands", "highwaymen", "pirates"),
1347            "soldier`noun`",
1348            makeList("soldier", "warrior", "fighter", "mercenary", "trooper", "combatant"),
1349            "soldier`nouns`",
1350            makeList("soldiers", "warriors", "fighters", "mercenaries", "troops", "combatants"),
1351            "guard`noun`",
1352            makeList("protector", "guardian", "warden", "defender", "guard", "shield", "sentinel", "watchman", "knight", "paladin", "templar"),
1353            "guard`nouns`",
1354            makeList("protectors", "guardians", "wardens", "defenders", "guards", "shields", "sentinels", "watchmen", "knights", "paladins", "templars"),
1355            "hunter`noun`",
1356            makeList("hunter", "poacher", "trapper", "warden", "stalker", "tracker"),
1357            "explorer`noun`",
1358            makeList("explorer", "pathfinder", "seeker", "questant", "wanderer", "nomad"),
1359            "hunter`nouns`",
1360            makeList("hunters", "poachers", "trappers", "wardens", "stalkers", "trackers"),
1361            "explorer`nouns`",
1362            makeList("explorers", "pathfinders", "seekers", "questants", "wanderers", "nomads"),
1363            "rage`noun`",
1364            makeList("rage", "fury", "anger", "wrath", "frenzy", "vengeance"),
1365            "ominous`adj`",
1366            makeList("ominous", "foreboding", "fateful", "baleful", "portentous"),
1367            "many`adj`",
1368            makeList("many", "myriad", "thousandfold", "infinite", "countless", "unlimited", "manifold"),
1369            "impossible`adj`",
1370            makeList("impossible", "forbidden", "incomprehensible", "ineffable", "unearthly", "abominable", "unspeakable", "indescribable"),
1371            "gaze`noun`",
1372            makeList("eye", "gaze", "stare", "observation", "purveyance", "watch"),
1373            "pain`noun`",
1374            makeList("pain", "agony", "misery", "excruciation", "torture"),
1375            "god`noun`",
1376            makeList("god", "deity", "ruler", "king", "father", "lord", "lordship"),
1377            "goddess`noun`",
1378            makeList("goddess", "deity", "ruler", "queen", "mother", "lady", "ladyship"),
1379            "hero`noun`",
1380            makeList("hero", "champion", "savior", "crusader", "knight"),
1381            "heroes`nouns`",
1382            makeList("heroes", "champions", "saviors", "crusaders", "knights"),
1383            "heroine`noun`",
1384            makeList("heroine", "champion", "savior", "crusader", "knight", "maiden"),
1385            "heroines`nouns`",
1386            makeList("heroines", "champions", "saviors", "crusaders", "knights", "maidens"),
1387            "popular`adj`",
1388            makeList("beloved", "adored", "revered", "worshipped"),
1389            "unpopular`adj`",
1390            makeList("reviled", "despised", "hated", "loathed"),
1391            "glyph`noun`",
1392            makeList("glyph", "sign", "symbol", "sigil", "seal", "mark"),
1393            "glyph`nouns`",
1394            makeList("glyphs", "signs", "symbols", "sigils", "seals", "marks"),
1395            "power`noun`",
1396            makeList("power", "force", "potency", "strength", "authority", "dominance"),
1397            "power`adj`",
1398            makeList("powerful", "forceful", "potent", "strong", "authoritative", "dominant"),
1399            "plant`term`",  plantTerms ,
1400            "fruit`term`",  fruitTerms ,
1401            "nut`term`",    nutTerms   ,
1402            "flower`term`", flowerTerms,
1403            "potion`term`", potionTerms
1404            );
1405    public static final OrderedMap<String, ArrayList<String>>
1406            adjective = new OrderedMap<>(categories),
1407            noun = new OrderedMap<>(categories),
1408            nouns = new OrderedMap<>(categories);
1409    public static final OrderedMap<CharSequence, FakeLanguageGen> languages = new OrderedMap<CharSequence, FakeLanguageGen>(
1410            FakeLanguageGen.registeredNames.length, Hashers.caseInsensitiveStringHasher);
1411    static {
1412        for (int i = 0; i < FakeLanguageGen.registeredNames.length; i++) {
1413            languages.put(FakeLanguageGen.registeredNames[i].replace(' ', '_').toLowerCase() + "`gen`", FakeLanguageGen.registered[i]);
1414        }
1415    }
1416    public static final OrderedMap<CharSequence, Integer> numbers = new OrderedMap<CharSequence, Integer>(
1417            21, Hashers.caseInsensitiveStringHasher
1418    ).putPairs(
1419            "zero`noun`", 0,
1420            "one`noun`", 1,
1421            "two`noun`", 2,
1422            "three`noun`", 3,
1423            "four`noun`", 4,
1424            "five`noun`", 5,
1425            "six`noun`", 6,
1426            "seven`noun`", 7,
1427            "eight`noun`", 8,
1428            "nine`noun`", 9,
1429            "ten`noun`", 10,
1430            "eleven`noun`", 11,
1431            "twelve`noun`", 12,
1432            "thirteen`noun`", 13,
1433            "fourteen`noun`", 14,
1434            "fifteen`noun`", 15,
1435            "sixteen`noun`", 16,
1436            "seventeen`noun`", 17,
1437            "eighteen`noun`", 18,
1438            "nineteen`noun`", 19,
1439            "twenty`noun`", 20
1440    ),
1441            numberAdjectives = new OrderedMap<CharSequence, Integer>(
1442                    21, Hashers.caseInsensitiveStringHasher
1443            ).putPairs(
1444                    "zero`adj`", 0,
1445                    "one`adj`", 1,
1446                    "two`adj`", 2,
1447                    "three`adj`", 3,
1448                    "four`adj`", 4,
1449                    "five`adj`", 5,
1450                    "six`adj`", 6,
1451                    "seven`adj`", 7,
1452                    "eight`adj`", 8,
1453                    "nine`adj`", 9,
1454                    "ten`adj`", 10,
1455                    "eleven`adj`", 11,
1456                    "twelve`adj`", 12,
1457                    "thirteen`adj`", 13,
1458                    "fourteen`adj`", 14,
1459                    "fifteen`adj`", 15,
1460                    "sixteen`adj`", 16,
1461                    "seventeen`adj`", 17,
1462                    "eighteen`adj`", 18,
1463                    "nineteen`adj`", 19,
1464                    "twenty`adj`", 20
1465            );
1466
1467    /**
1468     * Thesaurus preset that changes all text to sound like this speaker: "Desaurus preset dat changez all text to sound
1469     * like dis speakah." You may be familiar with a certain sci-fi game that has orks who sound like this.
1470     */
1471    public static Thesaurus ORK = new Thesaurus("WAAAAAGH!");
1472    static {
1473        ORK.alterations.add(new FakeLanguageGen.Alteration("\\bth", "d"));
1474        ORK.alterations.add(new FakeLanguageGen.Alteration("th", "dd"));
1475        ORK.alterations.add(new FakeLanguageGen.Alteration("er\\b", "ah"));
1476        ORK.alterations.add(new FakeLanguageGen.Alteration("es\\b", "ez"));
1477        ORK.addReplacement("the", "da")
1478                .addReplacement("their", "deyr")
1479                .addReplacement("yes", "ya")
1480                .addReplacement("your", "youse")
1481                .addReplacement("yours", "youses")
1482                .addReplacement("going", "gon'")
1483                .addReplacement("and", "an'")
1484                .addReplacement("to", "*snort*")
1485                .addReplacement("rhythm", "riddim")
1486                .addReplacement("get", "git")
1487                .addReplacement("good", "gud");
1488        
1489        // not related to ORK; this filters out synonyms that aren't in the appropriate list
1490        Iterator<String> it = adjective.keySet().iterator();
1491        while (it.hasNext()){
1492            if(!it.next().contains("`adj`"))
1493                it.remove();
1494        }
1495        it = noun.keySet().iterator();
1496        while (it.hasNext()){
1497            if(!it.next().contains("`noun`"))
1498                it.remove();
1499        }
1500        it = nouns.keySet().iterator();
1501        while (it.hasNext()){
1502            if(!it.next().contains("`nouns`"))
1503                it.remove();
1504        }
1505    }
1506
1507    /**
1508     * Gets a stable (large) String that stores all categories this version of Thesaurus knows, as well as all of the
1509     * words each category includes. This can be useful in conjunction with {@link #addArchivedCategories(String)} to
1510     * load a set of categories stored from an earlier version of SquidLib.
1511     * @return a category archive String that can be passed to {@link #addArchivedCategories(String)}
1512     */
1513    public static String archiveCategories(){
1514        return Converters.convertOrderedMap(
1515                        Converters.convertString,
1516                        Converters.convertArrayList(Converters.convertString)
1517                ).stringify(categories);
1518    }
1519}