initial import
This commit is contained in:
commit
45f4d6e401
24
LICENSE
Normal file
24
LICENSE
Normal file
|
@ -0,0 +1,24 @@
|
|||
This is free and unencumbered software released into the public domain.
|
||||
|
||||
Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||
distribute this software, either in source code form or as a compiled
|
||||
binary, for any purpose, commercial or non-commercial, and by any
|
||||
means.
|
||||
|
||||
In jurisdictions that recognize copyright laws, the author or authors
|
||||
of this software dedicate any and all copyright interest in the
|
||||
software to the public domain. We make this dedication for the benefit
|
||||
of the public at large and to the detriment of our heirs and
|
||||
successors. We intend this dedication to be an overt act of
|
||||
relinquishment in perpetuity of all present and future rights to this
|
||||
software under copyright law.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||
OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
For more information, please refer to <https://unlicense.org>
|
129
README.md
Normal file
129
README.md
Normal file
|
@ -0,0 +1,129 @@
|
|||
# D&D Language Generator
|
||||
|
||||
This package is a fantasy language generator. By defining a number of characteristics about your imagined language -- the graphemes, their relative frequency distributions, the construction of syllables, and so on -- you can generate random but internally consistent gibberish that feels distinct, evocative, and appropriate to your setting.
|
||||
|
||||
## Quick Start
|
||||
|
||||
```
|
||||
>>> from language imported supported_languages
|
||||
>>> common = supported_languages['common']
|
||||
>>> common.word(2)
|
||||
['apsoo', 'nirtoet']
|
||||
>>> common.text()
|
||||
Proitsiiiy be itkif eesof detytaen. Ojaot tyskuaz apsoo nirtoet prenao.
|
||||
>>> commoner = f"{common.Name}"
|
||||
"Quiet" Gushi Murk Lirpusome
|
||||
>>> common.Name.name()
|
||||
{
|
||||
name: ['Gushi', 'Murk'],
|
||||
surname: ['Lirpusome'],
|
||||
adjective: ["Quiet"],
|
||||
}
|
||||
```
|
||||
|
||||
## Supported Languages
|
||||
|
||||
A number of D&D languages are defined, with rules built according to the
|
||||
conventions established by my D&D group over several years of play in our
|
||||
homebrew setting. You can find all supported languages [in the languages
|
||||
submodule](language/languages/); each submodule contains a README that
|
||||
describes the basic characteristics of the language, along with examples.
|
||||
|
||||
## Defining a Language
|
||||
|
||||
### Layout
|
||||
|
||||
A language consists of several submodules:
|
||||
|
||||
* `base.py`, which contains grapheme definitions and the `Language` subclasses;
|
||||
* `names.py`, which defines the `NameGenerator` subclasses; and
|
||||
* `rules.py`, which is optional but defines the rules all words in the language must follow.
|
||||
|
||||
|
||||
### Language Construction
|
||||
|
||||
Let's look at a simple example, the Gnomish language. Here's the `Language`
|
||||
subclass that would be defined in `base.py`:
|
||||
|
||||
```
|
||||
from language import defaults, types
|
||||
from .rules import rules
|
||||
|
||||
consonants = types.WeightedSet(
|
||||
("b", 0.5), ("d", 0.5), ("f", 0.3), ("g", 0.3),
|
||||
("h", 0.5), ("j", 0.2), ("l", 1.0), ("m", 0.5),
|
||||
("n", 1.0), ("p", 0.5), ("r", 1.0), ("s", 1.0),
|
||||
("t", 1.0), ("v", 0.3), ("w", 0.2), ("z", 0.1),
|
||||
)
|
||||
|
||||
suffixes = types.equal_weights(["a", "e", "i", "o", "y"], 1.0, blank=False)
|
||||
|
||||
|
||||
Language = types.Language(
|
||||
name="gnomish",
|
||||
vowels=defaults.vowels,
|
||||
consonants=consonants,
|
||||
prefixes=None,
|
||||
suffixes=suffixes,
|
||||
syllables=types.SyllableSet(
|
||||
(types.Syllable(template="consonant,vowel,vowel"), 1.0),
|
||||
(types.Syllable(template="consonant,vowel"), 0.33),
|
||||
),
|
||||
rules=rules,
|
||||
minimum_grapheme_count=2,
|
||||
)
|
||||
```
|
||||
|
||||
#### Defining Graphemes
|
||||
|
||||
A Language definition includes *graphemes*, the basic building blocks of any
|
||||
language. We start with **vowels**, **consonants**, which are required in every
|
||||
language; Gnomish also includes **suffixes**, but no **prefixes**. Each
|
||||
grapheme is a `WeightedSet`, which is like a regular set except its members
|
||||
consist of a tuple of a string and a relative weight from 0.0 to 1.0. These
|
||||
weights will be used when selecting a random grapheme.
|
||||
|
||||
Gnomish uses the default vowels, defined in [the
|
||||
language.defaults](language/defaults.py) submodule, but define our consonants
|
||||
with a subset of English consonants. By experimenting with different sets and
|
||||
different weights, you can generate radically different feeling text output!
|
||||
|
||||
Gnomish also uses suffixes, which are graphemes added to the ends of words.
|
||||
Here we use the helper function `types.equal_weights()`, which returns
|
||||
a `WeightedSet` where each member is given the same weight. Normally this
|
||||
function also inserts the grapheme `("", 1.0)` into the set, but we disable
|
||||
this behaviour by specifying `blank=False`.
|
||||
|
||||
#### Defining Syllables
|
||||
|
||||
A syllable is a collection of graphemes, including at least one vowel. When we
|
||||
create words, we select a random syllable template from a `SyllableSet`, which
|
||||
is just a `WeightedSet` whose members are `Syllable` instances. Each `Syllable`
|
||||
declares a `template`, and like graphemes, has a weight associated with it that
|
||||
will make it more or less likely to be chosen for a word.
|
||||
|
||||
A syllable's template consists of a comma-separated string of grapheme names.
|
||||
In Gnomish, we have two possible syllable templates, `consonant,vowel,vowel`
|
||||
and the shorter `consonant,vowel`, which will be selected one third as often.
|
||||
|
||||
Templates can also support randomly-selected graphemes by joining two or more
|
||||
grapheme types with a vertical bar, for example `vowel|consonant` would choose
|
||||
one or the other; `vowel|consonant,vowel` would result in a vowel or
|
||||
a consonant followed by a vowel.
|
||||
|
||||
### How Words Are Constructed:
|
||||
|
||||
The main interface for callers is word(), which returns a randomly-generated
|
||||
word in the language according to the following algorithm:
|
||||
|
||||
1. Choose a random syllable from the syllable set
|
||||
2. For each grapheme in the syllable
|
||||
3. Choose a random grapheme template
|
||||
4. Choose a random sequence from the language for that grapheme
|
||||
5. Validate the word against the language rules
|
||||
6. Repeat 1-5 until a valid word is generated
|
||||
7. Add a prefix and suffix, if they are defined
|
||||
|
||||
When graphemes are chosen, the following rules are applied:
|
||||
* Every syllable must have at least one vowel; and
|
||||
* A syllable may never have three consecutive consonants.
|
1
language/__init__.py
Normal file
1
language/__init__.py
Normal file
|
@ -0,0 +1 @@
|
|||
from .languages import supported_languages
|
72
language/cli.py
Normal file
72
language/cli.py
Normal file
|
@ -0,0 +1,72 @@
|
|||
import logging
|
||||
import os
|
||||
from enum import Enum
|
||||
from types import ModuleType
|
||||
|
||||
|
||||
import typer
|
||||
from rich.logging import RichHandler
|
||||
from rich.console import Console
|
||||
from rich.markdown import Markdown
|
||||
|
||||
from language import supported_languages
|
||||
|
||||
app = typer.Typer()
|
||||
|
||||
app_state = {}
|
||||
|
||||
Supported = Enum("Supported", ((k, k) for k in supported_languages.keys()))
|
||||
|
||||
|
||||
def print_sample(lang: str, module: ModuleType) -> None:
|
||||
summary = module.__doc__.replace("\n", " \n")
|
||||
print(f"{summary}")
|
||||
print(f" Text: {module.Language.text(10)}")
|
||||
print(f" Names: {module.Name}")
|
||||
print(f" {module.Name}")
|
||||
print(f" {module.Name}")
|
||||
if module.NobleName != module.Name:
|
||||
print(f" Noble Names: {module.NobleName}")
|
||||
print(f" {module.NobleName}")
|
||||
print(f" {module.NobleName}")
|
||||
print("")
|
||||
|
||||
|
||||
@app.callback()
|
||||
def main(
|
||||
language: Supported = typer.Option("common", help="The language to use."),
|
||||
):
|
||||
app_state["language"] = supported_languages[language.name]
|
||||
|
||||
debug = os.getenv("DEBUG", None)
|
||||
logging.basicConfig(
|
||||
format="%(message)s",
|
||||
level=logging.DEBUG if debug else logging.INFO,
|
||||
handlers=[RichHandler(rich_tracebacks=True, tracebacks_suppress=[typer])],
|
||||
)
|
||||
|
||||
|
||||
@app.command()
|
||||
def words(count: int = typer.Option(50, help="The number of words to generate.")):
|
||||
print(" ".join(list(app_state["language"].Language.word(count))))
|
||||
|
||||
|
||||
@app.command()
|
||||
def names(
|
||||
count: int = typer.Option(50, help="The number of names to generate."),
|
||||
noble: bool = typer.Option(False, help="Generate noble names."),
|
||||
):
|
||||
generator = app_state["language"].Name if not noble else app_state["language"].NobleName
|
||||
for name in generator.name(count):
|
||||
print(name["fullname"])
|
||||
|
||||
|
||||
@app.command()
|
||||
def list():
|
||||
console = Console(width=80)
|
||||
for lang, module in supported_languages.items():
|
||||
console.print(Markdown(module.__doc__))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app()
|
864
language/defaults.py
Normal file
864
language/defaults.py
Normal file
|
@ -0,0 +1,864 @@
|
|||
from language import types
|
||||
|
||||
vowels = types.equal_weights(["a", "e", "i", "o", "u"], 1.0)
|
||||
|
||||
consonants = types.equal_weights(
|
||||
["b", "c", "d", "f", "g", "h", "j", "k", "l", "m", "n", "p", "q", "r", "s", "t", "v", "w", "x", "y", "z"], 1.0
|
||||
)
|
||||
|
||||
adjectives = types.equal_weights(
|
||||
["big", "tiny", "wild", "crazy", "quiet", "red", "black", "blue", "sick", "dancing", "jumping"], 0.05
|
||||
)
|
||||
|
||||
titles = types.equal_weights(["sir", "dame", "capt.", "sgt.", "miss", "mrs.", "mr.", "dr."], 0.01) + types.WeightedSet(
|
||||
("lord", 0.05), ("lady", 0.05)
|
||||
)
|
||||
|
||||
counts = types.equal_weights(["II", "III", "IV", "V", "VI", "VII", "VIII", "IX"], 0.01)
|
||||
|
||||
|
||||
# source: http://ideonomy.mit.edu/essays/traits.html
|
||||
personality = types.equal_weights(
|
||||
[
|
||||
"Accessible",
|
||||
"Active",
|
||||
"Adaptable",
|
||||
"Admirable",
|
||||
"Adventurous",
|
||||
"Agreeable",
|
||||
"Alert",
|
||||
"Allocentric",
|
||||
"Amiable",
|
||||
"Anticipative",
|
||||
"Appreciative",
|
||||
"Articulate",
|
||||
"Aspiring",
|
||||
"Athletic",
|
||||
"Attractive",
|
||||
"Balanced",
|
||||
"Benevolent",
|
||||
"Brilliant",
|
||||
"Calm",
|
||||
"Capable",
|
||||
"Captivating",
|
||||
"Caring",
|
||||
"Challenging",
|
||||
"Charismatic",
|
||||
"Charming",
|
||||
"Cheerful",
|
||||
"Clean",
|
||||
"Clear-headed",
|
||||
"Clever",
|
||||
"Colorful",
|
||||
"Companionly",
|
||||
"Compassionate",
|
||||
"Conciliatory",
|
||||
"Confident",
|
||||
"Conscientious",
|
||||
"Considerate",
|
||||
"Constant",
|
||||
"Contemplative",
|
||||
"Cooperative",
|
||||
"Courageous",
|
||||
"Courteous",
|
||||
"Creative",
|
||||
"Cultured",
|
||||
"Curious",
|
||||
"Daring",
|
||||
"Debonair",
|
||||
"Decent",
|
||||
"Decisive",
|
||||
"Dedicated",
|
||||
"Deep",
|
||||
"Dignified",
|
||||
"Directed",
|
||||
"Disciplined",
|
||||
"Discreet",
|
||||
"Dramatic",
|
||||
"Dutiful",
|
||||
"Dynamic",
|
||||
"Earnest",
|
||||
"Ebullient",
|
||||
"Educated",
|
||||
"Efficient",
|
||||
"Elegant",
|
||||
"Eloquent",
|
||||
"Empathetic",
|
||||
"Energetic",
|
||||
"Enthusiastic",
|
||||
"Esthetic",
|
||||
"Exciting",
|
||||
"Extraordinary",
|
||||
"Fair",
|
||||
"Faithful",
|
||||
"Farsighted",
|
||||
"Felicific",
|
||||
"Firm",
|
||||
"Flexible",
|
||||
"Focused",
|
||||
"Forecful",
|
||||
"Forgiving",
|
||||
"Forthright",
|
||||
"Freethinking",
|
||||
"Friendly",
|
||||
"Fun-loving",
|
||||
"Gallant",
|
||||
"Generous",
|
||||
"Gentle",
|
||||
"Genuine",
|
||||
"Good-natured",
|
||||
"Gracious",
|
||||
"Hardworking",
|
||||
"Healthy",
|
||||
"Hearty",
|
||||
"Helpful",
|
||||
"Herioc",
|
||||
"High-minded",
|
||||
"Honest",
|
||||
"Honorable",
|
||||
"Humble",
|
||||
"Humorous",
|
||||
"Idealistic",
|
||||
"Imaginative",
|
||||
"Impressive",
|
||||
"Incisive",
|
||||
"Incorruptible",
|
||||
"Independent",
|
||||
"Individualistic",
|
||||
"Innovative",
|
||||
"Inoffensive",
|
||||
"Insightful",
|
||||
"Insouciant",
|
||||
"Intelligent",
|
||||
"Intuitive",
|
||||
"Invulnerable",
|
||||
"Kind",
|
||||
"Knowledge",
|
||||
"Leaderly",
|
||||
"Leisurely",
|
||||
"Liberal",
|
||||
"Logical",
|
||||
"Lovable",
|
||||
"Loyal",
|
||||
"Lyrical",
|
||||
"Magnanimous",
|
||||
"Many-sided",
|
||||
"Masculine",
|
||||
"Manly",
|
||||
"Mature",
|
||||
"Methodical",
|
||||
"Maticulous",
|
||||
"Moderate",
|
||||
"Modest",
|
||||
"Multi-leveled",
|
||||
"Neat",
|
||||
"Nonauthoritarian",
|
||||
"Objective",
|
||||
"Observant",
|
||||
"Open",
|
||||
"Optimistic",
|
||||
"Orderly",
|
||||
"Organized",
|
||||
"Original",
|
||||
"Painstaking",
|
||||
"Passionate",
|
||||
"Patient",
|
||||
"Patriotic",
|
||||
"Peaceful",
|
||||
"Perceptive",
|
||||
"Perfectionist",
|
||||
"Personable",
|
||||
"Persuasive",
|
||||
"Planful",
|
||||
"Playful",
|
||||
"Polished",
|
||||
"Popular",
|
||||
"Practical",
|
||||
"Precise",
|
||||
"Principled",
|
||||
"Profound",
|
||||
"Protean",
|
||||
"Protective",
|
||||
"Providential",
|
||||
"Prudent",
|
||||
"Punctual",
|
||||
"Pruposeful",
|
||||
"Rational",
|
||||
"Realistic",
|
||||
"Reflective",
|
||||
"Relaxed",
|
||||
"Reliable",
|
||||
"Resourceful",
|
||||
"Respectful",
|
||||
"Responsible",
|
||||
"Responsive",
|
||||
"Reverential",
|
||||
"Romantic",
|
||||
"Rustic",
|
||||
"Sage",
|
||||
"Sane",
|
||||
"Scholarly",
|
||||
"Scrupulous",
|
||||
"Secure",
|
||||
"Selfless",
|
||||
"Self-critical",
|
||||
"Self-defacing",
|
||||
"Self-denying",
|
||||
"Self-reliant",
|
||||
"Self-sufficent",
|
||||
"Sensitive",
|
||||
"Sentimental",
|
||||
"Seraphic",
|
||||
"Serious",
|
||||
"Sexy",
|
||||
"Sharing",
|
||||
"Shrewd",
|
||||
"Simple",
|
||||
"Skillful",
|
||||
"Sober",
|
||||
"Sociable",
|
||||
"Solid",
|
||||
"Sophisticated",
|
||||
"Spontaneous",
|
||||
"Sporting",
|
||||
"Stable",
|
||||
"Steadfast",
|
||||
"Steady",
|
||||
"Stoic",
|
||||
"Strong",
|
||||
"Studious",
|
||||
"Suave",
|
||||
"Subtle",
|
||||
"Sweet",
|
||||
"Sympathetic",
|
||||
"Systematic",
|
||||
"Tasteful",
|
||||
"Teacherly",
|
||||
"Thorough",
|
||||
"Tidy",
|
||||
"Tolerant",
|
||||
"Tractable",
|
||||
"Trusting",
|
||||
"Uncomplaining",
|
||||
"Understanding",
|
||||
"Undogmatic",
|
||||
"Unfoolable",
|
||||
"Upright",
|
||||
"Urbane",
|
||||
"Venturesome",
|
||||
"Vivacious",
|
||||
"Warm",
|
||||
"Well-bred",
|
||||
"Well-read",
|
||||
"Well-rounded",
|
||||
"Winning",
|
||||
"Wise",
|
||||
"Witty",
|
||||
"Youthful",
|
||||
"Absentminded",
|
||||
"Aggressive",
|
||||
"Ambitious",
|
||||
"Amusing",
|
||||
"Artful",
|
||||
"Ascetic",
|
||||
"Authoritarian",
|
||||
"Big-thinking",
|
||||
"Boyish",
|
||||
"Breezy",
|
||||
"Businesslike",
|
||||
"Busy",
|
||||
"Casual",
|
||||
"Crebral",
|
||||
"Chummy",
|
||||
"Circumspect",
|
||||
"Competitive",
|
||||
"Complex",
|
||||
"Confidential",
|
||||
"Conservative",
|
||||
"Contradictory",
|
||||
"Crisp",
|
||||
"Cute",
|
||||
"Deceptive",
|
||||
"Determined",
|
||||
"Dominating",
|
||||
"Dreamy",
|
||||
"Driving",
|
||||
"Droll",
|
||||
"Dry",
|
||||
"Earthy",
|
||||
"Effeminate",
|
||||
"Emotional",
|
||||
"Enigmatic",
|
||||
"Experimental",
|
||||
"Familial",
|
||||
"Folksy",
|
||||
"Formal",
|
||||
"Freewheeling",
|
||||
"Frugal",
|
||||
"Glamorous",
|
||||
"Guileless",
|
||||
"High-spirited",
|
||||
"Huried",
|
||||
"Hypnotic",
|
||||
"Iconoclastic",
|
||||
"Idiosyncratic",
|
||||
"Impassive",
|
||||
"Impersonal",
|
||||
"Impressionable",
|
||||
"Intense",
|
||||
"Invisible",
|
||||
"Irreligious",
|
||||
"Irreverent",
|
||||
"Maternal",
|
||||
"Mellow",
|
||||
"Modern",
|
||||
"Moralistic",
|
||||
"Mystical",
|
||||
"Neutral",
|
||||
"Noncommittal",
|
||||
"Noncompetitive",
|
||||
"Obedient",
|
||||
"Old-fashined",
|
||||
"Ordinary",
|
||||
"Outspoken",
|
||||
"Paternalistic",
|
||||
"Physical",
|
||||
"Placid",
|
||||
"Political",
|
||||
"Predictable",
|
||||
"Preoccupied",
|
||||
"Private",
|
||||
"Progressive",
|
||||
"Proud",
|
||||
"Pure",
|
||||
"Questioning",
|
||||
"Quiet",
|
||||
"Religious",
|
||||
"Reserved",
|
||||
"Restrained",
|
||||
"Retiring",
|
||||
"Sarcastic",
|
||||
"Self-conscious",
|
||||
"Sensual",
|
||||
"Skeptical",
|
||||
"Smooth",
|
||||
"Soft",
|
||||
"Solemn",
|
||||
"Solitary",
|
||||
"Stern",
|
||||
"Stoiid",
|
||||
"Strict",
|
||||
"Stubborn",
|
||||
"Stylish",
|
||||
"Subjective",
|
||||
"Surprising",
|
||||
"Soft",
|
||||
"Tough",
|
||||
"Unaggressive",
|
||||
"Unambitious",
|
||||
"Unceremonious",
|
||||
"Unchanging",
|
||||
"Undemanding",
|
||||
"Unfathomable",
|
||||
"Unhurried",
|
||||
"Uninhibited",
|
||||
"Unpatriotic",
|
||||
"Unpredicatable",
|
||||
"Unreligious",
|
||||
"Unsentimental",
|
||||
"Whimsical",
|
||||
"Abrasive",
|
||||
"Abrupt",
|
||||
"Agonizing",
|
||||
"Aimless",
|
||||
"Airy",
|
||||
"Aloof",
|
||||
"Amoral",
|
||||
"Angry",
|
||||
"Anxious",
|
||||
"Apathetic",
|
||||
"Arbitrary",
|
||||
"Argumentative",
|
||||
"Arrogantt",
|
||||
"Artificial",
|
||||
"Asocial",
|
||||
"Assertive",
|
||||
"Astigmatic",
|
||||
"Barbaric",
|
||||
"Bewildered",
|
||||
"Bizarre",
|
||||
"Bland",
|
||||
"Blunt",
|
||||
"Biosterous",
|
||||
"Brittle",
|
||||
"Brutal",
|
||||
"Calculating",
|
||||
"Callous",
|
||||
"Cantakerous",
|
||||
"Careless",
|
||||
"Cautious",
|
||||
"Charmless",
|
||||
"Childish",
|
||||
"Clumsy",
|
||||
"Coarse",
|
||||
"Cold",
|
||||
"Colorless",
|
||||
"Complacent",
|
||||
"Complaintive",
|
||||
"Compulsive",
|
||||
"Conceited",
|
||||
"Condemnatory",
|
||||
"Conformist",
|
||||
"Confused",
|
||||
"Contemptible",
|
||||
"Conventional",
|
||||
"Cowardly",
|
||||
"Crafty",
|
||||
"Crass",
|
||||
"Crazy",
|
||||
"Criminal",
|
||||
"Critical",
|
||||
"Crude",
|
||||
"Cruel",
|
||||
"Cynical",
|
||||
"Decadent",
|
||||
"Deceitful",
|
||||
"Delicate",
|
||||
"Demanding",
|
||||
"Dependent",
|
||||
"Desperate",
|
||||
"Destructive",
|
||||
"Devious",
|
||||
"Difficult",
|
||||
"Dirty",
|
||||
"Disconcerting",
|
||||
"Discontented",
|
||||
"Discouraging",
|
||||
"Discourteous",
|
||||
"Dishonest",
|
||||
"Disloyal",
|
||||
"Disobedient",
|
||||
"Disorderly",
|
||||
"Disorganized",
|
||||
"Disputatious",
|
||||
"Disrespectful",
|
||||
"Disruptive",
|
||||
"Dissolute",
|
||||
"Dissonant",
|
||||
"Distractible",
|
||||
"Disturbing",
|
||||
"Dogmatic",
|
||||
"Domineering",
|
||||
"Dull",
|
||||
"Easily Discouraged",
|
||||
"Egocentric",
|
||||
"Enervated",
|
||||
"Envious",
|
||||
"Erratic",
|
||||
"Escapist",
|
||||
"Excitable",
|
||||
"Expedient",
|
||||
"Extravagant",
|
||||
"Extreme",
|
||||
"Faithless",
|
||||
"False",
|
||||
"Fanatical",
|
||||
"Fanciful",
|
||||
"Fatalistic",
|
||||
"Fawning",
|
||||
"Fearful",
|
||||
"Fickle",
|
||||
"Fiery",
|
||||
"Fixed",
|
||||
"Flamboyant",
|
||||
"Foolish",
|
||||
"Forgetful",
|
||||
"Fraudulent",
|
||||
"Frightening",
|
||||
"Frivolous",
|
||||
"Gloomy",
|
||||
"Graceless",
|
||||
"Grand",
|
||||
"Greedy",
|
||||
"Grim",
|
||||
"Gullible",
|
||||
"Hateful",
|
||||
"Haughty",
|
||||
"Hedonistic",
|
||||
"Hesitant",
|
||||
"Hidebound",
|
||||
"High-handed",
|
||||
"Hostile",
|
||||
"Ignorant",
|
||||
"Imitative",
|
||||
"Impatient",
|
||||
"Impractical",
|
||||
"Imprudent",
|
||||
"Impulsive",
|
||||
"Inconsiderate",
|
||||
"Incurious",
|
||||
"Indecisive",
|
||||
"Indulgent",
|
||||
"Inert",
|
||||
"Inhibited",
|
||||
"Insecure",
|
||||
"Insensitive",
|
||||
"Insincere",
|
||||
"Insulting",
|
||||
"Intolerant",
|
||||
"Irascible",
|
||||
"Irrational",
|
||||
"Irresponsible",
|
||||
"Irritable",
|
||||
"Lazy",
|
||||
"Libidinous",
|
||||
"Loquacious",
|
||||
"Malicious",
|
||||
"Mannered",
|
||||
"Mannerless",
|
||||
"Mawkish",
|
||||
"Mealymouthed",
|
||||
"Mechanical",
|
||||
"Meddlesome",
|
||||
"Melancholic",
|
||||
"Meretricious",
|
||||
"Messy",
|
||||
"Miserable",
|
||||
"Miserly",
|
||||
"Misguided",
|
||||
"Mistaken",
|
||||
"Money-minded",
|
||||
"Monstrous",
|
||||
"Moody",
|
||||
"Morbid",
|
||||
"Muddle-headed",
|
||||
"Naive",
|
||||
"Narcissistic",
|
||||
"Narrow",
|
||||
"Narrow-minded",
|
||||
"Natty",
|
||||
"Negativistic",
|
||||
"Neglectful",
|
||||
"Neurotic",
|
||||
"Nihilistic",
|
||||
"Obnoxious",
|
||||
"Obsessive",
|
||||
"Obvious",
|
||||
"Odd",
|
||||
"Offhand",
|
||||
"One-dimensional",
|
||||
"One-sided",
|
||||
"Opinionated",
|
||||
"Opportunistic",
|
||||
"Oppressed",
|
||||
"Outrageous",
|
||||
"Overimaginative",
|
||||
"Paranoid",
|
||||
"Passive",
|
||||
"Pedantic",
|
||||
"Perverse",
|
||||
"Petty",
|
||||
"Pharissical",
|
||||
"Phlegmatic",
|
||||
"Plodding",
|
||||
"Pompous",
|
||||
"Possessive",
|
||||
"Power-hungry",
|
||||
"Predatory",
|
||||
"Prejudiced",
|
||||
"Presumptuous",
|
||||
"Pretentious",
|
||||
"Prim",
|
||||
"Procrastinating",
|
||||
"Profligate",
|
||||
"Provocative",
|
||||
"Pugnacious",
|
||||
"Puritanical",
|
||||
"Quirky",
|
||||
"Reactionary",
|
||||
"Reactive",
|
||||
"Regimental",
|
||||
"Regretful",
|
||||
"Repentant",
|
||||
"Repressed",
|
||||
"Resentful",
|
||||
"Ridiculous",
|
||||
"Rigid",
|
||||
"Ritualistic",
|
||||
"Rowdy",
|
||||
"Ruined",
|
||||
"Sadistic",
|
||||
"Sanctimonious",
|
||||
"Scheming",
|
||||
"Scornful",
|
||||
"Secretive",
|
||||
"Sedentary",
|
||||
"Selfish",
|
||||
"Self-indulgent",
|
||||
"Shallow",
|
||||
"Shortsighted",
|
||||
"Shy",
|
||||
"Silly",
|
||||
"Single-minded",
|
||||
"Sloppy",
|
||||
"Slow",
|
||||
"Sly",
|
||||
"Small-thinking",
|
||||
"Softheaded",
|
||||
"Sordid",
|
||||
"Steely",
|
||||
"Stiff",
|
||||
"Strong-willed",
|
||||
"Stupid",
|
||||
"Submissive",
|
||||
"Superficial",
|
||||
"Superstitious",
|
||||
"Suspicious",
|
||||
"Tactless",
|
||||
"Tasteless",
|
||||
"Tense",
|
||||
"Thievish",
|
||||
"Thoughtless",
|
||||
"Timid",
|
||||
"Transparent",
|
||||
"Treacherous",
|
||||
"Trendy",
|
||||
"Troublesome",
|
||||
"Unappreciative",
|
||||
"Uncaring",
|
||||
"Uncharitable",
|
||||
"Unconvincing",
|
||||
"Uncooperative",
|
||||
"Uncreative",
|
||||
"Uncritical",
|
||||
"Unctuous",
|
||||
"Undisciplined",
|
||||
"Unfriendly",
|
||||
"Ungrateful",
|
||||
"Unhealthy",
|
||||
"Unimaginative",
|
||||
"Unimpressive",
|
||||
"Unlovable",
|
||||
"Unpolished",
|
||||
"Unprincipled",
|
||||
"Unrealistic",
|
||||
"Unreflective",
|
||||
"Unreliable",
|
||||
"Unrestrained",
|
||||
"Unself-critical",
|
||||
"Unstable",
|
||||
"Vacuous",
|
||||
"Vague",
|
||||
"Venal",
|
||||
"Venomous",
|
||||
"Vindictive",
|
||||
"Vulnerable",
|
||||
"Weak",
|
||||
"Weak-willed",
|
||||
"Well-meaning",
|
||||
"Willful",
|
||||
"Wishful",
|
||||
"Zany",
|
||||
],
|
||||
1.0,
|
||||
)
|
||||
|
||||
|
||||
positive_adjectives = types.equal_weights(
|
||||
[
|
||||
"able",
|
||||
"clean",
|
||||
"enthusiastic",
|
||||
"heartening",
|
||||
"meek",
|
||||
"reasonable",
|
||||
"talented",
|
||||
"accommodating",
|
||||
"clever",
|
||||
"ethical",
|
||||
"helpful",
|
||||
"meritorious",
|
||||
"refined",
|
||||
"temperate",
|
||||
"accomplished",
|
||||
"commendable",
|
||||
"excellent",
|
||||
"moral",
|
||||
"reliable",
|
||||
"terrific",
|
||||
"adept",
|
||||
"compassionate",
|
||||
"exceptional",
|
||||
"honest",
|
||||
"neat",
|
||||
"remarkable",
|
||||
"tidy",
|
||||
"admirable",
|
||||
"composed",
|
||||
"exemplary",
|
||||
"honorable",
|
||||
"noble",
|
||||
"resilient",
|
||||
"quality",
|
||||
"agreeable",
|
||||
"considerate",
|
||||
"exquisite",
|
||||
"hopeful",
|
||||
"obliging",
|
||||
"respectable",
|
||||
"tremendous",
|
||||
"amazing",
|
||||
"consummate",
|
||||
"extraordinary",
|
||||
"humble",
|
||||
"observant",
|
||||
"respectful",
|
||||
"trustworthy",
|
||||
"appealing",
|
||||
"cooperative",
|
||||
"fabulous",
|
||||
"important",
|
||||
"optimistic",
|
||||
"resplendent",
|
||||
"trusty",
|
||||
"astute",
|
||||
"correct",
|
||||
"faithful",
|
||||
"impressive",
|
||||
"organized",
|
||||
"responsible",
|
||||
"truthful",
|
||||
"attractive",
|
||||
"courageous",
|
||||
"fantastic",
|
||||
"incisive",
|
||||
"outstanding",
|
||||
"robust",
|
||||
"unbeatable",
|
||||
"awesome",
|
||||
"courteous",
|
||||
"fascinating",
|
||||
"incredible",
|
||||
"peaceful",
|
||||
"selfless",
|
||||
"understanding",
|
||||
"beautiful",
|
||||
"dazzling",
|
||||
"fine",
|
||||
"innocent",
|
||||
"perceptive",
|
||||
"sensational",
|
||||
"unequaled",
|
||||
"benevolent",
|
||||
"decent",
|
||||
"classy",
|
||||
"insightful",
|
||||
"perfect",
|
||||
"sensible",
|
||||
"unparalleled",
|
||||
"brave",
|
||||
"delightful",
|
||||
"fortitudinous",
|
||||
"inspiring",
|
||||
"pleasant",
|
||||
"serene",
|
||||
"upbeat",
|
||||
"breathtaking",
|
||||
"dependable",
|
||||
"gallant",
|
||||
"intelligent",
|
||||
"pleasing",
|
||||
"sharp",
|
||||
"valiant",
|
||||
"bright",
|
||||
"devoted",
|
||||
"generous",
|
||||
"joyful",
|
||||
"polite",
|
||||
"shining",
|
||||
"valuable",
|
||||
"brilliant",
|
||||
"diplomatic",
|
||||
"gentle",
|
||||
"judicious",
|
||||
"positive",
|
||||
"shrewd",
|
||||
"vigilant",
|
||||
"bubbly",
|
||||
"discerning",
|
||||
"gifted",
|
||||
"just",
|
||||
"praiseworthy",
|
||||
"smart",
|
||||
"vigorous",
|
||||
"buoyant",
|
||||
"disciplined",
|
||||
"giving",
|
||||
"kindly",
|
||||
"precious",
|
||||
"sparkling",
|
||||
"virtuous",
|
||||
"calm",
|
||||
"elegant",
|
||||
"gleaming",
|
||||
"laudable",
|
||||
"priceless",
|
||||
"spectacular",
|
||||
"well mannered",
|
||||
"capable",
|
||||
"elevating",
|
||||
"glowing",
|
||||
"likable",
|
||||
"principled",
|
||||
"splendid",
|
||||
"wholesome",
|
||||
"charitable",
|
||||
"enchanting",
|
||||
"good",
|
||||
"lovable",
|
||||
"prompt",
|
||||
"steadfast",
|
||||
"wise",
|
||||
"charming",
|
||||
"encouraging",
|
||||
"gorgeous",
|
||||
"lovely",
|
||||
"prudent",
|
||||
"stunning",
|
||||
"witty",
|
||||
"chaste",
|
||||
"endearing",
|
||||
"graceful",
|
||||
"loyal",
|
||||
"punctual",
|
||||
"super",
|
||||
"wonderful",
|
||||
"cheerful",
|
||||
"energetic",
|
||||
"gracious",
|
||||
"luminous",
|
||||
"pure",
|
||||
"superb",
|
||||
"worthy",
|
||||
"chivalrous",
|
||||
"engaging",
|
||||
"great",
|
||||
"magnanimous",
|
||||
"quick",
|
||||
"superior",
|
||||
"zesty",
|
||||
"gallant",
|
||||
"enhanced",
|
||||
"happy",
|
||||
"magnificent",
|
||||
"radiant",
|
||||
"supportive",
|
||||
"civil",
|
||||
"enjoyable",
|
||||
"hardy",
|
||||
"marvelous",
|
||||
"rational",
|
||||
"supreme",
|
||||
],
|
||||
1.0,
|
||||
)
|
14
language/languages/__init__.py
Normal file
14
language/languages/__init__.py
Normal file
|
@ -0,0 +1,14 @@
|
|||
import importlib
|
||||
import pkgutil
|
||||
import sys
|
||||
|
||||
|
||||
def import_submodules(module):
|
||||
pkgs = pkgutil.iter_modules(module.__path__)
|
||||
for loader, module_name, is_pkg in pkgs:
|
||||
yield importlib.import_module(f"{module.__name__}.{module_name}")
|
||||
|
||||
|
||||
supported_languages = dict(
|
||||
(module.__name__.split(".")[-1], module) for module in list(import_submodules(sys.modules[__name__]))
|
||||
)
|
16
language/languages/abyssal/README.md
Normal file
16
language/languages/abyssal/README.md
Normal file
|
@ -0,0 +1,16 @@
|
|||
### Abyssal
|
||||
|
||||
Low-ranking demons speak in a guttural, broken dialect that is louder and more
|
||||
consonant, like dogs barking. The written language and naming conventions
|
||||
reflect the more refined language of demons, a quiet, sibilant susurrus that
|
||||
sounds at times like waves, at others like buzzing, keening insects.
|
||||
|
||||
*Whuewhezoawh ..uesewhaza whs..uwhiezowhisoi aizi..wheuz, whoshesowhewh
|
||||
she..iwhzzos..ozi iuzu..ueshes izsha..ua..uashezshuesas, ..awho....uza
|
||||
whuashoowhsz!*
|
||||
|
||||
**Abyssal Names:**
|
||||
|
||||
* Aa..Whaazwhis
|
||||
* Az..Zazzou
|
||||
* Uzeushsoshe..Osezawhwh
|
7
language/languages/abyssal/__init__.py
Normal file
7
language/languages/abyssal/__init__.py
Normal file
|
@ -0,0 +1,7 @@
|
|||
"""
|
||||
Abyssal
|
||||
"""
|
||||
from .base import Language
|
||||
from .names import Name, NobleName
|
||||
|
||||
__all__ = [Language, Name, NobleName]
|
18
language/languages/abyssal/base.py
Normal file
18
language/languages/abyssal/base.py
Normal file
|
@ -0,0 +1,18 @@
|
|||
from language import defaults, types
|
||||
|
||||
vowels = types.equal_weights(["a", "e", "i"], 1.0, blank=False)
|
||||
consonants = types.equal_weights(["z", "sh", "s", "wh", ".."], 1.0, blank=False)
|
||||
|
||||
Language = types.Language(
|
||||
name="celstial",
|
||||
vowels=defaults.vowels,
|
||||
consonants=consonants,
|
||||
prefixes=None,
|
||||
suffixes=None,
|
||||
rules=[],
|
||||
syllables=types.SyllableSet(
|
||||
(types.Syllable(template="vowel|consonant") * 10, 1.0),
|
||||
(types.Syllable(template="vowel|consonant") * 15, 1.0),
|
||||
(types.Syllable(template="vowel|consonant") * 20, 1.0),
|
||||
),
|
||||
)
|
11
language/languages/abyssal/names.py
Normal file
11
language/languages/abyssal/names.py
Normal file
|
@ -0,0 +1,11 @@
|
|||
from language import types
|
||||
from language.languages.abyssal import Language
|
||||
|
||||
Name = types.NameGenerator(
|
||||
language=Language,
|
||||
templates=types.NameSet(
|
||||
(types.NameTemplate("name"), 1.0),
|
||||
),
|
||||
)
|
||||
|
||||
NobleName = Name
|
15
language/languages/celestial/README.md
Normal file
15
language/languages/celestial/README.md
Normal file
|
@ -0,0 +1,15 @@
|
|||
### Celestial
|
||||
|
||||
Celestial is sung, not spoken, and sounds to mortal ears like stacked harmonies
|
||||
of an impossible choir. Written Celestial is akin to sheet music; it records
|
||||
the grammar and vocabulary, but cannot convey meaning.
|
||||
|
||||
*U̇ôeȧäuueüiäûoîịėeöäueaȯịo äuäuiaîiüoûoėeäoîeȧü öoaịäu̇ôuüöeiääoȧėu
|
||||
ôaîaêaịeiîäoöiôoėöi, ueûuêûiüȧuuȧaüeeêeuȯ oääuêaüâiîȧiûeu̇ėaâaȧ
|
||||
uîoėu̇au̇oîäeiäüuȧoêaiêu̇u uaäeîeoäuiaịôoėȯîiäiȧė îûȧäuaûėuoäôiûäuaȧ!
|
||||
Âȧuȯüiêịiäaiėöaöoi.*
|
||||
|
||||
**Celestial Names:**
|
||||
* Ịaêäuuȧoeėuäuoaịooȯîoäu̇Au̇Aû
|
||||
* Ûịịeịuȧoȯuȯaäâeeu̇Ėuiüaị
|
||||
* Au̇Üaoịėuüöaâaėoöȧeäėu
|
7
language/languages/celestial/__init__.py
Normal file
7
language/languages/celestial/__init__.py
Normal file
|
@ -0,0 +1,7 @@
|
|||
"""
|
||||
Celestial
|
||||
"""
|
||||
from .base import Language
|
||||
from .names import Name, NobleName
|
||||
|
||||
__all__ = [Language, Name, NobleName]
|
21
language/languages/celestial/base.py
Normal file
21
language/languages/celestial/base.py
Normal file
|
@ -0,0 +1,21 @@
|
|||
from language import defaults, types
|
||||
|
||||
vowels = types.equal_weights(["a", "e", "i", "o", "u"], 1.0, blank=False)
|
||||
consonants = types.equal_weights(
|
||||
["î", "ê", "â", "û", "ô", "ä", "ö", "ü", "äu", "ȧ", "ė", "ị", "ȯ", "u̇"], 1.0, blank=False
|
||||
)
|
||||
|
||||
Language = types.Language(
|
||||
name="celstial",
|
||||
vowels=defaults.vowels,
|
||||
consonants=consonants,
|
||||
prefixes=None,
|
||||
suffixes=None,
|
||||
rules=[],
|
||||
syllables=types.SyllableSet(
|
||||
(types.Syllable(template="vowel|consonant") * 20, 1.0),
|
||||
(types.Syllable(template="vowel|consonant") * 25, 1.0),
|
||||
(types.Syllable(template="vowel|consonant") * 30, 1.0),
|
||||
(types.Syllable(template="vowel|consonant") * 35, 1.0),
|
||||
),
|
||||
)
|
11
language/languages/celestial/names.py
Normal file
11
language/languages/celestial/names.py
Normal file
|
@ -0,0 +1,11 @@
|
|||
from language import types
|
||||
from language.languages.celestial import Language
|
||||
|
||||
Name = types.NameGenerator(
|
||||
language=Language,
|
||||
templates=types.NameSet(
|
||||
(types.NameTemplate("name"), 1.0),
|
||||
),
|
||||
)
|
||||
|
||||
NobleName = Name
|
16
language/languages/common/README.md
Normal file
16
language/languages/common/README.md
Normal file
|
@ -0,0 +1,16 @@
|
|||
### Common
|
||||
|
||||
Common is a complicated pidgin of influences with multiple regional dialects. Written Common is the language of
|
||||
traders, and can be relied upon to be understood by most peoples to a greater or lesser degree.
|
||||
|
||||
*Proitsiiiy be itkif eesof detytaen. Ojaot tyskuaz apsoo nirtoet prenao.*
|
||||
|
||||
**Common Names:**
|
||||
* Rubi Ca Momaman
|
||||
* "Quiet" Gushi Murk Lirpusome
|
||||
* Fewse Kerloborg
|
||||
|
||||
**Noble Common Names:**
|
||||
* Lord Pasti Quusi Maghiheim
|
||||
* Lady Gotki Lane Lopileigh III
|
||||
* Dame Cu Lehaberry IX
|
7
language/languages/common/__init__.py
Normal file
7
language/languages/common/__init__.py
Normal file
|
@ -0,0 +1,7 @@
|
|||
"""
|
||||
Common
|
||||
"""
|
||||
from .base import Language
|
||||
from .names import Name, NobleName
|
||||
|
||||
__all__ = [Language, Name, NobleName]
|
158
language/languages/common/base.py
Normal file
158
language/languages/common/base.py
Normal file
|
@ -0,0 +1,158 @@
|
|||
from language import types
|
||||
|
||||
from .rules import rules
|
||||
|
||||
vowels = types.WeightedSet(
|
||||
("a", 1.0),
|
||||
("e", 1.0),
|
||||
("i", 1.0),
|
||||
("o", 0.8),
|
||||
("u", 0.7),
|
||||
("y", 0.01),
|
||||
)
|
||||
consonants = types.WeightedSet(
|
||||
("b", 0.5),
|
||||
("c", 0.5),
|
||||
("d", 0.5),
|
||||
("f", 0.3),
|
||||
("g", 0.3),
|
||||
("h", 0.5),
|
||||
("j", 0.2),
|
||||
("k", 0.3),
|
||||
("l", 1.0),
|
||||
("m", 0.5),
|
||||
("n", 1.0),
|
||||
("p", 0.5),
|
||||
("q", 0.05),
|
||||
("r", 1.0),
|
||||
("s", 1.0),
|
||||
("t", 1.0),
|
||||
("v", 0.3),
|
||||
("w", 0.2),
|
||||
("x", 0.2),
|
||||
("y", 0.01),
|
||||
("z", 0.1),
|
||||
("bs", 0.3),
|
||||
("ct", 0.4),
|
||||
("ch", 0.4),
|
||||
("ck", 0.4),
|
||||
("dd", 0.2),
|
||||
("ff", 0.2),
|
||||
("gh", 0.3),
|
||||
("gs", 0.2),
|
||||
("ms", 0.4),
|
||||
("ns", 0.4),
|
||||
("ps", 0.3),
|
||||
("qu", 0.2),
|
||||
("rb", 0.1),
|
||||
("rd", 0.2),
|
||||
("rf", 0.1),
|
||||
("rk", 0.2),
|
||||
("rl", 0.2),
|
||||
("rm", 0.2),
|
||||
("rn", 0.2),
|
||||
("rp", 0.1),
|
||||
("rs", 0.75),
|
||||
("rt", 0.75),
|
||||
("ry", 0.5),
|
||||
("sh", 1.0),
|
||||
("sk", 0.5),
|
||||
("ss", 0.75),
|
||||
("st", 1.0),
|
||||
("sy", 0.5),
|
||||
("th", 1.0),
|
||||
("tk", 0.5),
|
||||
("ts", 1.0),
|
||||
("tt", 1.0),
|
||||
("ty", 1.0),
|
||||
("ws", 0.5),
|
||||
)
|
||||
|
||||
prefixes = types.equal_weights(["ex", "re", "pre", "de", "pro", "anti"], 0.05)
|
||||
|
||||
suffixes = types.equal_weights(
|
||||
[
|
||||
"ad",
|
||||
"ed",
|
||||
"id",
|
||||
"od",
|
||||
"ud",
|
||||
"af",
|
||||
"ef",
|
||||
"if",
|
||||
"of",
|
||||
"uf",
|
||||
"ah",
|
||||
"eh",
|
||||
"ih",
|
||||
"oh",
|
||||
"uh",
|
||||
"al",
|
||||
"el",
|
||||
"il",
|
||||
"ol",
|
||||
"ul",
|
||||
"am",
|
||||
"em",
|
||||
"im",
|
||||
"om",
|
||||
"um",
|
||||
"an",
|
||||
"en",
|
||||
"in",
|
||||
"on",
|
||||
"un",
|
||||
"ar",
|
||||
"er",
|
||||
"ir",
|
||||
"or",
|
||||
"ur",
|
||||
"as",
|
||||
"es",
|
||||
"is",
|
||||
"os",
|
||||
"us",
|
||||
"at",
|
||||
"et",
|
||||
"it",
|
||||
"ot",
|
||||
"ut",
|
||||
"ax",
|
||||
"ex",
|
||||
"ix",
|
||||
"ox",
|
||||
"ux",
|
||||
"ay",
|
||||
"ey",
|
||||
"iy",
|
||||
"oy",
|
||||
"uy",
|
||||
"az",
|
||||
"ez",
|
||||
"iz",
|
||||
"oz",
|
||||
"uz",
|
||||
"ing",
|
||||
],
|
||||
0.05,
|
||||
)
|
||||
|
||||
Language = types.Language(
|
||||
name="common",
|
||||
vowels=vowels,
|
||||
consonants=consonants,
|
||||
prefixes=prefixes,
|
||||
suffixes=suffixes,
|
||||
syllables=types.SyllableSet(
|
||||
(types.Syllable(template="vowel|consonant"), 0.01),
|
||||
(types.Syllable(template="vowel|consonant") * 2, 0.2),
|
||||
(types.Syllable(template="vowel|consonant") * 3, 0.4),
|
||||
(types.Syllable(template="vowel|consonant") * 3, 0.5),
|
||||
(types.Syllable(template="vowel|consonant") * 4, 1.0),
|
||||
(types.Syllable(template="vowel|consonant") * 5, 0.3),
|
||||
(types.Syllable(template="vowel|consonant") * 6, 0.2),
|
||||
(types.Syllable(template="vowel|consonant") * 7, 0.05),
|
||||
),
|
||||
rules=rules,
|
||||
minimum_grapheme_count=1,
|
||||
)
|
82
language/languages/common/names.py
Normal file
82
language/languages/common/names.py
Normal file
|
@ -0,0 +1,82 @@
|
|||
from language import defaults, types
|
||||
from language.languages.common import Language
|
||||
|
||||
suffixes = types.equal_weights(
|
||||
[
|
||||
"berg",
|
||||
"borg",
|
||||
"borough",
|
||||
"bury",
|
||||
"berry",
|
||||
"by",
|
||||
"ford",
|
||||
"gard",
|
||||
"grave",
|
||||
"grove",
|
||||
"gren",
|
||||
"hardt",
|
||||
"hart",
|
||||
"heim",
|
||||
"holm",
|
||||
"land",
|
||||
"leigh",
|
||||
"ley",
|
||||
"ly",
|
||||
"lof",
|
||||
"love",
|
||||
"lund",
|
||||
"man",
|
||||
"mark",
|
||||
"ness",
|
||||
"olf",
|
||||
"olph",
|
||||
"quist",
|
||||
"rop",
|
||||
"rup",
|
||||
"stad",
|
||||
"stead",
|
||||
"stein",
|
||||
"strom",
|
||||
"thal",
|
||||
"thorpe",
|
||||
"ton",
|
||||
"vall",
|
||||
"wich",
|
||||
"win",
|
||||
"some",
|
||||
"smith",
|
||||
"bridge",
|
||||
"cope",
|
||||
"town",
|
||||
"er",
|
||||
"don",
|
||||
"den",
|
||||
"dell",
|
||||
"son",
|
||||
]
|
||||
)
|
||||
|
||||
Name = types.NameGenerator(
|
||||
language=Language,
|
||||
syllables=types.SyllableSet(
|
||||
(types.Syllable(template="vowel|consonant"), 0.01),
|
||||
(types.Syllable(template="consonant,vowel"), 0.2),
|
||||
(types.Syllable(template="consonant,vowel") * 2, 1.0),
|
||||
),
|
||||
templates=types.NameSet(
|
||||
(types.NameTemplate("adjective,title,name,surname,count"), 1.0),
|
||||
(types.NameTemplate("title,name,name,surname,count"), 1.0),
|
||||
(types.NameTemplate("title,name,name,surname,surname,count"), 1.0),
|
||||
),
|
||||
names=None,
|
||||
surnames=None,
|
||||
nicknames=None,
|
||||
adjectives=defaults.adjectives,
|
||||
titles=defaults.titles,
|
||||
counts=defaults.counts,
|
||||
suffixes=suffixes,
|
||||
)
|
||||
Name.language.prefixes = None
|
||||
Name.language.suffixes = None
|
||||
|
||||
NobleName = Name
|
115
language/languages/common/rules.py
Normal file
115
language/languages/common/rules.py
Normal file
|
@ -0,0 +1,115 @@
|
|||
import logging
|
||||
import re
|
||||
|
||||
from language.rules import default_rules
|
||||
from language.types import Language
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
permitted_starting_clusters = [
|
||||
"bh",
|
||||
"bl",
|
||||
"br",
|
||||
"bw",
|
||||
"by",
|
||||
"ch",
|
||||
"cl",
|
||||
"cr",
|
||||
"cw",
|
||||
"cy",
|
||||
"dh",
|
||||
"dj",
|
||||
"dr",
|
||||
"dw",
|
||||
"dy",
|
||||
"fl",
|
||||
"fn",
|
||||
"fr",
|
||||
"fw",
|
||||
"fy",
|
||||
"gh",
|
||||
"gl",
|
||||
"gn",
|
||||
"gr",
|
||||
"gw",
|
||||
"gy",
|
||||
"hy",
|
||||
"jh",
|
||||
"jy",
|
||||
"kh",
|
||||
"kl",
|
||||
"kr",
|
||||
"kw",
|
||||
"ky",
|
||||
"ll",
|
||||
"ly",
|
||||
"mw",
|
||||
"my",
|
||||
"ny",
|
||||
"ph",
|
||||
"pl",
|
||||
"pn",
|
||||
"pr",
|
||||
"pw",
|
||||
"py",
|
||||
"rh",
|
||||
"ry",
|
||||
"sb",
|
||||
"sc",
|
||||
"sd",
|
||||
"sf",
|
||||
"sg",
|
||||
"sh",
|
||||
"sj",
|
||||
"sk",
|
||||
"sl",
|
||||
"sm",
|
||||
"sn",
|
||||
"sp",
|
||||
"sr",
|
||||
"st",
|
||||
"sv",
|
||||
"sw",
|
||||
"sy",
|
||||
"th",
|
||||
"tr",
|
||||
"tw",
|
||||
"ty",
|
||||
"vy",
|
||||
"wh",
|
||||
"wr",
|
||||
"wy",
|
||||
"xh",
|
||||
"xy",
|
||||
"yh",
|
||||
"zb",
|
||||
"zc",
|
||||
"zd",
|
||||
"zh",
|
||||
"zl",
|
||||
"zm",
|
||||
"zn",
|
||||
"zr",
|
||||
"zw",
|
||||
"zy",
|
||||
]
|
||||
|
||||
|
||||
def cannot_start_with_two_consonants(language: Language, word: str) -> bool:
|
||||
found = re.compile(r"(^[bcdfghjklmnpqrstvwxz]{2})").search(word)
|
||||
if not found:
|
||||
return True
|
||||
first, second = found.group(1)
|
||||
|
||||
if first == second:
|
||||
logger.debug(f"{word} starts with a repeated consonant.")
|
||||
return False
|
||||
|
||||
return found in permitted_starting_clusters
|
||||
|
||||
|
||||
rules = default_rules.union(
|
||||
{
|
||||
cannot_start_with_two_consonants,
|
||||
}
|
||||
)
|
23
language/languages/draconic/README.md
Normal file
23
language/languages/draconic/README.md
Normal file
|
@ -0,0 +1,23 @@
|
|||
### Draconic
|
||||
|
||||
The tongue of dragons and the Dragonborn. It is punctuated by low, gutteral
|
||||
grunts denoted by an apostrophe ('), the particular inflections of which can
|
||||
change the meaning of an utterance dramatically. When shouting, a speaker of
|
||||
Draconic should accentuate these grunts with spurts of flame, acid, ice, and so
|
||||
on.
|
||||
|
||||
The high form is reserved for the names of nobility -- that is, dragons -- who
|
||||
typically choose an appelation for themselves in the common tongue, the better
|
||||
to spread the terror of their name.
|
||||
|
||||
*Sey'so'jsii ysuut 'sei'gk gf'seiv 'se'su'rx 'se'yv hsi', sahsei' k'si'saa' f'si''see?*
|
||||
|
||||
**Draconic Names:**
|
||||
* V'Sei'Sey'a
|
||||
* Sir''Seytza
|
||||
* 'Soh'Suutus
|
||||
|
||||
**Noble (Dragon) Names:**
|
||||
* Seytthix the Willful
|
||||
* Saseezex the Enervated
|
||||
* Sahkzis the Miserly
|
7
language/languages/draconic/__init__.py
Normal file
7
language/languages/draconic/__init__.py
Normal file
|
@ -0,0 +1,7 @@
|
|||
"""
|
||||
Draconic
|
||||
"""
|
||||
from .base import Language
|
||||
from .names import Name, NobleName
|
||||
|
||||
__all__ = [Language, Name, NobleName]
|
47
language/languages/draconic/base.py
Normal file
47
language/languages/draconic/base.py
Normal file
|
@ -0,0 +1,47 @@
|
|||
from language import types
|
||||
|
||||
from .rules import rules
|
||||
|
||||
vowels = types.equal_weights(
|
||||
["sa", "saa", "sah", "se", "see", "sei", "sey", "si", "sii", "sir", "so", "su", "suu"], 1.0, blank=False
|
||||
)
|
||||
|
||||
consonants = types.WeightedSet(
|
||||
("d", 1.0),
|
||||
("f", 0.5),
|
||||
("g", 0.5),
|
||||
("h", 1.0),
|
||||
("j", 1.0),
|
||||
("k", 1.0),
|
||||
("l", 0.3),
|
||||
("n", 0.3),
|
||||
("r", 0.2),
|
||||
("t", 1.0),
|
||||
("v", 1.0),
|
||||
("x", 1.0),
|
||||
("y", 1.0),
|
||||
("z", 1.0),
|
||||
)
|
||||
|
||||
|
||||
class DraconicLanguage(types.Language):
|
||||
stops = types.equal_weights(["'"], 1.0)
|
||||
|
||||
def get_grapheme_vowel(self) -> str:
|
||||
return self.stops.random() + self.vowels.random() + self.stops.random()
|
||||
|
||||
|
||||
Language = DraconicLanguage(
|
||||
name="draconic",
|
||||
vowels=vowels,
|
||||
consonants=consonants,
|
||||
prefixes=None,
|
||||
suffixes=None,
|
||||
syllables=types.SyllableSet(
|
||||
(types.Syllable(template="consonant|vowel") * 2, 0.2),
|
||||
(types.Syllable(template="consonant|vowel") * 3, 1.0),
|
||||
(types.Syllable(template="consonant|vowel") * 4, 1.0),
|
||||
),
|
||||
rules=rules,
|
||||
minimum_grapheme_count=2,
|
||||
)
|
88
language/languages/draconic/names.py
Normal file
88
language/languages/draconic/names.py
Normal file
|
@ -0,0 +1,88 @@
|
|||
from language import defaults, types
|
||||
from language.languages.draconic import Language
|
||||
|
||||
# dragon_titles = types.equal_weights([
|
||||
# ], 1.0)
|
||||
|
||||
|
||||
class DraconicNameGenerator(types.NameGenerator):
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
language=Language,
|
||||
templates=types.NameSet(
|
||||
(types.NameTemplate("name"), 1.0),
|
||||
(types.NameTemplate("adjective,name"), 1.0),
|
||||
),
|
||||
adjectives=defaults.adjectives,
|
||||
)
|
||||
self.language.minimum_grapheme_count = 2
|
||||
self.suffixes = types.equal_weights(["us", "ius", "eus", "a", "an", "is"], 1.0, blank=False)
|
||||
|
||||
def get_name(self) -> str:
|
||||
return super().get_name() + self.suffixes.random()
|
||||
|
||||
|
||||
class NobleDraconicNameGenerator(types.NameGenerator):
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
language=Language,
|
||||
templates=types.NameSet(
|
||||
(types.NameTemplate("surname,the,title"), 1.0),
|
||||
),
|
||||
# titles=dragon_titles,
|
||||
syllables=types.SyllableSet(
|
||||
(types.Syllable(template="consonant|vowel") * 2, 1.0),
|
||||
),
|
||||
)
|
||||
self.language.minimum_grapheme_count = 2
|
||||
self.suffixes = types.equal_weights(
|
||||
[
|
||||
"thus",
|
||||
"thux",
|
||||
"thas",
|
||||
"thax",
|
||||
"this",
|
||||
"thix",
|
||||
"thes",
|
||||
"thex",
|
||||
"xus",
|
||||
"xux",
|
||||
"xas",
|
||||
"xax",
|
||||
"xis",
|
||||
"xix",
|
||||
"xes",
|
||||
"xex",
|
||||
"ssus",
|
||||
"ssux",
|
||||
"ssas",
|
||||
"ssax",
|
||||
"ssis",
|
||||
"ssix",
|
||||
"sses",
|
||||
"ssex",
|
||||
"zus",
|
||||
"zux",
|
||||
"zas",
|
||||
"zax",
|
||||
"zis",
|
||||
"zix",
|
||||
"zes",
|
||||
"zex",
|
||||
],
|
||||
1.0,
|
||||
blank=False,
|
||||
)
|
||||
|
||||
def get_title(self) -> str:
|
||||
p = ""
|
||||
while not p:
|
||||
p = defaults.personality.random()
|
||||
return p
|
||||
|
||||
def get_surname(self) -> str:
|
||||
return super().get_name().replace("'", "").title() + self.suffixes.random()
|
||||
|
||||
|
||||
Name = DraconicNameGenerator()
|
||||
NobleName = NobleDraconicNameGenerator()
|
7
language/languages/draconic/rules.py
Normal file
7
language/languages/draconic/rules.py
Normal file
|
@ -0,0 +1,7 @@
|
|||
import logging
|
||||
|
||||
from language.rules import default_rules
|
||||
|
||||
logger = logging.getLogger("draconic-rules")
|
||||
|
||||
rules = default_rules
|
14
language/languages/dwarvish/README.md
Normal file
14
language/languages/dwarvish/README.md
Normal file
|
@ -0,0 +1,14 @@
|
|||
### Dwarvish
|
||||
|
||||
Dwarvish words are short, sharp, and to the point. Much like the Dwarves
|
||||
themselves. This is "Low Dwarvish"; an ancient form of High Dwarvish still
|
||||
exists in some regions, notably the Dewa Q'Asos area of southern Vosh, but it
|
||||
is considered "dead" by most scholars and is reserved for arcane legal
|
||||
contracts (and those signifying a high social status).
|
||||
|
||||
*Dowêchâ khâ, natu phe tu, futê dêdachî pê she wu?*
|
||||
|
||||
**Dwarvish Names:**
|
||||
* "Black" Yzh Se
|
||||
* Ka Shidothir
|
||||
* Zhâ Syzhon
|
7
language/languages/dwarvish/__init__.py
Normal file
7
language/languages/dwarvish/__init__.py
Normal file
|
@ -0,0 +1,7 @@
|
|||
"""
|
||||
Dwarvish
|
||||
"""
|
||||
from .base import Language
|
||||
from .names import Name, NobleName
|
||||
|
||||
__all__ = [Language, Name, NobleName]
|
54
language/languages/dwarvish/base.py
Normal file
54
language/languages/dwarvish/base.py
Normal file
|
@ -0,0 +1,54 @@
|
|||
from language import types
|
||||
|
||||
from .rules import rules
|
||||
|
||||
vowels = types.WeightedSet(
|
||||
("a", 1.0),
|
||||
("e", 1.0),
|
||||
("i", 0.3),
|
||||
("o", 0.8),
|
||||
("u", 0.7),
|
||||
("y", 0.3),
|
||||
("j", 0.05),
|
||||
("î", 0.3),
|
||||
("ê", 1.0),
|
||||
("â", 1.0),
|
||||
("û", 1.0),
|
||||
)
|
||||
consonants = types.WeightedSet(
|
||||
("b", 0.3),
|
||||
("c", 0.5),
|
||||
("d", 1.0),
|
||||
("f", 0.5),
|
||||
("k", 1.0),
|
||||
("l", 0.3),
|
||||
("m", 0.3),
|
||||
("n", 0.3),
|
||||
("p", 1.0),
|
||||
("s", 1.0),
|
||||
("t", 1.0),
|
||||
("v", 0.5),
|
||||
("w", 0.5),
|
||||
("y", 0.3),
|
||||
("ph", 1.0),
|
||||
("th", 1.0),
|
||||
("ch", 1.0),
|
||||
("kh", 1.0),
|
||||
("zh", 1.0),
|
||||
("sh", 1.0),
|
||||
)
|
||||
|
||||
Language = types.Language(
|
||||
name="dwarvish",
|
||||
vowels=vowels,
|
||||
consonants=consonants,
|
||||
prefixes=None,
|
||||
suffixes=None,
|
||||
syllables=types.SyllableSet(
|
||||
(types.Syllable(template="consonant,vowel|consonant") * 1, 1.0),
|
||||
(types.Syllable(template="consonant,vowel|consonant") * 2, 0.5),
|
||||
(types.Syllable(template="consonant,vowel|consonant") * 3, 0.2),
|
||||
),
|
||||
rules=rules,
|
||||
minimum_grapheme_count=1,
|
||||
)
|
25
language/languages/dwarvish/names.py
Normal file
25
language/languages/dwarvish/names.py
Normal file
|
@ -0,0 +1,25 @@
|
|||
from language import defaults, types
|
||||
from language.languages.dwarvish import Language
|
||||
|
||||
|
||||
class DwarvishNameGenerator(types.NameGenerator):
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
language=Language,
|
||||
templates=types.NameSet(
|
||||
# (types.NameTemplate("adjective,name,nickname,surname"), 1.0),
|
||||
(types.NameTemplate("adjective,name,surname"), 1.0),
|
||||
),
|
||||
affixes=None,
|
||||
adjectives=defaults.adjectives,
|
||||
titles=defaults.titles,
|
||||
)
|
||||
self.language.minimum_grapheme_count = 2
|
||||
self.suffixes = types.equal_weights(["son", "sson", "zhon", "dottir", "dothir", "dottyr"], 1.0)
|
||||
|
||||
def get_surname(self) -> str:
|
||||
return super().get_surname() + self.suffixes.random()
|
||||
|
||||
|
||||
Name = DwarvishNameGenerator()
|
||||
NobleName = Name
|
24
language/languages/dwarvish/rules.py
Normal file
24
language/languages/dwarvish/rules.py
Normal file
|
@ -0,0 +1,24 @@
|
|||
import logging
|
||||
import re
|
||||
|
||||
from language.rules import default_rules
|
||||
from language.types import Language
|
||||
|
||||
logger = logging.getLogger("dwarvish-rules")
|
||||
|
||||
|
||||
def cannot_start_with_repeated_consonants(language: Language, word: str) -> bool:
|
||||
found = re.compile(r"(^[bcdfghklmnpqrstvwxz]{2})").search(word)
|
||||
if not found:
|
||||
return True
|
||||
|
||||
first, second = found.group(1)
|
||||
if first == second:
|
||||
logger.debug(f"{word} starts with a repeated consonant.")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
rules = default_rules
|
||||
rules.add(cannot_start_with_repeated_consonants)
|
20
language/languages/elvish/README.md
Normal file
20
language/languages/elvish/README.md
Normal file
|
@ -0,0 +1,20 @@
|
|||
### Elvish
|
||||
|
||||
> The Elvish tongue flows like a river and pools upon the lips like sweetest wine. -- Anwin am Vakaralithien
|
||||
|
||||
Elvish is an ornate, multisyllabic language featuring a complex and nuanced set
|
||||
of vowel sounds that can take non-native speakers decades to master. Surnames
|
||||
are typically derived from place names; the more noble the name, the more
|
||||
baroque its construction.
|
||||
|
||||
*Aaclysmior uewhön fyaeiath nöagefniath iedyglion ryavies, eamwien acyöe, uëstr udrocrem!*
|
||||
|
||||
**Elvish Names:**
|
||||
* Afydyioth um Envyien
|
||||
* Wjillias am Aofll
|
||||
* Anyir um Edyoieth
|
||||
|
||||
**Noble Elvish Names:**
|
||||
* Uebs Sviiies um Udjsmierios
|
||||
* Nytyuia Oudryhn um Ioltier
|
||||
* Emyea Whonoel an Ofyediar
|
7
language/languages/elvish/__init__.py
Normal file
7
language/languages/elvish/__init__.py
Normal file
|
@ -0,0 +1,7 @@
|
|||
"""
|
||||
Elvish
|
||||
"""
|
||||
from .base import Language
|
||||
from .names import Name, NobleName
|
||||
|
||||
__all__ = [Language, Name, NobleName]
|
129
language/languages/elvish/base.py
Normal file
129
language/languages/elvish/base.py
Normal file
|
@ -0,0 +1,129 @@
|
|||
from language import types
|
||||
|
||||
from .rules import rules
|
||||
|
||||
vowels = types.WeightedSet(
|
||||
("a", 1.0),
|
||||
("e", 1.0),
|
||||
("i", 0.3),
|
||||
("o", 0.8),
|
||||
("u", 0.7),
|
||||
("y", 0.3),
|
||||
("j", 0.05),
|
||||
("ä", 0.1),
|
||||
("ë", 0.1),
|
||||
("ö", 0.1),
|
||||
("ü", 0.1),
|
||||
)
|
||||
consonants = types.WeightedSet(
|
||||
("b", 0.5),
|
||||
("c", 0.3),
|
||||
("d", 0.3),
|
||||
("f", 0.4),
|
||||
("g", 0.3),
|
||||
("h", 0.5),
|
||||
("k", 0.05),
|
||||
("l", 1.0),
|
||||
("m", 1.0),
|
||||
("n", 1.0),
|
||||
("p", 0.5),
|
||||
("r", 1.0),
|
||||
("s", 1.0),
|
||||
("t", 0.7),
|
||||
("v", 0.3),
|
||||
("w", 0.2),
|
||||
("y", 0.3),
|
||||
) + types.equal_weights(
|
||||
[
|
||||
"ch",
|
||||
"cl",
|
||||
"cr",
|
||||
"cw",
|
||||
"cy",
|
||||
"dh",
|
||||
"dj",
|
||||
"dr",
|
||||
"dw",
|
||||
"dy",
|
||||
"fl",
|
||||
"fn",
|
||||
"fr",
|
||||
"fw",
|
||||
"fy",
|
||||
"gl",
|
||||
"ll",
|
||||
"ly",
|
||||
"ml",
|
||||
"mw",
|
||||
"my",
|
||||
"ny",
|
||||
"rh",
|
||||
"ry",
|
||||
"sh",
|
||||
"sl",
|
||||
"sm",
|
||||
"sn",
|
||||
"st",
|
||||
"sv",
|
||||
"sw",
|
||||
"sy",
|
||||
"th",
|
||||
"tr",
|
||||
"tw",
|
||||
"ty",
|
||||
"vy",
|
||||
"wh",
|
||||
"wr",
|
||||
"wy",
|
||||
"yh",
|
||||
]
|
||||
)
|
||||
|
||||
suffixes = types.equal_weights(
|
||||
[
|
||||
"a",
|
||||
"i",
|
||||
"e",
|
||||
"t",
|
||||
"s",
|
||||
"m",
|
||||
"n",
|
||||
"l",
|
||||
"r",
|
||||
"d",
|
||||
"a",
|
||||
"th",
|
||||
"ss",
|
||||
"ieth",
|
||||
"ies",
|
||||
"ier",
|
||||
"ien",
|
||||
"iath",
|
||||
"ias",
|
||||
"iar",
|
||||
"ian",
|
||||
"ioth",
|
||||
"ios",
|
||||
"ior",
|
||||
"ion",
|
||||
],
|
||||
weight=1.0,
|
||||
)
|
||||
|
||||
Language = types.Language(
|
||||
name="elvish",
|
||||
vowels=vowels,
|
||||
consonants=consonants,
|
||||
prefixes=None,
|
||||
suffixes=suffixes,
|
||||
syllables=types.SyllableSet(
|
||||
(types.Syllable(template="vowel|consonant") * 3, 0.4),
|
||||
(types.Syllable(template="vowel|consonant") * 3, 0.5),
|
||||
(types.Syllable(template="vowel|consonant") * 4, 1.0),
|
||||
(types.Syllable(template="vowel|consonant") * 5, 0.5),
|
||||
(types.Syllable(template="vowel|consonant") * 6, 0.1),
|
||||
(types.Syllable(template="vowel|consonant") * 7, 0.1),
|
||||
),
|
||||
rules=rules,
|
||||
minimum_grapheme_count=3,
|
||||
)
|
77
language/languages/elvish/names.py
Normal file
77
language/languages/elvish/names.py
Normal file
|
@ -0,0 +1,77 @@
|
|||
from language import defaults, types
|
||||
from language.languages.elvish import Language
|
||||
from language.languages.elvish.base import suffixes
|
||||
|
||||
PlaceName = types.NameGenerator(
|
||||
language=Language,
|
||||
syllables=types.SyllableSet(
|
||||
(types.Syllable(template="vowel,vowel|consonant,vowel|consonant"), 1.0),
|
||||
(types.Syllable(template="consonant,vowel|consonant,vowel|consonant"), 0.3),
|
||||
),
|
||||
templates=types.NameSet(
|
||||
(types.NameTemplate("affix,name"), 1.0),
|
||||
),
|
||||
affixes=types.WeightedSet(("el", 1.0)),
|
||||
adjectives=defaults.adjectives,
|
||||
suffixes=suffixes,
|
||||
)
|
||||
|
||||
|
||||
class ElvishNameGenerator(types.NameGenerator):
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
language=Language,
|
||||
syllables=Language.syllables,
|
||||
templates=types.NameSet(
|
||||
(types.NameTemplate("name,affix,surname"), 1.0),
|
||||
),
|
||||
affixes=types.equal_weights(["am", "an", "al", "um"], weight=1.0, blank=False),
|
||||
adjectives=defaults.adjectives,
|
||||
titles=defaults.titles,
|
||||
suffixes=suffixes,
|
||||
)
|
||||
self.language.minimum_grapheme_count = 2
|
||||
self.place_generator = PlaceName
|
||||
|
||||
def get_surname(self) -> str:
|
||||
return self.place_generator.name()[0]["name"][0]
|
||||
|
||||
|
||||
class NobleElvishNameGenerator(types.NameGenerator):
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
language=Language,
|
||||
syllables=Language.syllables,
|
||||
templates=types.NameSet(
|
||||
(types.NameTemplate("name,name,affix,surname"), 1.0),
|
||||
),
|
||||
affixes=types.equal_weights(["am", "an", "al", "um"], weight=1.0, blank=False),
|
||||
adjectives=defaults.adjectives,
|
||||
titles=defaults.titles,
|
||||
)
|
||||
self.language.minimum_grapheme_count = 2
|
||||
self.place_generator = PlaceName
|
||||
self.suffixes = types.equal_weights(
|
||||
[
|
||||
"ieth",
|
||||
"ies",
|
||||
"ier",
|
||||
"ien",
|
||||
"iath",
|
||||
"ias",
|
||||
"iar",
|
||||
"ian",
|
||||
"ioth",
|
||||
"ios",
|
||||
"ior",
|
||||
"ion",
|
||||
],
|
||||
1.0,
|
||||
)
|
||||
|
||||
def get_surname(self) -> str:
|
||||
return self.place_generator.name()[0]["name"][0] + self.suffixes.random()
|
||||
|
||||
|
||||
Name = ElvishNameGenerator()
|
||||
NobleName = NobleElvishNameGenerator()
|
109
language/languages/elvish/rules.py
Normal file
109
language/languages/elvish/rules.py
Normal file
|
@ -0,0 +1,109 @@
|
|||
import logging
|
||||
import re
|
||||
|
||||
from language.types import Language
|
||||
|
||||
logger = logging.getLogger("elvish-rules")
|
||||
|
||||
permitted_starting_clusters = [
|
||||
"ch",
|
||||
"cl",
|
||||
"cr",
|
||||
"cw",
|
||||
"cy",
|
||||
"dh",
|
||||
"dj",
|
||||
"dr",
|
||||
"dw",
|
||||
"dy",
|
||||
"fl",
|
||||
"fn",
|
||||
"fr",
|
||||
"fw",
|
||||
"fy",
|
||||
"gl",
|
||||
"ll",
|
||||
"ly",
|
||||
"ml",
|
||||
"mw",
|
||||
"my",
|
||||
"ny",
|
||||
"rh",
|
||||
"ry",
|
||||
"sh",
|
||||
"sl",
|
||||
"sm",
|
||||
"sn",
|
||||
"st",
|
||||
"sv",
|
||||
"sw",
|
||||
"sy",
|
||||
"th",
|
||||
"tr",
|
||||
"tw",
|
||||
"ty",
|
||||
"vy",
|
||||
"wh",
|
||||
"wr",
|
||||
"wy",
|
||||
"yh",
|
||||
]
|
||||
|
||||
|
||||
def cannot_start_with_two_consonants(language: Language, word: str) -> bool:
|
||||
found = re.compile(r"(^[bcdfghklmnpqrstvwxz]{2})").search(word)
|
||||
if not found:
|
||||
return True
|
||||
|
||||
first, second = found.group(1)
|
||||
if first == second and first != "l":
|
||||
logger.debug(f"{word} starts with a repeated consonant.")
|
||||
return False
|
||||
|
||||
if found.group(1) not in permitted_starting_clusters:
|
||||
logger.debug("f{word} cannot start with {first}{second}")
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def too_many_vowels(language: Language, word: str) -> bool:
|
||||
found = re.compile(r"[" + "".join(language.vowels.members) + "]{4}").findall(word)
|
||||
if found == []:
|
||||
return True
|
||||
logger.debug(f"{word} has too many contiguous vowels: {found}")
|
||||
return False
|
||||
|
||||
|
||||
def too_many_consonants(language: Language, word: str) -> bool:
|
||||
found = re.compile(r"[bcdfghklmnprstvw]{3}").findall(word)
|
||||
if found == []:
|
||||
return True
|
||||
logger.debug(f"{word} has too many contiguous consonants: {found}")
|
||||
return False
|
||||
|
||||
|
||||
def cannot_have_just_repeated_vowels(language: Language, word: str) -> bool:
|
||||
if len(word) == 1:
|
||||
return True
|
||||
uniq = {letter for letter in word}
|
||||
if len(uniq) > 1:
|
||||
return True
|
||||
logger.debug(f"{word} consists of only one repeated letter.")
|
||||
return False
|
||||
|
||||
|
||||
def must_have_a_vowel(language: Language, word: str) -> bool:
|
||||
for vowel in language.vowels.members:
|
||||
if vowel in word:
|
||||
return True
|
||||
logger.debug(f"{word} does not contain a vowel.")
|
||||
return False
|
||||
|
||||
|
||||
rules = {
|
||||
must_have_a_vowel,
|
||||
too_many_vowels,
|
||||
too_many_consonants,
|
||||
cannot_have_just_repeated_vowels,
|
||||
cannot_start_with_two_consonants,
|
||||
}
|
13
language/languages/gnomish/README.md
Normal file
13
language/languages/gnomish/README.md
Normal file
|
@ -0,0 +1,13 @@
|
|||
### Gnomish
|
||||
|
||||
Several distinct dialects of Gnomish exist; a provincial Rock Gnome may not
|
||||
understand a Forest Gnome at all! This is "Golden Gnomish," the mother tongue
|
||||
of these localized variants, and functions much like Common -- a trader's
|
||||
tongue of common understanding.
|
||||
|
||||
*Nuio duy lao guey houe lo nouo, pia roa tai.*
|
||||
|
||||
**Gnomish Names:**
|
||||
* Fuey Voii
|
||||
* Juio See
|
||||
* Tuai Reaa
|
7
language/languages/gnomish/__init__.py
Normal file
7
language/languages/gnomish/__init__.py
Normal file
|
@ -0,0 +1,7 @@
|
|||
"""
|
||||
Gnomish
|
||||
"""
|
||||
from .base import Language
|
||||
from .names import Name, NobleName
|
||||
|
||||
__all__ = [Language, Name, NobleName]
|
48
language/languages/gnomish/base.py
Normal file
48
language/languages/gnomish/base.py
Normal file
|
@ -0,0 +1,48 @@
|
|||
from language import defaults, types
|
||||
|
||||
from .rules import rules
|
||||
|
||||
consonants = types.WeightedSet(
|
||||
("b", 0.5),
|
||||
("d", 0.5),
|
||||
("f", 0.3),
|
||||
("g", 0.3),
|
||||
("h", 0.5),
|
||||
("j", 0.2),
|
||||
("l", 1.0),
|
||||
("m", 0.5),
|
||||
("n", 1.0),
|
||||
("p", 0.5),
|
||||
("r", 1.0),
|
||||
("s", 1.0),
|
||||
("t", 1.0),
|
||||
("v", 0.3),
|
||||
("w", 0.2),
|
||||
("z", 0.1),
|
||||
)
|
||||
|
||||
suffixes = types.equal_weights(
|
||||
[
|
||||
"a",
|
||||
"e",
|
||||
"i",
|
||||
"o",
|
||||
"y",
|
||||
],
|
||||
1.0,
|
||||
blank=False,
|
||||
)
|
||||
|
||||
Language = types.Language(
|
||||
name="gnomish",
|
||||
vowels=defaults.vowels,
|
||||
consonants=consonants,
|
||||
prefixes=None,
|
||||
suffixes=suffixes,
|
||||
syllables=types.SyllableSet(
|
||||
(types.Syllable(template="consonant,vowel,vowel"), 1.0),
|
||||
(types.Syllable(template="consonant,vowel"), 0.33),
|
||||
),
|
||||
rules=rules,
|
||||
minimum_grapheme_count=2,
|
||||
)
|
10
language/languages/gnomish/names.py
Normal file
10
language/languages/gnomish/names.py
Normal file
|
@ -0,0 +1,10 @@
|
|||
from language import types
|
||||
from language.languages.gnomish import Language
|
||||
|
||||
Name = types.NameGenerator(
|
||||
language=Language,
|
||||
templates=types.NameSet(
|
||||
(types.NameTemplate("name,surname"), 1.0),
|
||||
),
|
||||
)
|
||||
NobleName = Name
|
7
language/languages/gnomish/rules.py
Normal file
7
language/languages/gnomish/rules.py
Normal file
|
@ -0,0 +1,7 @@
|
|||
import logging
|
||||
|
||||
from language.rules import default_rules
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
rules = default_rules
|
13
language/languages/halfling/README.md
Normal file
13
language/languages/halfling/README.md
Normal file
|
@ -0,0 +1,13 @@
|
|||
### Halfling
|
||||
|
||||
Halfings speak a trilling, lilting language with meaning and inflection rooted
|
||||
in meter. It has proven to be nearly impossisble for most non-Halflings to
|
||||
master, to the point that most cosmopolitan Halfings choose a Common nickname
|
||||
by which they are known.
|
||||
|
||||
*Siheme'li'ba'e lo'neno'ne'o, mo'tepa'ga'i wezyropi'a lo'se'ni'metyo zevypiwee vyta'mo'a, da'na'jo'lee ti'soe fe'soe!*
|
||||
|
||||
**Halfling Names:**
|
||||
* Hi'rina'pa'no'y Nylao Pe'lawyle'lo'a "Temperate"
|
||||
* Jo'la'rogi'o Mi'li're'ne'pe'y Terinite've'e "Wise"
|
||||
* Sa'la're'ta'je'o Re'dya Fetobo'a "Meritorious"
|
7
language/languages/halfling/__init__.py
Normal file
7
language/languages/halfling/__init__.py
Normal file
|
@ -0,0 +1,7 @@
|
|||
"""
|
||||
Halfling
|
||||
"""
|
||||
from .base import Language
|
||||
from .names import Name, NobleName
|
||||
|
||||
__all__ = [Language, Name, NobleName]
|
54
language/languages/halfling/base.py
Normal file
54
language/languages/halfling/base.py
Normal file
|
@ -0,0 +1,54 @@
|
|||
from language import types
|
||||
|
||||
from .rules import rules
|
||||
|
||||
vowels = types.equal_weights(["a'", "e'", "i'", "o'"], 1.0, blank=False) + types.equal_weights(
|
||||
["a", "e", "i", "o", "y"], 0.5, blank=False
|
||||
)
|
||||
|
||||
consonants = types.WeightedSet(
|
||||
("b", 0.5),
|
||||
("d", 0.5),
|
||||
("f", 0.3),
|
||||
("g", 0.3),
|
||||
("h", 0.5),
|
||||
("j", 0.2),
|
||||
("l", 1.0),
|
||||
("m", 0.5),
|
||||
("n", 1.0),
|
||||
("p", 0.5),
|
||||
("r", 1.0),
|
||||
("s", 1.0),
|
||||
("t", 1.0),
|
||||
("v", 0.3),
|
||||
("w", 0.2),
|
||||
("z", 0.1),
|
||||
)
|
||||
|
||||
suffixes = types.equal_weights(
|
||||
[
|
||||
"a",
|
||||
"e",
|
||||
"i",
|
||||
"o",
|
||||
"y",
|
||||
],
|
||||
1.0,
|
||||
blank=False,
|
||||
)
|
||||
|
||||
Language = types.Language(
|
||||
name="halfling",
|
||||
vowels=vowels,
|
||||
consonants=consonants,
|
||||
prefixes=None,
|
||||
suffixes=suffixes,
|
||||
syllables=types.SyllableSet(
|
||||
(types.Syllable(template="consonant,vowel") * 2, 0.5),
|
||||
(types.Syllable(template="consonant,vowel") * 3, 0.75),
|
||||
(types.Syllable(template="consonant,vowel") * 4, 1.0),
|
||||
(types.Syllable(template="consonant,vowel") * 5, 1.0),
|
||||
),
|
||||
rules=rules,
|
||||
minimum_grapheme_count=2,
|
||||
)
|
17
language/languages/halfling/names.py
Normal file
17
language/languages/halfling/names.py
Normal file
|
@ -0,0 +1,17 @@
|
|||
from language import defaults, types
|
||||
from language.languages.halfling import Language
|
||||
|
||||
|
||||
class HalflingNameGenerator(types.NameGenerator):
|
||||
def get_name(self) -> str:
|
||||
return super().get_name().lower().capitalize()
|
||||
|
||||
|
||||
Name = HalflingNameGenerator(
|
||||
language=Language,
|
||||
nicknames=defaults.positive_adjectives,
|
||||
templates=types.NameSet(
|
||||
(types.NameTemplate("name,name,name,nickname"), 1.0),
|
||||
),
|
||||
)
|
||||
NobleName = Name
|
7
language/languages/halfling/rules.py
Normal file
7
language/languages/halfling/rules.py
Normal file
|
@ -0,0 +1,7 @@
|
|||
import logging
|
||||
|
||||
from language.rules import default_rules
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
rules = default_rules
|
19
language/languages/infernal/README.md
Normal file
19
language/languages/infernal/README.md
Normal file
|
@ -0,0 +1,19 @@
|
|||
### Infernal
|
||||
|
||||
The language of devils is a collection of gutteral barks that is unmistakable,
|
||||
and unmistakably terrifying, to the ear of common folk. Traditional names
|
||||
typically begin with the name of the ruler of whatever circle of Hell the devil
|
||||
calls home, but some Tieflings (and others of Infernal origin) reject their
|
||||
Infernal legacies and choose less noble names.
|
||||
|
||||
*Z'zait cocf k'd z'xz t'ugoo! Yipd k'j x'euy, z'edu k'oczu!*
|
||||
|
||||
**Infernal Names:**
|
||||
* P'Utufus
|
||||
* Golden Bdaeus
|
||||
* Timeless Ujius
|
||||
|
||||
**Noble Infernal Names:**
|
||||
* Mephistopheles T'Xieis
|
||||
* Asmodeus T'Opakan
|
||||
* Mammon K'Nusan
|
7
language/languages/infernal/__init__.py
Normal file
7
language/languages/infernal/__init__.py
Normal file
|
@ -0,0 +1,7 @@
|
|||
"""
|
||||
Infernal
|
||||
"""
|
||||
from .base import Language
|
||||
from .names import Name, NobleName
|
||||
|
||||
__all__ = [Language, Name, NobleName]
|
52
language/languages/infernal/base.py
Normal file
52
language/languages/infernal/base.py
Normal file
|
@ -0,0 +1,52 @@
|
|||
from language import defaults, types
|
||||
|
||||
from .rules import rules
|
||||
|
||||
consonants = types.WeightedSet(
|
||||
("b", 1.0),
|
||||
("c", 1.0),
|
||||
("d", 1.0),
|
||||
("f", 0.5),
|
||||
("g", 0.5),
|
||||
("j", 1.0),
|
||||
("k", 1.0),
|
||||
("l", 0.3),
|
||||
("m", 0.3),
|
||||
("n", 0.3),
|
||||
("p", 1.0),
|
||||
("r", 0.2),
|
||||
("s", 0.1),
|
||||
("t", 1.0),
|
||||
("v", 1.0),
|
||||
("x", 1.0),
|
||||
("y", 1.0),
|
||||
("z", 1.0),
|
||||
)
|
||||
|
||||
prefixes = types.equal_weights(
|
||||
[
|
||||
"t'",
|
||||
"x'",
|
||||
"k'",
|
||||
"p'",
|
||||
"z'",
|
||||
],
|
||||
0.5,
|
||||
)
|
||||
|
||||
Language = types.Language(
|
||||
name="infernal",
|
||||
vowels=defaults.vowels,
|
||||
consonants=consonants,
|
||||
prefixes=prefixes,
|
||||
suffixes=None,
|
||||
syllables=types.SyllableSet(
|
||||
(types.Syllable(template="consonant|vowel") * 2, 0.05),
|
||||
(types.Syllable(template="consonant|vowel") * 3, 1.0),
|
||||
(types.Syllable(template="consonant|vowel") * 4, 0.75),
|
||||
(types.Syllable(template="consonant|vowel") * 5, 0.5),
|
||||
(types.Syllable(template="consonant|vowel") * 6, 0.25),
|
||||
),
|
||||
rules=rules,
|
||||
minimum_grapheme_count=2,
|
||||
)
|
87
language/languages/infernal/names.py
Normal file
87
language/languages/infernal/names.py
Normal file
|
@ -0,0 +1,87 @@
|
|||
from language import types
|
||||
from language.languages.infernal import Language
|
||||
|
||||
adjectives = types.equal_weights(
|
||||
[
|
||||
"eternal",
|
||||
"wondrous",
|
||||
"luminous",
|
||||
"perfect",
|
||||
"essential",
|
||||
"golden",
|
||||
"unfailing",
|
||||
"perpetual",
|
||||
"infinite",
|
||||
"exquisite",
|
||||
"sinless",
|
||||
"ultimate",
|
||||
"flawless",
|
||||
"timeless",
|
||||
"glorious",
|
||||
"absolute",
|
||||
"boundless",
|
||||
"true",
|
||||
"incredible",
|
||||
"virtuous",
|
||||
"supreme",
|
||||
"enchanted",
|
||||
"magnificent",
|
||||
"superior",
|
||||
"spectacular",
|
||||
"divine",
|
||||
],
|
||||
0.25,
|
||||
)
|
||||
|
||||
bloodlines = types.equal_weights(
|
||||
[
|
||||
"Asmodeus",
|
||||
"Baalzebul",
|
||||
"Rimmon",
|
||||
"Dispater",
|
||||
"Fierna",
|
||||
"Glasya",
|
||||
"Levistus",
|
||||
"Mammon",
|
||||
"Mephistopheles",
|
||||
"Zariel",
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
class InfernalNameGenerator(types.NameGenerator):
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
language=Language,
|
||||
templates=types.NameSet(
|
||||
# (types.NameTemplate("adjective,name,nickname,surname"), 1.0),
|
||||
(types.NameTemplate("name"), 0.25),
|
||||
(types.NameTemplate("adjective,name"), 0.25),
|
||||
),
|
||||
adjectives=adjectives,
|
||||
)
|
||||
self.language.minimum_grapheme_count = 2
|
||||
self.suffixes = types.equal_weights(["us", "ius", "eus", "a", "an", "is"], 1.0, blank=False)
|
||||
|
||||
def get_name(self) -> str:
|
||||
return super().get_name() + self.suffixes.random()
|
||||
|
||||
|
||||
class NobleInfernalNameGenerator(types.NameGenerator):
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
language=Language,
|
||||
templates=types.NameSet(
|
||||
(types.NameTemplate("adjective,name"), 1.0),
|
||||
),
|
||||
adjectives=bloodlines,
|
||||
)
|
||||
self.language.minimum_grapheme_count = 2
|
||||
self.suffixes = types.equal_weights(["us", "ius", "to", "tro", "eus", "a", "an", "is"], 1.0, blank=False)
|
||||
|
||||
def get_name(self) -> str:
|
||||
return super().get_name() + self.suffixes.random()
|
||||
|
||||
|
||||
Name = InfernalNameGenerator()
|
||||
NobleName = NobleInfernalNameGenerator()
|
7
language/languages/infernal/rules.py
Normal file
7
language/languages/infernal/rules.py
Normal file
|
@ -0,0 +1,7 @@
|
|||
import logging
|
||||
|
||||
from language.rules import default_rules
|
||||
|
||||
logger = logging.getLogger("infernal-rules")
|
||||
|
||||
rules = default_rules
|
14
language/languages/lizardfolk/README.md
Normal file
14
language/languages/lizardfolk/README.md
Normal file
|
@ -0,0 +1,14 @@
|
|||
### Lizardfolk
|
||||
|
||||
The Lizardfolk language is a unique form of telepathy borne by pheramonne
|
||||
exchange. It is literally impossible for non-Lizardfolk to speak it, though
|
||||
with practice they can learn to recognize particular smells. Lizardfolk names
|
||||
are collections of scent pattterns, the first of which denotes the Lizardfolk's
|
||||
family origins.
|
||||
|
||||
*[No written language]*
|
||||
|
||||
**Lizardfolk Name Scents:**
|
||||
* Carmelized Roast Pomegranate
|
||||
* Rotting Hazelnut Citric
|
||||
* Pepper Broth Grape
|
7
language/languages/lizardfolk/__init__.py
Normal file
7
language/languages/lizardfolk/__init__.py
Normal file
|
@ -0,0 +1,7 @@
|
|||
"""
|
||||
Lizardfolk
|
||||
"""
|
||||
from .base import Language
|
||||
from .names import Name, NobleName
|
||||
|
||||
__all__ = [Language, Name, NobleName]
|
118
language/languages/lizardfolk/base.py
Normal file
118
language/languages/lizardfolk/base.py
Normal file
|
@ -0,0 +1,118 @@
|
|||
from language import types
|
||||
|
||||
scents = types.equal_weights(
|
||||
[
|
||||
"honey",
|
||||
"caramel",
|
||||
"maple syrup",
|
||||
"molasses",
|
||||
"dark chocolate",
|
||||
"chocolate",
|
||||
"almond",
|
||||
"hazelnut",
|
||||
"peanut",
|
||||
"clove",
|
||||
"cinnamon",
|
||||
"nutmeg",
|
||||
"anise",
|
||||
"malt",
|
||||
"grain",
|
||||
"roast",
|
||||
"smoke",
|
||||
"ash",
|
||||
"acrid",
|
||||
"rubber",
|
||||
"skunk",
|
||||
"petroleum",
|
||||
"medicine",
|
||||
"salt",
|
||||
"bitter",
|
||||
"phrenolic",
|
||||
"meat",
|
||||
"broth",
|
||||
"animal",
|
||||
"musty",
|
||||
"earth",
|
||||
"mould",
|
||||
"damp",
|
||||
"wood",
|
||||
"paper",
|
||||
"cardboard",
|
||||
"stale",
|
||||
"herb",
|
||||
"hay",
|
||||
"grass",
|
||||
"peapod",
|
||||
"whisky",
|
||||
"wine",
|
||||
"malic",
|
||||
"citric",
|
||||
"isovaleric",
|
||||
"butyric",
|
||||
"acetic",
|
||||
"lime",
|
||||
"lemon",
|
||||
"orange",
|
||||
"grapefruit",
|
||||
"pear",
|
||||
"peach",
|
||||
"apple",
|
||||
"grape",
|
||||
"pineapple",
|
||||
"pomegranate",
|
||||
"cherry",
|
||||
"coconut",
|
||||
"prune",
|
||||
"raisin",
|
||||
"strawberry",
|
||||
"blueberry",
|
||||
"raspberry",
|
||||
"blackberry",
|
||||
"jasmine",
|
||||
"rose",
|
||||
"camomile",
|
||||
"tobacco",
|
||||
],
|
||||
1.0,
|
||||
blank=False,
|
||||
)
|
||||
|
||||
family = types.equal_weights(
|
||||
[
|
||||
"sweet",
|
||||
"floral",
|
||||
"fruity",
|
||||
"sour",
|
||||
"fermented",
|
||||
"green",
|
||||
"vegetal",
|
||||
"old",
|
||||
"roasted",
|
||||
"spiced",
|
||||
"nutty",
|
||||
"cocoa",
|
||||
"pepper",
|
||||
"pungent",
|
||||
"burnt",
|
||||
"carmelized",
|
||||
"raw",
|
||||
"rotting",
|
||||
"dead",
|
||||
"young",
|
||||
],
|
||||
1.0,
|
||||
blank=False,
|
||||
)
|
||||
|
||||
Language = types.Language(
|
||||
name="lizardfolk",
|
||||
vowels=scents,
|
||||
consonants=family,
|
||||
prefixes=None,
|
||||
suffixes=None,
|
||||
syllables=types.SyllableSet(
|
||||
(types.Syllable(template="vowel"), 1.0),
|
||||
),
|
||||
rules=(),
|
||||
minimum_grapheme_count=1,
|
||||
)
|
19
language/languages/lizardfolk/names.py
Normal file
19
language/languages/lizardfolk/names.py
Normal file
|
@ -0,0 +1,19 @@
|
|||
from language import types
|
||||
from language.languages.lizardfolk import Language
|
||||
|
||||
|
||||
class LizardfolkNameGenerator(types.NameGenerator):
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
language=Language,
|
||||
templates=types.NameSet(
|
||||
(types.NameTemplate("surname,name,name"), 1.0),
|
||||
),
|
||||
)
|
||||
|
||||
def get_surname(self) -> str:
|
||||
return self.language.consonants.random().title()
|
||||
|
||||
|
||||
Name = LizardfolkNameGenerator()
|
||||
NobleName = Name
|
13
language/languages/orcish/README.md
Normal file
13
language/languages/orcish/README.md
Normal file
|
@ -0,0 +1,13 @@
|
|||
### Orcish
|
||||
|
||||
Spoken by Full- and Half-Orcs alike, Orcish bears some similarlities to
|
||||
Dwarvish's clipped, mono-syllablic construction. They differ in Half-Orc's
|
||||
prominent use of sibilants, perhaps as a result of being spoken by people with
|
||||
large, protruding tusks.
|
||||
|
||||
*Dbod hanot neb, hushi dupo shiza fesha keke fucha tpu.*
|
||||
|
||||
**Orcish Names:**
|
||||
* "Mad" Nha Pazk
|
||||
* Zashi Dizh
|
||||
* Pik Decht
|
7
language/languages/orcish/__init__.py
Normal file
7
language/languages/orcish/__init__.py
Normal file
|
@ -0,0 +1,7 @@
|
|||
"""
|
||||
Orcish
|
||||
"""
|
||||
from .base import Language
|
||||
from .names import Name, NobleName
|
||||
|
||||
__all__ = [Language, Name, NobleName]
|
36
language/languages/orcish/base.py
Normal file
36
language/languages/orcish/base.py
Normal file
|
@ -0,0 +1,36 @@
|
|||
from language import defaults, types
|
||||
|
||||
from .rules import rules
|
||||
|
||||
consonants = types.WeightedSet(
|
||||
("b", 1.0),
|
||||
("c", 1.0),
|
||||
("d", 1.0),
|
||||
("f", 0.5),
|
||||
("h", 1.0),
|
||||
("k", 1.0),
|
||||
("m", 0.3),
|
||||
("n", 0.3),
|
||||
("p", 1.0),
|
||||
("r", 0.2),
|
||||
("s", 0.1),
|
||||
("t", 1.0),
|
||||
("z", 1.0),
|
||||
("ch", 1.0),
|
||||
("sh", 0.7),
|
||||
("br", 1.0),
|
||||
)
|
||||
|
||||
Language = types.Language(
|
||||
name="orcish",
|
||||
vowels=defaults.vowels,
|
||||
consonants=consonants,
|
||||
prefixes=None,
|
||||
suffixes=None,
|
||||
syllables=types.SyllableSet(
|
||||
(types.Syllable(template="consonant,vowel") * 2, 1.0),
|
||||
(types.Syllable(template="consonant,vowel,consonant,vowel,consonant"), 0.5),
|
||||
),
|
||||
rules=rules,
|
||||
minimum_grapheme_count=1,
|
||||
)
|
60
language/languages/orcish/names.py
Normal file
60
language/languages/orcish/names.py
Normal file
|
@ -0,0 +1,60 @@
|
|||
from language import defaults, types
|
||||
from language.languages.orcish import Language
|
||||
|
||||
|
||||
class OrcishNameGenerator(types.NameGenerator):
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
language=Language,
|
||||
templates=types.NameSet(
|
||||
# (types.NameTemplate("adjective,name,nickname,surname"), 1.0),
|
||||
(types.NameTemplate("adjective,name,surname"), 1.0),
|
||||
),
|
||||
affixes=None,
|
||||
adjectives=defaults.adjectives,
|
||||
titles=defaults.titles,
|
||||
)
|
||||
self.language.minimum_grapheme_count = 2
|
||||
self.suffixes = types.equal_weights(
|
||||
[
|
||||
"acht",
|
||||
"echt",
|
||||
"icht",
|
||||
"ocht",
|
||||
"ucht",
|
||||
"ak",
|
||||
"ek",
|
||||
"ik",
|
||||
"ok",
|
||||
"uk",
|
||||
"ach",
|
||||
"ech",
|
||||
"ich",
|
||||
"och",
|
||||
"uch",
|
||||
"atch",
|
||||
"etch",
|
||||
"itch",
|
||||
"otch",
|
||||
"utch",
|
||||
"azk",
|
||||
"ezk",
|
||||
"izk",
|
||||
"ozk",
|
||||
"uzk",
|
||||
"azh",
|
||||
"ezh",
|
||||
"izh",
|
||||
"ozh",
|
||||
"uzh",
|
||||
],
|
||||
1.0,
|
||||
blank=False,
|
||||
)
|
||||
|
||||
def get_surname(self) -> str:
|
||||
return self.language.add_grapheme(word="", template="consonant").strip().title() + self.suffixes.random()
|
||||
|
||||
|
||||
Name = OrcishNameGenerator()
|
||||
NobleName = Name
|
24
language/languages/orcish/rules.py
Normal file
24
language/languages/orcish/rules.py
Normal file
|
@ -0,0 +1,24 @@
|
|||
import logging
|
||||
import re
|
||||
|
||||
from language.rules import default_rules
|
||||
from language.types import Language
|
||||
|
||||
logger = logging.getLogger("orcish-rules")
|
||||
|
||||
|
||||
def cannot_start_with_repeated_consonants(language: Language, word: str) -> bool:
|
||||
found = re.compile(r"(^[bcdfghklmnpqrstvwxz]{3})").search(word)
|
||||
if not found:
|
||||
return True
|
||||
|
||||
first, second, third = found.group(1)
|
||||
if first == second == third:
|
||||
logger.debug(f"{word} starts with a repeated consonant.")
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
rules = default_rules
|
||||
rules.add(cannot_start_with_repeated_consonants)
|
18
language/languages/undercommon/README.md
Normal file
18
language/languages/undercommon/README.md
Normal file
|
@ -0,0 +1,18 @@
|
|||
### Undercommon
|
||||
|
||||
The language of the Drow is the defacto language of the Underdark, and is
|
||||
spoken by most peoples there. Like the Drow themselves, Undercommon diverged
|
||||
from an Elvish dialect in ancient times. The two still bear some resemblance,
|
||||
notably in the construction of names.
|
||||
|
||||
*Okvösaa licopod lohiwia uüyötüe uywubit uegäsit uäpisoa, caquyee redoxöd, mraomoa.*
|
||||
|
||||
**Undercommon Names:**
|
||||
* Tfubam Umaiuhüen
|
||||
* Nanosd Aneedaöbaas
|
||||
* Rerwus Amzuremolr
|
||||
|
||||
**Noble Undercommon (Drow) Names:**
|
||||
* Uopiyie Alzejakäurn
|
||||
* Opyäulol Elchöläss
|
||||
* Iqyäuwed Anlcoduir
|
7
language/languages/undercommon/__init__.py
Normal file
7
language/languages/undercommon/__init__.py
Normal file
|
@ -0,0 +1,7 @@
|
|||
"""
|
||||
Undercommon
|
||||
"""
|
||||
from .base import Language
|
||||
from .names import Name, NobleName
|
||||
|
||||
__all__ = [Language, Name, NobleName]
|
21
language/languages/undercommon/base.py
Normal file
21
language/languages/undercommon/base.py
Normal file
|
@ -0,0 +1,21 @@
|
|||
from language import defaults, types
|
||||
|
||||
from .rules import rules
|
||||
|
||||
vowels = defaults.vowels + types.equal_weights(["ä", "ö", "ü", "äu"], 0.5, blank=False)
|
||||
prefixes = defaults.vowels + types.equal_weights(["c", "g", "l", "m", "n", "r", "s", "t", "v", "z"], 1.0, blank=False)
|
||||
suffixes = types.equal_weights(["a", "e", "i", "t", "s", "m", "n", "l", "r", "d", "a", "th"], 1.0, blank=False)
|
||||
|
||||
Language = types.Language(
|
||||
name="undercommon",
|
||||
vowels=vowels,
|
||||
consonants=defaults.consonants,
|
||||
prefixes=prefixes,
|
||||
suffixes=suffixes,
|
||||
syllables=types.SyllableSet(
|
||||
(types.Syllable(template="vowel,consonant,vowel") * 2, 0.15),
|
||||
(types.Syllable(template="consonant|vowel,consonant,vowel,consonant,vowel"), 1.0),
|
||||
),
|
||||
rules=rules,
|
||||
minimum_grapheme_count=2,
|
||||
)
|
37
language/languages/undercommon/names.py
Normal file
37
language/languages/undercommon/names.py
Normal file
|
@ -0,0 +1,37 @@
|
|||
import random
|
||||
|
||||
from language import defaults, types
|
||||
from language.languages.undercommon import Language
|
||||
|
||||
PlaceName = types.NameGenerator(
|
||||
language=Language,
|
||||
syllables=Language.syllables,
|
||||
templates=types.NameSet(
|
||||
(types.NameTemplate("affix,name"), 1.0),
|
||||
),
|
||||
affixes=types.WeightedSet(("el", 1.0)),
|
||||
adjectives=defaults.adjectives,
|
||||
suffixes=Language.suffixes,
|
||||
)
|
||||
|
||||
|
||||
class DrowName(types.NameGenerator):
|
||||
def __init__(self):
|
||||
super().__init__(
|
||||
language=Language,
|
||||
syllables=Language.syllables,
|
||||
templates=types.NameSet(
|
||||
(types.NameTemplate("name,surname"), 1.0),
|
||||
),
|
||||
)
|
||||
self.language.minimum_grapheme_count = 2
|
||||
self.place_generator = PlaceName
|
||||
self.affixes = types.equal_weights(["am", "an", "al", "um"], weight=1.0, blank=False)
|
||||
|
||||
def get_surname(self) -> str:
|
||||
name = self.place_generator.name()[0]["name"][0]
|
||||
return (self.affixes.random() + name + random.choice(["th", "s", "r", "n"])).title()
|
||||
|
||||
|
||||
Name = DrowName()
|
||||
NobleName = Name
|
88
language/languages/undercommon/rules.py
Normal file
88
language/languages/undercommon/rules.py
Normal file
|
@ -0,0 +1,88 @@
|
|||
import logging
|
||||
import re
|
||||
|
||||
from language.rules import default_rules
|
||||
from language.types import Language
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
valid_consonant_sequences = [
|
||||
"cc",
|
||||
"ht",
|
||||
"kd",
|
||||
"kl",
|
||||
"km",
|
||||
"kp",
|
||||
"kt",
|
||||
"kv",
|
||||
"kw",
|
||||
"ky",
|
||||
"lc",
|
||||
"ld",
|
||||
"lf",
|
||||
"ll",
|
||||
"lm",
|
||||
"lp",
|
||||
"lt",
|
||||
"lv",
|
||||
"lw",
|
||||
"ly",
|
||||
"mb",
|
||||
"mm",
|
||||
"mp",
|
||||
"my",
|
||||
"nc",
|
||||
"nd",
|
||||
"ng",
|
||||
"nn",
|
||||
"nt",
|
||||
"nw",
|
||||
"ny",
|
||||
"ps",
|
||||
"pt",
|
||||
"rc",
|
||||
"rd",
|
||||
"rm",
|
||||
"rn",
|
||||
"rp",
|
||||
"rr",
|
||||
"rs",
|
||||
"rt",
|
||||
"rw",
|
||||
"ry",
|
||||
"sc",
|
||||
"ss",
|
||||
"ts",
|
||||
"tt",
|
||||
"th",
|
||||
"tw",
|
||||
"ty",
|
||||
]
|
||||
|
||||
|
||||
def valid_sequences(language: Language, word: str) -> bool:
|
||||
found = re.compile(r"([bcdfghjklmnpqrstvwxz]{2})").findall(word)
|
||||
if not found:
|
||||
return True
|
||||
|
||||
invalid = [seq for seq in found if seq not in valid_consonant_sequences]
|
||||
if invalid:
|
||||
logger.debug(f"{word} contains invalid consonant sequences: {invalid}")
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def too_many_vowels(language: Language, word: str) -> bool:
|
||||
found = re.compile(r"[" + "".join(language.vowels.members) + r"]{3}").findall(word)
|
||||
if found == []:
|
||||
return True
|
||||
logger.debug(f"{word} has too many contiguous vowels: {found}")
|
||||
return False
|
||||
|
||||
|
||||
rules = default_rules.union(
|
||||
{
|
||||
valid_sequences,
|
||||
too_many_vowels,
|
||||
}
|
||||
)
|
43
language/rules.py
Normal file
43
language/rules.py
Normal file
|
@ -0,0 +1,43 @@
|
|||
import logging
|
||||
import re
|
||||
|
||||
from language.types import Language
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
|
||||
def too_many_vowels(language: Language, word: str) -> bool:
|
||||
found = re.compile(r"[aeiou]{3}").findall(word)
|
||||
if found == []:
|
||||
return True
|
||||
logger.debug(f"{word} has too many contiguous vowels: {found}")
|
||||
return False
|
||||
|
||||
|
||||
def too_many_consonants(language: Language, word: str) -> bool:
|
||||
found = re.compile(r"[bcdfghjklmnpqrstvwxz]{3}").findall(word)
|
||||
if found == []:
|
||||
return True
|
||||
logger.debug(f"{word} has too many contiguous consonants: {found}")
|
||||
return False
|
||||
|
||||
|
||||
def cannot_have_just_repeated_vowels(language: Language, word: str) -> bool:
|
||||
if len(word) == 1:
|
||||
return True
|
||||
uniq = {letter for letter in word}
|
||||
if len(uniq) > 1:
|
||||
return True
|
||||
logger.debug(f"{word} consists of only one repeated letter.")
|
||||
return False
|
||||
|
||||
|
||||
def must_have_a_vowel(language: Language, word: str) -> bool:
|
||||
for vowel in language.vowels.members:
|
||||
if vowel in word:
|
||||
return True
|
||||
logger.debug(f"{word} does not contain a vowel.")
|
||||
return False
|
||||
|
||||
|
||||
default_rules = {must_have_a_vowel, too_many_vowels, too_many_consonants, cannot_have_just_repeated_vowels}
|
461
language/types.py
Normal file
461
language/types.py
Normal file
|
@ -0,0 +1,461 @@
|
|||
import inspect
|
||||
import random
|
||||
from collections import defaultdict
|
||||
from typing import Union
|
||||
|
||||
|
||||
class LanguageError(Exception):
|
||||
"""
|
||||
Thrown when an error is encountered in language construction.
|
||||
"""
|
||||
|
||||
|
||||
class ImprobableTemplateError(Exception):
|
||||
"""
|
||||
Thrown when too many successive attempts to create a word which passes all
|
||||
language rules fails.
|
||||
"""
|
||||
|
||||
|
||||
class WeightedSet:
|
||||
"""
|
||||
A set in which members each have a weight, used for selecting at random.
|
||||
|
||||
Usage:
|
||||
>>> ws = WeightedSet(('foo', 1.0), ('bar', 0.5))
|
||||
>>> ws.random()
|
||||
('foo', 1.0)
|
||||
"""
|
||||
|
||||
def __init__(self, *weighted_members: tuple):
|
||||
self.members = []
|
||||
self.weights = []
|
||||
if weighted_members:
|
||||
self.members, self.weights = list(zip(*weighted_members))
|
||||
|
||||
def random(self) -> str:
|
||||
return random.choices(self.members, self.weights)[0]
|
||||
|
||||
def __add__(self, obj):
|
||||
ws = WeightedSet()
|
||||
ws.members = self.members + obj.members
|
||||
ws.weights = self.weights + obj.weights
|
||||
return ws
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.members}\n{self.weights}"
|
||||
|
||||
|
||||
class Syllable:
|
||||
"""
|
||||
One syllable of a word. Used to populate a SyllableSet.
|
||||
|
||||
A syllable template is a string consisting of one or more grapheme types
|
||||
separated by a vertical pipe (|). Multiple template strings can be
|
||||
concatenated together with commas. When words are constructed, each
|
||||
syllable is populated with a random sequence chosen by
|
||||
Language.add_grapheme().
|
||||
|
||||
A syllable template must contain at least one 'vowel'.
|
||||
|
||||
Syllables can be multiplied by integers to produce repeated templates.
|
||||
|
||||
Usage:
|
||||
# A syllable consisting of either a vowel or a consonant, followed by
|
||||
# a vowel, followed by either a vowel or consonant.
|
||||
>>> foo = Syllable(template='vowel|consonant,vowel,consonant|vowel')
|
||||
|
||||
# Example multiplication
|
||||
>>> print(Syllable(template='vowel|consonant') * 3)
|
||||
vowel|consonant vowel|consonant vowel|consonant
|
||||
"""
|
||||
|
||||
def __init__(self, template: str = "vowel|consonant"):
|
||||
self.template = template
|
||||
self.validate()
|
||||
|
||||
def validate(self):
|
||||
if "vowel" not in self.template:
|
||||
raise LanguageError(
|
||||
f"Invalid syllable template {self.template}!\n"
|
||||
"Syllables must have at least one vowel in the template."
|
||||
)
|
||||
|
||||
def __mul__(self, count: int):
|
||||
return Syllable(template=",".join([self.template] * count))
|
||||
|
||||
__rmul__ = __mul__
|
||||
|
||||
def __str__(self):
|
||||
return self.template
|
||||
|
||||
|
||||
class SyllableSet(WeightedSet):
|
||||
"""
|
||||
A WeightedSet that selects random syllables.
|
||||
|
||||
Usage:
|
||||
>>> word = SyllableSet(
|
||||
(Syllable('vowel'), 1.0),
|
||||
(Syllable('vowel|consonant') * 2, 1.0),
|
||||
(syllable('vowel|consonant,vowel|consonant') * 3, 0.75)
|
||||
)
|
||||
>>> word.random()
|
||||
vowel consonant consonant vowel
|
||||
"""
|
||||
|
||||
def random(self) -> iter:
|
||||
for syllable in random.choices(self.members, self.weights)[0].template.split(","):
|
||||
grapheme_template = random.choice(syllable.split("|"))
|
||||
yield grapheme_template.lower()
|
||||
|
||||
|
||||
class Language:
|
||||
"""
|
||||
A class representing a language.
|
||||
|
||||
Usage:
|
||||
>>> Common = Language(
|
||||
name="common",
|
||||
vowels=WeightedSet(("a", 1.0), ("e", 1.0), ("i", 1.0), ...),
|
||||
consonants=WeightedSet(("b", 0.5), ("c", 0.5), ("d", 0.5), ...),
|
||||
prefixes=WeightedSet(("re", 0.5), ("de", 0.5), ("", 1.0), ...),
|
||||
suffixes=WeightedSet(("ed", 0.5), ("ing", 0.5), ("", 1.0), ...),
|
||||
syllables=SyllableSet(
|
||||
(Syllable('consonant|vowel'), 1.0),
|
||||
(Syllable('consonant|vowel') * 2, 0.75),
|
||||
...
|
||||
),
|
||||
rules=set(callable1, callable2, ...),
|
||||
minimum_grapheme_count=2,
|
||||
)
|
||||
>>> Common.word()
|
||||
reibing
|
||||
|
||||
How Words Are Constructed:
|
||||
|
||||
The main interface for callers is word(), which returns a
|
||||
randomly-generated word in the language according to the following
|
||||
algorithm:
|
||||
|
||||
1. Choose a random syllable from the syllable set
|
||||
2. For each grapheme in the syllable
|
||||
3. Choose a random grapheme template
|
||||
4. Choose a random sequence from the language for that grapheme
|
||||
5. Validate the word against the language rules
|
||||
6. Repeat 1-5 until a valid word is generated
|
||||
7. Add a prefix and suffix, if they are defined
|
||||
|
||||
The following graphemes are supported by default:
|
||||
- vowel
|
||||
- consonant
|
||||
- prefix
|
||||
- suffix
|
||||
|
||||
When graphemes are chosen, the following rules are applied:
|
||||
- Every syllable must have at least one vowel
|
||||
- A syllable may never have three consecutive consonants
|
||||
|
||||
How Words Are Validated:
|
||||
|
||||
Once a word has been constructed by populating syllable templates, it is
|
||||
tested against one or more language rules.
|
||||
|
||||
The default rules are defined in language.rules.default_rules; they are:
|
||||
- the word must contain at least one vowel
|
||||
- the word must not contain 3 or more contiguous english vowels
|
||||
- the word must not contain 3 or more contiguous english consonants
|
||||
- the word must not consist of just one vowel, repeated
|
||||
|
||||
Since it is possible to craft Syllables resulting in grapheme
|
||||
selections that rarely or never yield valid words, or rules that
|
||||
reject every word, an ImprobableTemplateError will be thrown if
|
||||
10 successive attempts to create a valid word fail.
|
||||
|
||||
Extending Languages:
|
||||
|
||||
Graphemes are populated by means of callbacks which select a member
|
||||
of the associated weighted set at random. Graphemes can be any string,
|
||||
so long as the Language class has a matching callback.
|
||||
|
||||
To add support for a new grapheme type, define a method on your
|
||||
Language class called get_grapheme_TYPE, where TYPE is the string
|
||||
used in your Syllable templates. Examine test cases in test_types.py
|
||||
for examples.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
vowels: WeightedSet,
|
||||
consonants: WeightedSet,
|
||||
prefixes: WeightedSet,
|
||||
suffixes: WeightedSet,
|
||||
syllables: SyllableSet,
|
||||
rules: set = set(),
|
||||
minimum_grapheme_count: int = 1,
|
||||
):
|
||||
"""
|
||||
Args:
|
||||
name - friendly name for the language
|
||||
vowels - the weighted set of vowel graphemes
|
||||
consonants - the weighted set of consonant graphemes
|
||||
prefixes - the weighted set of prefix graphemes
|
||||
suffixes - the weighted set of suffix graphemes
|
||||
rules - a set of rules callbacks; see above.
|
||||
minimum_grapheme_count - the minimum number of graphemes in each word
|
||||
"""
|
||||
self.name = name
|
||||
self.vowels = vowels
|
||||
self.consonants = consonants
|
||||
self.prefixes = prefixes
|
||||
self.suffixes = suffixes
|
||||
self.rules = rules
|
||||
self.syllables = syllables
|
||||
self.minimum_grapheme_count = minimum_grapheme_count
|
||||
self.validate_syllable_set()
|
||||
|
||||
self.handlers = dict([(n, v) for (n, v) in inspect.getmembers(self, inspect.ismethod) if n.startswith("get_")])
|
||||
|
||||
def validate(self, word: str) -> bool:
|
||||
"""
|
||||
Returns true if the given word is possible in the current language.
|
||||
"""
|
||||
if not word:
|
||||
return False
|
||||
for rule in self.rules:
|
||||
if not rule(self, word):
|
||||
return False
|
||||
return True
|
||||
|
||||
def validate_syllable_set(self):
|
||||
for syllable in self.syllables.members:
|
||||
if len(syllable.template.split(",")) < self.minimum_grapheme_count:
|
||||
raise ImprobableTemplateError(
|
||||
f"Syllable {syllable} does not define enough graphemes ({self.minimum_grapheme_count} required)."
|
||||
)
|
||||
|
||||
def validate_graphemes(self, graphemes: list) -> bool:
|
||||
if len(graphemes) < self.minimum_grapheme_count:
|
||||
return False
|
||||
|
||||
last = ""
|
||||
count = 0
|
||||
for g in graphemes:
|
||||
if g == last:
|
||||
count += 1
|
||||
if count == 3:
|
||||
return False
|
||||
else:
|
||||
count = 1
|
||||
last = g
|
||||
return True
|
||||
|
||||
def word(self, count: int = 1) -> list:
|
||||
"""
|
||||
Yields words composed of randomized phonemes built from a random word template.
|
||||
"""
|
||||
words = []
|
||||
for _ in range(count):
|
||||
random_word = ""
|
||||
attempts = 0
|
||||
while not self.validate(random_word):
|
||||
if attempts == 10:
|
||||
raise ImprobableTemplateError(
|
||||
f"Exhausted all attempts to create a valid word. Last attempt: {random_word}. "
|
||||
"If you're getting this a lot, try enabling debugging to see what rules are failing."
|
||||
)
|
||||
graphemes = []
|
||||
random_word = ""
|
||||
while not self.validate_graphemes(graphemes):
|
||||
graphemes = list(self.syllables.random())
|
||||
for grapheme in graphemes:
|
||||
random_word = self.add_grapheme(random_word, grapheme)
|
||||
attempts += 1
|
||||
if self.prefixes:
|
||||
random_word = self.get_grapheme_prefix() + random_word
|
||||
if self.suffixes:
|
||||
random_word = random_word + self.get_grapheme_suffix()
|
||||
words.append(random_word)
|
||||
return words
|
||||
|
||||
def add_grapheme(self, word: str, template: str) -> str:
|
||||
"""
|
||||
Returns a random grapheme of a supported type. The class must support a method of the name:
|
||||
get_grapheme_{template}
|
||||
"""
|
||||
template = template.lower()
|
||||
try:
|
||||
return word + self.handlers[f"get_grapheme_{template}"]()
|
||||
except KeyError:
|
||||
raise NotImplementedError(
|
||||
f"No handler found for grapheme template '{template}'. "
|
||||
f"Do you need to define get_grapheme_{template}()?\n"
|
||||
"Supported handlers: " + self.handlers.keys
|
||||
)
|
||||
|
||||
def get_grapheme_consonant(self) -> str:
|
||||
return self.consonants.random()
|
||||
|
||||
def get_grapheme_vowel(self) -> str:
|
||||
return self.vowels.random()
|
||||
|
||||
def get_grapheme_prefix(self) -> str:
|
||||
return self.prefixes.random()
|
||||
|
||||
def get_grapheme_suffix(self) -> str:
|
||||
return self.suffixes.random()
|
||||
|
||||
def text(self, count: int = 25) -> str:
|
||||
phrases = []
|
||||
phrase = []
|
||||
for word in self.word(count):
|
||||
phrase.append(str(word))
|
||||
if len(phrase) >= random.randint(1, 12):
|
||||
phrases.append(" ".join(phrase))
|
||||
phrase = []
|
||||
if phrase:
|
||||
phrases.append(" ".join(phrase))
|
||||
|
||||
paragraph = phrases[0].capitalize()
|
||||
for phrase in phrases[1:]:
|
||||
if random.choice([0, 0, 1]):
|
||||
paragraph = paragraph + random.choice("?!.") + " " + phrase.capitalize()
|
||||
else:
|
||||
paragraph = paragraph + ", " + phrase
|
||||
paragraph = paragraph + random.choice("?!.")
|
||||
return paragraph
|
||||
|
||||
def copy(self):
|
||||
return self.__class__(
|
||||
name=self.name,
|
||||
vowels=self.vowels,
|
||||
consonants=self.consonants,
|
||||
prefixes=self.prefixes,
|
||||
suffixes=self.suffixes,
|
||||
rules=self.rules,
|
||||
syllables=self.syllables,
|
||||
minimum_grapheme_count=self.minimum_grapheme_count,
|
||||
)
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.word()[0]
|
||||
|
||||
|
||||
NameSet = SyllableSet
|
||||
|
||||
|
||||
class Name(defaultdict):
|
||||
def __str__(self):
|
||||
return self["fullname"][0]
|
||||
|
||||
|
||||
class NameTemplate(Syllable):
|
||||
def validate(self):
|
||||
pass
|
||||
|
||||
|
||||
class NameGenerator:
|
||||
def __init__(
|
||||
self,
|
||||
language: Language,
|
||||
templates: NameSet,
|
||||
syllables: Union[SyllableSet, None] = None,
|
||||
names: Union[WeightedSet, None] = None,
|
||||
surnames: Union[WeightedSet, None] = None,
|
||||
nicknames: Union[WeightedSet, None] = None,
|
||||
adjectives: Union[WeightedSet, None] = None,
|
||||
titles: Union[WeightedSet, None] = None,
|
||||
counts: Union[WeightedSet, None] = None,
|
||||
affixes: Union[WeightedSet, None] = None,
|
||||
suffixes: Union[WeightedSet, None] = None,
|
||||
):
|
||||
self.language = language.copy()
|
||||
if syllables:
|
||||
self.language.syllables = syllables
|
||||
self.templates = templates
|
||||
self._names = names
|
||||
self._surnames = surnames
|
||||
self._nicknames = nicknames
|
||||
self._adjectives = adjectives
|
||||
self._titles = titles
|
||||
self._counts = counts
|
||||
self._suffixes = suffixes
|
||||
self._affixes = affixes
|
||||
|
||||
self.handlers = dict([(n, v) for (n, v) in inspect.getmembers(self, inspect.ismethod) if n.startswith("get_")])
|
||||
|
||||
def name(self, count: int = 1) -> list:
|
||||
"""
|
||||
Generate Name instances.
|
||||
"""
|
||||
names = []
|
||||
for _ in range(count):
|
||||
name = Name(list)
|
||||
fullname = []
|
||||
for part in self.templates.random():
|
||||
thisname = self.add_part(part).strip()
|
||||
if not thisname:
|
||||
continue
|
||||
name[part].append(thisname)
|
||||
fullname.append(thisname)
|
||||
name["fullname"] = " ".join(fullname)
|
||||
names.append(name)
|
||||
return names
|
||||
|
||||
def add_part(self, template: str) -> str:
|
||||
template = template.lower()
|
||||
try:
|
||||
return self.handlers[f"get_{template}"]()
|
||||
except KeyError:
|
||||
raise NotImplementedError(
|
||||
f"No handler found for name template '{template}' on class {self.__class__.__name__}. "
|
||||
f"Do you need to define get_{template}()?\nSupported Handlers: "
|
||||
+ ",".join(n for n in dir(self) if n.startswith("get_"))
|
||||
)
|
||||
|
||||
def get_name(self) -> str:
|
||||
name = (self._names.random() if self._names else self.language.word())[0]
|
||||
return name.title()
|
||||
|
||||
def get_surname(self) -> str:
|
||||
name = (self._surnames.random() if self._surnames else self.language.word())[0]
|
||||
if self._suffixes:
|
||||
name = name + self._suffixes.random()
|
||||
if len(name) == 1:
|
||||
name = f"{name}."
|
||||
return name.title()
|
||||
|
||||
def get_adjective(self) -> str:
|
||||
return (self._adjectives.random() if self._adjectives else "").title()
|
||||
|
||||
def get_affix(self) -> str:
|
||||
return self._affixes.random() if self._affixes else ""
|
||||
|
||||
def get_title(self) -> str:
|
||||
return (self._titles.random() if self._titles else "").title()
|
||||
|
||||
def get_the(self) -> str:
|
||||
return "the"
|
||||
|
||||
def get_count(self) -> str:
|
||||
return self._counts.random() if self._counts else ""
|
||||
|
||||
def get_nickname(self) -> str:
|
||||
name = (self._nicknames.random() if self._nicknames else "").title()
|
||||
if name:
|
||||
return '"' + name + '"'
|
||||
return ""
|
||||
|
||||
def get_initial(self) -> str:
|
||||
return
|
||||
|
||||
def __str__(self) -> str:
|
||||
return self.name()[0]["fullname"]
|
||||
|
||||
|
||||
def equal_weights(terms: list, weight: float = 1.0, blank: bool = True) -> WeightedSet:
|
||||
ws = WeightedSet(*[(term, weight) for term in terms])
|
||||
if blank:
|
||||
ws = WeightedSet(("", 1.0)) + ws
|
||||
return ws
|
47
pyproject.toml
Normal file
47
pyproject.toml
Normal file
|
@ -0,0 +1,47 @@
|
|||
[tool.poetry]
|
||||
name = "dnd-name-generator"
|
||||
version = "1.0"
|
||||
description = "Fantasy language generators for D&D"
|
||||
authors = ["evilchili <evilchili@gmail.com>"]
|
||||
license = "The Unlicense"
|
||||
packages = [
|
||||
{ include = 'language' },
|
||||
]
|
||||
|
||||
[tool.poetry.dependencies]
|
||||
python = "^3.10"
|
||||
typer = "latest"
|
||||
rich = "latest"
|
||||
dice = "latest"
|
||||
|
||||
[tool.poetry.dev-dependencies]
|
||||
pytest = "latest"
|
||||
black = "^23.3.0"
|
||||
isort = "^5.12.0"
|
||||
pyproject-autoflake = "^1.0.2"
|
||||
|
||||
[build-system]
|
||||
requires = ["poetry-core>=1.0.0"]
|
||||
build-backend = "poetry.core.masonry.api"
|
||||
|
||||
[tool.black]
|
||||
line-length = 120
|
||||
target-version = ['py310']
|
||||
|
||||
[tool.isort]
|
||||
multi_line_output = 3
|
||||
line_length = 120
|
||||
include_trailing_comma = true
|
||||
|
||||
[tool.autoflake]
|
||||
check = false # return error code if changes are needed
|
||||
in-place = true # make changes to files instead of printing diffs
|
||||
recursive = true # drill down directories recursively
|
||||
remove-all-unused-imports = true # remove all unused imports (not just those from the standard library)
|
||||
ignore-init-module-imports = true # exclude __init__.py when removing unused imports
|
||||
remove-duplicate-keys = true # remove all duplicate keys in objects
|
||||
remove-unused-variables = true # remove unused variables
|
||||
|
||||
[tool.poetry.scripts]
|
||||
fanlang = "language.cli:app"
|
||||
|
Loading…
Reference in New Issue
Block a user