|
|
function shuffled(list) {
|
|
|
var newlist = [];
|
|
|
for (var i = 0; i < list.length; i++) {
|
|
|
newlist.push(list[i]);
|
|
|
}
|
|
|
for (var i = list.length - 1; i > 0; i--) {
|
|
|
var tmp = newlist[i];
|
|
|
var j = randrange(i);
|
|
|
newlist[i] = newlist[j];
|
|
|
newlist[j] = tmp;
|
|
|
}
|
|
|
return newlist;
|
|
|
}
|
|
|
|
|
|
function choose(list, exponent) {
|
|
|
exponent = exponent || 1;
|
|
|
return list[Math.floor(Math.pow(Math.random(), exponent) * list.length)];
|
|
|
}
|
|
|
|
|
|
function randrange(lo, hi) {
|
|
|
if (hi == undefined) {
|
|
|
hi = lo;
|
|
|
lo = 0;
|
|
|
}
|
|
|
return Math.floor(Math.random() * (hi - lo)) + lo;
|
|
|
}
|
|
|
|
|
|
function join(list, sep) {
|
|
|
if (list.length == 0) return '';
|
|
|
sep = sep || '';
|
|
|
var s = list[0];
|
|
|
for (var i = 1; i < list.length; i++) {
|
|
|
s += sep;
|
|
|
s += list[i];
|
|
|
}
|
|
|
return s;
|
|
|
}
|
|
|
|
|
|
function capitalize(word) {
|
|
|
return word[0].toUpperCase() + word.slice(1);
|
|
|
}
|
|
|
|
|
|
function spell(lang, syll) {
|
|
|
if (lang.noortho) return syll;
|
|
|
var s = '';
|
|
|
for (var i = 0; i < syll.length; i++) {
|
|
|
var c = syll[i];
|
|
|
s += lang.cortho[c] || lang.vortho[c] || defaultOrtho[c] || c;
|
|
|
}
|
|
|
return s;
|
|
|
}
|
|
|
|
|
|
function makeSyllable(lang) {
|
|
|
while (true) {
|
|
|
var syll = "";
|
|
|
for (var i = 0; i < lang.structure.length; i++) {
|
|
|
var ptype = lang.structure[i];
|
|
|
if (lang.structure[i+1] == '?') {
|
|
|
i++;
|
|
|
if (Math.random() < 0.5) {
|
|
|
continue;
|
|
|
}
|
|
|
}
|
|
|
syll += choose(lang.phonemes[ptype], lang.exponent);
|
|
|
}
|
|
|
var bad = false;
|
|
|
for (var i = 0; i < lang.restricts.length; i++) {
|
|
|
if (lang.restricts[i].test(syll)) {
|
|
|
bad = true;
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
if (bad) continue;
|
|
|
return spell(lang, syll);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
function getMorpheme(lang, key) {
|
|
|
if (lang.nomorph) {
|
|
|
return makeSyllable(lang);
|
|
|
}
|
|
|
key = key || '';
|
|
|
var list = lang.morphemes[key] || [];
|
|
|
var extras = 10;
|
|
|
if (key) extras = 1;
|
|
|
while (true) {
|
|
|
var n = randrange(list.length + extras);
|
|
|
if (list[n]) return list[n];
|
|
|
var morph = makeSyllable(lang);
|
|
|
var bad = false;
|
|
|
for (var k in lang.morphemes) {
|
|
|
if (lang.morphemes[k].includes(morph)) {
|
|
|
bad = true;
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
if (bad) continue;
|
|
|
list.push(morph);
|
|
|
lang.morphemes[key] = list;
|
|
|
return morph;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
function makeWord(lang, key) {
|
|
|
var nsylls = randrange(lang.minsyll, lang.maxsyll + 1);
|
|
|
var w = '';
|
|
|
var keys = [];
|
|
|
keys[randrange(nsylls)] = key;
|
|
|
for (var i = 0; i < nsylls; i++) {
|
|
|
w += getMorpheme(lang, keys[i]);
|
|
|
}
|
|
|
return w;
|
|
|
}
|
|
|
|
|
|
function getWord(lang, key) {
|
|
|
key = key || '';
|
|
|
var ws = lang.words[key] || [];
|
|
|
var extras = 3;
|
|
|
if (key) extras = 2;
|
|
|
while (true) {
|
|
|
var n = randrange(ws.length + extras);
|
|
|
var w = ws[n];
|
|
|
if (w) {
|
|
|
return w;
|
|
|
}
|
|
|
w = makeWord(lang, key);
|
|
|
var bad = false;
|
|
|
for (var k in lang.words) {
|
|
|
if (lang.words[k].includes(w)) {
|
|
|
bad = true;
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
if (bad) continue;
|
|
|
ws.push(w);
|
|
|
lang.words[key] = ws;
|
|
|
return w;
|
|
|
}
|
|
|
}
|
|
|
function makeName(lang, key) {
|
|
|
key = key || '';
|
|
|
lang.genitive = lang.genitive || getMorpheme(lang, 'of');
|
|
|
lang.definite = lang.definite || getMorpheme(lang, 'the');
|
|
|
while (true) {
|
|
|
var name = null;
|
|
|
if (Math.random() < 0.5) {
|
|
|
name = capitalize(getWord(lang, key));
|
|
|
} else {
|
|
|
var w1 = capitalize(getWord(lang, Math.random() < 0.6 ? key : ''));
|
|
|
var w2 = capitalize(getWord(lang, Math.random() < 0.6 ? key : ''));
|
|
|
if (w1 == w2) continue;
|
|
|
if (Math.random() > 0.5) {
|
|
|
name = join([w1, w2], lang.joiner);
|
|
|
} else {
|
|
|
name = join([w1, lang.genitive, w2], lang.joiner);
|
|
|
}
|
|
|
}
|
|
|
if (Math.random() < 0.1) {
|
|
|
name = join([lang.definite, name], lang.joiner);
|
|
|
}
|
|
|
|
|
|
if ((name.length < lang.minchar) || (name.length > lang.maxchar)) continue;
|
|
|
var used = false;
|
|
|
for (var i = 0; i < lang.names.length; i++) {
|
|
|
var name2 = lang.names[i];
|
|
|
if ((name.indexOf(name2) != -1) || (name2.indexOf(name) != -1)) {
|
|
|
used = true;
|
|
|
break;
|
|
|
}
|
|
|
}
|
|
|
if (used) continue;
|
|
|
lang.names.push(name);
|
|
|
return name;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
function makeBasicLanguage() {
|
|
|
return {
|
|
|
phonemes: {
|
|
|
C: "ptkmnls",
|
|
|
V: "aeiou",
|
|
|
S: "s",
|
|
|
F: "mn",
|
|
|
L: "rl"
|
|
|
},
|
|
|
structure: "CVC",
|
|
|
exponent: 2,
|
|
|
restricts: [],
|
|
|
cortho: {},
|
|
|
vortho: {},
|
|
|
noortho: true,
|
|
|
nomorph: true,
|
|
|
nowordpool: true,
|
|
|
minsyll: 1,
|
|
|
maxsyll: 1,
|
|
|
morphemes: {},
|
|
|
words: {},
|
|
|
names: [],
|
|
|
joiner: ' ',
|
|
|
maxchar: 12,
|
|
|
minchar: 5
|
|
|
};
|
|
|
}
|
|
|
|
|
|
function makeOrthoLanguage() {
|
|
|
var lang = makeBasicLanguage();
|
|
|
lang.noortho = false;
|
|
|
return lang;
|
|
|
}
|
|
|
|
|
|
function makeRandomLanguage() {
|
|
|
var lang = makeBasicLanguage();
|
|
|
lang.noortho = false;
|
|
|
lang.nomorph = false;
|
|
|
lang.nowordpool = false;
|
|
|
lang.phonemes.C = shuffled(choose(consets, 2).C);
|
|
|
lang.phonemes.V = shuffled(choose(vowsets, 2).V);
|
|
|
lang.phonemes.L = shuffled(choose(lsets, 2).L);
|
|
|
lang.phonemes.S = shuffled(choose(ssets, 2).S);
|
|
|
lang.phonemes.F = shuffled(choose(fsets, 2).F);
|
|
|
lang.structure = choose(syllstructs);
|
|
|
lang.restricts = ressets[2].res;
|
|
|
lang.cortho = choose(corthsets, 2).orth;
|
|
|
lang.vortho = choose(vorthsets, 2).orth;
|
|
|
lang.minsyll = randrange(1, 3);
|
|
|
if (lang.structure.length < 3) lang.minsyll++;
|
|
|
lang.maxsyll = randrange(lang.minsyll + 1, 7);
|
|
|
lang.joiner = choose(' -');
|
|
|
return lang;
|
|
|
}
|
|
|
var defaultOrtho = {
|
|
|
'ʃ': 'sh',
|
|
|
'ʒ': 'zh',
|
|
|
'ʧ': 'ch',
|
|
|
'ʤ': 'j',
|
|
|
'ŋ': 'ng',
|
|
|
'j': 'y',
|
|
|
'x': 'kh',
|
|
|
'ɣ': 'gh',
|
|
|
'ʔ': '‘',
|
|
|
A: "á",
|
|
|
E: "é",
|
|
|
I: "í",
|
|
|
O: "ó",
|
|
|
U: "ú"
|
|
|
};
|
|
|
|
|
|
var corthsets = [
|
|
|
{
|
|
|
name: "Default",
|
|
|
orth: {}
|
|
|
},
|
|
|
{
|
|
|
name: "Slavic",
|
|
|
orth: {
|
|
|
'ʃ': 'š',
|
|
|
'ʒ': 'ž',
|
|
|
'ʧ': 'č',
|
|
|
'ʤ': 'ǧ',
|
|
|
'j': 'j'
|
|
|
}
|
|
|
},
|
|
|
{
|
|
|
name: "German",
|
|
|
orth: {
|
|
|
'ʃ': 'sch',
|
|
|
'ʒ': 'zh',
|
|
|
'ʧ': 'tsch',
|
|
|
'ʤ': 'dz',
|
|
|
'j': 'j',
|
|
|
'x': 'ch'
|
|
|
}
|
|
|
},
|
|
|
{
|
|
|
name: "French",
|
|
|
orth: {
|
|
|
'ʃ': 'ch',
|
|
|
'ʒ': 'j',
|
|
|
'ʧ': 'tch',
|
|
|
'ʤ': 'dj',
|
|
|
'x': 'kh'
|
|
|
}
|
|
|
},
|
|
|
{
|
|
|
name: "Chinese (pinyin)",
|
|
|
orth: {
|
|
|
'ʃ': 'x',
|
|
|
'ʧ': 'q',
|
|
|
'ʤ': 'j',
|
|
|
}
|
|
|
}
|
|
|
];
|
|
|
|
|
|
var vorthsets = [
|
|
|
{
|
|
|
name: "Ácutes",
|
|
|
orth: {}
|
|
|
},
|
|
|
{
|
|
|
name: "Ümlauts",
|
|
|
orth: {
|
|
|
A: "ä",
|
|
|
E: "ë",
|
|
|
I: "ï",
|
|
|
O: "ö",
|
|
|
U: "ü"
|
|
|
}
|
|
|
},
|
|
|
{
|
|
|
name: "Welsh",
|
|
|
orth: {
|
|
|
A: "â",
|
|
|
E: "ê",
|
|
|
I: "y",
|
|
|
O: "ô",
|
|
|
U: "w"
|
|
|
}
|
|
|
},
|
|
|
{
|
|
|
name: "Diphthongs",
|
|
|
orth: {
|
|
|
A: "au",
|
|
|
E: "ei",
|
|
|
I: "ie",
|
|
|
O: "ou",
|
|
|
U: "oo"
|
|
|
}
|
|
|
},
|
|
|
{
|
|
|
name: "Doubles",
|
|
|
orth: {
|
|
|
A: "aa",
|
|
|
E: "ee",
|
|
|
I: "ii",
|
|
|
O: "oo",
|
|
|
U: "uu"
|
|
|
}
|
|
|
}
|
|
|
];
|
|
|
|
|
|
var consets = [
|
|
|
{
|
|
|
name: "Minimal",
|
|
|
C: "ptkmnls"
|
|
|
},
|
|
|
{
|
|
|
name: "English-ish",
|
|
|
C: "ptkbdgmnlrsʃzʒʧ"
|
|
|
},
|
|
|
{
|
|
|
name: "Pirahã (very simple)",
|
|
|
C: "ptkmnh"
|
|
|
},
|
|
|
{
|
|
|
name: "Hawaiian-ish",
|
|
|
C: "hklmnpwʔ"
|
|
|
},
|
|
|
{
|
|
|
name: "Greenlandic-ish",
|
|
|
C: "ptkqvsgrmnŋlj"
|
|
|
},
|
|
|
{
|
|
|
name: "Arabic-ish",
|
|
|
C: "tksʃdbqɣxmnlrwj"
|
|
|
},
|
|
|
{
|
|
|
name: "Arabic-lite",
|
|
|
C: "tkdgmnsʃ"
|
|
|
},
|
|
|
{
|
|
|
name: "English-lite",
|
|
|
C: "ptkbdgmnszʒʧhjw"
|
|
|
}
|
|
|
];
|
|
|
|
|
|
var ssets = [
|
|
|
{
|
|
|
name: "Just s",
|
|
|
S: "s"
|
|
|
},
|
|
|
{
|
|
|
name: "s ʃ",
|
|
|
S: "sʃ"
|
|
|
},
|
|
|
{
|
|
|
name: "s ʃ f",
|
|
|
S: "sʃf"
|
|
|
}
|
|
|
];
|
|
|
|
|
|
var lsets = [
|
|
|
{
|
|
|
name: "r l",
|
|
|
L: "rl"
|
|
|
},
|
|
|
{
|
|
|
name: "Just r",
|
|
|
L: "r"
|
|
|
},
|
|
|
{
|
|
|
name: "Just l",
|
|
|
L: "l"
|
|
|
},
|
|
|
{
|
|
|
name: "w j",
|
|
|
L: "wj"
|
|
|
},
|
|
|
{
|
|
|
name: "r l w j",
|
|
|
L: "rlwj"
|
|
|
}
|
|
|
];
|
|
|
|
|
|
var fsets = [
|
|
|
{
|
|
|
name: "m n",
|
|
|
F: "mn"
|
|
|
},
|
|
|
{
|
|
|
name: "s k",
|
|
|
F: "sk"
|
|
|
},
|
|
|
{
|
|
|
name: "m n ŋ",
|
|
|
F: "mnŋ"
|
|
|
},
|
|
|
{
|
|
|
name: "s ʃ z ʒ",
|
|
|
F: "sʃzʒ"
|
|
|
}
|
|
|
];
|
|
|
|
|
|
var vowsets = [
|
|
|
{
|
|
|
name: "Standard 5-vowel",
|
|
|
V: "aeiou"
|
|
|
},
|
|
|
{
|
|
|
name: "3-vowel a i u",
|
|
|
V: "aiu"
|
|
|
},
|
|
|
{
|
|
|
name: "Extra A E I",
|
|
|
V: "aeiouAEI"
|
|
|
},
|
|
|
{
|
|
|
name: "Extra U",
|
|
|
V: "aeiouU"
|
|
|
},
|
|
|
{
|
|
|
name: "5-vowel a i u A I",
|
|
|
V: "aiuAI"
|
|
|
},
|
|
|
{
|
|
|
name: "3-vowel e o u",
|
|
|
V: "eou"
|
|
|
},
|
|
|
{
|
|
|
name: "Extra A O U",
|
|
|
V: "aeiouAOU"
|
|
|
}
|
|
|
];
|
|
|
|
|
|
var syllstructs = [
|
|
|
"CVC",
|
|
|
"CVV?C",
|
|
|
"CVVC?", "CVC?", "CV", "VC", "CVF", "C?VC", "CVF?",
|
|
|
"CL?VC", "CL?VF", "S?CVC", "S?CVF", "S?CVC?",
|
|
|
"C?VF", "C?VC?", "C?VF?", "C?L?VC", "VC",
|
|
|
"CVL?C?", "C?VL?C", "C?VLC?"];
|
|
|
|
|
|
var ressets = [
|
|
|
{
|
|
|
name: "None",
|
|
|
res: []
|
|
|
},
|
|
|
{
|
|
|
name: "Double sounds",
|
|
|
res: [/(.)\1/]
|
|
|
},
|
|
|
{
|
|
|
name: "Doubles and hard clusters",
|
|
|
res: [/[sʃf][sʃ]/, /(.)\1/, /[rl][rl]/]
|
|
|
}
|
|
|
];
|
|
|
|