Procedurally generates a radio weather report

let's talk numbers

Changed files
+164 -1
src
+164 -1
src/voice.ts
··· 1 - 1 + import path from 'path'; 2 2 3 3 interface Voice { 4 4 directory: string; ··· 7 7 8 8 type Voices = { [name: string]: Voice }; 9 9 10 + const LINES = { 11 + NEGATIVE: 'negative', 12 + POINT: 'point', 13 + ZERO: 'zero', 14 + ONE: 'one', 15 + TWO: 'two', 16 + THREE: 'three', 17 + FOUR: 'four', 18 + FIVE: 'five', 19 + SIX: 'six', 20 + SEVEN: 'seven', 21 + EIGHT: 'eight', 22 + NINE: 'nine', 23 + TEN: 'ten', 24 + ELEVEN: 'eleven', 25 + TWELVE: 'twelve', 26 + THIRTEEN: 'thirteen', 27 + FIFTEEN: 'fifteen', 28 + TEEN: 'teen', 29 + TWENTY: 'twenty', 30 + THIRTY: 'thirty', 31 + FORTY: 'forty', 32 + FIFTY: 'fifty', 33 + SIXTY: 'sixty', 34 + SEVENTY: 'seventy', 35 + EIGHTY: 'eighty', 36 + NINETY: 'ninety', 37 + HUNDRED: 'hundred', 38 + THOUSAND: 'thousand', 39 + MILLION: 'million', 40 + BILLION: 'billion', 41 + TRILLION: 'trillion' 42 + } 43 + 44 + function formatNumber(num: number): string { 45 + const parts = num.toString().split("."); 46 + parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ","); 47 + return parts.join("."); 48 + } 49 + 50 + function digitByDigit(str: string): string[] { 51 + const tokens: string[] = []; 52 + const input = str.replaceAll(',', ''); 53 + const map = [ 54 + LINES.ZERO, 55 + LINES.ONE, 56 + LINES.TWO, 57 + LINES.THREE, 58 + LINES.FOUR, 59 + LINES.FIVE, 60 + LINES.SIX, 61 + LINES.SEVEN, 62 + LINES.EIGHT, 63 + LINES.NINE 64 + ] 65 + for (let i = 0; i < input.length; i++) { 66 + tokens.push(map[parseInt(input.charAt(i))]); 67 + } 68 + return tokens; 69 + } 70 + 71 + function tens(str: string): string[] { 72 + if (str === '0') { 73 + return [LINES.ZERO]; 74 + } 75 + const tokens: string[] = []; 76 + const num = parseInt(str); 77 + const map = { 78 + '2': LINES.TWENTY, 79 + '3': LINES.THIRTY, 80 + '4': LINES.FORTY, 81 + '5': LINES.FIFTY, 82 + '6': LINES.SIXTY, 83 + '7': LINES.SEVENTY, 84 + '8': LINES.EIGHTY, 85 + '9': LINES.NINETY 86 + }; 87 + const ones = str.charAt(str.length - 1); 88 + if (num === 0) { 89 + return []; 90 + } 91 + else if (num >= 20) { 92 + tokens.push(map[str.charAt(0)]); 93 + if (ones !== '0') { 94 + tokens.push(...digitByDigit(ones)); 95 + } 96 + } 97 + else if (num < 10) { 98 + tokens.push(...digitByDigit(ones)); 99 + } 100 + else { 101 + const weirdoNumberMap = [ 102 + [LINES.TEN], 103 + [LINES.ELEVEN], 104 + [LINES.TWELVE], 105 + [LINES.THIRTEEN], 106 + [LINES.FOUR, LINES.TEEN], 107 + [LINES.FIFTEEN], 108 + [LINES.SIX, LINES.TEEN], 109 + [LINES.SEVEN, LINES.TEEN], 110 + [LINES.EIGHT, LINES.TEEN], 111 + [LINES.NINE, LINES.TEEN], 112 + ] 113 + tokens.push(...weirdoNumberMap[num - 10]); 114 + } 115 + return tokens; 116 + } 117 + 118 + function hundreds(str: string): string[] { 119 + const tokens: string[] = []; 120 + if (str.length === 3 && str.charAt(0) !== '0') { 121 + tokens.push(...digitByDigit(str.charAt(0))); 122 + tokens.push(LINES.HUNDRED); 123 + str = str.substring(1); 124 + } 125 + tokens.push(...tens(str)); 126 + return tokens; 127 + } 128 + 129 + function integer(str: string): string[] { 130 + const tokens: string[] = []; 131 + const numGroups = str.split(','); 132 + const seperators = [LINES.TRILLION, LINES.BILLION, LINES.MILLION, LINES.THOUSAND]; 133 + if (numGroups.length > 5) { 134 + return digitByDigit(str); 135 + } 136 + seperators.splice(0, seperators.length - numGroups.length + 1); 137 + numGroups.forEach(g => { 138 + if (g !== '000') { 139 + tokens.push(...hundreds(g)); 140 + } 141 + if (seperators.length === 0) { 142 + return; 143 + } 144 + const sep = seperators.shift(); 145 + if (g !== '000') { 146 + tokens.push(sep); 147 + } 148 + }); 149 + return tokens; 150 + } 151 + 152 + function voiceLines(voice: Voice, num: number): string[] { 153 + if (Math.abs(num) > 999999999999999 || isNaN(num)) { 154 + return []; 155 + } 156 + const tokens: string[] = []; 157 + const str = formatNumber(num); 158 + const parts = str.split('.'); 159 + if (parts[0].startsWith('-')) { 160 + tokens.push(LINES.NEGATIVE); 161 + parts[0] = parts[0].substring(1); 162 + } 163 + tokens.push(...integer(parts[0])); 164 + if (parts.length > 1) { 165 + tokens.push(LINES.POINT); 166 + tokens.push(...digitByDigit(parts[1])); 167 + } 168 + return tokens.map(l => path.join(voice.directory, `${l}.${voice.extension}`)); 169 + } 170 + 171 + export default voiceLines; 172 + export { voiceLines }; 10 173 export type { Voice, Voices };