Procedurally generates a radio weather report
at v0.0.4 4.3 kB view raw
1import path from 'path'; 2 3interface Voice { 4 directory: string; 5 extension: string; 6} 7 8type Voices = { [name: string]: Voice }; 9 10const 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 44function 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 50function 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 71function 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 118function 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 129function 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 seperators.splice(0, seperators.length - numGroups.length + 1); 134 numGroups.forEach(g => { 135 if (g !== '000') { 136 tokens.push(...hundreds(g)); 137 } 138 if (seperators.length === 0) { 139 return; 140 } 141 const sep = seperators.shift(); 142 if (g !== '000') { 143 tokens.push(sep); 144 } 145 }); 146 return tokens; 147} 148 149function voiceLines(voice: Voice, num: number): string[] { 150 if (Math.abs(num) > 999999999999999 || isNaN(num)) { 151 return []; 152 } 153 const tokens: string[] = []; 154 const str = formatNumber(num); 155 const parts = str.split('.'); 156 if (parts[0].startsWith('-')) { 157 tokens.push(LINES.NEGATIVE); 158 parts[0] = parts[0].substring(1); 159 } 160 tokens.push(...integer(parts[0])); 161 if (parts.length > 1) { 162 tokens.push(LINES.POINT); 163 tokens.push(...digitByDigit(parts[1])); 164 } 165 return tokens.map(l => path.join(voice.directory, `${l}${voice.extension.length > 0 && voice.extension.charAt(0) !== '.' ? '.' : ''}${voice.extension}`)); 166} 167 168export default voiceLines; 169export { voiceLines, LINES }; 170export type { Voice, Voices };