1 /** 2 IKEA Name Generator 3 4 Based on code by Kate McFaul: 5 https://github.com/craftykate/ikea-name-generator 6 */ 7 module namegen.ikea; 8 9 string generateName() 10 { 11 import std.conv: to; 12 13 return generateNameWide.to!string; 14 } 15 16 wchar[] generateNameWide() 17 { 18 wchar[] word; 19 20 ubyte minLength = 3; 21 ubyte maxLength = 10; 22 const ubyte wordLength = uniform(minLength, maxLength, rndg); 23 24 for (ubyte i = 0; i < wordLength; i++) 25 word ~= returnNextLetter(word); 26 27 // Checks if word ended with two consonants. For readability, end with an extra vowel 28 word.endWord; 29 30 // Format name to add Swedish characters if possible 31 word.formatSwedish; 32 33 import std.string: capitalize; 34 35 return word.capitalize; 36 } 37 38 unittest 39 { 40 import std.stdio; 41 42 foreach(i; 0 .. 13) 43 { 44 auto s = generateName; 45 46 assert(i != 0 || s == "Åökpe", s); 47 assert(i != 5 || s == "Öålih", s); 48 assert(i != 12 || s == "Tigra", s); 49 } 50 51 // It is to reset RNDG to initial value 52 rndg = Random(42); 53 } 54 55 private: 56 57 static import alphabet = namegen.ikea.alphabet; 58 import std.random; 59 60 // Seed a random generator with a constant 61 auto rndg = Random(42); 62 63 // Main function to get and return the next letter 64 char returnNextLetter(in wchar[] word) 65 { 66 static ubyte numConsonants; 67 static ubyte numVowels; 68 69 char nextLetter; 70 71 // If it's the first letter, grab any letter 72 if (word.length == 0) 73 { 74 nextLetter = grabAnyLetter(); 75 76 } 77 // If it's not the first letter... 78 // And there are too many consonants before it, grab a vowel 79 // Or it's the second letter and the first wasn't a vowel (this makes sure there's a vowel in the first two letters for readability) 80 else if (numConsonants == 2 || (word.length == 1 && numConsonants == 1)) 81 { 82 nextLetter = grabAVowel(); 83 } 84 // Or if there are too many vowels grab a consonant 85 else if (numVowels == 2) 86 { 87 nextLetter = grabAConsonant(); 88 numConsonants++; 89 numVowels = 0; 90 } 91 else // Otherwise, grab the next acceptable letter 92 { 93 nextLetter = grabNextGoodLetter(word); 94 numVowels++; 95 numConsonants=0; 96 } 97 98 return nextLetter; 99 } 100 101 /// Get random letter from string of letters 102 char grabRandomLetter(in string str) 103 { 104 auto idx = uniform(0, str.length, rndg); 105 106 return str[idx]; 107 } 108 109 /// Get any letter from alphabet 110 char grabAnyLetter() 111 { 112 return alphabet.whole.grabRandomLetter; 113 } 114 115 char grabAVowel() 116 { 117 return alphabet.vowels.grabRandomLetter; 118 } 119 120 char grabAConsonant() 121 { 122 return alphabet.consonants.grabRandomLetter; 123 } 124 125 /// Get a letter than can follow the last letter 126 char grabNextGoodLetter(in wchar[] word) 127 { 128 const lastLetter = word[$-1]; 129 130 return alphabet.after[lastLetter].grabRandomLetter; 131 } 132 133 import std.algorithm.searching: canFind; 134 135 bool isVowel(wchar c) 136 { 137 return alphabet.vowels.canFind(c); 138 } 139 140 // For readability, make sure word has a vowel in the last two letters 141 void endWord(ref wchar[] word) 142 { 143 if(!word[$-2].isVowel && !word[$-1].isVowel) 144 word ~= grabAVowel(); 145 } 146 147 // Change first a and o (if present) to swedish characters 148 void formatSwedish(ref wchar[] word) 149 { 150 // 50/50 chance of changing first 'a' 151 if(dice(rndg, 50, 50) == 0) 152 word.replaceFirst('a', 'å'); 153 154 // 50/50 chance of changing first 'o' 155 if(dice(rndg, 50, 50) == 0) 156 word.replaceFirst('o', 'ö'); 157 } 158 159 void replaceFirst(ref wchar[] word, char from, wchar to) 160 { 161 auto idx = canFind(word, from); 162 word[idx] = to; 163 }